实现效果

安装Fullcalendar相关插件
npm install @fullcalendar/core @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/list @fullcalendar/interaction --save
- 代码中使用到了时间转换和element-plus,安装dayjs和element-plus
npm install element-plus @element-plus/icons-vue dayjs
- 样式sass
npm install sass sass-loader
实现代码
注意:月份中想要横向拖拽几天日期,配置项属性中必须allDaySlot为true,默认为true。同时,周页面中这个日期是全天才可以横向拖拽几天日期
<template>
  <div class="calender-container">
    <el-card>
      <!-- 自定义头部,切换视图类型和切换日期 -->
      <div class="calender-header mb2">
        <div class="header-left">
          <span class="time-title">{{ currentDate }}</span>
          <el-button
            :icon="ArrowLeftBold"
            circle
            @click="
              Tcalendar.prev();
              dayTime();
            "
          />
          <el-button
            :icon="ArrowRightBold"
            circle
            @click="
              Tcalendar.next();
              dayTime();
            "
          />
        </div>
        <div class="header-right">
          <el-button
            class="btn-m2"
            type="primary"
            @click="
              Tcalendar.today();
              dayTime();
            "
            plain
            round
            >今天</el-button
          >
          <el-select
            v-model="type"
            placeholder="视图类型"
            style="width: 80px"
            @change="changeType"
          >
            <el-option label="月" value="dayGridMonth" />
            <el-option label="周" value="timeGridWeek" />
            <el-option label="天" value="timeGridDay" />
            <el-option label="列" value="listWeek" />
          </el-select>
          <!-- 选择月份的日期框 -->
          <el-date-picker
            v-if="type === 'dayGridMonth'"
            v-model="showMonth"
            type="month"
            :clearable="false"
            placeholder="请选择日期"
            style="margin-left: 10px; vertical-align: middle"
            @change="changeDate"
          />
          <el-button class="ml2" type="primary" :icon="Plus" plain
            >新增排班</el-button
          >
        </div>
      </div>
      <div ref="fullcalendar" class="card"></div>
    </el-card>
    <!-- 查看任务详情弹窗 -->
    <calendarDetailDialog
      v-model:dialogVisible="calendarDialogVisible"
      :detailInfo="detailInfo"
    />
    <!-- 新增任务弹窗 -->
    <drawerAddPlan v-model:drawerVisile="drawerVisile" />
  </div>
