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

jordium-gantt-vue3-lian

v1.0.1

Published

<p align="center"> <a href="https://www.npmjs.com/package/jordium-gantt-vue3"> <img src="https://img.shields.io/npm/v/jordium-gantt-vue3?style=flat-square" alt="npm version"> </a> <a href="https://www.npmjs.com/package/jordium-gantt-vue3"> <

Readme

jordium-gantt-vue3


✨ 简介

jordium-gantt-vue3 是一个现代化的 Vue3 甘特图组件,内置资源视图与资源规划能力。在统一界面中同时管理任务、时间线以及资源分配,适用于项目计划与资源调度场景。

核心特性

  • 📊资源计划视图 - Vue 3 生态唯一支持资源视图的甘特图,可按资源(人员/设备)展示任务分配与工时占用
  • 📊 功能完整 - 任务管理、里程碑、依赖关系、进度追踪
  • 🎨 主题系统 - 内置亮色/暗色主题,支持自定义样式
  • 🖱️ 交互流畅 - 拖拽调整、缩放、双击编辑、右键菜单
  • 🌍 国际化 - 内置中英文,可扩展其他语言
  • 高性能 - 虚拟滚动、懒加载,轻松处理大量数据
  • 💎 类型安全 - 完整 TypeScript 支持

效果预览

亮色主题

暗色主题


📦 安装

使用你喜欢的包管理器安装:

# npm
npm install jordium-gantt-vue3

# yarn
yarn add jordium-gantt-vue3

# pnpm
pnpm add jordium-gantt-vue3

🚀 快速开始

组件引入

在组件中引入 GanttChart 组件和样式:

<script setup lang="ts">
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
</script>

提示: 样式文件只需在项目中引入一次即可,建议在 main.ts 或根组件中引入。

第一个示例

创建你的第一个甘特图:

<template>
  <div style="height: 600px;">
    <GanttChart :tasks="tasks" :milestones="milestones" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref([
  {
    id: 1,
    name: '项目启动',
    startDate: '2025-01-01',
    endDate: '2025-01-10',
    progress: 100,
  },
  {
    id: 2,
    name: '需求分析',
    startDate: '2025-01-11',
    endDate: '2025-01-20',
    progress: 80,
    predecessor: [1],
  },
  {
    id: 3,
    name: '系统设计',
    startDate: '2025-01-21',
    endDate: '2025-02-05',
    progress: 50,
    predecessor: [2],
  },
])

const milestones = ref([
  {
    id: 101,
    name: '项目立项',
    startDate: '2025-01-01',
    type: 'milestone',
  },
])
</script>

🎯 立即体验 Github在线Demo → 推荐使用 DOVE VPN,快速、稳定。 (注意:请合法使用 VPN 资源)

🌞 NPM包使用示例

请参考项目下的npm-demo,这是一个独立的项目,可以使用IDE单独浏览和启动,运行前请安装element plus以及jordium-gantt-vue3插件包

# npm
npm install element-plus
npm install jordium-gantt-vue3
npm run dev

组件指南

GanttChart 组件

GanttChart 是组件库的核心入口,提供了完整的甘特图功能。

基础属性

| 属性名 | 类型 | 默认值 | 说明 | | --------------------------- | ----------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------- | | tasks | Task[] | [] | 任务数据数组 | | milestones | Task[] | [] | 里程碑数据数组(注意:类型为 Task[],需设置 type='milestone') | | resources v1.9.0 | Resource[] | [] | 资源数据数组(资源计划视图使用) | | viewMode v1.9.0 | 'task' \| 'resource' | 'task' | 视图模式:'task' 任务计划视图 | 'resource' 资源计划视图 | | showToolbar | boolean | true | 是否显示工具栏 | | useDefaultDrawer | boolean | true | 是否使用内置任务编辑抽屉(TaskDrawer) | | useDefaultMilestoneDialog | boolean | true | 是否使用内置里程碑编辑对话框(MilestoneDialog) | | autoSortByStartDate | boolean | false | 是否根据开始时间自动排序任务 | | allowDragAndResize | boolean | true | 是否允许拖拽和调整任务/里程碑大小 | | enableTaskRowMove | boolean | false | 是否允许拖拽和摆放TaskRow | | enableTaskListContextMenu | boolean | true | 是否启用 TaskList(TaskRow)右键菜单功能。为 true 时:未声明 task-list-context-menu 插槽则使用内置菜单,声明了插槽则使用自定义菜单;为 false 时右键菜单完全禁用 | | enableTaskBarContextMenu | boolean | true | 是否启用 TaskBar 右键菜单功能。为 true 时:未声明 task-bar-context-menu 插槽则使用内置菜单,声明了插槽则使用自定义菜单;为 false 时右键菜单完全禁用 | | assigneeOptions | Array<{ key?: string \| number; value: string \| number; label: string }> | [] | 任务编辑抽屉中负责人下拉菜单的选项列表 | | locale v1.7.1 | 'zh-CN' \| 'en-US' | 'zh-CN' | 语言设置(响应式)。设置后组件内部语言将跟随变化 | | theme v1.7.1 | 'light' \| 'dark' | 'light' | 主题模式(响应式)。设置后组件主题将跟随变化 | | timeScale v1.7.1 | 'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year' | 'week' | 时间刻度(响应式)。设置后时间线刻度将跟随变化 | | fullscreen v1.7.1 | boolean | false | 全屏状态控制(响应式)。设置后组件全屏状态将跟随变化 | | expandAll v1.7.1 | boolean | true | 展开/收起所有任务(响应式)。设置后所有任务的展开状态将跟随变化 | | enableLinkAnchor v1.7.2 | boolean | true | 是否开启Taskbar的关系线锚点,默认值: true | | pendingTaskBackgroundColor v1.8.0 | string | '#409eff' | 待处理任务的TaskBar背景色。支持十六进制颜色值(如 '#409eff')。优先级:高于系统默认,低于 Task 对象的 barColor 属性 | | delayTaskBackgroundColor v1.8.0 | string | '#f56c6c' | 已逾期任务的TaskBar背景色。支持十六进制颜色值(如 '#f56c6c')。优先级:高于系统默认,低于 Task 对象的 barColor 属性 | | completeTaskBackgroundColor v1.8.0 | string | '#909399' | 已完成任务的TaskBar背景色。支持十六进制颜色值(如 '#909399')。优先级:高于系统默认,低于 Task 对象的 barColor 属性 | | ongoingTaskBackgroundColor v1.8.0 | string | '#e6a23c' | 进行中任务的TaskBar背景色。支持十六进制颜色值(如 '#e6a23c')。优先级:高于系统默认,低于 Task 对象的 barColor 属性 | | showActualTaskbar v1.8.0 | boolean | false | 是否显示实际TaskBar(在计划TaskBar下方显示实际执行进度) | | enableTaskbarTooltip v1.8.0 | boolean | true | 是否启用TaskBar悬停提示框(鼠标悬停显示任务详情) | | enableMilestoneTooltip v1.10.2 | boolean | true | 是否启用里程碑悬停提示框(鼠标悬停显示里程碑名称和日期) | | showConflicts v1.9.0 | boolean | true | 是否显示资源冲突可视化层(资源视图下显示斜纹背景标识超载区域) | | showTaskbarTab v1.9.0 | boolean | true | 是否显示TaskBar上的资源Tab标签(资源视图下TaskBar的资源分配标签) | | enableTaskListCollapsible v1.9.2 | boolean | true | 是否允许折叠/展开 TaskList 面板。false 时强制隐藏 TaskList、SplitterBar 及折叠按钮,Timeline 独占全宽 | | taskListVisible v1.9.2 | boolean | true | 控制 TaskList 的显隐状态(响应式)。仅在 enableTaskListCollapsible=true 时有效 | | enableTaskDrawerAutoClose v1.9.3 | boolean | true | 是否允许 TaskDrawer 自动关闭(外总点击或按 Esc 时自动关闭)。设为 false 时禁用自动关闭,仅可通过内部按钮手动关闭 |

