fullcalendar实战使用(vue3 + ts)

fullcalendar实战使用(vue3 + ts)

需求背景

要求:

1、日历可以单击选择日期,也可以按住左键拖动选择多个日期,像windows按住左键多选文件夹一样;

2、顶部标题需要左右切换月份,中间显示日期,格式YYYY年M月,右侧可以选择月份;

3、左侧是日历,选择日期后在右侧提交表单,提交后在对应的日期上显示内容,点击日期上的内容则删除该日期的内容。

项目使用的是antd vue组件库,日历组件不满足第一点要求,寻找了一遍后发现fullcalendar这个库刚好符合要求

介绍

FullCalendar,当下最受欢迎的全尺寸JavaScript日历 FullCalendar的三大特点:

强大轻巧:拥有100多种可自定义的设置。作为单独的模块构建,以减小文件大小。

开发人员友好:支持React、Vue、Angular三大主流框架

开源:所有代码都在GitHub上开源,但是也有一个非免费的"高级"版本

文档地址

一、安装

sh 复制代码
pnpm add @fullcalendar/vue3 @fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction

二、实现需求

1、日历可以单击选择日期,也可以按住左键拖动选择多个日期,像windows按住左键多选文件夹一样;

引入interaction插件后日历就可以选择了

vue 复制代码
<template>
  <FullCalendar ref="fullCalendarRef" :options="calendarOptions" />
</template>
ts 复制代码
<script lang="ts" setup>
import { Ref } from 'vue';
import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid'; // 日历展示方式:月
import interactionPlugin from '@fullcalendar/interaction'; // for selectable
import { CalendarOptions } from '@fullcalendar/core'; //类型定义
const calendarOptions: Ref<CalendarOptions> = ref({
  plugins: [interactionPlugin, dayGridPlugin],//加载插件
  initialView: 'dayGridMonth', // 日历展示方式:月, 依赖`dayGridPlugin`插件
  selectable: true, // 表格可选择,依赖 `interactionPlugin` 插件
})
<script>

2、顶部标题需要左右切换月份,中间显示日期,格式YYYY年M月,右侧可以选择月份;

  • 显示日期

看文档没有看到头部插槽,直接隐藏日历头部,自己写

js 复制代码
import { CalendarOptions } from '@fullcalendar/core'; //类型定义

const calendarOptions: Ref<CalendarOptions> = ref({
 ...
 headerToolbar: false
})
  • 左右切换月份

getApi可以获取FullCalendar里面的方法

js 复制代码
import { CalendarApi } from '@fullcalendar/core'; //类型定义

/** 日历ref */
const fullCalendarRef = ref(null);
/** 日历的api */
let fullcalendarApi = ref<CalendarApi>();
onMounted(() => {
  fullcalendarApi.value = Object.getOwnPropertyDescriptor(
    fullCalendarRef.value,
    'getApi',
  )?.value();
});
//上个月
fullcalendarApi.value?.prev();
//下个月
fullcalendarApi.value?.next();
  • 右侧可以选择月份 项目用的antd vue组件库,picker属性为month即可选择月份
vue 复制代码
<template>
  <a-date-picker picker="month" @change="handleMonthChange"/>
</template>

gotoDate即可切换日历的月份

ts 复制代码
/** 选择月份change事件 */
const handleMonthChange = (date) => {
  if (!date) return;
  fullcalendarApi.value?.gotoDate(dayjs(date).format('YYYY-MM-DD'));
};

3、左侧是日历,选择日期后在右侧提交表单,提交后在对应的日期上显示内容,点击日期上的内容则删除该日期的内容。

events是响应式数据,不需要调用更新视图的方法,直接修改events就能在日历上进行更新了

js 复制代码
import { Modal } from 'ant-design-vue';

const eventsArr: Ref<any[]> = ref([
    {
      id: '1',
      title: '周末双休',
      start: '2024-03-18',
      end: '2024-03-18',
    },
    {
      id: '2',
      title: '周末双休',
      start: '2024-03-20',
      end: '2024-03-20',
    },
  ]);
  // https://fullcalendar.io/docs#toc
