前情介绍:vue3 fullcalendar实现日历视图创建事项
Demo简省版
考虑到很多人找我要demo代码,但我又不一定能及时回复,所以我决定再详细的列一下我的代码:

<template>
  <div v-loading="loading" class="plan_management">
  <!-- 这里是自定义头部,切换视图类型和切换日期 -->
    <div class="calendarHeader">
      <div class="header_left">
        <h1>{{ type ==='3' ? '计划列表' : calendarTitle }}</h1>
      </div>
      <div class="header_right">
        <span v-if="type!=='3'&&isShowBack" class="blue-color backToday" @click="getToday()">{{ type==='1'?'返回本月':'返回本周' }}</span>
        <el-select v-model="type" placeholder="视图类型" style="width: 80px" size="small" class="header_select" @change="handleChangeType">
          <el-option label="" value="1" />
          <el-option label="" value="2" />
          <el-option label="" value="3" />
        </el-select>
        <!-- 选择月份的日期框 -->
        <el-date-picker v-if="type === '1'" v-model="showMonth" type="month" size="small" :clearable="false" placeholder="请选择日期" style="margin-left: 10px; vertical-align: middle;" @change="changeDate" />
        <el-button-group v-if="type === '2'" style="margin-left: 10px;">
          <el-button size="small" class="el-icon-arrow-left" @click="getPrev()">上一周</el-button>
          <el-button size="small" @click="getNext()">下一周<i class="el-icon-arrow-right" /></el-button>
        </el-button-group>
        <el-select v-model="planCategoryId" placeholder="计划分类" style="width: 120px" class="header_select" size="small" @change="handleChangePlanId">
          <el-option label="全部" value="" />
          <el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id" />
        </el-select>
        <div v-if="categoryList.length" class="separator" />
        <el-tooltip content="类目维护" placement="top" effect="light">
          <el-button size="small" class="el-icon-setting setting_btn" @click="handleSetting()" />
        </el-tooltip>
        <el-button v-if="categoryList.length" size="small" type="primary" class="el-icon-plus" @click="handleAddPlan()"> 新增计划</el-button>
      </div>
    </div>
    <!-- 月视图和周视图显示,列视图显示表格形式 -->
    <div v-show="type !== '3'" ref="fullcalendar" class="card" />
    <el-table
      v-show="type === '3'"
      ref="tableRef"
      v-loading="loading"
      :data="infoList"
      fit
      border
      height="auto"
      size="medium"
      class="dark-table base-table format-height-table"
    >
      <el-table-column label="标题" prop="title " :min-width="110" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="nowrap blue-color" @click="handleClickList(row)">{{ row.title || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="类型" prop="planCategoryName" :minwidth="110" show-overflow-tooltip>
        <template #default="{ row }">
          <span>{{ row.planCategoryName || '--' }}</span>
        </template>
      </el-table-column>
      <el-table-column label="负责人/协作者" prop="managerId " :min-width="110" show-overflow-tooltip>
        <template #default="{ row }">
          {{ row.managerId  }}
        </template>
      </el-table-column>
      <el-table-column label="开始时间" prop="startDate " :minwidth="110" show-overflow-tooltip>
        <template #default="{ row }">
          <span>{{ row.startDate }} {{ row.startDateMinute }}</span>
        </template>
      </el-table-column>
      <el-table-column label="结束时间" prop="endDate " :minwidth="110" show-overflow-tooltip>
        <template #default="{ row }">
          <span>{{ row.endDate }} {{ row.endDateMinute }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" :width="110" fixed="right" class-name="fixed-right">
        <template #default="{ row }">
          <span class="blue-color" @click="handleEdit(row)">编辑</span>
          <span class="blue-color" @click="handleDelete(row)">删除</span>
        </template>
      </el-table-column>
    </el-table>
    <!-- 新建编辑日程 -->
    <DrawerAddPlan :drawer="drawerVisiable" :drawer-type="drawerType" :category-list="categoryAllList" :detail-data="detailData" @closeDrawer="closeDrawer" />
    <!-- 类目维护,我这边有维护类型的概念,这里可以删除 -->
    <DialogCategory :dialog-show="dialogCategory" :detail-list="categoryAllList" @closeDialog="closeDialogCategory" />
    <!-- 查看计划 -->
    <DialogCalendar :dialog-show="dialogCalendar" :detail-info="detailInfo" :category-json="categoryJSON" @closeDialog="closeDialogCalendar" />
  </div>
</template>

<script>
import { reactive, toRefs, ref, onMounted, getCurrentInstance } from 'vue'
import { formatDateFilter, formatDateDay, formatDate, formatCalendar, formatYM, getWeekNumber } from '@/utils/formatTime'
import DrawerAddPlan from './components/drawer-add-plan.vue'
import DialogCategory from './components/dialog-category.vue'
import DialogCalendar from './components/dialog-calendar.vue'
import { Calendar } from '@fullcalendar/core'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import listPlugin from '@fullcalendar/list'
import interactionPlugin from '@fullcalendar/interaction'
import { documentPlanCategoryList, plandetailList, plandetailId, deletePlanDetail, savePlanDetail } from '@/api/planManagement'

export default {
  name: 'PlanManagement',
  components: { DrawerAddPlan, DialogCategory, DialogCalendar},
  setup() {
    const { proxy } = getCurrentInstance()
    const state = reactive({
      calendarTitle: new Date().getFullYear() + '年' + Number(new Date().getMonth() + 1) + '月', // 日历头部显示文字
      dialogVisiable: false,
      showMonth: formatYM(new Date()), // 显示月份
      loading: false,
      isShowBack: false, // 是否显示回到当月或当周
      planCategoryId: '', // 计划分类Id
      type: '1',
      dialogType: '',
      detailInfo: {},
      Tcalendar: null,
      drawerVisiable: false,
      drawerType: '',
      colorJSON: { // 我这里有类别的概念,不同的类别又有不同的颜色
        'green': { title: '#00B578', class: 'green' },
        'red': { title: '#FA5151', class: 'red' },
        'orange': { title: '#FF8F1F', class: 'orange' },
        'yellow': { title: '#FFC300', class: 'yellow' },
        'cyan': { title: '#07B9B9', class: 'cyan' },
        'blue': { title: '#3662EC', class: 'blue' },
        'purple': { title: '#8A38F5', class: 'purple' },
        'magenta': { title: '#EB2F96', class: 'magenta' }
      },
      fullcalendar: ref(),
      detailData: {},
      calendarList: [],
      calendarViewType: {
        1: 'dayGridMonth',
        2: 'timeGridWeek',
        3: 'listMonth'
      },
      nowDate: new Date(),
      dialogCategory: false, // 计划分类弹出窗
      dialogCalendar: false, // 计划详情弹出窗
      infoList: [], // 日历显示的列信息
      categoryJSON: {}, // 计划分类json
      categoryAllList: [], // 全部计划分类
      categoryList: [] // 已启用计划分类
    })
    onMounted(() => {
      initCalendar()
      getPlanCategoryList()
    })
    const initCalendar = () => {
      state.Tcalendar = new Calendar(state.fullcalendar, {
        plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
        initialView: 'dayGridMonth',
        aspectRatio: 2.2,
        locale: 'zh-cn',
        handleWindowResize: true,
        editable: true, // 允许编辑表格
        droppable: true,
        eventDurationEditable: true,
        eventResizableFromStart: true,
        selectable: true, // 允许用户通过单击和拖动来突出显示多个日期或时间段
        firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推。
        unselectAuto: true, // 当点击页面日历以外的位置时,是否自动取消当前的选中状态
        unselectCancel: '.el-drawer',
        dayMaxEvents: true,
        // eventLimit: true,
        headerToolbar: false,
        buttonText: {
          today: '回到今天',
          month: '月',
          week: '周',
          list: '列',
          day: '日'
        },
        allDayText: '全天',
        events: state.infoList,
        eventClassNames: function(arg) { // 添加自定义class
          return [arg.event.extendedProps.class]
        },
        eventContent: function(arg) {
          const italicEl = document.createElement('div')
          if (arg.event.extendedProps.startDateMinute && state.type === '1') {
            const childEl = document.createElement('span')
            childEl.innerHTML = arg.event.extendedProps.startDateMinute
            italicEl.append(childEl)
          }
          italicEl.append(arg.event.title)
          italicEl.setAttribute('class', `plan_title ${arg.event.extendedProps.class}`)
          return { domNodes: [italicEl] }
        },
        eventDrop: function(info) {
          // 拖拽停止时触发
          handleDrap(info)
        },
        eventClick: function(info) {
          // 点击查看时触发
          handleClick(info)
        },
        select: function(info) {
          // 视图选择日期触发
          handleSelectDate(info)
        },
        eventResize: function(info) {
          handleEventResize(info)
        }
      })
      state.Tcalendar.render()
    }
    // 上一月、周、日
    const getPrev = () => {
      state.Tcalendar.prev()
      state.calendarTitle = state.Tcalendar.view.title
      const nowDate = formatDateFilter(state.calendarTitle)
      // 判断已经是当前周隐藏返回当前周按钮
      if ((getWeekNumber(nowDate) === getWeekNumber(new Date())) && (new Date(nowDate).getFullYear() === new Date().getFullYear())) {
        state.isShowBack = false
      } else {
        state.isShowBack = true
      }
      getCalendarList()
    }
    // 下一月、周、日
    const getNext = () => {
      state.Tcalendar.next()
      state.calendarTitle = state.Tcalendar.view.title
      const nowDate = formatDateFilter(state.calendarTitle)
      // 判断已经是当前周隐藏返回当前周按钮
      if ((getWeekNumber(nowDate) === getWeekNumber(new Date())) && (new Date(nowDate).getFullYear() === new Date().getFullYear())) {
        state.isShowBack = false
      } else {
        state.isShowBack = true
      }
      getCalendarList()
    }
    // 回到今天
    const getToday = () => {
      state.Tcalendar.today()
      state.calendarTitle = state.Tcalendar.view.title
      state.isShowBack = false
      state.showMonth = formatYM(new Date())
      getCalendarList()
    }
    // 计划分类列表
    const getPlanCategoryList = () => {
      state.loading = true
      state.categoryList = []
      state.categoryAllList = []
      state.categoryJSON = {}
      documentPlanCategoryList({ page: '1', limit: '-1' }).then(res => {
        state.loading = false
        if (res) {
          res.data.data.forEach(item => {
            state.categoryJSON[item.id] = { lable: item.name, color: item.color, status: item.status }
            state.categoryAllList.push(item)
            if (item.status) {
              state.categoryList.push(item)
            }
          })
          getCalendarList()
        }
      })
    }
    const handleSetting = () => {
      state.dialogCategory = true
    }
    const getCalendarList = () => {
      const params = {
        planCategoryId: state.planCategoryId,
        startDate: state.type === '3' ? '' : formatDateFilter(state.calendarTitle),
        type: state.type
      }
      state.loading = true
      state.Tcalendar.getEventSources().forEach(item => {
        item.remove()
      })
      plandetailList(params).then(res => {
        state.loading = false
        if (res) {
          state.infoList = res.data.data
          state.infoList.forEach(item => {
            item.class = state.colorJSON[state.categoryJSON[item.planCategoryId]?.color]?.class
            item.start = item.startDateMinute ? item.startDate + ' ' + item.startDateMinute : item.startDate
            if ((item.startDate !== item.endDate) || item.isAllDay) {
              item.end = item.endDateMinute ? formatDate(new Date(item.endDate).getTime()) + ' ' + item.endDateMinute : formatDate(new Date(item.endDate).getTime() + 1000 * 60 * 60 * 24)
            } else {
              item.end = item.endDateMinute ? item.endDate + ' ' + item.endDateMinute : item.endDate
            }
          })
          state.Tcalendar.addEventSource(state.infoList)
        }
      })
    }
    // 点击计划查看
    const handleClick = (info) => {
      const detail = info.event._def
      plandetailId(detail.publicId).then(res => {
        if (res) {
          state.detailInfo = res.data.data
          state.dialogCalendar = true
        }
      })
    }
    // 列视图点击查看
    const handleClickList = (row) => {
      plandetailId(row.id).then(res => {
        if (res) {
          state.detailInfo = res.data.data
          state.dialogCalendar = true
        }
      })
    }
    // 删除
    const handleDelete = (row) => {
      proxy.$confirm('是否确认删除', '删除确认', {
        confirmButtonText: '确认删除',
        cancelButtonText: '取消',
        showCancelButton: true,
        closeOnClickModal: false,
        type: 'warning'
      }).then(() => {
        state.loading = true
        deletePlanDetail(row.id).then(function(res) {
          state.loading = false
          if (res) {
            proxy.$message.success('删除成功!')
            getCalendarList()
          }
        })
      }).catch(() => {})
    }
    // 编辑
    const handleEdit = (row) => {
      state.loading = true
      plandetailId(row.id).then(res => {
        state.loading = false
        if (res) {
          state.detailData = res.data.data
          state.drawerType = 'edit'
          state.drawerVisiable = true
        }
      })
    }
    const closeDialog = () => {
      state.dialogVisiable = false
    }
    const closeDialogCategory = (val) => {
      state.dialogCategory = false
      if (val) {
        getPlanCategoryList()
      }
    }
    const closeDialogCalendar = (val) => {
      state.dialogCalendar = false
      if (val?.isRefresh) {
        getCalendarList()
      }
      if (val?.isEdit) {
        // 编辑计量计划
        state.detailData = val.info
        state.drawerType = 'edit'
        state.drawerVisiable = true
      }
    }
    const closeDrawer = (val) => {
      state.drawerVisiable = false
      if (val) {
        getCalendarList()
      }
    }
    // 切换视图类型
    const handleChangeType = (val) => {
      if (val === '1') {
        state.Tcalendar.changeView('dayGridMonth')
        state.showMonth = formatYM(new Date())
      } else if (val === '2') {
        state.Tcalendar.changeView('timeGridWeek')
      } else {
        state.Tcalendar.changeView('listMonth')
      }
      state.isShowBack = false
      state.calendarTitle = state.Tcalendar.view.title
      getToday()
    }
    // 切换类型
    const handleChangePlanId = () => {
      getCalendarList()
    }
    // 新增计划
    const handleAddPlan = () => {
      state.drawerVisiable = true
      state.drawerType = 'add'
      state.detailData = {
        managerIds: [],
        fileList: [],
        isAllDay: 0,
        startDate: formatDate(new Date()),
        endDate: formatDate(new Date()),
        startDateMinute: new Date().getHours() < 23 ? new Date().getHours() + 1 + ':00' : '23:00',
        endDateMinute: new Date().getHours() < 22 ? new Date().getHours() + 2 + ':00' : '23:00'
      }
    }
    // 拖拽计划时触发
    const handleDrap = (info) => {
      const params = { ...info.event.extendedProps, id: info.event.id }
      params.startDate = formatCalendar(info.event.start)
      if (info.event.allDay) {
        // 全天
        params.startDateMinute = ''
        params.endDateMinute = ''
        params.isAllDay = 1
        params.endDate = info.event.end ? formatCalendar(new Date(info.event.end).getTime() - 24 * 3600 * 1000) : formatCalendar(info.event.start)
      } else {
        // 非全天
        params.startDateMinute = formatCalendar(info.event.start, 'hour')
        params.endDateMinute = formatCalendar(new Date(info.event.end), 'hour')
        params.endDate = info.event.end ? formatCalendar(new Date(info.event.end)) : formatCalendar(info.event.start)
        params.isAllDay = 0
      }
      state.loading = true
      savePlanDetail(params).then(res => {
        state.loading = false
        if (res) {
          proxy.$message.success('修改成功!')
          getCalendarList()
        }
      })
    }
    // 调整大小时触发
    const handleEventResize = (info) => {
      const params = { ...info.event.extendedProps, id: info.event.id }
      params.startDate = formatCalendar(info.event.start)
      if (info.event.allDay) {
        // 全天
        params.startDateMinute = ''
        params.endDateMinute = ''
        params.isAllDay = 1
        params.endDate = info.event.end ? formatCalendar(new Date(info.event.end).getTime() - 24 * 3600 * 1000) : formatCalendar(info.event.start)
      } else {
        // 非全天
        params.startDateMinute = formatCalendar(info.event.start, 'hour')
        params.endDateMinute = formatCalendar(new Date(info.event.end), 'hour')
        params.endDate = info.event.end ? formatCalendar(new Date(info.event.end)) : formatCalendar(info.event.start)
        params.isAllDay = 0
      }
      state.loading = true
      savePlanDetail(params).then(res => {
        state.loading = false
        if (res) {
          proxy.$message.success('修改成功!')
          getCalendarList()
        }
      })
    }
    // 拖拽触发
    const handleSelectDate = (info) => {
      if (info.view.type === 'timeGridWeek') {
        // 周视图
        if (info.allDay) {
          state.detailData = {
            startDate: formatCalendar(info.startStr),
            endDate: formatCalendar(new Date(info.endStr).getTime() - 24 * 3600 * 1000),
            managerIds: [],
            fileList: [],
            startDateMinute: '',
            endDateMinute: '',
            isAllDay: 1
          }
        } else {
          state.detailData = {
            startDate: formatCalendar(info.startStr),
            endDate: formatCalendar(info.endStr),
            managerIds: [],
            fileList: [],
            startDateMinute: formatCalendar(info.startStr, 'hour'),
            endDateMinute: formatCalendar(info.endStr, 'hour'),
            isAllDay: 0
          }
        }
      } else {
        // 月视图
        if (info.startStr === formatDate(new Date(info.endStr).getTime() - 24 * 3600 * 1000)) {
          // 只选择一天,默认非全天
          state.detailData = {
            startDate: info.startStr,
            endDate: formatDate(new Date(info.endStr).getTime() - 24 * 3600 * 1000),
            managerIds: [],
            fileList: [],
            startDateMinute: new Date().getHours() < 23 ? new Date().getHours() + 1 + ':00' : '23:00',
            endDateMinute: new Date().getHours() < 22 ? new Date().getHours() + 2 + ':00' : '23:00',
            isAllDay: 0
          }
        } else {
          // 跨天,默认全天
          state.detailData = {
            startDate: info.startStr,
            endDate: formatDate(new Date(info.endStr).getTime() - 24 * 3600 * 1000),
            managerIds: [],
            fileList: [],
            startDateMinute: '',
            endDateMinute: '',
            isAllDay: 1
          }
        }
      }
      state.drawerVisiable = true
      state.drawerType = 'add'
    }
    // 切换月份和日期
    const changeDate = (date) => {
      state.Tcalendar.gotoDate(formatDate(date))
      // 判断不是当前月份,显示返回当前月
      if (date.getMonth() !== new Date().getMonth() || (new Date().getFullYear() !== new Date(date).getFullYear())) {
        state.isShowBack = true
      } else {
        state.isShowBack = false
      }
      state.calendarTitle = state.Tcalendar.view.title
      getCalendarList()
    }
    return {
      ...toRefs(state),
      formatDateFilter,
      changeDate,
      getPrev,
      handleDrap,
      handleAddPlan,
      handleClickList,
      handleDelete,
      handleEdit,
      handleEventResize,
      handleChangePlanId,
      getNext,
      getToday,
      closeDrawer,
      getCalendarList,
      handleSetting,
      handleChangeType,
      closeDialog,
      handleClick,
      closeDialogCategory,
      closeDialogCalendar,
      handleSelectDate,
      formatDateDay,
      formatDate
    }
  }
}
</script>
<style lang="scss" scoped>
.calendarHeader {
  margin: 0 0 20px 0;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  .header_select {
    margin: 0 0 0 10px;
    display: inline-block;
    vertical-align: middle;
  }
  .separator {
    display: inline-block;
    position: relative;
    margin: 0 12px;
    &:after {
      content: '';
      position: absolute;
      top: -16px;
      left: 0;
      height: 24px;
      width: 1px;
      background: #DCDFE6;
    }
  }
}
h1 {
  font-size: 20px;
  font-weight: 500;
  line-height: 32px;
  margin: 0 0 0 0;
  text-align: left;
  vertical-align: middle;
  display: inline-block;
  color: #303133;
}
// .el-button-group {
//   vertical-align: top;
// }

</style>
<style lang="scss" scoped>
@import 'fullcalendar.scss';

</style>

以上是主要页面的代码部分,其中样式在fullcalendar.scss这里,内容是:

.plan_management {
  .fc-col-header {
    height: 32px;
    line-height: 32px;
    background-color: #f5f7fa;
  }
  .fc-theme-standard .fc-scrollgrid {
    border: 0 !important;
  }
  .fc-daygrid-day-top {
    line-height: 18px;
    flex-direction: row !important;
  }
  .fc .fc-button-primary {
    background-color: $background-color;
    color: #606266;
    border-color: #DCDFE6;
  }
  .fc .fc-button-primary:not(:disabled).fc-button-active, .fc .fc-button-primary:not(:disabled):active {
    color: #fff;
    background-color: $tes-primary;
    border-color: $tes-primary;
  }
  .fc .fc-button-primary:not(:disabled).fc-button-active:focus, .fc .fc-button-primary:not(:disabled):active:focus, .fc .fc-button-primary:focus {
    box-shadow: none;
  }
  .fc-theme-standard td, .fc-theme-standard .fc-scrollgrid {
    border: 1px solid #ebeef5;
  }
  .fc-theme-standard td:first-child {
    border-left: 0;
    border-right: 0;
  }
  .fc-theme-standard th {
    border-bottom: 1px solid #ebeef5;
    border-right: 0;
    border-left: 0;
  }
  .fc .fc-button-primary:hover {
    color: $tes-primary;
    background-color: #fff;
    border-color: #DCDFE6;
  }
  .fc .fc-button-primary:disabled {
    color: #fff;
    background-color: $tes-primary;
    border-color: $tes-primary;
  }
  .fc-event-title {
    line-height: 16px;
  }
  .fc .fc-highlight {
    background: $tes-primary2;
  }
  // 周视图
  .fc .fc-timegrid-body {
    .fc-timegrid-slots {
      table tbody tr:nth-of-type(odd) td {
        border-bottom-color: transparent;
      }
    }
  }
  // 列视图
  .fc-theme-standard .fc-list-day-cushion {
    line-height: 20px;
    background-color: #f5f7fa;
  }
  .fc .fc-list-table td {
    line-height: 18px;
  }
  .fc-theme-standard .fc-list {
    border: 1px solid #ebeef5;
  }
  .fc .fc-daygrid-day-number {
    margin: 8px 0 0 8px;
    color: #303133;
    font-weight: 500;
  }
  .fc-scrollgrid-sync-inner {
    color: #303133;
  }
  .fc .fc-daygrid-day.fc-day-today {
    background-color: $tes-primary2;
    .fc-daygrid-day-number {
      background-color: $tes-primary;
      color: #fff;
      border-radius: 4px;
    }
  }
  .fc .fc-col-header-cell-cushion {
    padding: 0;
    user-select: none;
  }
  .fc-timegrid-slot-label-cushion {
    user-select: none;
  }
  .fc-event-title {
    line-height: 20px;
  }
  .fc .fc-daygrid-event {
    margin: 0 0 4px 0;
  }
  .fc-theme-standard .fc-popover-header {
    background-color: #fff;
  }
  .fc .fc-popover {
    z-index: 20;
  }
  .fc .fc-popover-header {
    padding: 20px 20px 14px 20px;
    border-radius: 10px 10px 0 0;
  }
  .fc .fc-popover-title {
    color: $tes-primary;
    font-size: 20px;
  }
  .fc .fc-more-popover .fc-popover-body {
    padding: 0 20px 16px 20px;
    border-radius: 0 0 10px 10px;
  }
  .fc-theme-standard th {
    font-weight: 400;
  }
  .fc-theme-standard .fc-popover {
    border-radius: 4px;
  }
  .fc-daygrid-day-bottom {
    color: $tes-primary;
  }
  .fc .fc-timegrid-col.fc-day-today {
    background-color: $tes-primary2;
  }
  .fc .fc-timegrid-slot {
    height: 48px;
  }
  .fc .fc-timegrid-divider {
    padding: 0;
  }
  .fc .fc-daygrid-body-natural .fc-daygrid-day-events {
    margin-bottom: 3px;
  }
  .fc-event {
    position: relative;
    line-height: 20px;
    padding: 0 0 0 8px;
    &:after {
      content: '';
      width: 4px;
      height: 100%;
      border-radius: 2px 0 0 2px;
      position: absolute;
      top: 0;
      left: 0;
    }
    .plan_title {
      color: #303133;
      max-width: 98%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    span {
      margin-right: 6px;
    }
  }
  
  .green.fc-event {
    &:after {
      background-color: #00B578;
    }
    span {
      color: #00B578;
    }
  }
  .red.fc-event {
    &:after {
      background-color: #FA5151;
    }
    span {
      color: #FA5151;
    }
  }
  .orange.fc-event {
    &:after {
      background-color: #FF8F1F;
    }
    span {
      color: #FF8F1F;
    }
  }
  .yellow.fc-event {
    &:after {
      background-color: #FFC300;
    }
    span {
      color: #FFC300;
    }
  }
  .cyan.fc-event {
    &:after {
      background-color: #07B9B9;
    }
    span {
      color: #07B9B9;
    }
  }
  .blue.fc-event {
    &:after {
      background-color: #3662EC;
    }
    span {
      color: #3662EC;
    }
  }
  .purple.fc-event {
    &:after {
      background-color: #8A38F5;
    }
    span {
      color: #8A38F5;
    }
  }
  .magenta.fc-event {
    &:after {
      background-color: #EB2F96;
    }
    span {
      color: #EB2F96;
    }
  }
  .fc-h-event {
    border-color: transparent;
  }
  .fc-direction-ltr .fc-daygrid-event.fc-event-end, .fc-direction-rtl .fc-daygrid-event.fc-event-start {
    margin-left: 4px;
    margin-right: 4px;
  }
  
  .fc-event.fc-event-draggable {
    overflow: hidden;
  }
  
  .fc-event.fc-daygrid-event {
    transition: all 0.1s;
  }
  
  .fc-event.fc-daygrid-event.green {
    background-color: rgba(0, 181, 120, 0.1);
    &:hover {
      background-color: rgba(0, 181, 120, 0.2);
    }
  }
  .fc-event.fc-daygrid-event.red {
    background-color: rgba(250, 81, 81, 0.1);
    &:hover {
      background-color: rgba(250, 81, 81, 0.2);
    }
  }
  .fc-event.fc-daygrid-event.orange {
    background-color: rgba(255, 143, 31, 0.1);
    &:hover {
      background-color: rgba(255, 143, 31, 0.2);
    }
  }
  .fc-event.fc-daygrid-event.yellow {
    background-color: rgba(255, 195, 0, 0.1);
    &:hover {
      background-color: rgba(255, 195, 0, 0.2);
    }
  }
  .fc-event.fc-daygrid-event.cyan {
    background-color: rgba(7, 185, 185, 0.1);
    &:hover {
      background-color: rgba(7, 185, 185, 0.2);
    }
  }
  .fc-event.fc-daygrid-event.blue {
    background-color: rgba(54, 98, 236, 0.1);
    &:hover {
      background-color: rgba(54, 98, 236, 0.2);
    }
  }
  .fc-event.fc-daygrid-event.purple {
    background-color: rgba(138, 56, 245, 0.1);
    &:hover {
      background-color: rgba(138, 56, 245, 0.2);
    }
  }
  .fc-event.fc-event-draggable.magenta {
    background-color: rgba(235, 47, 150, 0.1);
    &:hover {
      background-color: rgba(235, 47, 150, 0.2);
    }
  }
  .fc-timegrid-event {
    font-size: 14px;
  }
  .fc-event.fc-timegrid-event {
    background-color: #fff;
    border-radius: 0px 6px 6px 0px;
    box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
    padding: 7px 7px 7px 12px;
    height: 100%;
  }
  .fc-v-event {
    border: 0;
  }  
}

以上为这块儿的主要部分,其中新增,删除,编辑的组件不算复杂,都是系统里面普通的组件,没什么特别的地方!

DrawerAddPlan 、DialogCategory(维护类目用的) 、DialogCalendar 为自定义封装的组件
formatDateFilter, formatDateDay, formatDate, formatCalendar, formatYM, getWeekNumber是为了方便显示和格式化时间写的一些方法。
考虑到很多人想直接拿来用,这里把这些东西都放上来

组件部分:
目录地址:
在这里插入图片描述
drawer-add-plan.vue(新增、编辑日程)

<template>
  <el-drawer
    v-model="showDrawer"
    :title="titleJSON[type]"
    direction="rtl"
    :before-close="handleClose"
    :size="744"
    destroy-on-close
    :close-on-click-modal="false"
    custom-class="page-drawer"
    @opened="handleOpened"
  >
    <div v-loading="drawerLoading">
      <el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" class="form-height-auto">
        <el-row :gutter="5">
          <el-col :span="18">
            <el-form-item label="标题:" prop="title">
              <el-input
                ref="inputRef"
                v-model="formData.title"
                v-trim
                maxlength="100"
                placeholder="请输入标题"
                @change="handleModify"
              />
            </el-form-item>
          </el-col>
          <el-col :span="6" class="padding_form">
            <el-form-item prop="planCategoryId">
              <el-select
                v-model="formData.planCategoryId"
                placeholder="请选择计划类型"
                style="width: 100%"
                @change="handleModify"
              >
                <el-option-group v-for="item in categorySelectList" :key="item.label" :label="item.label">
                  <el-option
                    v-for="val in item.group"
                    :key="val.id"
                    :label="val.name"
                    :value="val.id"
                    :disabled="val.status !== 1"
                  >
                    <span style="float: left">{{ val.name }}</span>
                    <span v-if="val.status !== 1" class="fr" style="color: red; font-size: 10px">已停用</span>
                  </el-option>
                </el-option-group>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col v-if="formData.isAllDay === 1" :span="21">
            <el-form-item label="计划时间:">
              <el-date-picker
                v-model="formData.dateArray"
                type="daterange"
                range-separator="至"
                start-placeholder="开始时间"
                end-placeholder="结束时间"
                :shortcuts="shortcuts"
                size="small"
                style="width: 100%"
                @change="handleChangeDate"
              />
            </el-form-item>
          </el-col>
          <!-- 不勾选全天的显示 -->
          <el-col v-else :span="21">
            <el-row :gutter="4">
              <el-col :span="6">
                <el-form-item label="计划时间:" prop="startDate">
                  <el-date-picker
                    v-model="formData.startDate"
                    type="date"
                    placeholder="开始日期"
                    size="small"
                    style="width: 100%"
                    @change="
                      val => {
                        return handleFormat(val, 'startDate');
                      }
                    "
                  />
                </el-form-item>
              </el-col>
              <el-col :span="5" class="padding_form">
                <el-form-item label="" prop="startDateMinute">
                  <el-time-select
                    v-model="formData.startDateMinute"
                    placeholder="开始"
                    start="00:00"
                    step="00:30"
                    end="23:30"
                    :max-time="formData.startDate === formData.endDate ? formData.endDateMinute : ''"
                    class="select-time"
                    style="width: 100%"
                    @change="handleModify"
                  />
                </el-form-item>
              </el-col>
              <el-col :span="2" class="padding_form text-center">
                <el-form-item></el-form-item>
              </el-col>
              <el-col :span="6" class="padding_form">
                <el-form-item prop="endDate">
                  <el-date-picker
                    v-model="formData.endDate"
                    type="date"
                    placeholder="结束日期"
                    size="small"
                    style="width: 100%"
                    @change="
                      val => {
                        return handleFormat(val, 'endDate');
                      }
                    "
                  />
                </el-form-item>
              </el-col>
              <el-col :span="5" class="padding_form">
                <el-form-item prop="endDateMinute">
                  <el-time-select
                    v-model="formData.endDateMinute"
                    placeholder="结束"
                    start="00:00"
                    step="00:30"
                    end="23:30"
                    :min-time="formData.startDate === formData.endDate ? formData.startDateMinute : ''"
                    class="select-time"
                    style="width: 100%"
                    @change="handleModify"
                  />
                </el-form-item>
              </el-col>
            </el-row>
          </el-col>
          <el-col :span="3" class="padding_form">
            <el-form-item prop="isAllDay">
              <el-checkbox
                v-model="formData.isAllDay"
                label="全天"
                size="small"
                :true-label="1"
                :false-label="0"
                style="margin-left: 20px"
                @change="handleChangeCheckbox"
              />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="负责人/协作者:" props="managerId">
              <el-select
                v-model="formData.managerIds"
                multiple
                placeholder="请选择负责人/协作者"
                style="width: 100%"
                size="small"
                @change="handleModify"
              >
                <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-upload
              class="upload-demo"
              :file-list="formData.attachmentVoList"
              :action="uploadAction"
              :headers="headerconfig"
              :on-remove="handleRemove"
              :on-success="uploadSuccess"
              :before-upload="beforeUpload"
              :on-preview="handleClickFile"
            >
              <div class="title-group">
                <el-button size="small" type="primary" @keyup.prevent @keydown.enter.prevent>上传附件</el-button>
                <span class="tip">支持扩展名: .doc .docx .pdf .jpg ...</span>
              </div>
            </el-upload>
          </el-col>
        </el-row>
      </el-form>
      <div class="drawer-footer">
        <el-button type="primary" :loading="drawerLoading" @click="onSubmit">保存</el-button>
        <el-button :loading="drawerLoading" @click="handleClose">取消</el-button>
      </div>
    </div>
  </el-drawer>
</template>

<script>
import { ref, watch, reactive, getCurrentInstance, toRefs } from 'vue';
import { formatDate, formatDateTime } from '@/utils/formatTime';
import { savePlanDetail, deleteFile } from '@/api/planManagement';

export default {
  name: 'DrawerUnit',
  components: {},
  props: {
    drawer: {
      type: Boolean,
      default: false
    },
    drawerType: {
      type: String,
      required: true
    },
    detailData: {
      type: Object,
      default: function () {
        return {};
      }
    },
    categoryList: {
      type: Array,
      default: function () {
        return [];
      }
    }
  },
  emits: ['closeDrawer'],
  setup(props, context) {
    const { proxy } = getCurrentInstance();
    // 抽屉事件
    const showDrawer = ref(props.drawer);
    const state = reactive({
      isModified: false,
      drawerLoading: false,
      inputRef: ref(),
      categorySelectList: [
        {
          label: '可选择',
          group: []
        },
        {
          label: '已停用',
          group: []
        }
      ],
      type: '',
      headerconfig: {},
      titleJSON: {
        add: '新增计划',
        edit: '编辑计划'
      },
      userList: [],
      options: {
        1: '年',
        2: '月'
      },
      shortcuts: [
        {
          text: '近三天',
          value: (() => {
            const end = new Date();
            const start = new Date();
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
            return [start, end];
          })()
        },
        {
          text: '最近一周',
          value: (() => {
            const end = new Date();
            const start = new Date();
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
            return [start, end];
          })()
        },
        {
          text: '最近一个月',
          value: (() => {
            const end = new Date();
            const start = new Date();
            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
            return [start, end];
          })()
        }
      ],
      detailData: {}, // 详情数据
      formRef: ref(),
      uploadAction: '/api-document/document/attachment/upload', // 上传附件的接口
      formData: {
        dateArray: []
      },
      categoryProps: {
        expandTrigger: 'hover',
        checkStrictly: true,
        children: 'children',
        label: 'name',
        value: 'id'
      }
    });
    // 开始日期不能大于结束日期
	function durationDate(rule, value, callback) {
	  let ruleValue = rule.value;
	  if (typeof ruleValue === 'function') {
	    ruleValue = rule.value();
	  } else if (typeof ruleValue === 'object') {
	    ruleValue = rule.value;
	  }
	  if (ruleValue.startDate && ruleValue.endDate) {
	    if (new Date(ruleValue.endDate).getTime() < new Date(ruleValue.startDate).getTime()) {
	      callback(new Error(rule.message));
	    } else {
	      callback();
	    }
	  } else {
	    callback();
	  }
	}
    const formRules = {
      title: [{ required: true, message: '请输入使用标题', trigger: 'change' }],
      planCategoryId: [{ required: true, message: '请选择计划类型', trigger: 'change' }],
      startDate: [
        { required: true, message: '请选择开始日期', trigger: 'change' },
        {
          validator: durationDate,
          value: () => ({
            startDate: state.formData.startDate,
            endDate: state.formData.endDate
          }),
          message: '不能大于结束时间',
          trigger: 'change'
        }
      ],
      startDateMinute: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
      endDate: [
        { required: true, message: '请选择结束日期', trigger: 'change' },
        {
          validator: durationDate,
          value: () => ({
            startDate: formatDate(state.formData.startDate),
            endDate: formatDate(state.formData.endDate)
          }),
          message: '不能小于开始日期',
          trigger: 'change'
        }
      ],
      endDateMinute: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
    };
    // 关闭抽屉
    const handleClose = () => {
      if (state.isModified) {
        proxy
          .$confirm('确认离开当前页面吗?离开后数据不可恢复', {
            confirmButtonText: '确认',
            cancelButtonText: '取消',
            type: 'warning',
            showClose: false,
            closeOnClickModal: false,
            closeOnPressEscape: false
          })
          .then(() => {
            showDrawer.value = false;
            state.isModified = false;
            context.emit('closeDrawer', false);
          })
          .catch(() => {});
      } else {
        showDrawer.value = false;
        context.emit('closeDrawer', false);
      }
    };
    // 格式化日期
    const handleChangeDate = val => {
      if (val?.length) {
        state.formData.startDate = formatDate(val[0]);
        state.formData.endDate = formatDate(val[1]);
      } else {
        state.formData.startDate = '';
        state.formData.endDate = '';
      }
    };
    watch(props, newValue => {
      showDrawer.value = newValue.drawer;
      if (showDrawer.value) {
        state.categorySelectList[0].group = [];
        state.categorySelectList[1].group = [];
        props.categoryList.forEach(item => {
          if (item.status) {
            state.categorySelectList[0].group.push(item);
          } else {
            state.categorySelectList[1].group.push(item);
          }
        });
        state.isModified = false;
        state.type = props.drawerType;
        if (props.drawerType === 'add') {
          initDetail(props.detailData);
        } else {
          getDetail(props.detailData);
        }
      }
    });
    const handleOpened = () => {
      if (state.inputRef) {
        state.inputRef.focus();
      }
    };
    const handleModify = () => {
      state.isModified = true;
    };
    const initDetail = newData => {
      state.formData = {
        ...newData,
        dateArray: [newData.startDate, newData.endDate],
        planCategoryId: state.categorySelectList[0].group[0]?.id,
        attachmentVoList: []
      };
    };
    const getDetail = newData => {
      const detailInfo = JSON.parse(JSON.stringify(newData));
      detailInfo.attachmentVoList.forEach(item => {
        item.name = item.fileName;
        item.id = item.fileId;
      });
      detailInfo.fileIdList = detailInfo.attachmentVoList.filter(item => {
        return item.fileId;
      });
      state.formData = {
        ...detailInfo,
        dateArray: [detailInfo.startDate, detailInfo.endDate],
        managerIds: detailInfo.managerId.split(',')
      };
    };
    // 新增、编辑
    const onSubmit = () => {
      proxy.$refs['formRef'].validate(valid => {
        if (valid) {
          state.drawerLoading = true;
          const params = {
            ...state.formData,
            fileIdList: state.formData.attachmentVoList.map(item => {
              return item.id;
            }),
            managerId: state.formData.managerIds.toString()
          };
          delete params.managerIds;
          delete params.attachmentVoList;
          delete params.dateArray;
          savePlanDetail(params).then(res => {
            state.drawerLoading = false;
            if (res) {
              state.isModified = false;
              context.emit('closeDrawer', true);
              proxy.$message.success('保存成功');
            }
          });
        } else {
          return false;
        }
      });
    };
    const uploadSuccess = (res, file, fileList) => {
      if (res.code === 200) {
        state.formData.attachmentVoList.push({ name: file.name, id: res.data[0] });
      } else {
        proxy.$message.error(res.message);
      }
    };
    const beforeUpload = file => {
      const fileSize = file.size / 1024 / 1024 < 20;
      if (!fileSize) {
        proxy.$message.error('上传附件大小不能超过20M');
        return false;
      } else if (file.size === 0) {
        proxy.$message.error('上传附件大小不能为空');
        return false;
      } else {
        return true;
      }
    };
    const handleRemove = (file, fileList) => {
      const fileId = file.id || file?.response?.data[0];
      if (fileId) {
        state.drawerLoading = true;
        deleteFile(fileId).then(res => {
          state.drawerLoading = false;
          if (res) {
            state.formData.attachmentVoList = state.formData.attachmentVoList.filter(item => {
              return item.id !== fileId;
            });
            proxy.$message.success('附件删除成功!');
            return true;
          } else {
            return false;
          }
        });
      }
    };
    const getContent = value => {
      state.formData.description = value;
    };
    // 切换全天和半天
    const handleChangeCheckbox = val => {
      if (val) {
        if (state.formData.startDate) {
          state.formData.dateArray = [state.formData.startDate, state.formData.endDate];
        } else {
          state.formData.dateArray = [];
        }
        state.formData.startDateMinute = '';
        state.formData.endDateMinute = '';
      } else {
        state.formData.dateArray = [];
      }
      handleModify();
    };
    // 格式化日期
    const handleFormat = (val, fieldName) => {
      if (val) {
        state.formData[fieldName] = formatDate(val);
      }
      handleModify();
    };
    // 点击附件
    const handleClickFile = file => {
      // console.log(file)
    };
    return {
      ...toRefs(state),
      getDetail,
      formRules,
      handleOpened,
      handleClickFile,
      handleFormat,
      handleChangeCheckbox,
      handleRemove,
      beforeUpload,
      uploadSuccess,
      getContent,
      handleModify,
      handleChangeDate,
      onSubmit,
      handleClose,
      formatDate,
      formatDateTime,
      showDrawer
    };
  }
};
</script>

<style lang="scss" scoped>
:deep(.el-input--medium) {
  line-height: 1;
}

.title-group {
  display: flex;
  align-items: center;
  gap: 10px;
  .tip {
    line-height: 20px;
    font-size: 12px;
    color: $tes-font2;
  }
}

.padding_form {
  padding: 32px 0 0 0;
}

.select-time {
  :deep(.el-icon-time) {
    line-height: 31px;
  }
}
:deep(.el-form .el-form-item .el-form-item__label) {
  color: $tes-font;
}
.drawer-footer {
  width: calc(100% - 80px);
  background: #fff;
}
</style>

dialog-calendar.vue内容:

<template>
  <el-dialog
    v-model="dialogVisiable"
    title="查看计划"
    :modal="false"
    :close-on-click-modal="true"
    :width="500"
    custom-class="calendar_plan_dialog"
    @close="handleClose"
  >
    <div v-if="dialogVisiable" v-loading="dialogLoading">
      <div :class="`fc-event dialog_header ${categoryJson[formData.planCategoryId]?.color}`">
        <div class="dialog_title">
          {{ formData.title || '--' }}
          <div class="dialog_icon">
            <i v-if="isCanEdit" class="el-icon-edit" @click="handleEdit()" />
            <i class="el-icon-delete" @click="handleDelete()" />
          </div>
        </div>
      </div>
      <el-form
        ref="formRef"
        :model="formData"
        :rules="ruleInfo"
        label-position="left"
        size="small"
        label-width="110px"
        class="my-form"
      >
        <el-row>
          <el-col :span="24">
            <el-form-item prop="managerId" label="负责人/协作者:">
              <span>{{ formData.managerId}}</span>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item prop="planCategoryName" label="类型:">
              <span>{{ formData.planCategoryName }}</span>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item prop="title" label="计划时间:">
              <span
                >{{ formData.startDate }} {{ formData.startDateMinute }} ~ {{ formData.endDate }}
                {{ formData.endDateMinute }}</span
              >
            </el-form-item>
          </el-col>
          <el-divider />
          <el-col :span="24">
            <div class="form_item_header">
              <div class="label">描述:</div>
              <span v-if="formData.description" class="font-sm" @click="isShowAll = !isShowAll">{{
                isShowAll ? '收起详情' : '展开详情'
              }}</span>
            </div>
            <div
              :class="`form_item_content ${isShowAll ? 'packDown' : 'packUp'} ${formData.description ? '' : 'empty'} ${
                formData.attachmentVoList.length ? 'margin_Bot' : ''
              }`"
              v-html="formData.description ? formData.description : '暂无描述'"
            />
            <div v-for="item in formData.attachmentVoList" :key="item.fileId" class="fileItem" @click="downLoad(item)">
              {{ item.fileName }}
            </div>
          </el-col>
          <el-divider />
          <el-col :span="24">
            <div class="form-item_flex">
              <el-form-item prop="createTime" label="创建时间:" label-width="70px">
                <span>{{ formatDateTime(formData.createTime) }}</span>
              </el-form-item>
              <el-form-item prop="createBy" label="创建人:" label-width="60px">
                {{ formData.createBy }}
              </el-form-item>
            </div>
          </el-col>
        </el-row>
      </el-form>
    </div>
  </el-dialog>
</template>
<script>
import { reactive, ref, toRefs, watch, getCurrentInstance } from 'vue';
import { formatDate, formatDateTime } from '@/utils/formatTime';
import { deletePlanDetail } from '@/api/planManagement';
export default {
  name: 'DialogCalendar',
  components: {},
  props: {
    dialogShow: {
      type: Boolean,
      default: false
    },
    detailInfo: {
      type: Object,
      default: () => {}
    },
    categoryJson: {
      type: Object,
      default: () => {}
    }
  },
  emits: ['closeDialog'],
  setup(props, context) {
    const { proxy } = getCurrentInstance();
    const state = reactive({
      formData: {
        attachmentVoList: []
      },
      formRef: ref(),
      dialogTitle: '新增日程',
      isShowAll: false,
      type: '',
      isAllDay: false, // 是否是全天
      colorJSON: {
        绿色: 'green',
        红色: 'red',
        橙色: 'orange',
        黄色: 'yellow',
        青色: 'cyan',
        蓝色: 'blue',
        紫色: 'purple',
        品红色: 'magenta'
      },
      categoryJson: {},
      nameList: [],
      dialogLoading: false,
      ruleInfo: {},
      isCanEdit: false, // 是否可以编辑
      differences: 0, // 开始时间结束时间相差天数
      isEdit: false,
      dialogVisiable: false,
      ruleForm: ref(),
      inputRef: ref(),
      loading: false
    });
    watch(props, newValue => {
      state.dialogVisiable = newValue.dialogShow;
      if (state.dialogVisiable) {
        state.categoryJson = props.categoryJson;
        state.isCanEdit = Object.values(state.categoryJson).some(item => {
          return item.status === 1;
        });
        state.formData = props.detailInfo;
      }
    });
    // 下载附件
    const downLoad = fileItem => {
      window.open(fileItem.fileUrl);
    };
    // 删除计划
    const handleDelete = () => {
      proxy
        .$confirm('是否确认删除', '删除确认', {
          confirmButtonText: '确认删除',
          cancelButtonText: '取消',
          showCancelButton: true,
          closeOnClickModal: false,
          type: 'warning'
        })
        .then(() => {
          state.dialogLoading = true;
          deletePlanDetail(state.formData.id).then(function (res) {
            state.dialogLoading = false;
            if (res) {
              proxy.$message.success('删除成功!');
              state.dialogVisiable = false;
              context.emit('closeDialog', { isRefresh: true });
            }
          });
        })
        .catch(() => {});
    };
    // 编辑计划
    const handleEdit = () => {
      state.dialogVisiable = false;
      context.emit('closeDialog', { isRefresh: false, isEdit: true, info: state.formData });
    };
    // 关闭弹出窗
    const handleClose = () => {
      context.emit('closeDialog');
    };
    return {
      ...toRefs(state),
      downLoad,
      handleDelete,
      handleEdit,
      handleClose,
      formatDate,
      formatDateTime
     };
  }
};
</script>
<style lang="scss" scoped>
.fileItem {
  display: inline-block;
  cursor: pointer;
}
.fc-event {
  background: #fff;
  line-height: 22px;
  font-size: 18px;
  box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
  border-radius: 6px;
  margin: 0 0 20px 0;
}
.form_item_content.packUp {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
.form_item_content.packDown {
  max-height: 15vh;
  overflow-y: auto;
}
.form_item_content.margin_Bot {
  margin-bottom: 16px;
}

.form_item_content.empty {
  text-align: center;
  color: $tes-font2;
}

.dialog_title {
  display: flex;
  justify-content: space-between;
  padding: 12px 12px 12px 18px;
  color: #606266;
  align-items: center;
}
.dialog_icon {
  display: flex;
  gap: 6px;
  cursor: pointer;
  user-select: none;
  color: $tes-font1;
  i {
    width: 24px;
    height: 24px;
    font-size: 14px;
    padding: 5px;
    border-radius: 3px;
    &:hover {
      background: $tes-border2;
    }
  }
}
.form_item_header {
  line-height: 32px;
  color: #909399;
  .label {
    display: inline-block;
  }
  span {
    float: right;
    cursor: pointer;
    user-select: none;
    &:hover {
      color: $tes-primary;
    }
  }
}
.form-item_flex {
  display: flex;
  align-items: center;
  gap: 20px;
}

.el-form .el-form-item {
  margin: 0;
}
.el-divider--horizontal {
  margin: 14px 0;
}
.el-dialog .el-dialog__body .el-form-item__label {
  color: #909399;
}
</style>
<style lang="scss">
@import '../fullcalendar.scss';
.calendar_plan_dialog {
  border-radius: 10px;
  border: 1px solid #dcdfe6;
  box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15);
  .el-dialog__header {
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
    background: #f0f2f5;
  }
  .el-dialog__body {
    border-bottom-left-radius: 10px;
    border-bottom-right-radius: 10px;
    background: linear-gradient(180deg, #f0f2f5 34px, #fff 35px);
    padding: 10px 20px 20px 20px !important;
    .el-form-item__label {
      color: #909399;
    }
  }
  .form_item_content {
    line-height: 20px;
    p,
    ul,
    li {
      margin: 0;
      padding: 0;
    }
  }
}
</style>

一些格式化时间日期的函数

// 过滤年月日
export function formatDateFilter(time) {
  let timeResult;
  if (time.indexOf('日') > -1) {
    timeResult = time.replace(//g, '-').replace(//g, '-').split('日')[0];
    timeResult = formatDate(timeResult);
  } else {
    timeResult = time.replace(//g, '-').replace(//g, '');
    timeResult = formatYM(timeResult);
  }
  return timeResult;
}

// 过滤只显示月日, date:只显示日期,month:只显示月份,不传全部显示
export function formatDateDay(time, type) {
  var date = new Date();
  if (time) {
    date = new Date(time);
  }
  const month = date.getMonth() + 1;
  const day = date.getDate();
  // if ((date.getMonth() + 1) < 10) {
  //   month = '0' + month
  // }
  if (type === 'date') {
    return day;
  } else if (type === 'month') {
    return month;
  } else {
    return month + '月' + day + '日';
  }
}

// 过滤年月日,包括星期几
export function formatDate(time, num, text) {
  var date = new Date();
  var time_str = '';
  var show_day = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
  if (time) {
    date = new Date(time);
  } else {
    return time;
  }
  let month = date.getMonth() + 1;
  let day = date.getDate();
  if (date.getMonth() + 1 < 10) {
    month = '0' + month;
  }
  if (date.getDate() < 10) {
    day = '0' + day;
  }

  if (num === 1) {
    time_str = date.getFullYear() + '年' + month + '月' + day + '日 ' + show_day[date.getDay()];
  } else {
    if (text) {
      time_str = date.getFullYear() + '年' + month + '月' + day + '日';
    } else {
      time_str = date.getFullYear() + '-' + month + '-' + day;
    }
  }
  if (time_str === 'NaN-NaN-NaN') {
    return '';
  } else {
    return time_str;
  }
}

// 过滤显示几点几分
export function formatCalendar(time, type) {
  let timeStr;
  var date = formatDateTime(time);
  if (type === 'hour') {
    timeStr = date.split(' ')[1].split(':')[0] + ':' + date.split(' ')[1].split(':')[1];
  } else {
    timeStr = date.split(' ')[0];
  }
  return timeStr;
}

// 过滤年月
export function formatYM(time) {
  var date = new Date();
  if (time) {
    date = new Date(time);
  }
  let month = date.getMonth() + 1;
  if (date.getMonth() + 1 < 10) {
    month = '0' + month;
  }
  return date.getFullYear() + '-' + month;
}

// 获取当前日期是一年的第几周
export function getWeekNumber(date) {
  const nowDate = new Date(date);
  const startOfYear = new Date(nowDate.getFullYear(), 0, 1);
  const startOfWeek = new Date(startOfYear);
  // 计算当前时间与一年的开始周的时间差,单位为毫秒
  const diff = nowDate.getTime() - startOfWeek.getTime();
  // 计算时间差对应的周数
  const currentWeek = Math.ceil(diff / (7 * 24 * 60 * 60 * 1000));
  return currentWeek;
}
GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