Bootstrap

vue之排班日历的 实现

vue之排班日历的 实现

  • 查询日期和数据,展示对于的排班日历,蓝色为高亮区,代表排班的日期

index.vue

<template>
  <div class="inspect_schedule_page repair_page spotCheck-page" v-cloak>
    <div class="content">
      <div class="do_content">
        <div class="content-top">
          <div class="search-left">
            <el-form :model="ruleForm" :rules="rules" ref="ruleFormRef" label-width="100px">
              <el-col :span="8">
                <el-form-item :label="年月份" prop="date">
                  <el-date-picker v-model="ruleForm.date" type="month" format="yyyy-MM" value-format="yyyy-MM"
                    :placeholder="选择月份" clearable>
                  </el-date-picker>
                </el-form-item>
              </el-col>
            </el-form>
          </div>
          <el-col :span="8">
            <div style="text-align:right">
              <el-button type="primary" @click="search">查询</el-button>
              <el-button @click="reset('ruleFormRef')">清除'</el-button>
            </div>
          </el-col>
        </div>
        <div class="content-button">
          <el-button type="primary" @click="setSchedule">排班设置</el-button>
          ruleForm - {{ ruleForm}}
        </div>
        <div class="table_wrapper table-wrap">
          <div class="current-time">
            {{ currentShowMonthDate }}
          </div>
          <div class="btn-wrap">
            <el-button :class="['baseBtn', activeMonth ==='prev' ? 'activeBtn':'']" @click="monthChange('prev')"
              :disabled="activeMonth ==='prev'">上个月</el-button>
            <el-button :class="['baseBtn', activeMonth ==='current' ? 'activeBtn':'']" @click="monthChange('current')"
              :disabled="activeMonth ==='current'">本月
            </el-button>
            <el-button :class="['baseBtn', activeMonth ==='next' ? 'activeBtn':'']" @click="monthChange('next')"
              :disabled="activeMonth ==='next'">下个月
            </el-button>
          </div>
          <calendar class="calendar" :width="'100%'" :currentShowMonth="currentShowMonth"
            :calendarData="currentTableData" userType="show">
          </calendar>
        </div>
      </div>
    </div>

    <!-- 设置弹窗  -->
    <ScheduleSetting v-if="showSchedule" :showSchedule="showSchedule" @changeScheduleDialog="changeScheduleDialog">
    </ScheduleSetting>
  </div>
</template>