</template>
<script setup lang="ts">
import { ref, nextTick, onMounted } from "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 dayjs from "dayjs";
import { ArrowLeftBold, ArrowRightBold, Plus } from "@element-plus/icons-vue";
import calendarDetailDialog from "./components/calendarDetailDialog.vue";
import drawerAddPlan from "./components/drawerAddPlan.vue";
const state = {
  infoList: [
    {
      id: "1",
      title: "老化实验",
      name: "张三",
      start: "2024-10-08",
      end: "2024-10-08",
      class: "tag_1",
      job: "产线员工",
      description: "XXXXXXXXX实验XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    },
    {
      id: "2",
      title: "第一个任务12312312312312312",
      name: "李四",
      start: "2024-10-10 13:30:00",
      end: "2024-10-10 14:30:00",
      class: "tag_2",
      job: "负责人",
      description: "测试XXXXXXX",
    },
    {
      id: "3",
      title: "第一个任务12312312312312312",
      name: "员工1",
      start: "2024-10-11 08:00:00",
      end: "2024-10-11 12:30:00",
      class: "tag_2",
      job: "员工",
      description: "测试XXXXXXXeqee",
    },
    {
      id: "4",
      title: "第一个任务12312312312312312",
      name: "员工",
      start: "2024-10-09 09:30:00",
      end: "2024-10-09 11:00:00",
      class: "tag_3",
      job: "生产员工3",
      description: "测试XXXXXXXeqee",
    },
    {
      id: "5",
      title: "第一个任务12312312312312312",
      name: "员工3",
      start: "2024-10-09 16:00:00",
      end: "2024-10-09 18:30:00",
      class: "tag_1",
      job: "生产员工2",
      description: "测试XXXXXXXeqee",
    },
    {
      id: "6",
      title: "第一个任务12312312312312312",
      name: "员工4",
      start: "2024-10-09 13:30:00",
      end: "2024-10-09 14:00:00",
      class: "tag_2",
      job: "生产员工1",
      description: "测试XXXXXXXeqee",
    },
  ],
  Tcalendar: ref(),
};
const fullcalendar = ref();
const Tcalendar = ref();
const type = ref("dayGridMonth"); // 默认月视图
const currentDate = ref(); // 当前时间
const showMonth = ref(dayjs().format("YYYY-MM")); // 默认当前月份
const calendarDialogVisible = ref(false); // 是否显示详情弹窗
const detailInfo = ref(); // 详情数据
const drawerVisile = ref(false); // 是否显示新增排班的抽屉
const initCalendar = () => {
  Tcalendar.value = new Calendar(fullcalendar.value, {
    plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
    initialView: type.value,
    aspectRatio: 2.2, // 宽度比
    locale: "zh-cn",
    handleWindowResize: true,
    //   loading: loading //控制表格加载
    editable: true, // 允许编辑表格
    droppable: true, //允许从外部拖拽进入日历
    eventDurationEditable: true, //控制时间段是否可以拖动
    eventResizableFromStart: true, //控制事件是否可以拖动
    selectable: true, // 允许用户通过单击和拖动来突出显示多个日期或时间段
    firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推。
    unselectAuto: true, // 当点击页面日历以外的位置时,是否自动取消当前的选中状态
    dayMaxEvents: true, //在dayGrid视图中,给定日期内的最大事件数
    headerToolbar: false, // 关闭默认日历头部,采取自定义的方式切换日历视图
    // allDaySlot: false, // 关闭全天选项
    allDayText: "全天",
    nowIndicator: true,
    eventMaxStack: 2,
    events: state.infoList, //主要数据
    eventClassNames: function (arg) {
      // 添加自定义class
      return [arg.event.extendedProps.class];
    },
    eventContent: function (arg) {
      // 日历上event显示的样式
      const italicEl = document.createElement("div");
      // 列表才显示
      if (type.value === "listWeek") {
        // 标题
        const nameEl = document.createElement("h4");
        nameEl.setAttribute("class", `h4`);
        nameEl.innerHTML = arg.event.extendedProps.name;
        italicEl.append(nameEl);
        // 岗位
        const text1El = document.createElement("p");
        text1El.innerHTML = arg.event.extendedProps.job;
        italicEl.append(text1El);
        // 面试官
        const text2El = document.createElement("p");
        text2El.innerHTML = "描述:" + arg.event.extendedProps.job;
        italicEl.append(text2El);
      } else {
        // 标题
        const titleEl = document.createElement("div");
        titleEl.setAttribute("class", `calendar-title`);
        const nameEl = document.createElement("span");
        nameEl.innerHTML = arg.event.extendedProps.name;
        titleEl.append(nameEl);
        // 时间
        const timeEl = document.createElement("span");
        if (arg.event.start && arg.event.end) {
          timeEl.innerHTML =
            dayjs(arg.event.start).format("HH:mm") +
            "-" +
            dayjs(arg.event.end).format("HH:mm");
          if (timeEl.innerHTML !== "00:00-00:00") {
            titleEl.append(timeEl);
          }
        }
        italicEl.append(titleEl);
      }
      italicEl.setAttribute("class", `calendar-card`);
      return { domNodes: [italicEl] };
    },
    noEventsContent: function () {
      const noEl = document.createElement("div");
      noEl.innerHTML = "暂无日程安排,请安排相关日程";
      return { domNodes: [noEl] };
    },
    // 点击查看时触发
    eventClick: function (info) {
      handleClick(info);
    },
    // 视图选择日期触发
    select: function (info) {
      handleSelectDate(info);
    },
    // 拖拽event大小时触发
    eventResize: function (info) {
      handleEventResize(info);
    },
    // 拖拽停止时触发
    eventDrop: function (info) {
      handleDrap(info);
    },
  });
  Tcalendar.value.render();
};
//   切换类型
const changeType = (type: any) => {
  Tcalendar.value.changeView(type);
  dayTime();
};
/**
 * 获取当前时间
 */
const dayTime = () => {
  if (type.value === "dayGridMonth") {
    currentDate.value = dayjs(Tcalendar.value.getDate()).format("YYYY年MM月");
    // showMonth.value = dayjs(Tcalendar.value.getDate()).format('YYYY-MM');
  } else if (type.value === "timeGridWeek" || type.value === "listWeek") {
    currentDate.value =
      dayjs(Tcalendar.value.getDate()).format("YYYY年MM月DD日") +
      " - " +
      dayjs(Tcalendar.value.getDate()).add(6, "day").format("DD日");
  } else if (type.value === "timeGridDay") {
    currentDate.value = dayjs(Tcalendar.value.getDate()).format(
      "YYYY年MM月DD日"
    );
  }
};
/**
 * 修改月份
 * @param date 跳转日期
 */
const changeDate = (date: any) => {
  Tcalendar.value.gotoDate(dayjs(date).format("YYYY-MM"));
  currentDate.value = dayjs(date).format("YYYY年MM月");
};
/**
 * 拖拽调整大小
 */
const handleDrap = (info: any) => {
  console.log("info1--", info);
};
/**
 * 拖拽调整大小时触发
 */
const handleEventResize = (info: any) => {
  console.log("info2--", info);
};
/**
 * 点击事件,查看任务详情
 */
const handleClick = (info: any) => {
  console.log("info-3-", info.event);
  detailInfo.value = info.event;
  calendarDialogVisible.value = true;
};
const handleSelectDate = (info: any) => {
  console.log("info4--", info);
  drawerVisile.value = true;
};
const handleOk = () => {};
onMounted(() => {
  nextTick(() => {
    initCalendar();
    dayTime();
  });
});
</script>
<style lang="scss" scoped>
.calender-container {
  width: 1525px;
  position: relative;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.calender-header {
  display: flex;
  justify-content: space-between;
  .header-left,
  .header-right {
    display: flex;
    align-items: center;
    .time-title {
      font-weight: bold;
      margin-right: 20px;
    }
  }
  .header-left {
    h1 {
      margin-right: 20px;
    }
  }
}
.btn-m2 {
  margin-right: 20px;
}
.ml2 {
  margin-left: 20px !important;
}
.mb2 {
  margin-bottom: 20px !important;
}
</style>
<style lang="scss">
.fc-col-header {
  background-color: #fafafa;
}
.calendar-card {
  display: block;
  padding: 2px 4px;
}
.calendar-title {
  display: flex;
  align-items: center;
  margin-left: 10px;
  font-size: 13px;
  span {
    margin-right: 5px;
  }
}
.fc .fc-daygrid-day.fc-day-today {
  background-color: rgba(101, 180, 230, 0.2);
  .fc-daygrid-day-number {
    background-color: #00b578;
    color: #fff;
    border-radius: 4px;
  }
}
.fc .fc-highlight {
  background: rgba(101, 180, 230, 0.2);
}
.fc .fc-timegrid-col.fc-day-today {
  background-color: rgba(101, 180, 230, 0.2);
}
.tag_1,
.tag_2,
.tag_3 {
  border-radius: 20px;
}
.tag_1 {
  background-color: #fab6b6;
}
.tag_2 {
  background-color: #a0cfff;
}
.tag_3 {
  background-color: #c8c9cc;
}
</style>
- 弹窗组件calendarDetailDialog.vue代码如下:
<template>
	<div class="container">
		<el-dialog :model-value="dialogVisible" width="25%" :before-close="handleClose">
			<template #header>
				<h3>{{ '任务详情' }}</h3>
			</template>
			<!-- 具体内容 -->
			<el-descriptions :column="1" border>
				<template #title>
					<div class="task-title">{{ taskTitle }}</div>
				</template>
				<el-descriptions-item label="执行人" min-width="90">{{ taskContent.name }}</el-descriptions-item>
				<el-descriptions-item label="岗位">{{ taskContent.job }}</el-descriptions-item>
				<el-descriptions-item label="执行时间">{{ taskStartTime }} - {{ taskEndTime }}</el-descriptions-item>
				<el-descriptions-item label="描述"> {{ taskContent.description }} </el-descriptions-item>
				<el-descriptions-item label="创建时间"> </el-descriptions-item>
				<el-descriptions-item label="创建人"> </el-descriptions-item>
			</el-descriptions>
		</el-dialog>
	</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import dayjs from 'dayjs';
// 接收父组件传值
const props = defineProps(['dialogVisible', 'detailInfo']);
const emits = defineEmits(['update:dialogVisible']);
const taskTitle = ref(''); // 任务标题
const taskContent = ref<any>('');
const taskStartTime = ref(''); // 任务开始时间
const taskEndTime = ref(''); // 任务结束时间
watch(
	() => props.detailInfo,
	(newValue) => {
		console.log(newValue);
		taskTitle.value = newValue.title;
		taskContent.value = newValue.extendedProps;
		taskStartTime.value = dayjs(newValue.start).format('YYYY-MM-DD HH:mm:ss');
		taskEndTime.value = dayjs(newValue.end).format('YYYY-MM-DD HH:mm:ss');
	}
);
/**
 * 弹窗关闭时操作
 */
const handleClose = () => {
	emits('update:dialogVisible', false);
};
</script>
<style lang="scss" scoped>
.task-title::before{
  content: '';
  width: 6px;
  height: 22px;
  display: inline-block;
  vertical-align: middle;
  background: #409eff;
  margin-right: 8px;
  border-radius: 10px;
}
.task-title{
  vertical-align: middle;
}
:deep(.el-overlay .el-overlay-dialog .el-dialog .el-dialog__body) {
	padding: 0 !important;
}
:deep(.el-overlay) {
	z-index: 9999 !important;
}
</style>
源码链接
日程安排表源码



