TaskListColumn 属性

TaskListColumn 组件用于在声明式模式(taskListColumnRenderMode="declarative")下定义任务列表的列。类似于 Element Plus 的 el-table-column 组件。

| 属性名 | 类型 | 默认值 | 说明 | | ---------- | ------------------------------ | -------- | ---------------------------------------------------------------------------------------------------------- | | prop | string | - | 列的属性名,用于访问任务对象的字段。例如:'name''assignee''progress' 等 | | label | string | - | 列的显示标题文本 | | width | number \| string | - | 列宽度。数字表示像素值(如 200),字符串支持百分比(如 '20%') | | align | 'left' \| 'center' \| 'right' | 'left' | 列内容对齐方式 | | cssClass | string | - | 自定义 CSS 类名,用于列样式定制 |

使用示例

<GanttChart 
  :tasks="tasks" 
  task-list-column-render-mode="declarative"
>
  <TaskListColumn prop="name" label="任务名称" width="300" />
  <TaskListColumn prop="assignee" label="负责人" width="150" align="center" />
  <TaskListColumn prop="progress" label="进度" width="100" align="center" />
  <TaskListColumn prop="startDate" label="开始日期" width="140" />
  <TaskListColumn prop="endDate" label="结束日期" width="140" />
</GanttChart>

💡 提示

  • TaskListColumn 组件本身不渲染任何内容,仅用于声明列配置
  • 必须在 GanttChart 组件内部使用,且设置 task-list-column-render-mode="declarative"
  • 列的显示顺序由 TaskListColumn 组件的声明顺序决定
  • 关于列内容自定义和插槽的详细使用方法,请参考 插槽 (Slots) 章节

TaskListContextMenu 属性

TaskListContextMenu 组件用于声明式定义 TaskList(TaskRow)的右键菜单。当 enableTaskListContextMenutrue 时生效。

| 属性名 | 类型 | 默认值 | 说明 | | ---------- | ---------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- | | taskType | string \| string[] | undefined | 指定哪些任务类型显示此右键菜单。不设置时遵循现有逻辑(所有任务都显示),设置后仅对指定类型任务显示。支持单个类型(如 'task')或多个类型(如 ['task', 'milestone']) |

使用示例

<GanttChart 
  :tasks="tasks" 
  :enable-task-list-context-menu="true"
>
  <!-- 默认行为:所有任务都显示此右键菜单 -->
  <TaskListContextMenu>
    <template #default="scope">
      <div class="custom-menu">
        <div class="menu-item" @click="editTask(scope.row)">编辑</div>
        <div class="menu-item" @click="deleteTask(scope.row)">删除</div>
      </div>
    </template>
  </TaskListContextMenu>
  
  <!-- 仅对 type='task' 的任务显示此菜单 -->
  <TaskListContextMenu task-type="task">
    <template #default="scope">
      <div class="custom-menu">
        <div class="menu-item">任务专属菜单</div>
      </div>
    </template>
  </TaskListContextMenu>
  
  <!-- 对多种类型显示菜单 -->
  <TaskListContextMenu :task-type="['task', 'milestone']">
    <template #default="scope">
      <div class="custom-menu">
        <div class="menu-item">任务和里程碑菜单</div>
      </div>
    </template>
  </TaskListContextMenu>
</GanttChart>

💡 提示

  • TaskListContextMenu 组件本身不渲染任何内容,仅用于声明菜单配置
  • 必须在 GanttChart 组件内部使用,且设置 enable-task-list-context-menu="true"
  • 菜单定位和显示状态由内部自动管理,用户只需关心菜单内容的 HTML 结构
  • 菜单会在点击外部或滚动时自动关闭
  • 关于插槽的详细使用方法,请参考 插槽 (Slots) 章节

TaskBarContextMenu 属性

TaskBarContextMenu 组件用于声明式定义 TaskBar(时间线任务条)的右键菜单。当 enableTaskBarContextMenutrue 时生效。

| 属性名 | 类型 | 默认值 | 说明 | | ---------- | ---------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- | | taskType | string \| string[] | undefined | 指定哪些任务类型显示此右键菜单。不设置时遵循现有逻辑(所有任务都显示),设置后仅对指定类型任务显示。支持单个类型(如 'task')或多个类型(如 ['task', 'milestone']) |

使用示例

<GanttChart 
  :tasks="tasks" 
  :enable-task-bar-context-menu="true"
>
  <!-- 默认行为:所有任务都显示此右键菜单 -->
  <TaskBarContextMenu>
    <template #default="scope">
      <div class="custom-menu">
        <div class="menu-item" @click="extendTask(scope.row)">延长任务</div>
        <div class="menu-item" @click="moveTask(scope.row)">移动任务</div>
      </div>
    </template>
  </TaskBarContextMenu>
  
  <!-- 仅对 type='task' 的任务显示此菜单 -->
  <TaskBarContextMenu task-type="task">
    <template #default="scope">
      <div class="custom-menu">
        <div class="menu-item">任务条专属菜单</div>
      </div>
    </template>
  </TaskBarContextMenu>
  
  <!-- 对多种类型显示菜单 -->
  <TaskBarContextMenu :task-type="['task', 'story']">
    <template #default="scope">
      <div class="custom-menu">
        <div class="menu-item">任务和故事菜单</div>
      </div>
    </template>
  </TaskBarContextMenu>
</GanttChart>

💡 提示

  • TaskBarContextMenu 组件本身不渲染任何内容,仅用于声明菜单配置
  • 必须在 GanttChart 组件内部使用,且设置 enable-task-bar-context-menu="true"
  • 菜单定位和显示状态由内部自动管理,用户只需关心菜单内容的 HTML 结构
  • 菜单会在点击外部或滚动时自动关闭
  • 关于插槽的详细使用方法,请参考 插槽 (Slots) 章节

配置对象属性

完整的配置对象说明请参考 ⚙️ 配置与扩展 章节。

| 属性名 | 类型 | 默认值 | 说明 | | ---------------- | ---------------------------- | ----------------------------------------------------------------------- | ---------------- | | toolbarConfig | ToolbarConfig | {} | 工具栏配置 | | taskListConfig | TaskListConfig | undefined | 任务列表配置 | | resourceListConfig v1.9.0 | ResourceListConfig | undefined | 资源列表配置 | | taskBarConfig | TaskBarConfig | undefined | 任务条样式配置 | | localeMessages | Partial<Messages['zh-CN']> | undefined | 自定义多语言配置 | | workingHours | WorkingHours | { morning: { start: 8, end: 11 }, afternoon: { start: 13, end: 17 } } | 工作时间配置 |

回调函数属性

| 属性名 | 类型 | 说明 | | -------------------- | ------------------------------------ | -------------------------------------------------------- | | onTodayLocate | () => void | 工具栏"今天"按钮点击回调 | | onExportCsv | () => boolean \| void | 工具栏"导出CSV"按钮点击回调,返回 false 可阻止默认导出 | | onExportPdf | () => void | 工具栏"导出PDF"按钮点击回调 | | onLanguageChange | (lang: 'zh-CN' \| 'en-US') => void | 语言切换回调 | | onThemeChange | (isDark: boolean) => void | 主题切换回调 | | onFullscreenChange | (isFullscreen: boolean) => void | 全屏切换回调 | | onExpandAll | () => void | 工具栏"全部展开"按钮点击回调 | | onCollapseAll | () => void | 工具栏"全部折叠"按钮点击回调 |