<script>
import calendar from './components/calendar.vue'
import ScheduleSetting from "./components/ScheduleSetting.vue"
import "@/views/repair/assets/css/repair.css"
export default {
  components: {
    calendar,
    ScheduleSetting,
  },
  name: 'index',
  data() {
    return {
      ruleForm: {
        date: "",
      },
      rules: {
        ],
        date: [
          { required: true, message: '请选择年月份', trigger: ['blur', 'change'] }
        ]
      },
      groupList: [], // 班组列表 数据
      currentMonth: "",// 当前月份
      currentShowMonth: "",// 当前月份的 临时变量
      activeMonth: "current", //当前月份 active
      currentTableData: [], // 当前 查询的tabel数据
      showSchedule: false, // 是否展示排班
    };
  },
  computed: {
    currentShowMonthDate() {
      let resMonth = "";
      if (this.currentShowMonth) {
        let tempArr = this.currentShowMonth.split("-");
        resMonth = tempArr[0] + " 年 " + parseInt(tempArr[1]) + " 月"
      } else {
        resMonth = ""
      }
      return resMonth;
    }
  },
  watch: {
    currentMonth(newV) {
      // console.log("监听", newV);
      this.currentShowMonth = newV;
      this.activeMonth = "current"
    }
  },
  methods: {
    // 上一个月份
    getPreMonth(date) {
      let arr = String(date).split('-');
      let year = arr[0]; //获取当前日期的年份
      let month = arr[1]; //获取当前日期的月份
      let year2 = year;
      let month2 = parseInt(month) - 1;
      if (month2 == 0) {
        year2 = parseInt(year2) - 1;
        month2 = 12;
      }
      if (month2 < 10) {
        month2 = '0' + month2;
      }
      let t2 = year2 + '-' + month2
      return t2;
    },
    // 下一个月份
    getNextMonth(date) {
      var arr = String(date).split('-');
      var year = arr[0]; //获取当前日期的年份
      var month = arr[1]; //获取当前日期的月份
      var year2 = year;
      var month2 = parseInt(month) + 1;
      if (month2 == 13) {
        year2 = parseInt(year2) + 1;
        month2 = 1;
      }
      if (month2 < 10) {
        month2 = '0' + month2;
      }
      var t2 = year2 + '-' + month2;
      return t2;
    },
    // 查询
    search() {
      console.log("查询", this.ruleForm);
      // 校验 班组与年月的必填
      this.$refs.ruleFormRef.validate((valid) => {
        if (valid) {
          this.searchSucObj.date = this.ruleForm.date;
          // 控制月份的展示
          this.currentMonth = this.ruleForm.date;
          this.currentShowMonth = this.ruleForm.date;
          this.activeMonth = "current";
          // 请求 table日历数据
          if (this.ruleForm.date == "2022-04") {
            this.currentTableData = ["2022-04-01", "2022-04-02", "2022-04-03", "2022-04-04", "2022-04-05", "2022-04-08"];
          } else if (this.ruleForm.date == "2022-05") {
            this.currentTableData = ["2022-05-08", "2022-05-09", "2022-05-10", "2022-05-11", "2022-05-13", "2022-05-17"];
          }
          console.log("currentTableData", this.currentTableData);
        } else {
          return false;
        }
      });
    },
    // 清除
    reset(formName) {
      // Object.keys(this.ruleForm).forEach(key => { this.ruleForm[key] = '' })
      this.$refs[formName].resetFields();
      this.currentShowMonth = "";
      this.activeMonth = "current"
      console.log("清除", this.ruleForm);
    },
    // 排班设置
    setSchedule() {
      this.showSchedule = true;
    },
    // 关闭 排班设置
    changeScheduleDialog(flag) {
      this.showSchedule = flag;
    },
    // 月份的改变
    monthChange(type) {
      this.activeMonth = type;
      if (type == "prev") {
        this.currentShowMonth = this.getPreMonth(this.currentMonth);
      } else if (type == "next") {
        this.currentShowMonth = this.getNextMonth(this.currentMonth);
      } else {
        this.currentShowMonth = this.currentMonth
      }
    },

  },
}
</script>
<style  lang="scss" scoped>
  .spotCheck-page {
    height: calc(100% - 20px);
    .content {
      &-top {
        position: relative;
        margin: 10px 0px;
        height: 60px;
        border-bottom: 1px solid #d9d9d9;
      }
      &-button {
        position: relative;
        top: -5px;
        margin-bottom: 10px;
      }
    }
    .table-wrap {
      .current-time {
        font-weight: 900;
        font-size: 18px;
      }
      .btn-wrap {
        position: relative;
        right: 0;
        margin-bottom: 5px;
        text-align: right;
        /deep/ .el-button + .el-button {
          margin-left: 0;
        }
        .baseBtn {
          color: #001e48;
          background: #0e91e3;
        }
        .activeBtn {
          background: #dbdbdb;
        }
      }
    }
  }
</style>

排班日历组件 calendar.vue

<template>
  <div class="calendar">
    <table class="calendar-table" :style="{width}">
      <thead>
        <tr>
          <th v-for="(item, i) in weeks" :key="i">{{ item }}</th>
        </tr>
      </thead>
      <tbody class="tbody">
        <tr v-for="(dates, i) in res" :key="i" :style="{height: tbodyHeight}">
          <td v-for="(item, index) in dates" :key="index"
            :class="{notCurMonth: !item.isCurMonth, currentDay: item.date === curDate, selectDay: item.isSelected, rangeSelectd: item.isRangeSelected, weekend: item.isWeekend}"
            @click="handleItemClick(item, i, index)">
            <span v-if="item.isCurMonth">{{ item.date }}</span>
            <slot :data="item" />
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
/**
 * 参数介绍
 * canSelect:是否禁用,点击table高亮; currentShowMonth:当前月份的值(eg:2022-04);calendarData:当前要展示高亮的日历数据(数组)
 * startOfWeek:开始第一周的起始位置;
 */
