econtrol-tools-calendar
v1.0.52
Published
A Vue 3 calendar component based on FullCalendar with Element Plus integration
Downloads
3,968
Maintainers
Readme
econtrol-tools-calendar
一个基于 FullCalendar 和 Element Plus 的 Vue 3 日历组件库。
✨ 特性
- 📅 基于 FullCalendar 的强大日历功能
- 🎨 集成 Element Plus UI 组件
- 📱 支持月视图、周视图、日视图和列表视图
- 🎯 支持事件拖拽、调整大小、添加、编辑、删除
- 🏷️ 支持节假日显示
- 🔒 支持权限控制(基于用户信息)
- 👤 支持用户信息管理(用户 ID 和用户名)
- ⚡ 支持快速添加事件(打开弹窗确认后保存)
- 📦 TypeScript 支持
- 🔄 单向数据流,所有操作通过事件通知父组件
📦 安装
npm install econtrol-tools-calendar
# 或
pnpm add econtrol-tools-calendar
# 或
yarn add econtrol-tools-calendar🚀 快速开始
基本使用
如果已经通过 app.use(SCalendar) 全局注册了组件,可以直接使用:
<template>
<SCalendar
:events="events"
:userinfo="userinfo"
:device-id="deviceId"
:device-info="deviceInfo"
:holidays="holidays"
@event-added="handleEventAdded"
@event-updated="handleEventUpdated"
@event-deleted="handleEventDeleted"
@events-change="handleEventsChange"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage } from "element-plus";
import type { EventInput } from "@fullcalendar/core";
import "econtrol-tools-calendar/style.css";
// 注意:还需要导入 Element Plus 的样式
import "element-plus/dist/index.css";
// 用户信息
const userinfo = {
userid: "user123",
username: "张三",
};
const deviceId = "device-001";
// 设备信息
const deviceInfo = {
id: "device-001",
name: "会议室 A",
status: "在线",
type: "会议室",
location: "3楼",
};
// 事件列表
const events = ref<EventInput[]>([
{
id: "1",
title: "团队会议",
start: "2025-01-15T10:00:00",
end: "2025-01-15T11:30:00",
extendedProps: {
organizer: "张三", // 保留用于兼容
username: "张三",
organizerId: "user123",
description: "团队周会",
deviceId: "device-001",
},
},
]);
// 节假日数据
const holidays = {
"2025-01-01": "元旦",
"2025-02-10": "春节",
// ...更多节假日
};
// 事件处理函数
function handleEventAdded(event: EventInput) {
// 如果事件没有ID,生成一个唯一ID(事件ID应该由外部提供)
if (!event.id) {
event.id = `event-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
events.value.push(event);
ElMessage.success("事件已添加");
}
function handleEventUpdated(event: EventInput) {
const index = events.value.findIndex((e) => e.id === event.id);
if (index !== -1) {
events.value[index] = event;
ElMessage.success("事件已更新");
}
}
function handleEventDeleted(event: EventInput) {
events.value = events.value.filter((e) => e.id !== event.id);
ElMessage.success(`事件"${event.title}"已删除`);
// 可以在这里调用 API 删除事件
// await deleteEventFromAPI(event);
}
function handleEventsChange(newEvents: EventInput[]) {
events.value = newEvents;
}
</script>注意:如果没有全局注册组件,需要导入组件:
<script setup lang="ts">
import { SCalendarComponent } from "econtrol-tools-calendar";
// ... 其他代码
</script>
<template>
<SCalendarComponent
:events="events"
:userinfo="userinfo"
:device-id="deviceId"
:device-info="deviceInfo"
:holidays="holidays"
@event-added="handleEventAdded"
@event-updated="handleEventUpdated"
@event-deleted="handleEventDeleted"
@events-change="handleEventsChange"
/>
</template>全局注册组件(推荐)
使用插件方式全局注册组件:
import { createApp } from "vue";
import App from "./App.vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import SCalendar from "econtrol-tools-calendar";
import "econtrol-tools-calendar/style.css";
const app = createApp(App);
app.use(ElementPlus); // 需要先注册 Element Plus
app.use(SCalendar); // 全局注册 SCalendar 组件
app.mount("#app");注册后,可以在任何组件中直接使用 <SCalendar />,无需再次导入。
📖 API 文档
Props
| 属性名 | 类型 | 默认值 | 说明 |
| ------------------- | ---------------------------------------- | ------- | ----------------------------------------------------------------------- |
| isOccupied | boolean | false | 是否处于占用状态,为 true 时不能新增事件 |
| allowOverlap | boolean | true | 是否允许时间冲突,为 false 时不允许事件时间重合 |
| userinfo | UserInfo \| null | null | 当前用户信息,包含 userid 和 username |
| deviceId | string | '' | 设备 ID,用于标识事件所属的设备(关键字段) |
| deviceInfo | DeviceInfo \| null | null | 设备详细信息,用于显示设备名称 |
| taskid | string | '' | 当前任务 ID,当事件的 extendedProps.taskid 与此值匹配时,事件显示红色 |
| quickAddTimeRange | { start: string; end: string } \| null | null | 快速添加的时间段(ISO 格式),设置后会显示"快速添加"按钮 |
| quickAddTaskName | string | '' | 快速添加的任务名称,点击"快速添加"按钮后会填入表单 |
| holidays | HolidayData \| null | null | 节假日数据对象,格式: { "YYYY-MM-DD": "节假日名称" } |
| events | EventInput[] | [] | 事件列表,从外部传入的任务数据 |
UserInfo 接口
interface UserInfo {
userid: string; // 用户ID
username: string; // 用户名
}DeviceInfo 接口
interface DeviceInfo {
id: string; // 设备ID(关键字段)
name: string; // 设备名称(显示在头部)
status?: string; // 设备状态
type?: string; // 设备类型
location?: string; // 设备位置
description?: string; // 设备描述
}HolidayData 接口
interface HolidayData {
[date: string]: string; // 格式: { "YYYY-MM-DD": "节假日名称" }
}Events
| 事件名 | 参数 | 说明 |
| --------------- | ---------------------- | -------------------------------------------------------------------- |
| quickAddSaved | event: EventInput | 快速添加事件保存后触发(已废弃,快速添加现在通过 eventAdded 触发) |
| eventAdded | event: EventInput | 事件添加后触发(包括快速添加确认保存后) |
| eventUpdated | event: EventInput | 事件更新后触发 |
| eventDeleted | event: EventInput | 事件删除后触发,包含完整的事件详情 |
| eventsChange | events: EventInput[] | 事件列表变化后触发,包含当前所有事件 |
| deviceClick | deviceId: string | 点击设备名称时触发,传递设备 ID |
EventInput 类型
事件数据遵循 FullCalendar 的 EventInput 类型:
interface EventInput {
id?: string;
title: string;
start: string; // ISO 格式: "YYYY-MM-DDTHH:mm:ss"
end?: string; // ISO 格式: "YYYY-MM-DDTHH:mm:ss"
allDay?: boolean;
backgroundColor?: string;
borderColor?: string;
editable?: boolean;
extendedProps?: {
organizer?: string; // 组织者/用户名(保留用于兼容)
username?: string; // 用户名(推荐使用)
organizerId?: string; // 用户ID(推荐使用)
description?: string; // 描述
deviceId?: string; // 设备ID(关键字段)
taskid?: string; // 任务ID,用于匹配 props.taskid,匹配的事件会显示红色
colorIndex?: number; // 颜色索引
[key: string]: any;
};
}💡 使用示例
完整示例
<template>
<div>
<SCalendar
:is-occupied="isOccupied"
:allow-overlap="allowOverlap"
:userinfo="userinfo"
:device-id="deviceId"
:device-info="deviceInfo"
:taskid="currentTaskId"
:holidays="holidays"
:events="events"
:quick-add-time-range="quickAddTimeRange"
:quick-add-task-name="quickAddTaskName"
@event-added="handleEventAdded"
@event-updated="handleEventUpdated"
@event-deleted="handleEventDeleted"
@events-change="handleEventsChange"
@device-click="handleDeviceClick"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { SCalendarComponent, getHolidays } from "econtrol-tools-calendar";
import type { EventInput } from "@fullcalendar/core";
import "econtrol-tools-calendar/style.css";
import "element-plus/dist/index.css";
// 状态管理
const isOccupied = ref(false);
const allowOverlap = ref(true);
// 用户信息
const userinfo = ref({
userid: "user123",
username: "张三",
});
const deviceId = ref("device-001");
// 当前任务ID:当事件的 extendedProps.taskid 与此值匹配时,事件会显示红色
const currentTaskId = ref("task-001");
// 设备信息
const deviceInfo = ref({
id: "device-001",
name: "会议室 A",
status: "在线",
type: "会议室",
location: "3楼",
description: "可容纳10人的会议室",
});
// 事件列表
const events = ref<EventInput[]>([
{
id: "event-1",
title: "团队会议",
start: "2025-01-15T10:00:00",
end: "2025-01-15T11:30:00",
backgroundColor: "#000000",
borderColor: "#000000",
editable: true,
extendedProps: {
organizer: "张三", // 保留用于兼容
username: "张三",
organizerId: "user123",
description: "每周团队例会",
deviceId: "device-001",
taskid: "task-001", // 如果此值与 props.taskid 匹配,事件会显示红色
},
},
]);
// 节假日数据(使用工具函数)
const holidays = ref(getHolidays(2025));
// 快速添加配置
// 设置 quickAddTimeRange 和 quickAddTaskName 后,组件会显示"快速添加"按钮
// 点击"快速添加"按钮后,会将时间段和任务名称填入表单并打开弹窗
// 用户可以在弹窗中确认或修改后点击保存,保存后会触发 eventAdded 事件
const quickAddTimeRange = ref({
start: "2025-01-16T10:00:00",
end: "2025-01-16T12:00:00",
});
const quickAddTaskName = ref("快速添加任务");
// 事件处理函数
function handleEventAdded(event: EventInput) {
// 如果事件没有ID,生成一个唯一ID(事件ID应该由外部提供)
if (!event.id) {
event.id = `event-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
events.value.push(event);
ElMessage.success("事件已添加");
// 可以在这里调用 API 保存事件
// await saveEventToAPI(event);
}
function handleEventUpdated(event: EventInput) {
const index = events.value.findIndex((e) => e.id === event.id);
if (index !== -1) {
events.value[index] = event;
ElMessage.success("事件已更新");
// 可以在这里调用 API 更新事件
// await updateEventInAPI(event);
}
}
function handleEventDeleted(event: EventInput) {
events.value = events.value.filter((e) => e.id !== event.id);
ElMessage.success(`事件"${event.title}"已删除`);
// 可以在这里调用 API 删除事件
// await deleteEventFromAPI(event);
}
function handleEventsChange(newEvents: EventInput[]) {
events.value = newEvents;
// 可以在这里同步更新到后端
// await syncEventsToAPI(newEvents);
}
function handleDeviceClick(deviceId: string) {
console.log("点击了设备:", deviceId);
// 可以在这里处理设备点击逻辑,比如打开设备详情页面、跳转路由等
// router.push(`/device/${deviceId}`);
}
</script>多设备场景
<template>
<div class="devices-container">
<SCalendar
v-for="device in devices"
:key="device.id"
:device-id="device.id"
:device-info="device"
:taskid="currentTaskId"
:events="deviceEvents[device.id] || []"
:userinfo="userinfo"
@event-added="(event) => handleEventAdded(device.id, event)"
@event-updated="(event) => handleEventUpdated(device.id, event)"
@event-deleted="(event) => handleEventDeleted(device.id, event)"
@events-change="(events) => handleEventsChange(device.id, events)"
@device-click="(deviceId) => handleDeviceClick(deviceId)"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { SCalendarComponent } from "econtrol-tools-calendar";
import type { EventInput } from "@fullcalendar/core";
// 当前任务ID:匹配的事件会显示红色
const currentTaskId = ref("task-001");
const devices = ref([
{ id: "device-001", name: "设备1" },
{ id: "device-002", name: "设备2" },
]);
const deviceEvents = ref<Record<string, EventInput[]>>({
"device-001": [
{
id: "event-1",
title: "会议1",
start: "2025-01-15T10:00:00",
end: "2025-01-15T11:00:00",
extendedProps: {
taskid: "task-001", // 匹配 currentTaskId,会显示红色
deviceId: "device-001",
},
},
],
"device-002": [
{
id: "event-2",
title: "会议2",
start: "2025-01-15T14:00:00",
end: "2025-01-15T15:00:00",
extendedProps: {
taskid: "task-002", // 不匹配 currentTaskId,显示其他颜色
deviceId: "device-002",
},
},
],
});
function handleEventAdded(deviceId: string, event: EventInput) {
if (!deviceEvents.value[deviceId]) {
deviceEvents.value[deviceId] = [];
}
deviceEvents.value[deviceId].push(event);
}
function handleEventUpdated(deviceId: string, event: EventInput) {
if (deviceEvents.value[deviceId]) {
const index = deviceEvents.value[deviceId].findIndex(
(e) => e.id === event.id
);
if (index !== -1) {
deviceEvents.value[deviceId][index] = event;
}
}
}
function handleEventDeleted(deviceId: string, event: EventInput) {
if (deviceEvents.value[deviceId]) {
deviceEvents.value[deviceId] = deviceEvents.value[deviceId].filter(
(e) => e.id !== event.id
);
ElMessage.success(`事件"${event.title}"已删除`);
// 可以在这里调用 API 删除事件
// await deleteEventFromAPI(event);
}
}
function handleEventsChange(deviceId: string, events: EventInput[]) {
deviceEvents.value[deviceId] = events;
}
function handleDeviceClick(deviceId: string) {
console.log("点击了设备:", deviceId);
// 可以在这里处理设备点击逻辑,比如打开设备详情页面、跳转路由等
// router.push(`/device/${deviceId}`);
}
</script>DeviceInfo 组件示例
DeviceInfo 组件是一个封装了 SCalendar 的设备信息展示组件,支持从 URL query 参数获取 taskid:
<template>
<DeviceInfo />
</template>
<script setup lang="ts">
import DeviceInfo from "./components/DeviceInfo.vue";
</script>功能特性:
- 自动从
/1.json加载设备数据 - 支持从 URL query 参数获取
taskid(例如:?taskid=74286) - 自动监听 URL 变化,更新
taskid - 将设备数据转换为
SCalendar需要的格式
数据格式:
DeviceInfo 组件期望从 /1.json 加载的数据格式如下:
{
"id": 74282,
"name": "设备名称",
"status": "PROCESSING",
"category": "设备类型",
"Notes": "设备备注",
"occInfo": [
{
"id": 74284,
"title": "事件标题",
"start": "2026-01-15",
"end": "2026-01-16",
"backgroundColor": "#000000",
"borderColor": "#000000",
"editable": true,
"extendedProps": {
"organizerId": "user-id",
"description": "事件描述",
"deviceId": 74282,
"colorIndex": -1,
"taskid": "74286"
}
}
]
}🛠️ 工具函数
组件库提供了节假日相关的工具函数:
import { getHolidays, isHoliday, formatDate } from "econtrol-tools-calendar";
// 获取指定年份的节假日数据
const holidays2025 = getHolidays(2025);
// 返回: { "2025-01-01": "元旦", "2025-02-10": "春节", ... }
// 检查指定日期是否为节假日
const holidayName = isHoliday("2025-01-01"); // 返回 '元旦' 或 null
// 格式化日期为 YYYY-MM-DD 格式
const dateStr = formatDate(new Date()); // 返回 'YYYY-MM-DD' 格式📋 依赖要求
该组件需要以下 peer dependencies:
vue>= 3.5.0element-plus>= 2.13.0@element-plus/icons-vue>= 2.3.0@fullcalendar/core>= 6.1.0@fullcalendar/daygrid>= 6.1.0@fullcalendar/interaction>= 6.1.0@fullcalendar/list>= 6.1.0@fullcalendar/timegrid>= 6.1.0@fullcalendar/vue3>= 6.1.0
安装依赖
npm install vue element-plus @element-plus/icons-vue \
@fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction \
@fullcalendar/list @fullcalendar/timegrid @fullcalendar/vue3🎨 样式定制
组件使用 Element Plus 的样式系统,你可以通过覆盖 CSS 变量来定制样式:
/* 自定义主题色 */
:root {
--el-color-primary: #409eff;
}
/* 自定义日历样式 */
.fullcalendar-demo {
/* 你的自定义样式 */
}🔧 开发
# 克隆仓库
git clone <repository-url>
# 安装依赖
pnpm install
# 开发模式
pnpm dev
# 构建库
pnpm build:lib
# 预览
pnpm preview📝 注意事项
数据流管理:组件采用单向数据流,所有事件操作(添加、更新、删除)都通过 emit 事件通知父组件,不会直接修改传入的
eventsprop。事件格式:事件的时间格式必须使用 ISO 8601 格式(
YYYY-MM-DDTHH:mm:ss),例如:"2025-01-15T10:00:00"。事件 ID:组件不会自动生成事件 ID,事件 ID 应该由外部(App.vue)的事件列表提供。当组件 emit
eventAdded事件时,如果事件没有 ID,父组件应该生成 ID 并更新eventsprop。权限控制:通过
userinfoprop 控制事件的可编辑性,只有extendedProps.organizerId匹配userinfo.userid的事件才能被修改。用户信息:组件会显示
username作为预订人,并在事件数据中保存username和organizerId。设备信息:组件头部显示设备名称(
deviceInfo.name),设备 ID(deviceId)作为关键字段保存在事件数据中。时间冲突:当
allowOverlap为false时,组件会自动检查时间冲突,不允许创建重叠的事件。节假日显示:节假日数据格式为
{ "YYYY-MM-DD": "节假日名称" },传入holidaysprop 后会在日历上高亮显示。快速添加功能:
- 设置
quickAddTimeRange和quickAddTaskNameprops 后,组件会显示"快速添加"按钮 - 点击"快速添加"按钮后,会将时间段和任务名称填入表单并打开弹窗
- 用户可以在弹窗中确认或修改后点击保存
- 保存后会触发
eventAdded事件,而不是直接保存 - 如果快速添加的时间段不在当前视图范围内,日历会自动导航到该时间段
- 设置
时间格式兼容:组件支持多种时间格式输入:
- 如果传入的时间只包含日期(如
"2025-01-15"),会自动补全为"2025-01-15T00:00:00" - 支持 ISO 格式(
"2025-01-15T10:00:00") - 支持
YYYY-MM-DD HH:mm:ss格式(会自动转换为 ISO 格式)
- 如果传入的时间只包含日期(如
任务 ID 和颜色显示:
taskidprop 用于标识当前任务 ID- 当事件的
extendedProps.taskid字段与props.taskid匹配时,该事件会显示为红色(#FF0000) - 判断逻辑:
event.extendedProps.taskid === props.taskid - 颜色分配规则:
- 如果传了
taskid:匹配的事件显示红色,其他事件根据其taskid(或id)分配不同颜色(避开红色) - 如果没有传
taskid:不同任务根据其taskid(或id)显示不同颜色(避开红色),确保同一任务的所有事件使用相同颜色
- 如果传了
- 颜色从预定义的颜色数组中分配,确保视觉区分度
从 URL Query 参数获取 taskid:
- 可以通过 URL query 参数传递
taskid,例如:?taskid=74286 - 组件会自动从
window.location.search中读取taskid参数 - 如果使用 vue-router,也可以使用
useRoute()获取 query 参数 - 示例代码:
// 方式1:使用浏览器原生 API(无需 vue-router) const urlParams = new URLSearchParams(window.location.search); const taskid = urlParams.get("taskid") || ""; // 方式2:使用 vue-router(如果项目已安装) import { useRoute } from "vue-router"; const route = useRoute(); const taskid = (route.query.taskid as string) || "";
- 可以通过 URL query 参数传递
响应式更新:
- 组件会监听
taskid和deviceInfo的变化,自动更新事件颜色和视图 - 当
taskid变化时,匹配的事件会立即更新为红色 - 当
deviceInfo变化时,日历会重新渲染以反映设备信息的变化
- 组件会监听
📄 许可证
MIT
🤝 贡献
欢迎提交 Issue 和 Pull Request!