组件事件(Events)

完整的事件说明请分别参考:

事件列表总览:

| 事件名 | 参数 | 说明 | | ------------------------ | --------------------------------- | -------------------------- | | add-task | - | 点击工具栏"添加任务"按钮 | | task-click | (task: Task, event: MouseEvent) | 点击任务 | | task-double-click | (task: Task) | 双击任务 | | task-added | { task: Task } | 任务添加后触发 | | task-updated | { task: Task } | 任务更新后触发 | | task-deleted | { task: Task } | 任务删除后触发 | | taskbar-drag-end | (task: Task) | 拖拽任务结束 | | taskbar-resize-end | (task: Task) | 调整任务大小结束 | | predecessor-added | { targetTask, newTask } | 添加前置任务 | | successor-added | { targetTask, newTask } | 添加后置任务 | | timer-started | (task: Task) | 任务计时器启动 | | timer-stopped | (task: Task) | 任务计时器停止 | | add-milestone | - | 点击工具栏"添加里程碑"按钮 | | milestone-saved | (milestone: Task) | 里程碑保存 | | milestone-deleted | { milestoneId: number } | 里程碑删除 | | milestone-icon-changed | { milestoneId, icon } | 里程碑图标变更 | | milestone-drag-end | (milestone: Task) | 拖拽里程碑结束 | | task-row-moved | payload: { draggedTask: Task, targetTask: Task, position: 'after' \| 'child', oldParent: Task \| null, newParent: Task \| null } | 拖拽TaskRow结束(可选) | | taskbar-resource-change v1.9.0 | payload: { task: Task, oldResourceId: string \| number, newResourceId: string \| number } | 任务跨资源移动事件(资源视图下拖拽任务到另一资源行) |

示例1:最简单的甘特图

<template>
  <div style="height: 600px;">
    <GanttChart :tasks="tasks" :assignee-options="assigneeOptions" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref([
  {
    id: 1,
    name: '任务1',
    startDate: '2025-01-01',
    endDate: '2025-01-10',
    progress: 100,
  },
])

const assigneeOptions = ref([
  { value: 'zhangsan', label: '张三' },
  { value: 'lisi', label: '李四' },
  { value: 'wangwu', label: '王五' },
])
</script>

示例2:带里程碑的甘特图

<template>
  <div style="height: 600px;">
    <GanttChart :tasks="tasks" :milestones="milestones" :assignee-options="assigneeOptions" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref([
  {
    id: 1,
    name: '项目启动',
    startDate: '2025-01-01',
    endDate: '2025-01-10',
    progress: 100,
  },
])

const milestones = ref([
  {
    id: 101,
    name: '项目立项',
    startDate: '2025-01-01',
    type: 'milestone',
    icon: 'diamond',
  },
])

const assigneeOptions = ref([
  { value: 'zhangsan', label: '张三' },
  { value: 'lisi', label: '李四' },
  { value: 'wangwu', label: '王五' },
])
</script>

示例3:隐藏工具栏,自定义控制按钮绑定事件

<template>
  <div>
    <!-- 自定义控制栏 -->
    <div class="custom-toolbar">
      <button @click="addTask">新增任务</button>
      <button @click="addMilestone">新增里程碑</button>
    </div>

    <!-- 甘特图组件,隐藏内置工具栏 -->
    <div style="height: 600px;">
      <GanttChart
        :tasks="tasks"
        :milestones="milestones"
        :show-toolbar="false"
        :assignee-options="assigneeOptions"
        @task-added="handleTaskAdded"
        @milestone-saved="handleMilestoneSaved"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref([])
const milestones = ref([])

const assigneeOptions = ref([
  { value: 'zhangsan', label: '张三' },
  { value: 'lisi', label: '李四' },
  { value: 'wangwu', label: '王五' },
])

const addTask = () => {
  const newTask = {
    id: Date.now(),
    name: '新任务',
    startDate: new Date().toISOString().split('T')[0],
    endDate: new Date().toISOString().split('T')[0],
    progress: 0,
  }
  tasks.value.push(newTask)
}

const addMilestone = () => {
  const newMilestone = {
    id: Date.now(),
    name: '新里程碑',
    startDate: new Date().toISOString().split('T')[0],
    type: 'milestone',
  }
  milestones.value.push(newMilestone)
}

const handleTaskAdded = e => {
  console.log('任务已添加:', e.task)
}

const handleMilestoneSaved = milestone => {
  console.log('里程碑已保存:', milestone)
}
</script>

示例4:外部组件控制状态(TimeScale、Fullscreen、Expand/Collapse、Locale、Theme)

通过响应式Props绑定来控制组件状态,组件状态会自动跟随Props变化。

<template>
  <div>
    <!-- 外部控制面板 -->
    <div class="control-panel">
      <button @click="propsFullscreen = !propsFullscreen">切换全屏</button>
      <button @click="propsExpandAll = !propsExpandAll">展开/收起所有</button>
      <button @click="propsLocale = 'zh-CN'">中文</button>
      <button @click="propsLocale = 'en-US'">English</button>
      <button @click="propsTimeScale = 'day'">日视图</button>
      <button @click="propsTimeScale = 'week'">周视图</button>
      <button @click="propsTimeScale = 'month'">月视图</button>
      <button @click="propsTheme = 'light'">亮色主题</button>
      <button @click="propsTheme = 'dark'">暗色主题</button>
    </div>

    <!-- 甘特图组件 -->
    <div style="height: 600px;">
      <GanttChart
        :tasks="tasks"
        :milestones="milestones"
        :locale="propsLocale"
        :theme="propsTheme"
        :time-scale="propsTimeScale"
        :fullscreen="propsFullscreen"
        :expand-all="propsExpandAll"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref([
  { id: 1, name: '任务1', startDate: '2025-01-01', endDate: '2025-01-10', progress: 50 },
  { id: 2, name: '任务2', startDate: '2025-01-05', endDate: '2025-01-15', progress: 30 },
])
const milestones = ref([])

// Props控制变量
const propsLocale = ref<'zh-CN' | 'en-US'>('zh-CN')
const propsTheme = ref<'light' | 'dark'>('light')
const propsTimeScale = ref<'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'>('week')
const propsFullscreen = ref(false)
const propsExpandAll = ref(false)
</script>

任务管理

任务是甘特图的核心元素,组件提供了完整的任务 CRUD 操作支持,包括添加、编辑、删除任务,以及丰富的交互事件。

Task 数据结构

