用 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>
相关推荐
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端
爱敲代码的小鱼7 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax