美文网首页让前端飞js css htmlweb前端技术分享
vue3 中 element-plus Calendar 实现自

vue3 中 element-plus Calendar 实现自

作者: 阿巳交不起水电费 | 来源:发表于2023-04-05 09:53 被阅读0次

    效果图:

    image.png

    版本与依赖:注意这里引入了webpack-merge

    image.png

    大概思路如下

    1.修改日历周一至周五的文本:

    解决办法:使用Config Provider

    2.自定义日历头部:

    首先element-plus官网提供了demo,但是我未使用这种方式,经测试发现这样不能通过顶部的日期组件选择日期

    image.png

    解决办法:使用dayjs自己实现日期快捷切换,这里把这部分逻辑抽离到myCalendar.ts中

    image.png
    注意Element-plus (opens new window)组件库默认支持 dayjs 进行日期时间处理,所以可以直接导入使用。

    3.使用date-cell 的 scoped-slot 来自定义日历单元格,从而接入拖拽事件

    4.使用 data.type === 'current-month'实现只显示当月日期

    5.若你的element-plus版本使用#date-cell不能实现自定义日历单元格,可改为#dateCell试下

    完整代码如下:

    index.vue

    <template>
      <div class="panel-content">
        <ELPlusLanguageConfig :local="local">
                <el-calendar :model-value="new Date(currentMonth)">
                  <template #header="{ date }">
    
                    <div class="button-box">
                      <div class="control-list">
                        <el-button round size="small" @click="selectDate('prev-year')">上一年</el-button>
                        <el-button round size="small" @click="selectDate('prev-month')">上一月</el-button>
                        <el-date-picker class="date-picker" v-model="currentMonth" :value-format="valueFormat" type="month"
                          placeholder="选择月份" />
                        <el-button round size="small" @click="selectDate('next-month')">下一月</el-button>
                        <el-button round size="small" @click="selectDate('next-year')">下一年</el-button>
                        <el-button round size="small" @click="selectDate('today')">今天</el-button>
                      </div>
                      <div>
                        <!-- <el-button type="primary" size="small">导入</el-button> -->
                      </div>
                    </div>
    
                  </template>
                  <template #dateCell="{ data }">
                    <div :class="{ 'dayBox': true }">
                      <div v-if="isCurrentMonth(data)" :class="{ 'dargIn': retData.dragIn === data.day }"
                        @click.stop="handleClick(data)" @drop="onDrop" @dragover="$event => onDragover($event, data)">
                        <h1>{{ formatDay(data) }}</h1>
                        <!-- 删除 -->
                        <el-icon class="del" @click.stop="del(data)">
                          <Delete />
                        </el-icon>
                      </div>
                    </div>
                  </template>
                </el-calendar>
              </ELPlusLanguageConfig>
    
              <div class="right-box">
                <div class="d-title">选择值班人员</div>
                <div class="content-box">
    
                  <div class="t-one" v-for="(item, index) in retData.people" :key="index">
                    <!-- 组 -->
                    <div class="draggable" v-if="item.type" :draggable="true" @dragstart="onDragStart(item)">
                      {{ item.name }}
                      <p><span v-for="(sub, subIndex) in item.children" :key="subIndex">{{ sub.name }}</span></p>
                    </div>
                    <!-- 未分组 -->
                    <div v-else>
                      {{ item.name }}
                      <p>
                        <span class="draggable" v-for="(sub, subIndex) in item.children" :key="subIndex" :draggable="true"
                          @dragstart="onDragStart(sub)">{{ sub.name }}</span>
                      </p>
                    </div>
                  </div>
    
                </div>
    
              </div>
    
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref, reactive } from 'vue'
    import ELPlusLanguageConfig from '../components/ELPlusLanguageConfig.vue'
    import { useCalendar } from '../components/myCalendar'
    
    type calendarDateCell = { type: 'prev-month' | 'current-month' | 'next-month', isSelected: boolean, day: string, date: Date }
    
    // 日历相关 start --------
    let valueFormat = 'YYYY-MM'
    const { currentMonth, selectDate, local } = useCalendar(valueFormat)
    
    // 日期格式化显示
    const formatDay = ({ day }:calendarDateCell) => {
      return Number(day.split('-')[2])
    }
    // 判断是否是当前月
    const isCurrentMonth = (data:calendarDateCell) => {
      return data.type === 'current-month'
    }
    
    const handleClick = (data:calendarDateCell) => {
      console.log(data, 'click')
    }
    
    // 删除
    const del = (data:calendarDateCell) => {
      console.log(data, 'del')
    }
    
    // 日历相关 end --------
    
    // 拖拽相关 start ------- 
    
    const retData = reactive({
      people: [
        {
          type: 'group',
          name: '组1',
          children: [
            {
              name: '李xx'
            },
            {
              name: '李bbb'
            }
          ]
        },
        {
          type: 'group',
          name: '组2',
          children: [
            {
              name: '李xx2'
            },
            {
              name: '李xxx3'
            }
          ]
        },
        {
          name: '未分配人员',
          children: [
            {
              name: '张三'
            },
            {
              name: '李四'
            }
          ]
        }
      ],
      dragIn: null, // 拖入的盒子,设置样式使用
      dragData: {} // 拖入的数据
    })
    
    const onDragStart = (data:any) => {
      console.log('onDragStart', data)
      retData.dragData = data
    }
    
    const onDragover = (e:DragEvent, data:any) => {
      // console.log('onDragover', e)
      e.preventDefault();
      retData.dragIn = data.day
    }
    const onDrop = (ev:DragEvent) => {
      // console.log('onDrop', ev)
      console.log('onDrop', retData.dragIn, retData.dragData)
      retData.dragIn = null
    }
    
    // 拖拽相关 end -------
    
    </script>
    
    <style lang="less" scoped>
    
    .panel-content {
      background-color: #122645;
      display: flex;
      justify-content: space-between;
      align-items: stretch;
      height: 100%;
      color: #ffffff;
    
      .button-box {
        width: 100%;
        display: flex;
        justify-content: space-between;
        align-items: center;
    
        .control-list {
          display: flex;
          justify-content: flex-start;
          align-items: center;
    
          button {
            height: 34px;
            width: 88px;
            font-size: 16px;
            border: none;
            transition: all .3s;
          }
    
          :deep(.date-picker) {
            width: 188px;
            height: 40px;
            margin: 0 15px;
          }
        }
    
    
      }
    
      .dayBox {
        position: relative;
        height: 100%;
        width: 100%;
        font-size: 30px;
        font-family: MicrosoftYaHei-Bold-, MicrosoftYaHei-Bold;
        cursor: default;
    
        >div {
          height: 100%;
        }
    
        h1 {
          padding: 10px 16px 0 16px;
          font-size: 30px;
          margin: 0;
        }
    
        .del {
          cursor: pointer;
          position: absolute;
          bottom: 9px;
          right: 9px;
          font-size: 16px;
    
          &:hover {
            color: #0d84ff;
          }
        }
      }
    
      .right-box {
        width: 729px;
        flex: none;
        background-color: rgba(45, 103, 254, 0.12);
        margin-top: 64px;
    
        .d-title {
          background-color: #1C4294;
          font-size: 18px;
          padding: 16px 38px;
        }
    
        @PADDING: 15px;
    
        .content-box {
          padding: 0 (38px - @PADDING);
          overflow-y: auto;
          box-sizing: border-box;
          max-height: calc(100vh - 300px);
          ;
        }
    
        .t-one {
          margin-top: 44px - 2*@PADDING;
    
          &:first-child {
            margin-top: 44px - @PADDING;
          }
    
          >div {
            padding: @PADDING;
          }
    
    
          p {
            display: flex;
            justify-content: flex-start;
            flex-wrap: wrap;
            flex-direction: row;
            margin: 0;
          }
    
          span {
            background-color: #122645;
            width: 138px;
            height: 40px;
            line-height: 40px;
            padding: 0 16px;
            border: 1px solid #196BD1;
            margin-right: 13px;
            margin-top: 17px;
          }
        }
      }
    }
    
    .draggable {
      cursor: move;
    
      &:hover {
        outline: 1px dashed #196BD1;
        box-shadow: 0px 0px 20px 1px rgb(25, 107, 209,0.5);
      }
    }
    
    .dargIn {
      outline: 1px dashed #196BD1;
    }
    </style>
    
    <!-- 全局样式 -->
    <style lang="less">
    @themeColor:#ffffff;
    .el-calendar {
      background-color: transparent;
    
      .el-calendar__header {
        margin-bottom: 0;
        padding-bottom: 0;
      }
    
      .el-calendar__body {
        padding-top: 5px;
        padding-bottom: 0;
      }
    
      .el-calendar-day {
        padding: 0;
        // border: 1px solid #b4bbc5;
        border-radius: 4px;
        height: 126px;
        background-color: #081827;
      }
    
      // 其他月隐藏
      // .el-calendar-table:not(.is-range) td.prev,
      // .el-calendar-table:not(.is-range) td.next {
      //   color: transparent;
      // }
    
      // 顶部周一、周日
      .el-calendar-table thead {
        background-color: #061529;
    
        th {
          font-weight: normal;
          color: @themeColor;
          font-size: 18px;
          padding: 10px 0;
        }
      }
    
      .el-calendar-table tr td:first-child {
        border-left: none;
      }
    
      .el-calendar-table tr td:last-child {
        border-right: none;
      }
    
      .el-calendar-table td.is-today {
        color: @themeColor;
    
        .el-calendar-day {
          background-color: rgba(45, 103, 254, 0.35);
        }
      }
    
      .el-calendar-table__row:last-child {
        td {
          border-bottom: none;
        }
      }
    
      // hover
      .el-calendar-table .el-calendar-day:hover {
        // .dayBox{
        //   background-color: rgba(102, 177, 255,0.5);
        // }
      }
    
      // 选中项设置
      .el-calendar-table td.is-selected {
        background-color: transparent;
    
        .el-calendar-day {
          border-color: #0d84ff;
        }
      }
    }
    
    </style>
    

    ELPlusLanguageConfig.vue

    <!-- 给elementplus 单独设置国际化内容 -->
    <template>
        <el-config-provider :locale="locale">
            <slot></slot>
        </el-config-provider>
    </template>
    
    <script setup lang="ts">
    
    // import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
    // import en from 'element-plus/dist/locale/en.mjs'
    
    import zhCn from 'element-plus/lib/locale/lang/zh-cn';
    // import en from 'element-plus/lib/locale/lang/en';
    import { merge } from 'webpack-merge'; 
    import {reactive,watch,ref} from 'vue'
    
    const props = defineProps({
        local: { // 国际化
            type: Object,
            default() {
                return {}
            }
        }
    })
    const locale = ref({})
    watch(()=>props.local,(newVal,oldVal)=>{
        locale.value = merge({}, zhCn, newVal)
    },{
        immediate:true
    })
    </script>
    
    <style scoped></style>
    

    myCalendar.ts

    import { ref, reactive } from 'vue'
    import {dayjs} from "element-plus"
    /**
     * 日历切换,快捷时间切换改为结合dayjs实现
     * @param {*} valueFormat 
     * @returns 
     */
    export function useCalendar(valueFormat = 'YYYY-MM') {
        // 修改element-plus日历文案
        const local = {
            el: {
                datepicker: {
                    weeks: {
                        fri: "周五",
                        mon: "周一",
                        sat: "周六",
                        sun: "周日",
                        thu: "周四",
                        tue: "周二",
                        wed: "周三",
                    }
                }
            }
        }
    
        // 日期
        const currentMonth = ref(dayjs(new Date()).format(valueFormat))
        // 这里没有使用element-plus案例中的方式【因为没有同时生效】,这里改为自己实现快捷切换
        const selectDate = (val:'prev-year'|'prev-month'|'next-month'|'next-year'|'today') => {
            let ploy = {
                // 上一年
                'prev-year': () => {
                    currentMonth.value = dayjs(currentMonth.value).add(-1, 'year').format(valueFormat)
                },
                // 上一月
                'prev-month': () => {
                    currentMonth.value = dayjs(currentMonth.value).add(-1, 'month').format(valueFormat)
                },
                // 下一月
                'next-month': () => {
                    currentMonth.value = dayjs(currentMonth.value).add(1, 'month').format(valueFormat)
                },
                // 下一年
                'next-year': () => {
                    currentMonth.value = dayjs(currentMonth.value).add(1, 'year').format(valueFormat)
                },
                // 今天
                'today': () => {
                    currentMonth.value = dayjs(new Date()).format(valueFormat)
                },
            }
    
            ploy[val]()
        }
        return {
            local,
            currentMonth, // 日历组件上的modelValue值,也是日期组件的-model值
            selectDate // 快捷切换时间的事件
        }
    }
    

    若对你有帮助,请点个赞吧,若能打赏不胜感激,谢谢支持!
    本文地址:https://www.jianshu.com/p/ac88cabc0af5?v=1680746028292,转载请注明出处,谢谢。

    参考:
    element-plus日历组件

    相关文章

      网友评论

        本文标题:vue3 中 element-plus Calendar 实现自

        本文链接:https://www.haomeiwen.com/subject/oyudddtx.html