| 字段名 | 类型 | 必填 | 默认值 | 说明 | | ------------------ | ---------- | ---- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- | | id | number | ✅ | - | 任务唯一标识符 | | name | string | ✅ | - | 任务名称 | | startDate | string | - | - | 开始日期,格式:'YYYY-MM-DD' 或 'YYYY-MM-DD HH:mm' | | endDate | string | - | - | 结束日期,格式:'YYYY-MM-DD' 或 'YYYY-MM-DD HH:mm' | | progress | number | - | 0 | 任务进度,范围 0-100 | | predecessor | number[] | - | - | 前置任务 ID 数组,标准格式:[1, 2, 3]兼容格式:也支持字符串 '1,2,3' 或字符串数组 ['1', '2', '3'],组件会自动解析 | | assignee | string | string[] v1.8.0 | - | - | 任务负责人,用作负责人下拉菜单的值绑定。支持单个负责人(字符串)或多个负责人(字符串数组) | | assigneeName | string | string[] v1.8.0 | - | - | 任务负责人姓名,自动从绑定的数据集assigneeOptions中获取Label作为显示,如果需要自定义,可以在GanttChart回调事件task-added中自定义信息。支持单个姓名(字符串)或多个姓名(字符串数组) | | avatar | string | string[] v1.8.0 | - | - | 任务负责人头像 URL。支持单个头像(字符串)或多个头像(字符串数组) | | estimatedHours | number | - | - | 预估工时(小时) | | actualHours | number | - | - | 实际工时(小时) | | parentId | number | - | - | 父任务 ID,用于任务分组 | | children | Task[] | - | - | 子任务数组 | | collapsed | boolean | - | false | 子任务是否折叠 | | isParent | boolean | - | - | 是否为父任务 | | type | string | - | - | 任务类型,'milestone' 表示里程碑,'milestone-group' 表示里程碑分组 | | description | string | - | - | 任务描述 | | icon | string | - | 'diamond' | 任务图标(用于里程碑),可选值:'diamond', 'flag', 'star', 'rocket' 等 | | level | number | - | 0 | 任务层级(自动计算) | | isTimerRunning | boolean | - | false | 计时器是否运行中 | | timerStartTime | number | - | - | 计时开始时间(时间戳) | | timerEndTime | number | - | - | 计时结束时间(时间戳) | | timerStartDesc | string | - | - | 计时开始时填写的描述 | | timerElapsedTime | number | - | 0 | 已计时的时长(毫秒) | | isEditable | boolean | - | true | 单个任务是否可编辑(可拖拽、拉伸),优先级高于全局 allowDragAndResize | | [key: string] | unknown | - | - | 支持自定义属性扩展,可添加任意额外字段 |

自定义属性扩展:Task 接口支持添加任意自定义字段,例如:prioritytagsstatusdepartment 等业务相关字段。

前置任务字段说明

  • 标准格式(推荐):predecessor: [1, 2, 3] - number 数组
  • 兼容格式1predecessor: '1,2,3' - 逗号分隔的字符串
  • 兼容格式2predecessor: ['1', '2', '3'] - 字符串数组
  • 组件内部会自动将所有格式统一解析为 number 数组
  • 无前置任务:使用空数组 []、空字符串 '' 或不设置该字段

任务相关属性

| 属性名 | 类型 | 默认值 | 说明 | | --------------------- | ---------------- | ----------- | -------------------------------------------------------------- | | tasks | Task[] | [] | 任务数据数组 | | useDefaultDrawer | boolean | true | 是否使用内置的任务编辑抽屉(TaskDrawer) | | taskBarConfig | TaskBarConfig | {} | 任务条样式配置,详见 TaskBarConfig 配置 | | taskListConfig | TaskListConfig | undefined | 任务列表配置,详见 TaskListConfig 配置 | | autoSortByStartDate | boolean | false | 是否根据开始时间自动排序任务 | | enableTaskRowMove | boolean | false | 是否允许拖拽和摆放TaskRow | | assigneeOptions | Array<{ key?: string \| number; value: string \| number; label: string }> | [] | 任务编辑抽屉中负责人下拉菜单的选项列表 | | taskListColumnRenderMode | 'default' \| 'declarative' | 'default' | 任务列表列渲染模式。'default':使用 TaskListColumnConfig 配置(兼容模式,将逐渐废弃);'declarative':使用 TaskListColumn 组件声明式定义列(推荐)。详见 TaskListColumn 声明式列定义 | | taskListRowClassName | string \| ((task: Task) => string) | undefined | 自定义任务行的 CSS 类名。可以是字符串或返回字符串的函数。注意:行的高度由组件内部统一管理,自定义高度样式不会生效 | | taskListRowStyle | CSSProperties \| ((task: Task) => CSSProperties) | undefined | 自定义任务行的内联样式。可以是样式对象或返回样式对象的函数。注意:行的高度和宽度由组件内部统一管理,自定义宽高样式不会生效 |

配置说明

  • 默认模式useDefaultDrawer=true(默认),双击任务自动打开内置 TaskDrawer
  • 自定义编辑器useDefaultDrawer=false 禁用内置抽屉,监听 @task-double-click 事件打开自定义编辑器
  • 只读模式useDefaultDrawer=false 且不监听 @task-double-click 事件,用户双击任务无反应

任务事件

💡 事件驱动架构:组件采用纯事件驱动设计,所有用户操作(添加、编辑、删除、拖拽等)都会触发对应事件,方便外部监听和处理。

| 事件名 | 参数 | 触发时机 | 说明 | | -------------------- | ----------------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | add-task | - | 点击工具栏"添加任务"按钮时 | 可用于自定义新增任务逻辑。如 useDefaultDrawer=true,组件会自动打开内置 TaskDrawer | | task-click | (task: Task, event: MouseEvent) => void | 点击任务条时 | 单击任务触发 | | task-double-click | (task: Task) => void | 双击任务条时 | 双击任务时始终触发useDefaultDrawer=true 时组件会额外打开内置编辑器,false 时不打开。事件触发与属性值无关 | | task-added | { task: Task } | 任务添加后 | 通过内置 TaskDrawer 添加任务后触发。注意:组件已自动更新 tasks 数据,外部只需监听此事件做额外处理(如调用 API 保存) | | task-updated | { task: Task } | 任务更新后 | 通过内置 TaskDrawer 或拖拽更新任务后触发。注意:组件已自动更新 tasks 数据,外部只需监听此事件做额外处理 | | task-deleted | { task: Task } | 任务删除后 | 通过内置 TaskDrawer 删除任务后触发。注意:组件已自动更新 tasks 数据,外部只需监听此事件做额外处理 | | taskbar-drag-end | (task: Task) => void | 拖拽任务条结束时 | 任务位置变化,startDate 和 endDate 已更新。注意:组件已自动更新 tasks 数据 | | taskbar-resize-end | (task: Task) => void | 调整任务条大小结束时 | 任务时长变化,endDate 已更新。注意:组件已自动更新 tasks 数据 | | predecessor-added | { targetTask: Task, newTask: Task } | 通过右键菜单添加前置任务后 | targetTask 是被添加前置任务的任务,newTask 是新创建的前置任务 | | successor-added | { targetTask: Task, newTask: Task } | 通过右键菜单添加后置任务后 | targetTask 是原任务,newTask 是新创建的后置任务(其 predecessor 已包含 targetTask.id) | | timer-started | (task: Task) => void | 任务计时器启动时 | 开始记录任务工时 | | timer-stopped | (task: Task) => void | 任务计时器停止时 | 停止记录任务工时 | | task-row-moved | payload: { draggedTask: Task, targetTask: Task, position: 'after' \| 'child', oldParent: Task \| null, newParent: Task \| null } | 拖拽TaskRow结束(可选) | 组件已自动完成数据移动和TaskList/Timeline同步。监听此事件为完全可选,仅用于显示提示、调用API保存等。position: 'after'=同级放置,'child'=作为子任务 |

数据同步说明

  • 组件内部自动更新:所有任务的增删改操作,组件都会自动更新 props.tasks 数据
  • 事件仅做通知:外部监听事件主要用于:显示提示消息、调用后端 API、更新其他相关数据等
  • 避免重复操作:不要在事件处理器中再次修改 tasks 数据,否则会导致重复更新

示例1:基础任务操作

<template>
  <div style="height: 600px;">
    <GanttChart
      :tasks="tasks"
      :assignee-options="assigneeOptions"
      @add-task="handleAddTask"
      @task-added="handleTaskAdded"
      @task-updated="handleTaskUpdated"
      @task-deleted="handleTaskDeleted"
      @task-click="handleTaskClick"
      @taskbar-drag-end="handleTaskDragEnd"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import type { Task } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref<Task[]>([
  {
    id: 1,
    name: '项目规划',
    startDate: '2025-01-01',
    endDate: '2025-01-10',
    progress: 100,
    assignee: '张三',
    estimatedHours: 40,
  },
  {
    id: 2,
    name: '需求分析',
    startDate: '2025-01-11',
    endDate: '2025-01-20',
    progress: 60,
    assignee: '李四',
    predecessor: [1], // 依赖任务1
  },
])