const calendarOptions: Ref<CalendarOptions> = ref({
  plugins: [interactionPlugin],
  initialView: 'dayGridMonth',
  selectable: true, // 表格可选择,依赖 `interactionPlugin` 插件
  /** 事件(在日历上做标记) */
  events: eventsArr.value,
  /** 事件点击(删除节假日) */
  eventClick: clickCalendar
})
/** 日历点击事件 */
function clickCalendar(value) {
  console.log('🚀 ~ eventClick::', value);
  console.log('🚀 ~ eventClick:startStr', value.event.startStr);
  Modal.confirm({
    title: '是否删除当前节假日',
    content: `${value.event.startStr}   ${value.event.title}`,
    okText: '删除节假日',
    onOk() {
      const index = eventsArr.value.findIndex((item) => item.id === value.event.id);
      eventsArr.value.splice(index, 1);
    },
    onCancel() {},
  });
}
/** 表单提交 */
const onFinish = (values: any) => {
  console.log('Success:', values);
  eventsArr.value.push({
    id: Date.now(),
    title: values.holidayName,
    start: dayjs(values.dateTime[0]).format('YYYY-MM-DD'),
    end: dayjs(values.dateTime[1]).format('YYYY-MM-DD'),
  });
};

最后实现的效果图

完整代码

ts 复制代码
<template>
    <div class="lg:flex p4">
      <!-- 左侧日历 -->
      <div class="lg:w900px">
        <div class="flex justify-between items-center mb1">
          <div>
            <a-space>
              <a-button type="primary" @click="prevMonth">
                <template #icon><left-outlined /></template>
              </a-button>
              <a-button type="primary" @click="nextMonth">
                <template #icon><right-outlined /></template>
              </a-button>
            </a-space>
          </div>
          <h3>{{ calendarTitle }}</h3>
          <div>
            <a-date-picker v-model:value="monthPicker" picker="month" @change="handleMonthChange" />
          </div>
        </div>
        <FullCalendar ref="fullCalendarRef" :options="calendarOptions" />
      </div>
      <!-- 右侧表单 -->
      <div class="flex-auto <lg:mt1">
        <a-form
          ref="formRef"
          :model="formState"
          name="basic"
          :label-col="{ span: 8 }"
          :wrapper-col="{ span: 16 }"
          autocomplete="off"
          @finish="onFinish"
          @finish-failed="onFinishFailed"
        >
          <a-form-item
            label="节假日名称"
            name="holidayName"
            :rules="[{ required: true, message: '请输入节假日名称' }]"
          >
            <a-input v-model:value="formState.holidayName" />
          </a-form-item>

          <a-form-item
            label="时间段"
            name="dateTime"
            :rules="[{ required: true, message: '请选择时间段' }]"
          >
            <a-range-picker v-model:value="formState.dateTime" disabled />
          </a-form-item>
          <a-form-item label="间隔天数" name="interval">
            <a-input v-model:value="formState.interval" disabled />
          </a-form-item>
          <a-form-item :wrapper-col="{ offset: 8, span: 16 }">
            <a-space wrap>
              <a-button type="primary" html-type="submit">提交</a-button>
              <a-button @click="resetForm">重置</a-button>
            </a-space>
          </a-form-item>
        </a-form>
      </div>
    </div>
