用 ElementUI 的日历组件 Calendar 自定义渲染

文章目录

    • 需求
    • 分析
      • [1. 页面渲染](#1. 页面渲染)
      • [2. 获取页面上的开始日期和结束日期](#2. 获取页面上的开始日期和结束日期)
      • [3. 总的代码](#3. 总的代码)

需求

之前实现过一版用 ElementPlus 的日历组件 Calendar 自定义渲染,是在 Vue3 项目中实现的,现在需求在 Vue2 中也实现一版




分析

1. 页面渲染

javascript 复制代码
<el-calendar v-model="calendar" style="padding-bottom: 0">
   <template slot="dateCell" slot-scope="{ data }">
     <div class="calendar-day">
       <div
         :class="data.isSelected ? 'is-selected' : ''"
         style="font-weight: bold"
       >
         {{ data.day.split("-").slice(1).join("-") }}
       </div>
       <div class="duty-text">
         值班人员:
         <span v-for="item in tableData" :key="item.id">
           <span
             v-if="item.dutyDate == data.day"
             style="font-weight: bold"
           >
             {{ item.dutyUser }}
           </span>
         </span>
         <div>
           <el-button
             size="mini"
             type="text"
             style="color: #1e80ff; margin-right: 10px"
             @click.stop="handleEdit(data)"
             >编辑</el-button
           >
           <el-button
             slot="reference"
             size="mini"
             type="text"
             style="color: #1e80ff"
             @click="handleRemove(data)"
             >删除</el-button
           >
         </div>
       </div>
     </div>
   </template>
 </el-calendar>
javascript 复制代码
data(){
	calendar: new Date(),
}

2. 获取页面上的开始日期和结束日期

javascript 复制代码
methods: {
  // 获取日历显示时间范围
  getRange (date) {
    if (date) {
    } else {
      this.calendar = new Date()
    }
    // 日历第一天
    let firstDay = ''
    // 日历最后一天
    let lastDay = ''
    // 今天
    const today = date || new Date()
    // 上月
    const m = today.getMonth()
    // 本月
    const cm = m + 1
    // 下月
    const lm = m + 2 > 12 ? 1 : m + 2
    // 要显示的本月
    const currentMonth = cm < 10 ? '0' + cm : cm
    // 要显示的本本年
    const currentYear = today.getFullYear()
    // 要显示的上个月的年份,m = 0 则当前1月,上月则是去年12月
    const prevYear = m == 0 ? currentYear - 1 : currentYear
    const prevMonth = m == 0 ? 12 : m < 10 ? '0' + m : m
    // 上个月天数
    const pmd = new Date(prevYear, m, 0).getDate()
    // 下个月的年份,当前12月,则需要加一年
    const lastYear = cm + 1 > 12 ? currentYear + 1 : currentYear
    const lastMonth = lm < 10 ? '0' + lm : lm
    // 1号是周几
    const firstWeek = new Date(today.setDate(1)).getDay()
    // 如果是周日,则不需要显示上个月
    if (firstWeek == 0) {
      firstDay = `${currentYear}-${currentMonth}-01`
    }
    // 其他周几,对应用上个月的天数往前推算
    else {
      firstDay = `${prevYear}-${prevMonth}-${pmd - (firstWeek - 1)}`
    }
    // 这个月天数
    const currentMonthDate = new Date(currentYear, cm, 0).getDate()
    // 最后一天是周几
    const lastWeek = new Date(today.setDate(currentMonthDate)).getDay()
    // 周六显示当月最后一天
    if (lastWeek == 6) {
      lastDay = `${currentYear}-${currentMonth}-${currentMonthDate}`
    }
    // 其他周几,对应往后推算
    else {
      const day = ['06', '05', '04', '03', '02', '01']
      lastDay = `${lastYear}-${lastMonth}-${day[lastWeek]}`
    }
    this.timeList.startDate = firstDay
    this.timeList.endDate = lastDay
    this.getPageList()
  },
}

3. 总的代码

  • plan.vue
javascript 复制代码
<template>
  <div
    v-loading="loading"
    class="module-container"
    element-loading-text="加载中...."
  >
    <!-- {{ timeList.startDate }}-{{ timeList.endDate }} -->
    <MyCard :is-have-left="false">
      <template slot="right-body">
        <div class="split-head" style="margin-bottom: -40px">
          <el-button
            type="primary"
            size="small"
            style="margin-left: 20px"
            @click="handlePlan()"
          >
            排班计划
          </el-button>
        </div>
        <div class="split-body">
          <template>
            <el-calendar v-model="calendar" style="padding-bottom: 0">
              <template slot="dateCell" slot-scope="{ data }">
                <div class="calendar-day">
                  <div
                    :class="data.isSelected ? 'is-selected' : ''"
                    style="font-weight: bold"
                  >
                    {{ data.day.split("-").slice(1).join("-") }}
                  </div>
                  <div class="duty-text">
                    值班人员:
                    <span v-for="item in tableData" :key="item.id">
                      <span
                        v-if="item.dutyDate == data.day"
                        style="font-weight: bold"
                      >
                        {{ item.dutyUser }}
                      </span>
                    </span>
                    <div>
                      <el-button
                        size="mini"
                        type="text"
                        style="color: #1e80ff; margin-right: 10px"
                        @click.stop="handleEdit(data)"
                        >编辑</el-button
                      >
                      <el-button
                        slot="reference"
                        size="mini"
                        type="text"
                        style="color: #1e80ff"
                        @click="handleRemove(data)"
                        >删除</el-button
                      >
                    </div>
                  </div>
                </div>
              </template>
            </el-calendar>
          </template>
        </div>
      </template>
    </MyCard>
    <Edit
      :scheduling-visible.sync="schedulingVisible"
      :edit_visible.sync="edit_visible"
      :duty-info="dutyInfo"
      @getRange="getRange"
    />
  </div>
</template>

<script>
import Edit from './child/edit.vue'

export default {
  name: '',
  components: {
    Edit
  },
  props: {},
  data () {
    return {
      projectId: this.$store.state.project.projectId,
      structId: this.$store.state.struct.structId,
      loading: false,
      visible: false,
      schedulingVisible: false,
      calendar: new Date(),
      editForm: {},
      tableData: [],
      timeList: {
        startDate: '',
        endDate: ''
      },
      edit_visible: false,
      dutyInfo: {}
    }
  },
  watch: {
    calendar (n, o) {
      if (n.getFullYear() !== o.getFullYear() || n.getMonth() !== o.getMonth()) {
        this.getRange(n)
      }
    }
  },
  created () {
  },
  mounted () { this.getRange(this.calendar) },
  beforeDestroy () { },

  methods: {
    // 获取日历显示时间范围
    getRange (date) {
      if (date) {
      } else {
        this.calendar = new Date()
      }
      // 日历第一天
      let firstDay = ''
      // 日历最后一天
      let lastDay = ''
      // 今天
      const today = date || new Date()
      // 上月
      const m = today.getMonth()
      // 本月
      const cm = m + 1
      // 下月
      const lm = m + 2 > 12 ? 1 : m + 2
      // 要显示的本月
      const currentMonth = cm < 10 ? '0' + cm : cm
      // 要显示的本本年
      const currentYear = today.getFullYear()
      // 要显示的上个月的年份,m = 0 则当前1月,上月则是去年12月
      const prevYear = m == 0 ? currentYear - 1 : currentYear
      const prevMonth = m == 0 ? 12 : m < 10 ? '0' + m : m
      // 上个月天数
      const pmd = new Date(prevYear, m, 0).getDate()
      // 下个月的年份,当前12月,则需要加一年
      const lastYear = cm + 1 > 12 ? currentYear + 1 : currentYear
      const lastMonth = lm < 10 ? '0' + lm : lm
      // 1号是周几
      const firstWeek = new Date(today.setDate(1)).getDay()
      // 如果是周日,则不需要显示上个月
      if (firstWeek == 0) {
        firstDay = `${currentYear}-${currentMonth}-01`
      }
      // 其他周几,对应用上个月的天数往前推算
      else {
        firstDay = `${prevYear}-${prevMonth}-${pmd - (firstWeek - 1)}`
      }
      // 这个月天数
      const currentMonthDate = new Date(currentYear, cm, 0).getDate()
      // 最后一天是周几
      const lastWeek = new Date(today.setDate(currentMonthDate)).getDay()
      // 周六显示当月最后一天
      if (lastWeek == 6) {
        lastDay = `${currentYear}-${currentMonth}-${currentMonthDate}`
      }
      // 其他周几,对应往后推算
      else {
        const day = ['06', '05', '04', '03', '02', '01']
        lastDay = `${lastYear}-${lastMonth}-${day[lastWeek]}`
      }
      this.timeList.startDate = firstDay
      this.timeList.endDate = lastDay
      this.getPageList()
    },
    handlePlan () {
      this.schedulingVisible = true
    },
    getPageList () {
      this.loading = true
      const url = `client/${this.projectId}/dh-duty-plan/list?startDate=${this.timeList.startDate}&endDate=${this.timeList.endDate}`
      this.$axios
        .get(url)
        .then(res => {
          if (res.code !== 200) return
          this.tableData = res.data
          // this.total = res.data.total / 1
        })
        .finally(() => {
          this.loading = false
        })
    },
    handleEdit (row) {
      const url = `client/${this.projectId}/dh-duty-plan/list?dutyDate=${row.day}`
      this.$axios
        .get(url)
        .then((res) => {
          if (res.code !== 200) return
          if (res.data.length) {
            this.dutyInfo = res.data[0]
          } else {
            this.dutyInfo = {
              id: '',
              dutyUserId: '',
              dutyDate: row.day
            }
          }
        })
        .finally(() => {
        })
      this.edit_visible = true
    },
    async handleRemove (row) {
      const result = await this.$confirm(`确定要删除吗?`, {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        closeOnClickModal: false
      }).catch((err) => err)
      if (result === 'confirm') {
        const url = `client/${this.projectId}/dh-duty-plan/list?dutyDate=${row.day}`
        this.$axios
          .get(url)
          .then((res) => {
            if (res.code !== 200) return
            if (res.data.length) {
              const info = res.data[0]
              this.$axios.post(`client/${this.projectId}/dh-duty-plan/delete`, { id: info.id }).then(response => {
                if (response.code !== 200) return
                this.getPageList()
              })
            } else {
              this.$message('该日期下未查询到值班人员')
            }
          })
          .finally(() => {
            // this.getPageList()
          })
      } else {
        // this.$message('取消了删除')
      }
    },
  }
}
</script>

<style lang="scss" scoped>
.module-container {
  // padding-top: 15px;
  padding-left: 22px;
  height: 100%;
  // ::v-deep .card-body {
  //   background: #f6f9ff !important;
  //   display: flex;
  //   padding: 0px;
  // }
  .split-head {
    display: flex;
    justify-content: center;
    flex-direction: row;
  }
}
.duty-text {
  text-align: center;
  // color: #939fb6;
  font-size: large;
  line-height: 25px;
  padding-top: 5px;
}

.is-selected {
  color: #1989fa;
  font-weight: bold;
}

:deep(.el-calendar-table thead th) {
  font-weight: bold;
}
</style>
  • 弹窗 edit.vue
js 复制代码
<template>
  <div class="container">
    <el-dialog
      :title="'排班计划'"
      :visible="schedulingVisible"
      width="30%"
      top="20vh"
      :close-on-click-modal="false"
      :modal-append-to-body="false"
      custom-class="alarm-strategy"
      @update:visible="(bol) => $emit('update:schedulingVisible', bol)"
    >
      <div>
        <el-button
          v-show="activeName === '单人排班'"
          type="text"
          icon="el-icon-plus"
          class="add-one-btn"
          @click="handleaddNode"
          >新增一条</el-button
        >
        <el-tabs v-model="activeName" @tab-click="handleClick">
          <el-tab-pane label="单人排班" name="单人排班">
            <el-table :data="singleForm" style="width: 100%" height="250">
              <el-table-column label="日期" align="center">
                <template slot-scope="scope">
                  <el-date-picker
                    v-model="scope.row.dutyDate"
                    size="small"
                    type="date"
                    value-format="yyyy-MM-dd"
                    placeholder="选择日期"
                    style="width: 70%"
                  />
                </template>
              </el-table-column>
              <el-table-column label="人员" align="center">
                <template slot-scope="scope">
                  <el-select
                    v-model="scope.row.dutyUserId"
                    size="small"
                    clearable
                    placeholder="请选择"
                    style="width: 70%"
                  >
                    <el-option
                      v-for="item in person_options"
                      :key="item.id"
                      :label="item.name"
                      :value="item.id"
                    />
                  </el-select>
                </template>
              </el-table-column>
              <el-table-column v-if="singleForm.length > 1" label="操作">
                <template slot-scope="scope">
                  <div>
                    <el-button
                      type="text"
                      @click="handleremoveNode(scope.$index)"
                    >
                      移除
                    </el-button>
                  </div>
                </template>
              </el-table-column>
            </el-table>
          </el-tab-pane>
          <el-tab-pane label="批量排班" name="批量排班">
            <div class="time-range batch-box">
              <span>日期范围</span>
              <el-date-picker
                v-model="multiForm.time"
                size="small"
                type="daterange"
                align="right"
                unlink-panels
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :picker-options="pickerOptions"
                value-format="yyyy-MM-dd"
              />
            </div>

            <div class="personnel batch-box">
              <span>人员添加</span>
              <el-select
                v-model="multiForm.dutyUserIdList"
                size="small"
                multiple
                collapse-tags
                clearable
                placeholder="请选择"
              >
                <el-option
                  v-for="item in person_options"
                  :key="item.id"
                  :label="item.name"
                  :value="item.id"
                />
              </el-select>
            </div>
          </el-tab-pane>
        </el-tabs>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="$emit('update:schedulingVisible', false)"
          >取 消</el-button
        >
        <el-button type="primary" :loading="loading" @click="handleOk"
          >确 定</el-button
        >
      </span>
    </el-dialog>
    <el-dialog
      :title="'编辑'"
      :visible="edit_visible"
      width="30%"
      top="20vh"
      :close-on-click-modal="false"
      :modal-append-to-body="false"
      custom-class="alarm-strategy"
      @update:visible="handleEditOk"
    >
      <div class="time-range batch-box">
        <span>值班人</span>
        <el-select
          v-model="dutyInfo.dutyUserId"
          size="small"
          placeholder="请选择"
        >
          <el-option
            v-for="item in person_options"
            :key="item.id"
            :label="item.name"
            :value="item.id"
          />
        </el-select>
        <!-- {{ dutyInfo }} -->
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button @click="$emit('update:edit_visible', false)"
          >取 消</el-button
        >
        <el-button type="primary" @click="handleEditOk">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: '',
  components: {},
  props: {
    schedulingVisible: {
      type: Boolean,
      default: false
    },
    edit_visible: {
      type: Boolean,
      default: false
    },
    dutyInfo: {
      type: Object,
      default: () => { }
    }
  },
  data () {
    return {
      projectId: this.$store.state.project.projectId,
      structId: this.$store.state.struct.structId,
      loading: false,
      activeName: '单人排班',
      value: '',
      person_options: [],
      value1: '',
      pickerOptions: {
        shortcuts: [
          {
            text: '最近一周',
            onClick (picker) {
              const end = new Date()
              const start = new Date()
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
              picker.$emit('pick', [start, end])
            }
          },
          {
            text: '最近一个月',
            onClick (picker) {
              const end = new Date()
              const start = new Date()
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
              picker.$emit('pick', [start, end])
            }
          },
          {
            text: '最近三个月',
            onClick (picker) {
              const end = new Date()
              const start = new Date()
              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
              picker.$emit('pick', [start, end])
            }
          }
        ]
      },
      value2: '',
      singleForm: [{ dutyDate: '', dutyUserId: '' }],
      multiForm: {
        time: [],
        dutyUserIdList: []
      }
    }
  },
  created () {
  },
  mounted () {
    this.init()
  },
  beforeDestroy () { },
  methods: {
    handleClick (tab, event) {
      if (tab.label == '单人排班') {
        this.multiForm = {}
      } else {
        this.singleForm = [{ dutyDate: '', dutyUserId: '' }]
      }
    },
    handleremoveNode (data) {
      this.singleForm.splice(data, 1)
    },
    handleaddNode () {
      this.singleForm.push({
        dutyDate: null, // 日期
        dutyUserId: null // 人员
      })
    },
    // 提交
    handleOk () {
      if (this.activeName === '单人排班') {
        const flag = this.singleForm.some(item =>
          item.dutyDate == null || item.dutyUserId == null || item.dutyDate == undefined || item.dutyUserId == undefined || item.dutyDate == '' || item.dutyUserId == ''
        )
        if (!flag) {
          const url = `client/${this.projectId}/dh-duty-plan/danGeAdd`
          this.$axios
            .post(url, { dhDutyPlanList: this.singleForm })
            .then((res) => {
              if (res.code === 200) {
                this.$emit('getRange')
                this.$emit('update:schedulingVisible', false)
              }
            })
            .finally(() => {
              this.handleClick('单人排班')
            })
        } else {
          return this.$message.warning('请完整填写表单')
        }
      } else {
        if (this.multiForm.time.length && this.multiForm.dutyUserIdList.length) {
          const tempData = {
            startDate: this.multiForm.time[0],
            endDate: this.multiForm.time[1],
            dutyUserIdList: this.multiForm.dutyUserIdList
          }
          const url = `client/${this.projectId}/dh-duty-plan/batchAdd`
          this.$axios
            .post(url, tempData)
            .then((res) => {
              if (res.code === 200) {
                this.$emit('getRange')
                this.$emit('update:schedulingVisible', false)
              }
            })
            .finally(() => {
              this.handleClick('单人排班')
            })
        } else {
          return this.$message.warning('请完整填写表单')
        }
      }

    },
    getPersonList () {
      const url = `client/${this.projectId}/dh-duty-user/list`
      this.$axios
        .get(url)
        .then(res => {
          if (res.code !== 200) return
          this.person_options = res.data
        })
        .finally(() => {
        })
    },
    handleEditOk () {
      if (this.dutyInfo.dutyUserId) {
        if (this.dutyInfo.id) {
          const tempData = {
            id: this.dutyInfo.id,
            dutyUserId: this.dutyInfo.dutyUserId,
            dutyDate: this.dutyInfo.dutyDate
          }
          const url = `client/${this.projectId}/dh-duty-plan/update`
          this.$axios
            .post(url, tempData)
            .then((res) => {
              if (res.code !== 200) return
              this.$emit('getRange')
              this.$emit('update:edit_visible', false)
            })
            .finally(() => {
            })
        } else {
          const url = `client/${this.projectId}/dh-duty-plan/danGeAdd`
          this.$axios
            .post(url, { dhDutyPlanList: [{ dutyDate: this.dutyInfo.dutyDate, dutyUserId: this.dutyInfo.dutyUserId }] })
            .then((res) => {
              if (res.code === 200) {
                this.$emit('getRange')
                this.$emit('update:edit_visible', false)
              }
            })
            .finally(() => {
            })
        }
      } else {
        return this.$message.warning('请完整填写表单')
      }
    },
    init () {
      this.getPersonList()
    }
  }
}
</script>

<style lang="scss" scoped>
::v-deep .el-dialog__body {
  padding: 0 15px;
  padding-right: 0px;
  position: relative;
  margin-bottom: 30px;
  overflow: hidden;
}
.add-one-btn {
  position: absolute;
  right: 20px;
  z-index: 22;
}
.single-person-box {
  padding-right: 20px;
  max-height: 360px;
  overflow: auto;
  .limit {
    display: flex;
    margin-bottom: 12px;
    > div {
      flex: 1;
      margin-left: 10px;
    }
    .flex {
      display: flex;
      align-items: center;
      .el-date-editor {
        width: 160px;
      }
      .el-select {
        width: 160px;
      }
    }
    .time {
      margin-right: 10px;
    }
    .staff {
    }
    span {
      margin-right: 10px;
    }
  }
}
.batch-box {
  display: flex;
  align-items: center;
  width: 70%;
  margin-left: 10%;
  margin-top: 15px;
  span {
    margin-right: 10px;
  }
  .el-select,
  .el-date-editor {
    flex: 1;
  }
}
</style>
相关推荐
蟾宫曲34 分钟前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心36 分钟前
简单发布一个npm包
前端·javascript·webpack·npm·node.js
liuxin3344556637 分钟前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
qq132670294040 分钟前
运行Zr.Admin项目(前端)
前端·vue2·zradmin前端·zradmin vue·运行zradmin·vue2版本zradmin
魏时烟2 小时前
css文字折行以及双端对齐实现方式
前端·css
哥谭居民00013 小时前
将一个组件的propName属性与父组件中的variable变量进行双向绑定的vue3(组件传值)
javascript·vue.js·typescript·npm·node.js·css3
踢足球的,程序猿3 小时前
Android native+html5的混合开发
javascript
2401_882726483 小时前
低代码配置式组态软件-BY组态
前端·物联网·低代码·前端框架·编辑器·web
web130933203983 小时前
ctfshow-web入门-文件包含(web82-web86)条件竞争实现session会话文件包含
前端·github
胡西风_foxww3 小时前
【ES6复习笔记】迭代器(10)
前端·笔记·迭代器·es6·iterator