const assigneeOptions = ref([
  { value: 'zhangsan', label: '张三' },
  { value: 'lisi', label: '李四' },
  { value: 'wangwu', label: '王五' },
])

// 工具栏"添加任务"按钮点击事件
const handleAddTask = () => {
  console.log('准备新增任务...')
  // 组件会自动打开 TaskDrawer(如果 useDefaultDrawer=true)
  // 也可以在这里执行自定义逻辑,如显示提示消息
}

// 任务添加事件(通过内置抽屉添加)
const handleTaskAdded = (e: { task: Task }) => {
  console.log('新增任务:', e.task)
  // 注意:组件已自动将任务添加到 tasks 数组
  // 这里只需调用后端 API 保存即可
  // await api.createTask(e.task)
}

// 任务更新事件(通过内置抽屉或拖拽更新)
const handleTaskUpdated = (e: { task: Task }) => {
  console.log('更新任务:', e.task)
  // 注意:组件已自动更新 tasks 数组中的任务数据
  // 这里只需调用后端 API 更新即可
  // await api.updateTask(e.task.id, e.task)
}

// 任务删除事件
const handleTaskDeleted = (e: { task: Task }) => {
  console.log('删除任务:', e.task)
  // 注意:组件已自动从 tasks 数组中移除任务
  // 这里只需调用后端 API 删除即可
  // await api.deleteTask(e.task.id)
}

// 点击任务事件
const handleTaskClick = (task: Task) => {
  console.log('点击任务:', task.name)
}

// 拖拽任务结束事件
const handleTaskDragEnd = (task: Task) => {
  console.log('任务拖拽完成,新日期:', task.startDate, '至', task.endDate)
  // 可以在这里调用后端 API 保存新的日期
}
</script>

示例2:任务依赖关系(前置任务/后置任务)

任务可以通过 predecessor 字段配置前置任务,组件会自动绘制依赖关系连线:

<template>
  <GanttChart
    :tasks="tasks"
    :assignee-options="assigneeOptions"
    @predecessor-added="handlePredecessorAdded"
    @successor-added="handleSuccessorAdded"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import type { Task } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref<Task[]>([
  {
    id: 1,
    name: '需求分析',
    startDate: '2025-01-01',
    endDate: '2025-01-10',
    progress: 100,
    predecessor: [], // 无前置任务
  },
  {
    id: 2,
    name: '系统设计',
    startDate: '2025-01-11',
    endDate: '2025-01-20',
    progress: 80,
    predecessor: [1], // 依赖任务1(需求分析)
  },
  {
    id: 3,
    name: '数据库设计',
    startDate: '2025-01-11',
    endDate: '2025-01-18',
    progress: 90,
    predecessor: [1], // 依赖任务1
  },
  {
    id: 4,
    name: '前端开发',
    startDate: '2025-01-21',
    endDate: '2025-02-10',
    progress: 60,
    predecessor: [2], // 依赖任务2(系统设计)
  },
  {
    id: 5,
    name: '后端开发',
    startDate: '2025-01-19',
    endDate: '2025-02-08',
    progress: 70,
    predecessor: [2, 3], // 同时依赖任务2和3
  },
  {
    id: 6,
    name: '集成测试',
    startDate: '2025-02-11',
    endDate: '2025-02-20',
    progress: 30,
    predecessor: [4, 5], // 依赖前端和后端开发完成
  },
])

const assigneeOptions = ref([
  { value: 'zhangsan', label: '张三' },
  { value: 'lisi', label: '李四' },
  { value: 'wangwu', label: '王五' },
])

// 通过右键菜单添加前置任务时触发
const handlePredecessorAdded = (event: { targetTask: Task; newTask: Task }) => {
  console.log(`任务 [${event.targetTask.name}] 添加了前置任务 [${event.newTask.name}]`)
  // 组件会自动更新 targetTask 的 predecessor 数组(追加新任务 ID)
  // 这里可以调用后端 API 保存依赖关系
  // await api.addTaskDependency(event.targetTask.id, event.newTask.id)
}

// 通过右键菜单添加后置任务时触发
const handleSuccessorAdded = (event: { targetTask: Task; newTask: Task }) => {
  console.log(`任务 [${event.targetTask.name}] 添加了后置任务 [${event.newTask.name}]`)
  // 组件会自动更新 newTask 的 predecessor 数组(将 targetTask.id 添加进去)
  // 这里可以调用后端 API 保存依赖关系
  // await api.addTaskDependency(event.newTask.id, event.targetTask.id)
}
</script>

依赖关系说明

  • predecessor 字段支持多种格式
    • 标准格式(推荐):[1, 2, 3] - number 数组
    • 兼容格式1:'1,2,3' - 逗号分隔的字符串
    • 兼容格式2:['1', '2', '3'] - 字符串数组
    • 组件会自动解析所有格式
  • 前置任务:必须先完成的任务(例如:设计完成后才能开发)
  • 后置任务:依赖当前任务的任务(当前任务是其他任务的前置任务)
  • 组件会自动绘制依赖关系连线,从前置任务指向依赖它的任务
  • 可以通过内置右键菜单添加/删除前置任务和后置任务
  • 内置菜单删除任务时,组件会自动清理相关的依赖关系引用
  • 无前置任务:使用空数组 []、空字符串 '' 或不设置 predecessor 字段

示例3:隐藏工具栏,使用自定义按钮触发事件

适用于需要完全自定义控制栏的场景:

<template>
  <div>
    <!-- 自定义控制栏 -->
    <div class="custom-toolbar">
      <button @click="triggerAddTask">新增任务</button>
      <button @click="triggerAddMilestone">新增里程碑</button>
      <!-- 其他自定义按钮... -->
    </div>

    <!-- 甘特图组件,隐藏内置工具栏 -->
    <GanttChart
      :tasks="tasks"
      :milestones="milestones"
      :show-toolbar="false"
      :use-default-drawer="true"
      :use-default-milestone-dialog="true"
      :assignee-options="assigneeOptions"
      @add-task="handleAddTask"
      @add-milestone="handleAddMilestone"
      @task-added="handleTaskAdded"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref([])
const milestones = ref([])

const assigneeOptions = ref([
  { value: 'zhangsan', label: '张三' },
  { value: 'lisi', label: '李四' },
  { value: 'wangwu', label: '王五' },
])

// 自定义按钮触发事件(组件会响应并打开内置编辑器)
const triggerAddTask = () => {
  // 直接触发组件的 add-task 事件
  // 由于 useDefaultDrawer=true,组件会自动打开 TaskDrawer
}

const triggerAddMilestone = () => {
  // 直接触发组件的 add-milestone 事件
  // 由于 useDefaultMilestoneDialog=true,组件会自动打开 MilestoneDialog
}

// 监听事件处理逻辑
const handleAddTask = () => {
  console.log('准备新增任务(由自定义按钮触发)')
}

const handleAddMilestone = () => {
  console.log('准备新增里程碑(由自定义按钮触发)')
}

const handleTaskAdded = e => {
  console.log('任务已添加:', e.task)
  // 调用 API 保存...
}
</script>

💡 灵活性设计

  • 显示工具栏 + 默认编辑器:最简单的开箱即用方式
  • 隐藏工具栏 + 自定义按钮 + 默认编辑器:自定义控制栏样式,保留默认编辑功能
  • 隐藏工具栏 + 自定义按钮 + 自定义编辑器:完全自定义所有交互逻辑

示例4:任务行拖拽排序