</template>
ts 复制代码
<script lang="ts" setup>
  import { Ref, onMounted, reactive, ref } from 'vue';
  import dayjs from 'dayjs';
  import customParseFormat from 'dayjs/plugin/customParseFormat';
  import duration from 'dayjs/plugin/duration';
  import { Modal } from 'ant-design-vue';
  import type { FormInstance } from 'ant-design-vue';
  import { RightOutlined, LeftOutlined } from '@ant-design/icons-vue';
  import { PageWrapper } from '@/components/Page';
  import FullCalendar from '@fullcalendar/vue3';
  import dayGridPlugin from '@fullcalendar/daygrid'; // 日历展示方式:月
  import interactionPlugin from '@fullcalendar/interaction'; // for selectable
  import { CalendarOptions, CalendarApi } from '@fullcalendar/core'; //类型定义
  // 自定义格式化插件
  dayjs.extend(customParseFormat);
  // 计算间隔插件
  dayjs.extend(duration);
  /** 日历ref */
  const fullCalendarRef = ref(null);
  /** 日历的api */
  let fullcalendarApi = ref<CalendarApi>();
  onMounted(() => {
    /**getApi可以获取日历里的方法 */
    fullcalendarApi.value = Object.getOwnPropertyDescriptor(
      fullCalendarRef.value,
      'getApi',
    )?.value();
  });
  interface FormState {
    holidayName: string;
    /** 时间段 */
    dateTime: any[];
    /** 间隔日期 */
    interval?: string;
  }

  const formState = reactive<FormState>({
    holidayName: '',
    dateTime: [],
    interval: '',
  });

  const formRef = ref<FormInstance>();
  /** 表单提交 */
  const onFinish = (values: any) => {
    console.log('Success:', values);
    eventsArr.value.push({
      id: Date.now(),
      title: values.holidayName,
      start: dayjs(values.dateTime[0]).format('YYYY-MM-DD'),
      end: dayjs(values.dateTime[1]).format('YYYY-MM-DD'),
    });
  };

  const onFinishFailed = (errorInfo: any) => {
    console.log('Failed:', errorInfo);
  };

  const resetForm = () => {
    formRef.value?.resetFields();
  };
  const weekObj = { 0: '周日', 1: '周一', 2: '周二', 3: '周三', 4: '周四', 5: '周五', 6: '周六' };
  const eventsArr: Ref<any[]> = ref([
    {
      id: '1',
      title: '周末双休',
      start: '2024-03-18',
      end: '2024-03-18',
    },
    {
      id: '2',
      title: '周末双休',
      start: '2024-03-20',
      end: '2024-03-20',
    },
  ]);
  // https://fullcalendar.io/docs#toc
  const calendarOptions: Ref<CalendarOptions> = ref({
    plugins: [interactionPlugin, dayGridPlugin], //加载插件
    initialView: 'dayGridMonth', // 日历展示方式:月, 依赖`dayGridPlugin`插件
    selectable: true, // 表格可选择,依赖 `interactionPlugin` 插件
    handleWindowResize: true, // 是否随浏览器窗口大小变化而自动变化。
    // 星期映射为中文
    dayHeaderContent: function (arg) {
      return weekObj[arg.dow];
    },
    headerToolbar: false, //隐藏头部工具栏,因为没有插槽功能,所以需要自己在日历外面写头部工具栏
    aspectRatio: 1.5, //设置日历单元格宽度与高度的比例
    // locale: 'zh-cn', // 切换语言,当前为中文,切了之后每一天会变成1日,2日...所以不要切换,选择自定义 dayHeaderContent
    firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推
    /** 事件(在日历上做标记) */
    events: eventsArr.value,
    /** 事件点击(删除节假日) */
    eventClick: clickCalendar,
    select: function (value) {
      const startDate = dayjs(value.start);
      //结束日期会到下一天的0点,所以需要减一天
      const endDate = dayjs(value.end).subtract(1, 'day');
      formState.dateTime = [startDate, endDate];
      formState.interval = `${dayjs(value.end).diff(startDate, 'day')}天`;
      formRef.value?.validateFields(['dateTime']);
    },
  });
  /** 日历点击事件 */
  function clickCalendar(value) {
    console.log('🚀 ~ eventClick::', value);
    console.log('🚀 ~ eventClick:startStr', value.event.startStr);
    Modal.confirm({
      title: '是否删除当前节假日',
      content: `${value.event.startStr}   ${value.event.title}`,
      okText: '删除节假日',
      onOk() {
        const index = eventsArr.value.findIndex((item) => item.id === value.event.id);
        eventsArr.value.splice(index, 1);
      },
      onCancel() {},
    });
  }
  /** 日历标题:YYYY年M月 */
  const calendarTitle = ref();
  /** 月份选择器 */
  const monthPicker = ref();
  /** 跳转到上个月 */
  const prevMonth = () => {
    fullcalendarApi.value?.prev();
    console.log('🚀 ~ prevMonth:');
    updateCalendarTitle();
    monthPicker.value = dayjs(fullcalendarApi.value?.getDate());
  };
  /** 跳转到下个月 */
  const nextMonth = () => {
    fullcalendarApi.value?.next();
    updateCalendarTitle();
    monthPicker.value = dayjs(fullcalendarApi.value?.getDate());
  };
  /** 更新标题 */
  const updateCalendarTitle = () => {
    calendarTitle.value = dayjs(fullcalendarApi.value?.getDate()).format('YYYY年M月');
  };
  onMounted(() => {
    updateCalendarTitle();
  });
  /** 选择月份change事件 */
  const handleMonthChange = (date) => {
    if (!date) return;
    fullcalendarApi.value?.gotoDate(dayjs(date).format('YYYY-MM-DD'));
    updateCalendarTitle();
  };
</script>

总结

fullcalendar这个库很强大,还有很多其他的功能,这里我只使用到了选择表格的功能。

缺点是文档不易阅读也不能搜索,demo示例太少,配置全靠别人博客里的注释来理解,不像组件库的文档把各种配置和demo都详细列出来。

相关推荐
学习路上的小刘6 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js
&白帝&7 分钟前
vue3常用的组件间通信
前端·javascript·vue.js
冯宝宝^1 小时前
基于mongodb+flask(Python)+vue的实验室器材管理系统
vue.js·python·flask
cc蒲公英2 小时前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel
森叶2 小时前
Electron-vue asar 局部打包优化处理方案——绕开每次npm run build 超级慢的打包问题
vue.js·electron·npm
小小竹子3 小时前
前端vue-实现富文本组件
前端·vue.js·富文本
青稞儿3 小时前
面试题高频之token无感刷新(vue3+node.js)
vue.js·node.js
程序员凡尘4 小时前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
Bug缔造者10 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_10 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js