import { getDaysInMonth, handleCrateDate,parseTime} from './calendar.js'
export default {
  props: {
    userType: {
      type: String,
      default: "show"
    },
    canSelect: {
      type: Boolean,
      default: false
    },
    currentShowMonth: {
      type: String,
      default: "",
    },
    calendarData: {
      type: Array,
      default() {
        return []
      }
    },
    startOfWeek: {
      type: Number,
      default: 1
    },
    width: {
      type: String,
      default: '70%'
    },
    tbodyHeight: {
      type: String,
      default: '60px'
    }
  },
  data() {
    return {
      weeks: ['一', '二', '三', '四', '五', '六', '日'],
      days: 0, // 当前月总共天数
      curDate: parseTime(new Date().getTime()), // 当前日期 yyyy-MM-dd 格式,用来匹配是否是当前日期
      showDays: [], // 总共展示的42个日期
      res: [], // 二维数组
      selectedDates: [], // 选中的日期
    }
  },
  watch: {
    // 监听当前月份 更新日历数据
    'currentShowMonth': {
      immediate: true,
      handler(newV) {
        if (this.userType == "show") {
          if (!this.calendarData.length) return;
          let curYear = newV.split('-')[0];
          let curMonth = newV.split('-')[1] * 1 - 1; // 月份从0开始的 因此要-1
          this.handleGetDays(curYear, curMonth, this.startOfWeek) // 渲染月份数据
          // console.log("有数据执行", this.showDays);
          // 有后台 日历数据  选中 isSelected
          this.showDays = this.showDays.map(item => {
            this.calendarData.forEach(it => {
              if (it == item.date) {
                item.isSelected = true;
              }
            })
            return item;
          })
        } else if (this.userType = "edit") {
          console.log("编辑的", newV);
          let curYear = newV.split('-')[0];
          let curMonth = newV.split('-')[1] * 1 - 1; // 月份从0开始的 因此要-1
          this.handleGetDays(curYear, curMonth, this.startOfWeek) // 渲染月份数据
          // // 有后台 日历数据  选中 isSelected
          // this.showDays = this.showDays.map(item => {
          //   this.calendarData.forEach(it => {
          //     if (it == item.date) {
          //       item.isSelected = true;
          //     }
          //   })
          //   return item;
          // })
        }
      }
    }
  },
  created() {
    this.weeks.unshift(...this.weeks.splice(this.startOfWeek - 1))
  },
  mounted() {
    // if (localStorage.selectedDates) this.selectedDates = JSON.parse(localStorage.selectedDates)
  },
  methods: {
    // 处理 日期
    handleGetDays(year, month, startOfWeek) {
      this.showDays = []
      this.days = getDaysInMonth(year, month); // 拿到当前月份天数
      let firstDayOfWeek = new Date(`${year}-${month + 1}-01`).getDay() // 开始第一周 从那天排起
      // console.log("开始第一周 从周几排起", firstDayOfWeek);
      // 处理周起始日
      const obj = {
        1: '一',
        2: '二',
        3: '三',
        4: '四',
        5: '五',
        6: '六',
        0: '日'
      }
      const firstDayInCN = obj[firstDayOfWeek]
      const index = this.weeks.indexOf(firstDayInCN)
      // console.log("开始第一周 从周几排起索引", firstDayOfWeek, index)
      if (firstDayOfWeek === 0) { // 星期天为0 星期一为1 ,以此类推
        firstDayOfWeek = 7
      }
      let prevDays = handleCrateDate(year, month - 1, 1, index + 1, 'prev')
      // console.log("上个月prevDays", prevDays);
      let rearDays = handleCrateDate(year, month * 1 + 1, 1, 42 - this.days - (index), 'rear')
      // console.log("下个月rearDays", rearDays);
      let curDays = handleCrateDate(year, month, 1, this.days)
      // console.log("本月curDays", curDays);
      this.showDays.unshift(...prevDays)
      this.showDays.push(...curDays)
      this.showDays.push(...rearDays)
      this.res = this.handleFormatDates(this.showDays)
      //   this.res.splice(5,1)
      // 清除表格的空行
      // let countEmpty=0
      this.res.forEach((item, index) => {
        // console.log(Array.from(item).filter(i=>i.isCurMonth==false))
        if (Array.from(item).filter(i => i.isCurMonth == false).length == 7) {
          this.res.splice(index, 1)
        }
        // console.log(item,index)
      })
      // console.log('输出日历表二维数组', this.res)
    },
    // 传入长度42的原数组,最终转换成二维数组
    handleFormatDates(arr, size = 7) {
      const arr2 = []
      for (let i = 0; i < size; i++) {
        const temp = arr.slice(i * size, i * size + size)
        arr2.push(temp)
      }
      // console.log(arr2)
      return arr2
    },
    // 处理 日历 头部的展示
    handleTableHead(start) {
      const sliceDates = this.weeks.splice(start - 1)
      this.weeks.unshift(...sliceDates)
    },
    // 日历点击事件 
    handleItemClick(item, i, j) {
      // 若是 为展示状态的 则不可点击触发之,否则可以点击勾选选中日历排班
      if (!this.canSelect) return
      console.log('单元格点击', item)
      // console.log('选中的selectedDates', this.selectedDates)
      this.$nextTick(() => {
        this.res[i][j].isSelected = !this.res[i][j].isSelected
        if (this.res[i][j].isSelected) {
          this.selectedDates.push(this.res[i][j].date)
          this.selectedDates = Array.from(new Set(this.selectedDates))
        } else {
          this.selectedDates.splice(this.selectedDates.indexOf(item.date), 1)
        }
        console.log('选中的selectedDates22', this.selectedDates)
        this.$emit('dateSelected', this.selectedDates)
      })
    },
  }
}
</script>