允许用户通过拖拽 TaskRow 来调整任务的层级关系和前后顺序:

<template>
  <div style="height: 600px;">
    <GanttChart
      :tasks="tasks"
      :enable-task-row-move="true"
      :assignee-options="assigneeOptions"
      @task-row-moved="handleTaskRowMoved"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import type { Task } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const tasks = ref<Task[]>([
  {
    id: 1,
    name: '项目规划',
    startDate: '2025-01-01',
    endDate: '2025-01-10',
    progress: 100,
  },
  {
    id: 2,
    name: '需求分析',
    startDate: '2025-01-11',
    endDate: '2025-01-20',
    progress: 60,
    parentId: 1,
  },
  {
    id: 3,
    name: '系统设计',
    startDate: '2025-01-21',
    endDate: '2025-01-30',
    progress: 40,
  },
])

const assigneeOptions = ref([
  { value: 'zhangsan', label: '张三' },
  { value: 'lisi', label: '李四' },
  { value: 'wangwu', label: '王五' },
])

// 任务行拖拽完成事件(可选)
const handleTaskRowMoved = async (payload: {
  draggedTask: Task
  targetTask: Task
  position: 'after' | 'child'
  oldParent: Task | null
  newParent: Task | null
}) => {
  const { draggedTask, targetTask, position, oldParent, newParent } = payload
  
  // 组件已自动完成任务移动、parentId更新和TaskList/Timeline同步
  // 监听此事件为完全可选,仅用于:
  
  // 1. 显示自定义提示消息
  const oldParentName = oldParent?.name || '根目录'
  const newParentName = newParent?.name || '根目录'
  const positionText = position === 'after' ? '在目标任务之后' : '作为目标任务的子任务'
  showMessage(`任务 [${draggedTask.name}] 已从 [${oldParentName}] 移动到 [${newParentName}] (${positionText})`, 'success')
  
  // 2. 调用后端 API 保存新的任务层级关系
  try {
    await api.updateTaskHierarchy({
      taskId: draggedTask.id,
      targetTaskId: targetTask.id,
      position: position,
      oldParentId: oldParent?.id,
      newParentId: newParent?.id,
    })
  } catch (error) {
    console.error('保存任务层级失败:', error)
    showMessage('保存失败,请刷新页面', 'error')
  }
  
  // 3. 触发其他业务逻辑(如更新关联数据、记录操作日志等)
  // ...
}
</script>

