用 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>
相关推荐
i听风逝夜12 分钟前
Web 3D地球实时统计访问来源
前端·后端
iMonster16 分钟前
React 组件的组合模式之道 (Composition Pattern)
前端
呐呐呐呐呢24 分钟前
antd渐变色边框按钮
前端
元直数字电路验证43 分钟前
Jakarta EE Web 聊天室技术梳理
前端
wadesir1 小时前
Nginx配置文件CPU优化(从零开始提升Web服务器性能)
服务器·前端·nginx
牧码岛1 小时前
Web前端之canvas实现图片融合与清晰度介绍、合并
前端·javascript·css·html·web·canvas·web前端
灵犀坠1 小时前
前端面试八股复习心得
开发语言·前端·javascript
9***Y481 小时前
前端动画性能优化
前端
网络点点滴1 小时前
Vue3嵌套路由
前端·javascript·vue.js
牧码岛1 小时前
Web前端之Vue+Element打印时输入值没有及时更新dom的问题
前端·javascript·html·web·web前端