<style scoped lang="scss">
  .calendar {
    display: flex;
    flex-direction: column;
  }
  .calendar-table {
    table-layout: fixed;
    border-collapse: collapse;
    transition: 0.3s;
    thead tr {
      height: 45px;
      line-height: 45px;
    }
    tbody tr {
      &:first-child td {
        border-top: 1px solid #08a8a0;
      }
      td {
        cursor: pointer;
        border-right: 1px solid #08a8a0;
        border-bottom: 1px solid #08a8a0;
        &:first-child {
          border-left: 1px solid #08a8a0;
        }
      }
    }
  }
  .notCurMonth {
    color: #c0c4cc;
  }
  .currentDay {
    background-color: #f8f8de;
  }
  .selectDay {
    color: #fff;
    background-color: #409eff;
  }
  .rangeSelectd {
    color: #606266;
    background-color: #dee2e9;
  }
  .weekend {
    color: #f73131;
  }
</style>

calendar.js

  • 注意点:需要监听 日期数据的 变化 再进行验证
// 获取该月的天数
export const getDaysInMonth = (year, month) => {
  const day = new Date(year, month + 1, 0).getDate()
  // console.log("获取当前年", month, "当前月", month, "的天数", day);
  return day
}

// 创建日期 yyyy-MM-dd 格式, 用于创建非当前月的日期
export const handleCrateDate = (year, month, start, end, type) => {
  const arr = []
  if (type === 'prev') { // 上一月
    if (start === end) return []
    const daysInLastMonth = getDaysInMonth(year, month - 1) // 获取上一个月有多少天
    // console.log(`当前月是${month + 1}月, 上一月${month}月的天数是${daysInLastMonth}天`)
    for (let i = daysInLastMonth - end + 2; i <= daysInLastMonth; i++) {
      arr.push({
        // date: `${month === 0 ? year - 1 : year}-${(month + 1) < 10 ? month === 0 ? 12 : `0${month}` : month}-${i < 10 ? `0${i}` : i}`,
        date: parseTime(new Date(year, month - 1, i)),
        isCurMonth: false,
        isSelected: false,
        isRangeSelected: false
      })
    }
  } else if (type === 'rear') { // 下一月
    for (let i = start; i <= end; i++) {
      arr.push({
        // date: `${month === 11 ? year + 1 : year}-${(month + 1) < 9 ? `0${month + 2}` : month + 2 <= 12 ? month + 2 : (month + 2) % 12 < 10 ? `0${(month + 2) % 12}` : (month + 2) % 12}-${i < 10 ? `0${i}` : i}`,
        date: parseTime(new Date(year, month + 1, i)),
        isCurMonth: false,
        isSelected: false,
        isRangeSelected: false
      })
    }
  } else { // 本月
    for (let i = start; i <= end; i++) {
      arr.push({
        // date: `${year}-${(month + 1) < 10 ? `0${month + 1}` : month + 1}-${i < 10 ? `0${i}` : i}`,
        date: parseTime(new Date(year, month, i)),
        isCurMonth: true,
        isSelected: false,
        isRangeSelected: false
      })
    }
  }
  // console.log(arr)
  return arr
}

// 创建年月时间 函数
export const handleCreateDatePicker = () => {
  const years = []
  const months = []
  for (let i = 1970; i <= 2099; i++) {
    years.push({
      label: `${i}`,
      value: i
    })
  }
  for (let i = 0; i <= 11; i++) {
    months.push({
      label: `${i + 1}`,
      value: i
    })
  }
  return {
    years,
    months
  }
}

/**
 * Parse the time to string
 * @param {(Object|string|number)} time
 * @param {string} cFormat
 * @returns {string | null}
 */
export function parseTime(time, cFormat) {
  if (arguments.length === 0 || !time) {
    return null
  }
  const format = cFormat || '{y}-{m}-{d}'
  let date
  if (typeof time === 'object') {
    date = time
  } else {
    if ((typeof time === 'string')) {
      if ((/^[0-9]+$/.test(time))) {
        // support "1548221490638"
        time = parseInt(time)
      } else {
        // support safari
        // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
        time = time.replace(new RegExp(/-/gm), '/')
      }
    }

    if ((typeof time === 'number') && (time.toString().length === 10)) {
      time = time * 1000
    }
    date = new Date(time)
  }
  const formatObj = {
    y: date.getFullYear(),
    m: date.getMonth() + 1,
    d: date.getDate(),
    h: date.getHours(),
    i: date.getMinutes(),
    s: date.getSeconds(),
    a: date.getDay()
  }
  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
    const value = formatObj[key]
    // Note: getDay() returns 0 on Sunday
    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
    return value.toString().padStart(2, '0')
  })
  return time_str
}
  • 效果
    在这里插入图片描述
;