npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

econtrol-tools-calendar

v1.0.52

Published

A Vue 3 calendar component based on FullCalendar with Element Plus integration

Downloads

3,968

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 | 当前用户信息,包含 useridusername | | 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.0
  • element-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

📝 注意事项

  1. 数据流管理:组件采用单向数据流,所有事件操作(添加、更新、删除)都通过 emit 事件通知父组件,不会直接修改传入的 events prop。

  2. 事件格式:事件的时间格式必须使用 ISO 8601 格式(YYYY-MM-DDTHH:mm:ss),例如:"2025-01-15T10:00:00"

  3. 事件 ID:组件不会自动生成事件 ID,事件 ID 应该由外部(App.vue)的事件列表提供。当组件 emit eventAdded 事件时,如果事件没有 ID,父组件应该生成 ID 并更新 events prop。

  4. 权限控制:通过 userinfo prop 控制事件的可编辑性,只有 extendedProps.organizerId 匹配 userinfo.userid 的事件才能被修改。

  5. 用户信息:组件会显示 username 作为预订人,并在事件数据中保存 usernameorganizerId

  6. 设备信息:组件头部显示设备名称(deviceInfo.name),设备 ID(deviceId)作为关键字段保存在事件数据中。

  7. 时间冲突:当 allowOverlapfalse 时,组件会自动检查时间冲突,不允许创建重叠的事件。

  8. 节假日显示:节假日数据格式为 { "YYYY-MM-DD": "节假日名称" },传入 holidays prop 后会在日历上高亮显示。

  9. 快速添加功能

    • 设置 quickAddTimeRangequickAddTaskName props 后,组件会显示"快速添加"按钮
    • 点击"快速添加"按钮后,会将时间段和任务名称填入表单并打开弹窗
    • 用户可以在弹窗中确认或修改后点击保存
    • 保存后会触发 eventAdded 事件,而不是直接保存
    • 如果快速添加的时间段不在当前视图范围内,日历会自动导航到该时间段
  10. 时间格式兼容:组件支持多种时间格式输入:

    • 如果传入的时间只包含日期(如 "2025-01-15"),会自动补全为 "2025-01-15T00:00:00"
    • 支持 ISO 格式("2025-01-15T10:00:00"
    • 支持 YYYY-MM-DD HH:mm:ss 格式(会自动转换为 ISO 格式)
  11. 任务 ID 和颜色显示

    • taskid prop 用于标识当前任务 ID
    • 当事件的 extendedProps.taskid 字段与 props.taskid 匹配时,该事件会显示为红色(#FF0000
    • 判断逻辑:event.extendedProps.taskid === props.taskid
    • 颜色分配规则
      • 如果传了 taskid:匹配的事件显示红色,其他事件根据其 taskid(或 id)分配不同颜色(避开红色)
      • 如果没有传 taskid:不同任务根据其 taskid(或 id)显示不同颜色(避开红色),确保同一任务的所有事件使用相同颜色
    • 颜色从预定义的颜色数组中分配,确保视觉区分度
  12. 从 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) || "";
  13. 响应式更新

    • 组件会监听 taskiddeviceInfo 的变化,自动更新事件颜色和视图
    • taskid 变化时,匹配的事件会立即更新为红色
    • deviceInfo 变化时,日历会重新渲染以反映设备信息的变化

📄 许可证

MIT

🤝 贡献

欢迎提交 Issue 和 Pull Request!