拖拽排序说明

  • 启用拖拽:设置 enable-task-row-move="true" 启用任务行拖拽功能(默认为 false
  • 拖拽算法(组件内部自动执行):
    • 算法1(放置在后面):当目标任务没有子任务时,被拖拽的任务会放置在目标任务之后(同级),position='after'
    • 算法2(作为子任务):当目标任务有子任务时,被拖拽的任务会成为目标任务的第一个子任务,position='child'
  • 视觉反馈
    • 拖拽时会显示半透明的跟随元素
    • 悬停在有效目标任务上时显示蓝色边框提示
    • 无子任务的任务显示蓝色底部边框
    • 有子任务的任务显示蓝色四周边框
  • 自动同步:组件内部通过对象引用直接修改 props.tasks,自动完成任务移动、parentId 更新、children 数组调整以及 TaskList/Timeline 同步
  • 事件监听(可选)
    • task-row-moved 事件为完全可选,仅用于显示提示、调用API保存、记录日志等额外处理
    • 无需手动更新 tasks.value,组件已自动完成数据同步
  • 事件参数
    • draggedTask: 被拖拽的任务
    • targetTask: 目标任务
    • position: 放置位置('after' 或 'child')
    • oldParent: 原父任务(null 表示根目录)
    • newParent: 新父任务(null 表示根目录)
  • 限制条件
    • 不能拖拽到自己身上
    • 不能拖拽到自己的子任务上(避免循环引用)
    • 里程碑和里程碑分组不能被拖拽

资源管理 v1.9.0

资源管理用于管理项目中的人力或设备资源,支持资源视图下的任务分配、资源负载分析、冲突检测等功能。通过 viewMode="resource" 属性切换到资源计划视图。

核心特性

  • 📊 资源视图:按资源维度展示任务分配情况
  • 🎯 负载分析:实时显示资源占用率和超载状态
  • ⚠️ 冲突检测:自动检测资源时间冲突(如 A任务:40% + B任务:40% + C任务:30% = 110%超载)
  • 🎨 可视化:斜纹背景标识冲突区域,资源Tab显示占用比例
  • 🔄 跨资源移动:支持拖拽任务到不同资源行进行重新分配

视图限制

  • 任务关系线禁用:资源视图下不显示任务之间的前后置关系线,因为资源视图关注资源分配而非任务依赖关系
  • 不支持实际TaskBarshowActualTaskbar 属性在资源视图下无效,不会显示实际执行进度条

Resource 数据结构

| 字段名 | 类型 | 必填 | 默认值 | 说明 | | --------------- | ------------------- | ---- | ------ | ----------------------------------------------------------------------------------------------- | | id | string \| number | ✅ | - | 资源唯一标识符 | | name | string | ✅ | - | 资源名称(如人名、设备名) | | type | string | - | - | 资源类型(如 'developer', 'designer', 'device' 等) | | avatar | string | - | - | 资源头像 URL | | description | string | - | - | 资源描述 | | department | string | - | - | 所属部门 | | skills | string[] | - | - | 技能标签数组(如 ['Vue', 'React', 'TypeScript']) | | capacity | number | - | - | 资源容量/利用率(0-100),可用于表示资源的整体负载水平 | | color | string | - | - | 自定义资源行左边框颜色(如 '#ff5733'),若不设置则使用默认颜色方案 | | tasks | Task[] | - | [] | 分配给该资源的任务数组,每个任务需包含 resources 字段标注资源占用比例 | | [key: string] | unknown | - | - | 支持自定义属性扩展,可添加任意额外字段 |

自定义属性扩展:Resource 接口支持添加任意自定义字段,例如:emailphonelocationworkHours 等业务相关字段。

任务资源关联说明

  • 每个 Resource 包含一个 tasks 数组,存储分配给该资源的任务
  • 每个 Task 应包含 resources 字段,标注该任务使用了哪些资源及占用比例
  • 资源占用比例格式:task.resources = [{ id: 'resource1', capacity: 60 }, { id: 'resource2', capacity: 40 }]
  • capacity 范围:20-100,表示该任务占用该资源的百分比
  • 冲突检测:当同一资源在同一时间段的多个任务 capacity 总和 > 100% 时,会显示冲突警告

Resource 数据示例

import type { Resource, Task } from 'jordium-gantt-vue3'

const resources: Resource[] = [
  {
    id: 'dev-001',
    name: '张三',
    type: 'developer',
    avatar: '/avatars/zhangsan.jpg',
    department: '研发部',
    skills: ['Vue', 'TypeScript', 'Node.js'],
    capacity: 85, // 整体负载水平
    color: '#409eff',
    tasks: [
      {
        id: 1,
        name: '前端开发',
        startDate: '2026-02-01',
        endDate: '2026-02-10',
        progress: 50,
        resources: [
          { id: 'dev-001', capacity: 60 }, // 该任务占用张三60%的时间
          { id: 'dev-002', capacity: 40 }  // 同时占用李四40%的时间
        ]
      },
      {
        id: 2,
        name: '代码审查',
        startDate: '2026-02-05',
        endDate: '2026-02-08',
        progress: 0,
        resources: [
          { id: 'dev-001', capacity: 40 } // 该任务占用张三40%的时间
        ]
      }
      // 注意:如果两个任务时间重叠,张三在2月5-8日的总占用率为100%(60%+40%),临界值
    ]
  },
  {
    id: 'dev-002',
    name: '李四',
    type: 'developer',
    avatar: '/avatars/lisi.jpg',
    department: '研发部',
    skills: ['React', 'TypeScript'],
    tasks: []
  }
]

资源冲突检测示例

// 场景:张三在同一时间段被分配了3个任务
const resource = {
  id: 'dev-001',
  name: '张三',
  tasks: [
    {
      id: 1,
      name: '任务A',
      startDate: '2026-02-10',
      endDate: '2026-02-15',
      resources: [{ id: 'dev-001', capacity: 40 }] // 占用40%
    },
    {
      id: 2,
      name: '任务B',
      startDate: '2026-02-10',
      endDate: '2026-02-20',
      resources: [{ id: 'dev-001', capacity: 40 }] // 占用40%
    },
    {
      id: 3,
      name: '任务C',
      startDate: '2026-02-12',
      endDate: '2026-02-18',
      resources: [{ id: 'dev-001', capacity: 30 }] // 占用30%
    }
  ]
}

// 冲突分析:
// - 2月10-11日:A(40%) + B(40%) = 80%,未超载
// - 2月12-15日:A(40%) + B(40%) + C(30%) = 110%,超载!显示冲突警告
// - 2月16-18日:B(40%) + C(30%) = 70%,未超载
// - 2月19-20日:B(40%),未超载

资源相关属性

| 属性名 | 类型 | 默认值 | 说明 | | --------------------- | --------------------- | ------------ | --------------------------------------------------------------------------- | | resources | Resource[] | [] | 资源数据数组 | | viewMode | 'task' \| 'resource' | 'task' | 视图模式:'task' 任务计划视图,'resource' 资源计划视图 | | resourceListConfig | ResourceListConfig | undefined | 资源列表配置,类似 TaskListConfig,用于配置资源列表的列定义、宽度等 | | showConflicts | boolean | true | 是否显示资源冲突可视化层(资源视图下显示斜纹背景标识超载区域) | | showTaskbarTab | boolean | true | 是否显示TaskBar上的资源Tab标签(资源视图下TaskBar上的资源占用比例标签) |

资源事件

| 事件名 | 参数 | 触发时机 | 说明 | | -------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------- | | taskbar-resource-change | payload: { task: Task, oldResourceId: string \| number, newResourceId: string \| number } | 任务跨资源拖拽结束时 | 资源视图下拖拽任务到另一资源行时触发,组件已自动更新任务的 resources 字段 |

数据同步说明

  • 组件内部自动更新:资源相关操作(如任务跨资源移动)组件会自动更新 props.resources 和任务的 resources 字段
  • 事件仅做通知:外部监听事件主要用于:显示提示消息、调用后端 API、更新其他相关数据等
  • 避免重复操作:不要在事件处理器中再次修改数据,否则会导致重复更新

示例:资源视图基础用法

<template>
  <div style="height: 600px;">
    <GanttChart
      :resources="resources"
      view-mode="resource"
      :show-conflicts="true"
      :show-taskbar-tab="true"
      @taskbar-resource-change="handleTaskbarResourceChange"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import type { Resource } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const resources = ref<Resource[]>([
  {
    id: 'dev-001',
    name: '张三',
    type: 'developer',
    department: '研发部',
    tasks: [
      {
        id: 1,
        name: '前端开发',
        startDate: '2026-02-01',
        endDate: '2026-02-10',
        progress: 50,
        resources: [{ id: 'dev-001', capacity: 60 }]
      }
    ]
  }
])

const handleTaskbarResourceChange = (payload: any) => {
  console.log('任务资源变更:', payload)
  // 调用后端API保存资源分配变更
  // api.updateTaskResource(payload.task.id, payload.newResourceId)
}
</script>

里程碑管理

里程碑用于标记项目中的重要时间节点,如项目启动、阶段完成、产品发布等。组件提供了灵活的里程碑编辑配置,默认使用内置的 MilestoneDialog,也支持完全自定义编辑行为。

注意: 里程碑与任务是独立的数据集合,不存在直接关联关系。里程碑通过 milestones 属性独立管理。

Milestone 数据结构

| 字段名 | 类型 | 必填 | 默认值 | 说明 | | ------------- | -------- | ---- | ------------- | ---------------------------------------------------------- | | id | number | ✅ | - | 里程碑唯一标识符 | | name | string | ✅ | - | 里程碑名称 | | startDate | string | ✅ | - | 里程碑日期,格式:'YYYY-MM-DD' 或 'YYYY-MM-DD HH:mm' | | endDate | string | - | - | 结束日期(通常里程碑不需要,自动设置为与 startDate 相同) | | assignee | string | - | - | 负责人 | | type | string | ✅ | 'milestone' | 类型标识,必须设为 'milestone' | | icon | string | - | 'diamond' | 里程碑图标,可选值:'diamond', 'flag', 'star', 'rocket' 等 | | description | string | - | - | 里程碑描述 |

注意milestones 属性的类型为 Task[],需要确保每个里程碑对象的 type 字段设置为 'milestone'

里程碑相关属性

| 属性名 | 类型 | 默认值 | 说明 | | --------------------------- | --------- | ------ | -------------------------------------------------------- | | milestones | Task[] | [] | 里程碑数据数组(类型为 Task[],需确保 type='milestone') | | useDefaultMilestoneDialog | boolean | true | 是否使用内置的里程碑编辑对话框(MilestoneDialog) |

配置说明

  • 默认模式useDefaultMilestoneDialog=true(默认),双击里程碑自动打开内置 MilestoneDialog
  • 禁用编辑器useDefaultMilestoneDialog=false,双击里程碑无反应(组件不打开任何编辑器)
  • 自定义编辑器:可以监听 onMilestoneDoubleClick 回调或相关事件,实现自定义编辑逻辑

💡 里程碑与任务的区别

  • 里程碑数据通过 milestones 属性独立管理,与 tasks 分开
  • 里程碑对象的 type 字段必须设置为 'milestone'
  • 里程碑不支持子任务、依赖关系等复杂结构
  • 里程碑主要用于标记关键时间节点

里程碑回调函数(向后兼容)

⚠️ 已废弃:请使用新的事件驱动 API(见下方"里程碑事件"章节)

里程碑事件

💡 事件驱动架构:里程碑管理采用事件驱动设计,推荐使用事件 API 替代回调函数。

| 事件名 | 参数 | 触发时机 | 说明 | | ------------------------ | --------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | add-milestone | - | 点击工具栏"添加里程碑"按钮时 | 可用于自定义新增里程碑逻辑。如 useDefaultMilestoneDialog=true,组件会自动打开内置 MilestoneDialog | | milestone-saved | (milestone: Task) => void | 里程碑保存后(新增或编辑) | 通过内置 MilestoneDialog 保存里程碑后触发。注意:组件已自动更新 milestones 数据,外部只需监听此事件做额外处理(如调用 API 保存) | | milestone-deleted | { milestoneId: number } | 里程碑删除后 | 通过内置 MilestoneDialog 删除里程碑后触发。注意:组件已自动更新 milestones 数据,外部只需监听此事件做额外处理 | | milestone-icon-changed | { milestoneId: number, icon: string } | 里程碑图标变更后 | 通过内置 MilestoneDialog 修改图标后触发 | | milestone-drag-end | (milestone: Task) => void | 拖拽里程碑结束时 | 里程碑日期已更新。注意:组件已自动更新 milestones 数据 |

数据同步说明

  • 组件内部自动更新:所有里程碑的增删改操作,组件都会自动更新 props.milestones 数据
  • 事件仅做通知:外部监听事件主要用于:显示提示消息、调用后端 API、更新其他相关数据等
  • 避免重复操作:不要在事件处理器中再次修改 milestones 数据,否则会导致重复更新

示例1:使用事件驱动 API(推荐)

使用新的事件 API,组件会自动管理数据,更加简洁:

<template>
  <div style="height: 600px;">
    <GanttChart
      :milestones="milestones"
      @add-milestone="handleAddMilestone"
      @milestone-saved="handleMilestoneSaved"
      @milestone-deleted="handleMilestoneDeleted"
      @milestone-icon-changed="handleMilestoneIconChanged"
      @milestone-drag-end="handleMilestoneDrag"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import type { Task } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'

const milestones = ref<Task[]>([
  {
    id: 101,
    name: '项目启动',
    startDate: '2025-01-01',
    type: 'milestone',
    icon: 'diamond',
    assignee: '项目经理',
    description: '项目正式启动',
  },
  {
    id: 102,
    name: '需求评审',
    startDate: '2025-01-15',
    type: 'milestone',
    icon: 'flag',
  },
])

// 工具栏"添加里程碑"按钮点击事件
const handleAddMilestone = () => {
  console.log('准备新增里程碑...')
  // 组件会自动打开 MilestoneDialog(如果 useDefaultMilestoneDialog=true)
}

// 里程碑保存事件(添加或编辑)
const handleMilestoneSaved = (milestone: Task) => {
  console.log('里程碑已保存:', milestone)
  // 注意:组件已自动更新 milestones 数组
  // 这里只需调用后端 API 保存即可
  // await api.saveMilestone(milestone)
}

// 里程碑删除事件
const handleMilestoneDeleted = (e: { milestoneId: number }) => {
  console.log('里程碑已删除, ID:', e.milestoneId)
  // 注意:组件已自动从 milestones 数组中移除
  // 这里只需调用后端 API 删除即可
  // await api.deleteMilestone(e.milestoneId)
}

// 里程碑图标变更事件
const handleMilestoneIconChanged = (e: { milestoneId: number; icon: string }) => {
  console.log('里程碑图标已变更:', e.milestoneId, '->', e.icon)
  // 组件已自动更新图标,这里可以调用 API 保存
  // await api.updateMilestoneIcon(e.milestoneId, e.icon)
}

// 拖拽里程碑结束事件
const handleMilestoneDrag = (milestone: Task) => {
  console.log('里程碑拖拽完成,新日期:', milestone.startDate)
  // 组件已自动更新日期,这里可以调用 API 保存
  // await api.updateMilestoneDate(milestone.id, milestone.startDate)
}
</script>

示例2:使用自定义里程碑编辑对话框

如果需要完全自定义里程碑编辑界面,可以禁用内置对话框并使用自己的组件:

<template>
  <div style="height: 600px;">
    <GanttChart
      :milestones="milestones"
      :use-default-milestone-dialog="false"
      @add-milestone="handleAddMilestone"
      @milestone-saved="handleMilestoneSaved"
      @milestone-deleted="handleMilestoneDeleted"
      @milestone-drag-end="handleMilestoneDrag"
    />

    <!-- 自定义里程碑编辑对话框 -->
    <CustomMilestoneDialog
      v-model:visible="customDialogVisible"
      :milestone="editingMilestone"
      :is-new="isNewMilestone"
      @save="handleCustomDialogSave"
      @delete="handleCustomDialogDelete"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { GanttChart } from 'jordium-gantt-vue3'
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
import CustomMilestoneDialog from './CustomMilestoneDialog.vue'
import type { Task } from 'jordium-gantt-vue3'

const milestones = ref<Task[]>([
  {
    id: 101,
    name: '项目启动',
    startDate: '2025-01-01',
    type: 'milestone',
    icon: 'diamond',
    assignee: '项目经理',
    description: '项目正式启动',
  },
])

const customDialogVisible = ref(false)
const editingMilestone = ref<Task | null>(null)
const isNewMilestone = ref(false)

// 点击工具栏"添加里程碑"按钮
const handleAddMilestone = () => {
  editingMilestone.value = null
  isNewMilestone.value = true
  customDialogVisible.value = true
}

// 双击里程碑时打开自定义对话框
// 注意:需要监听 Timeline 组件的里程碑双击事件
// 或者通过外部按钮/列表项触发编辑
const openEditDialog = (milestone: Task) => {
  editingMilestone.value = { ...milestone }
  isNewMilestone.value = false
  customDialogVisible.value = true
}

// 自定义对话框保存事件
const handleCustomDialogSave = (milestone: Task) => {
  if (isNewMilestone.value) {
    // 新增里程碑
    const newMilestone = {
      ...milestone,
      id: Date.now(), // 生成新 ID
      type: 'milestone',
    }
    milestones.value.push(newMilestone)

    // 调用后端 API 保存
    // await api.createMilestone(newMilestone)
  } else {
    // 更新现有里程碑
    const index = milestones.value.findIndex(m => m.id === milestone.id)
    if (index !== -1) {
      milestones.value[index] = { ...milestone }
    }

    // 调用后端 API 更新
    // await api.updateMilestone(milestone)
  }

  customDialogVisible.value = false
}

// 自定义对话框删除事件
const handleCustomDialogDelete = (milestoneId: number) => {
  const index = milestones.value.findIndex(m => m.id === milestoneId)
  if (index !== -1) {
    milestones.value.splice(index, 1)
  }

  // 调用后端 API 删除
  // await api.deleteMilestone(milestoneId)

  customDialogVisible.value = false
}

// 以下事件处理器仍然有效(用于拖拽等操作)
const handleMilestoneSaved = (milestone: Task) => {
  console.log('里程碑已保存(通过其他方式):', milestone)
}

const handleMilestoneDeleted = (e: { milestoneId: number }) => {
  console.log('里程碑已删除(通过其他方式):', e.milestoneId)
}

const handleMilestoneDrag = (milestone: Task) => {
  console.log('里程碑拖拽完成:', milestone.startDate)
  // 调用 API 更新日期
  // await api.updateMilestoneDate(milestone.id, milestone.startDate)
}
</script>

自定义对话框组件示例 (CustomMilestoneDialog.vue - 使用 Element Plus):

注意:以下示例使用 Element Plus UI 框架。你也可以使用其他 UI 框架(如 Ant Design Vue、Naive UI 等)或原生 HTML 实现。

<template>
  <el-dialog
    v-model="dialogVisible"
    :title="isNew ? '新增里程碑' : '编辑里程碑'"
    width="500px"
    @close="handleClose"
  >
    <el-form :model="form" label-width="100px">
      <el-form-item label="里程碑名称">
        <el-input v-model="form.name" placeholder="请输入里程碑名称" />
      </el-form-item>

      <el-form-item label="日期">
        <el-date-picker
          v-model="form.startDate"
          type="date"
          placeholder="选择日期"
          value-format="YYYY-MM-DD"
        />
      </el-form-item>

      <el-form-item label="负责人">
        <el-input v-model="form.assignee" placeholder="请输入负责人" />
      </el-form-item>

      <el-form-item label="图标">
        <el-select v-model="form.icon" placeholder="选择图标">
          <el-option label="钻石" value="diamond" />
          <el-option label="旗帜" value="flag" />
          <el-option label="星星" value="star" />
          <el-option label="火箭" value="rocket" />
        </el-select>
      </el-form-item>

      <el-form-item label="描述">
        <el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入描述" />
      </el-form-item>
    </el-form>

    <template #footer>
      <div class="dialog-footer">
        <el-button v-if="!isNew" type="danger" @click="handleDelete"> 删除 </el-button>
        <el-button @click="handleClose">取消</el-button>
        <el-button type="primary" @click="handleSave">保存</el-button>
      </div>
    </template>
  </el-dialog>
</template>

<script