v1.8.0 - Add actual Taskbar component

This commit is contained in:
LINING-PC\lining
2026-01-17 18:03:19 +08:00
parent 9fc10b2851
commit 90f0f5bc60
23 changed files with 1445 additions and 127 deletions
+24
View File
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.8.0] - 2026-01-17
### Added
- 🎉 新增:GanttChart允许展示实际Taskbar
- 🎉 新增:TaskDrawer增加实际开始日期和实际结束日期的属性
- 🎉 新增:TaskDrawer增加当前Task状态:待处理、进行中、已完成、已逾期
- 🎉 新增:GanttChart增加Taskbar不同状态下背景色预设属性pendingTaskBackgroundColor、delayTaskBackgroundColo- pleteTaskBackgroundColor、ongoingTaskBackgroundColor
- 🎉 新增:Task对象增加barColor属性,允许自定义对应Taskbar的背景色,优先级高于GanttChart预设背景色
- 🎉 新增:Taskbar增加气泡提示框
- 🎉 新增:GanttChart增加showActualTaskbar属性用于控制是否展示实际Taskbar,默认值为false,需要时手动打开
- 🎉 新增:GanttChart增加enableTaskbarTooltip属性用于控制是否展示Taskbar的气泡提示框,默认值为true,需要时手动关闭
- 🎉 Added: GanttChart allows displaying actual Taskbar
- 🎉 Added: TaskDrawer adds properties for actual start date and actual end date
- 🎉 Added: TaskDrawer adds current Task status: Pending, Ongoing, Completed, Overdue
- 🎉 Added: GanttChart adds preset background color properties for Taskbar in different statuses: pendingTaskBackgroundColor, delayTaskBackgroundColor, completeTaskBackgroundColor, ongoingTaskBackgroundColor
- 🎉 Added: Task object adds barColor property to allow customization of the corresponding Taskbar background color, with higher priority than GanttChart preset background colors
- 🎉 Added: Taskbar adds tooltip
- 🎉 Added: GanttChart adds showActualTaskbar property to control whether to show actual Taskbar
- 🎉 Added: GanttChart adds enableTaskbarTooltip property to control whether to show Taskbar tooltip
### Enhancement
- 🎉 优化:Task对象assignee以及avatar属性允许接收数组,标记多为负责人
- Optimized: The assignee and avatar properties of the Task object now accept arrays to mark multiple assignees
## [1.7.2] - 2026-01-15 ## [1.7.2] - 2026-01-15
### Added ### Added
+9 -3
View File
@@ -210,6 +210,12 @@ npm run dev
| `fullscreen` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `false` | Fullscreen state control (reactive). Component's fullscreen state will follow changes | | `fullscreen` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `false` | Fullscreen state control (reactive). Component's fullscreen state will follow changes |
| `expandAll` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | Expand/collapse all tasks (reactive). All tasks' expand state will follow changes | | `expandAll` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | Expand/collapse all tasks (reactive). All tasks' expand state will follow changes |
| `enableLinkAnchor` ![v1.7.2](https://img.shields.io/badge/v1.7.2-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | Whether to enable Link AnchorDefault: true | | `enableLinkAnchor` ![v1.7.2](https://img.shields.io/badge/v1.7.2-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | Whether to enable Link AnchorDefault: true |
| `pendingTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#409eff'` | Background color for pending tasks' TaskBar. Supports hex color values (e.g., `'#409eff'`). **Priority**: Higher than system default, lower than Task object's `barColor` property |
| `delayTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#f56c6c'` | Background color for overdue tasks' TaskBar. Supports hex color values (e.g., `'#f56c6c'`). **Priority**: Higher than system default, lower than Task object's `barColor` property |
| `completeTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#909399'` | Background color for completed tasks' TaskBar. Supports hex color values (e.g., `'#909399'`). **Priority**: Higher than system default, lower than Task object's `barColor` property |
| `ongoingTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#e6a23c'` | Background color for ongoing tasks' TaskBar. Supports hex color values (e.g., `'#e6a23c'`). **Priority**: Higher than system default, lower than Task object's `barColor` property |
| `showActualTaskbar` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `false` | Whether to display actual TaskBar (shows actual execution progress below planned TaskBar) |
| `enableTaskbarTooltip` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | Whether to enable TaskBar hover tooltip (shows task details on mouse hover) |
#### TaskListColumn Component Props #### TaskListColumn Component Props
@@ -618,9 +624,9 @@ Tasks are the core elements of the Gantt chart. The component provides complete
| `endDate` | `string` | - | - | End date, format: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:mm' | | `endDate` | `string` | - | - | End date, format: 'YYYY-MM-DD' or 'YYYY-MM-DD HH:mm' |
| `progress` | `number` | - | `0` | Task progress, range 0-100 | | `progress` | `number` | - | `0` | Task progress, range 0-100 |
| `predecessor` | `number[]` | - | - | Array of predecessor task IDs, standard format: `[1, 2, 3]`<br/>**Compatible formats**: Also supports string `'1,2,3'` or string array `['1', '2', '3']`, component will auto-parse | | `predecessor` | `number[]` | - | - | Array of predecessor task IDs, standard format: `[1, 2, 3]`<br/>**Compatible formats**: Also supports string `'1,2,3'` or string array `['1', '2', '3']`, component will auto-parse |
| `assignee` | `string` | - | - | Task assignee, used as the value binding for the assignee dropdown menu | | `assignee` | `string` \| `string[]` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | - | - | Task assignee, used as the value binding for the assignee dropdown menu. Supports single assignee (string) or multiple assignees (string array) |
| `assigneeName` | `string` | - | - | Task assignee name, automatically obtained from the label in the bound `assigneeOptions` dataset; for custom display, you can set it in the `task-added` callback event of GanttChart | | `assigneeName` | `string` \| `string[]` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | - | - | Task assignee name, automatically obtained from the label in the bound `assigneeOptions` dataset; for custom display, you can set it in the `task-added` callback event of GanttChart. Supports single name (string) or multiple names (string array) |
| `avatar` | `string` | - | - | Avatar URL of task assignee | | `avatar` | `string` \| `string[]` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | - | - | Avatar URL of task assignee. Supports single avatar (string) or multiple avatars (string array) |
| `estimatedHours` | `number` | - | - | Estimated hours | | `estimatedHours` | `number` | - | - | Estimated hours |
| `actualHours` | `number` | - | - | Actual hours | | `actualHours` | `number` | - | - | Actual hours |
| `parentId` | `number` | - | - | Parent task ID, used for task grouping | | `parentId` | `number` | - | - | Parent task ID, used for task grouping |
+9 -3
View File
@@ -208,6 +208,12 @@ npm run dev
| `fullscreen` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `false` | 全屏状态控制(响应式)。设置后组件全屏状态将跟随变化 | | `fullscreen` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `false` | 全屏状态控制(响应式)。设置后组件全屏状态将跟随变化 |
| `expandAll` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | 展开/收起所有任务(响应式)。设置后所有任务的展开状态将跟随变化 | | `expandAll` ![v1.7.1](https://img.shields.io/badge/v1.7.1-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | 展开/收起所有任务(响应式)。设置后所有任务的展开状态将跟随变化 |
| `enableLinkAnchor` ![v1.7.2](https://img.shields.io/badge/v1.7.2-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | 是否开启Taskbar的关系线锚点,默认值: true | | `enableLinkAnchor` ![v1.7.2](https://img.shields.io/badge/v1.7.2-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | 是否开启Taskbar的关系线锚点,默认值: true |
| `pendingTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#409eff'` | 待处理任务的TaskBar背景色。支持十六进制颜色值(如 `'#409eff'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
| `delayTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#f56c6c'` | 已逾期任务的TaskBar背景色。支持十六进制颜色值(如 `'#f56c6c'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
| `completeTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#909399'` | 已完成任务的TaskBar背景色。支持十六进制颜色值(如 `'#909399'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
| `ongoingTaskBackgroundColor` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `string` | `'#e6a23c'` | 进行中任务的TaskBar背景色。支持十六进制颜色值(如 `'#e6a23c'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
| `showActualTaskbar` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `false` | 是否显示实际TaskBar(在计划TaskBar下方显示实际执行进度) |
| `enableTaskbarTooltip` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | `boolean` | `true` | 是否启用TaskBar悬停提示框(鼠标悬停显示任务详情) |
#### TaskListColumn 属性 #### TaskListColumn 属性
@@ -616,9 +622,9 @@ const propsExpandAll = ref(false)
| `endDate` | `string` | - | - | 结束日期,格式:'YYYY-MM-DD' 或 'YYYY-MM-DD HH:mm' | | `endDate` | `string` | - | - | 结束日期,格式:'YYYY-MM-DD' 或 'YYYY-MM-DD HH:mm' |
| `progress` | `number` | - | `0` | 任务进度,范围 0-100 | | `progress` | `number` | - | `0` | 任务进度,范围 0-100 |
| `predecessor` | `number[]` | - | - | 前置任务 ID 数组,标准格式:`[1, 2, 3]`<br/>**兼容格式**:也支持字符串 `'1,2,3'` 或字符串数组 `['1', '2', '3']`,组件会自动解析 | | `predecessor` | `number[]` | - | - | 前置任务 ID 数组,标准格式:`[1, 2, 3]`<br/>**兼容格式**:也支持字符串 `'1,2,3'` 或字符串数组 `['1', '2', '3']`,组件会自动解析 |
| `assignee` | `string` | - | - | 任务负责人,用作负责人下拉菜单的值绑定 | | `assignee` | `string` \| `string[]` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | - | - | 任务负责人,用作负责人下拉菜单的值绑定。支持单个负责人(字符串)或多个负责人(字符串数组) |
| `assigneeName` | `string` | - | - | 任务负责人姓名,自动从绑定的数据集`assigneeOptions`中获取Label作为显示,如果需要自定义,可以在GanttChart回调事件`task-added`中自定义信息 | | `assigneeName` | `string` \| `string[]` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | - | - | 任务负责人姓名,自动从绑定的数据集`assigneeOptions`中获取Label作为显示,如果需要自定义,可以在GanttChart回调事件`task-added`中自定义信息。支持单个姓名(字符串)或多个姓名(字符串数组) |
| `avatar` | `string` | - | - | 任务负责人头像 URL | | `avatar` | `string` \| `string[]` ![v1.8.0](https://img.shields.io/badge/v1.8.0-409EFF?style=flat-square&labelColor=ECF5FF) | - | - | 任务负责人头像 URL。支持单个头像(字符串)或多个头像(字符串数组) |
| `estimatedHours` | `number` | - | - | 预估工时(小时) | | `estimatedHours` | `number` | - | - | 预估工时(小时) |
| `actualHours` | `number` | - | - | 实际工时(小时) | | `actualHours` | `number` | - | - | 实际工时(小时) |
| `parentId` | `number` | - | - | 父任务 ID,用于任务分组 | | `parentId` | `number` | - | - | 父任务 ID,用于任务分组 |
+121 -4
View File
@@ -25,6 +25,9 @@ const { locale: demoLocale, messages: demoMessages, setLocale: setDemoLocale, fo
// Tool Settings // Tool Settings
const ts = computed(() => demoMessages.value.toolSettings || {}) const ts = computed(() => demoMessages.value.toolSettings || {})
// TaskBar Config
const taskBarConfigMessages = computed(() => demoMessages.value.taskBarConfig || {})
// GanttChart ref // GanttChart ref
const gantt = ref<InstanceType<typeof import('../src/components/GanttChart.vue').default> | null>(null) const gantt = ref<InstanceType<typeof import('../src/components/GanttChart.vue').default> | null>(null)
@@ -242,6 +245,13 @@ const taskBarOptions = ref({
dragDelayTime: 150, // dragDelayTime: 150, //
}) })
//
const showActualTaskBar = ref(true)
const pendingTaskBackgroundColor = ref('#409eff')
const delayTaskBackgroundColor = ref('#f56c6c')
const completeTaskBackgroundColor = ref('#909399')
const ongoingTaskBackgroundColor = ref('#e6a23c')
const taskBarConfig = computed<TaskBarConfig>(() => ({ const taskBarConfig = computed<TaskBarConfig>(() => ({
showAvatar: taskBarOptions.value.showAvatar, showAvatar: taskBarOptions.value.showAvatar,
showTitle: taskBarOptions.value.showTitle, showTitle: taskBarOptions.value.showTitle,
@@ -1302,6 +1312,53 @@ const handleCustomMenuAction = (action: string, task: Task) => {
<input v-model="taskBarOptions.showProgress" type="checkbox" /> <input v-model="taskBarOptions.showProgress" type="checkbox" />
<span class="taskbar-label">{{ t.taskBarConfig.display.showProgress }}</span> <span class="taskbar-label">{{ t.taskBarConfig.display.showProgress }}</span>
</label> </label>
<label class="taskbar-control">
<input v-model="showActualTaskBar" type="checkbox" />
<span class="taskbar-label">{{ taskBarConfigMessages.display?.showActualTaskBar }}</span>
</label>
</div>
<!-- 自定义任务状态背景色 -->
<div class="color-config-group">
<h6 class="color-config-title">{{ taskBarConfigMessages.colorConfig?.title }}</h6>
<div class="color-inputs-row">
<div class="color-input-item">
<label class="color-label">{{ taskBarConfigMessages.colorConfig?.pendingTask }}</label>
<input
v-model="pendingTaskBackgroundColor"
type="text"
class="color-input"
placeholder="#409eff"
/>
</div>
<div class="color-input-item">
<label class="color-label">{{ taskBarConfigMessages.colorConfig?.delayTask }}</label>
<input
v-model="delayTaskBackgroundColor"
type="text"
class="color-input"
placeholder="#f56c6c"
/>
</div>
<div class="color-input-item">
<label class="color-label">{{ taskBarConfigMessages.colorConfig?.completeTask }}</label>
<input
v-model="completeTaskBackgroundColor"
type="text"
class="color-input"
placeholder="#909399"
/>
</div>
<div class="color-input-item">
<label class="color-label">{{ taskBarConfigMessages.colorConfig?.ongoingTask }}</label>
<input
v-model="ongoingTaskBackgroundColor"
type="text"
class="color-input"
placeholder="#e6a23c"
/>
</div>
</div>
</div> </div>
</div> </div>
@@ -1324,11 +1381,11 @@ const handleCustomMenuAction = (action: string, task: Task) => {
<label class="taskbar-control"> <label class="taskbar-control">
<input v-model="enableTaskRowMove" type="checkbox" /> <input v-model="enableTaskRowMove" type="checkbox" />
<span class="taskbar-label"> <span class="taskbar-label">
启用TaskRow拖拽移动 {{ taskBarConfigMessages.enableTaskRowMove?.label }}
</span> </span>
</label> </label>
<span class="control-hint"> <span class="control-hint">
允许通过拖拽TaskRow来调整任务的层级和顺序 {{ taskBarConfigMessages.enableTaskRowMove?.hint }}
</span> </span>
</div> </div>
<div class="control-row"> <div class="control-row">
@@ -1819,12 +1876,16 @@ const handleCustomMenuAction = (action: string, task: Task) => {
:allow-drag-and-resize="allowDragAndResize" :allow-drag-and-resize="allowDragAndResize"
:enable-task-row-move="enableTaskRowMove" :enable-task-row-move="enableTaskRowMove"
:assignee-options="assigneeOptions" :assignee-options="assigneeOptions"
:task-list-row-class-name="getTaskRowClassName"
:task-list-row-style="getTaskRowStyle"
:on-export-csv="handleCustomCsvExport" :on-export-csv="handleCustomCsvExport"
:on-language-change="handleLanguageChange" :on-language-change="handleLanguageChange"
:on-theme-change="handleThemeChange" :on-theme-change="handleThemeChange"
:task-list-column-render-mode="taskListColumnRenderMode" :task-list-column-render-mode="taskListColumnRenderMode"
:show-actual-taskbar="showActualTaskBar"
:enable-task-bar-tooltip="true"
:pending-task-background-color="pendingTaskBackgroundColor"
:delay-task-background-color="delayTaskBackgroundColor"
:complete-task-background-color="completeTaskBackgroundColor"
:ongoing-task-background-color="ongoingTaskBackgroundColor"
@milestone-saved="handleMilestoneSaved" @milestone-saved="handleMilestoneSaved"
@milestone-deleted="handleMilestoneDeleted" @milestone-deleted="handleMilestoneDeleted"
@milestone-icon-changed="handleMilestoneIconChanged" @milestone-icon-changed="handleMilestoneIconChanged"
@@ -2528,6 +2589,62 @@ const handleCustomMenuAction = (action: string, task: Task) => {
color: var(--gantt-primary-color, #409eff); color: var(--gantt-primary-color, #409eff);
} }
/* 自定义颜色配置组 */
.color-config-group {
margin-top: 16px;
padding: 12px;
background: var(--gantt-bg-tertiary, #fafafa);
border-radius: 8px;
border: 1px solid var(--gantt-border-color, #e4e7ed);
}
.color-config-title {
font-size: 13px;
font-weight: 600;
color: var(--gantt-text-secondary, #606266);
margin: 0 0 12px 0;
}
.color-inputs-row {
display: flex;
gap: 12px;
flex-wrap: nowrap;
}
.color-input-item {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
min-width: 0;
}
.color-label {
font-size: 12px;
font-weight: 500;
color: var(--gantt-text-secondary, #606266);
}
.color-input {
padding: 6px 10px;
font-size: 13px;
border: 1px solid var(--gantt-border-color, #dcdfe6);
border-radius: 4px;
background: var(--gantt-bg-primary, #ffffff);
color: var(--gantt-text-primary, #333);
transition: all 0.2s ease;
}
.color-input:focus {
outline: none;
border-color: var(--gantt-primary-color, #409eff);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.color-input::placeholder {
color: var(--gantt-text-placeholder, #c0c4cc);
}
/* TaskBar 高级配置样式 */ /* TaskBar 高级配置样式 */
.taskbar-advanced-controls { .taskbar-advanced-controls {
display: flex; display: flex;
+22 -9
View File
@@ -27,21 +27,25 @@
"description": "评估ADX-2024在健康志愿者和患者中的安全性、耐受性和药代动力学特征", "description": "评估ADX-2024在健康志愿者和患者中的安全性、耐受性和药代动力学特征",
"custom": "test11111", "custom": "test11111",
"priority": "高", "priority": "高",
"barColor": "#7B3FF2",
"children": [ "children": [
{ {
"id": 1101, "id": 1101,
"name": "试验方案设计与<span style='font-weight: bold;color:red;'>伦理审查</span>", "name": "试验方案设计与<span style='font-weight: bold;color:red;'>伦理审查</span>",
"assignee": "方案设计师 李明", "assignee": "方案设计师 李明",
"avatar": "50-1.jpg", "avatar": ["50-1.jpg", "50-2.jpg"],
"startDate": "2025-01-01", "startDate": "2025-01-01",
"endDate": "2025-02-28", "endDate": "2025-02-28",
"actualStartDate": "2025-01-03",
"actualEndDate": "2025-02-25",
"progress": 100, "progress": 100,
"estimatedHours": 464, "estimatedHours": 464,
"actualHours": 480, "actualHours": 480,
"type": "task", "type": "task",
"parentId": 1100, "parentId": 1100,
"description": "完成I期试验方案设计、伦理委员会审批及监管部门申报", "description": "完成I期试验方案设计、伦理委员会审批及监管部门申报",
"custom": "test11111" "custom": "test11111",
"barColor": "#2E7D6B"
}, },
{ {
"id": 1102, "id": 1102,
@@ -50,6 +54,8 @@
"avatar": "50-2.jpg", "avatar": "50-2.jpg",
"startDate": "2025-03-01", "startDate": "2025-03-01",
"endDate": "2025-04-30", "endDate": "2025-04-30",
"actualStartDate": "2025-03-05",
"actualEndDate": "2025-04-28",
"progress": 90, "progress": 90,
"estimatedHours": 1392.5, "estimatedHours": 1392.5,
"actualHours": 1250.25, "actualHours": 1250.25,
@@ -57,15 +63,17 @@
"predecessor": [1101], "predecessor": [1101],
"parentId": 1100, "parentId": 1100,
"description": "招募24名健康志愿者,完成医学筛选和入组评估", "description": "招募24名健康志愿者,完成医学筛选和入组评估",
"custom": "test11111" "custom": "test11111",
"barColor": "#FF4D4F"
}, },
{ {
"id": 1103, "id": 1103,
"name": "药物给药与安全性监测", "name": "药物给药与安全性监测",
"assignee": "临床医生 Dr. Liu", "assignee": "临床医生 Dr. Liu",
"avatar": "50-3.jpg", "avatar": ["50-3.jpg", "50-2.jpg"],
"startDate": "2025-05-01", "startDate": "2025-05-01",
"endDate": "2025-08-31", "endDate": "2025-08-31",
"actualStartDate": "2025-05-10",
"progress": 40, "progress": 40,
"estimatedHours": 3400, "estimatedHours": 3400,
"actualHours": 1687, "actualHours": 1687,
@@ -73,7 +81,8 @@
"predecessor": [1102], "predecessor": [1102],
"parentId": 1100, "parentId": 1100,
"description": "按照剂量递增方案给药,密切监测不良反应和药代动力学参数", "description": "按照剂量递增方案给药,密切监测不良反应和药代动力学参数",
"custom": "test11111" "custom": "test11111",
"barColor": "#7B3FF2"
} }
], ],
"collapsed": false "collapsed": false
@@ -91,14 +100,16 @@
"parentId": 1000, "parentId": 1000,
"description": "在目标患者群体中评估ADX-2024的初步疗效和进一步的安全性", "description": "在目标患者群体中评估ADX-2024的初步疗效和进一步的安全性",
"custom": "test11111", "custom": "test11111",
"barColor": "#3F6DF6",
"children": [ "children": [
{ {
"id": 1201, "id": 1201,
"name": "多中心试验<span style='font-weight: bold; color: blue;'>启动</span>", "name": "多中心试验<span style='font-weight: bold; color: blue;'>启动</span>",
"assignee": "项目经理 王芳", "assignee": "项目经理 王芳",
"avatar": "50-10.jpg", "avatar": ["50-10.jpg", "50-2.jpg"],
"startDate": "2025-09-01", "startDate": "2025-09-01",
"endDate": "2025-11-30", "endDate": "2025-11-30",
"actualStartDate": "2025-09-01",
"progress": 25, "progress": 25,
"estimatedHours": 2160, "estimatedHours": 2160,
"actualHours": 324, "actualHours": 324,
@@ -106,13 +117,14 @@
"predecessor": [1103], "predecessor": [1103],
"parentId": 1200, "parentId": 1200,
"description": "启动5个临床中心,完成研究者培训和质量体系建立", "description": "启动5个临床中心,完成研究者培训和质量体系建立",
"custom": "test11111" "custom": "test11111",
"barColor": "#3F6DF6"
}, },
{ {
"id": 1202, "id": 1202,
"name": "患者入组与随机化", "name": "患者入组与随机化",
"assignee": "数据管理员 陈静", "assignee": "数据管理员 陈静",
"avatar": "50-6.jpg", "avatar": ["50-6.jpg", "50-10.jpg"],
"startDate": "2025-12-01", "startDate": "2025-12-01",
"endDate": "2026-03-31", "endDate": "2026-03-31",
"progress": 0, "progress": 0,
@@ -138,7 +150,8 @@
"predecessor": [1202], "predecessor": [1202],
"parentId": 1200, "parentId": 1200,
"description": "定期评估患者疗效指标,收集安全性数据,进行中期分析", "description": "定期评估患者疗效指标,收集安全性数据,进行中期分析",
"custom": "test11111" "custom": "test11111",
"barColor": "#FFA43A"
} }
], ],
"collapsed": false "collapsed": false
+16
View File
@@ -111,5 +111,21 @@
"false": "False" "false": "False"
} }
} }
},
"taskBarConfig": {
"display": {
"showActualTaskBar": "Show Actual TaskBar"
},
"colorConfig": {
"title": "Custom Task Status Background Colors",
"pendingTask": "Pending Task",
"delayTask": "Overdue Task",
"completeTask": "Completed Task",
"ongoingTask": "Ongoing Task"
},
"enableTaskRowMove": {
"label": "Enable TaskRow Drag and Drop",
"hint": "Allow dragging TaskRow to adjust task hierarchy and order"
}
} }
} }
+16
View File
@@ -111,5 +111,21 @@
"false": "假" "false": "假"
} }
} }
},
"taskBarConfig": {
"display": {
"showActualTaskBar": "显示实际TaskBar"
},
"colorConfig": {
"title": "自定义任务状态背景色",
"pendingTask": "待处理任务",
"delayTask": "逾期任务",
"completeTask": "已完成任务",
"ongoingTask": "进行中任务"
},
"enableTaskRowMove": {
"label": "启用TaskRow拖拽移动",
"hint": "允许通过拖拽TaskRow来调整任务的层级和顺序"
}
} }
} }
+105 -2
View File
@@ -27,11 +27,114 @@ interface LocaleMessages {
task: string task: string
} }
} }
taskBarConfig: {
display: {
showActualTaskBar: string
}
colorConfig: {
title: string
pendingTask: string
delayTask: string
completeTask: string
ongoingTask: string
}
enableTaskRowMove: {
label: string
hint: string
}
}
toolSettings: {
title: string
currentStatus: {
title: string
fullscreen: string
expandAll: string
locale: string
timeScale: string
theme: string
controlMode: string
active: string
inactive: string
expanded: string
collapsed: string
}
controlMode: {
title: string
expose: string
props: string
exposeHint: string
propsHint: string
}
exposeMethods: {
sectionTitle: string
fullscreen: {
title: string
enter: string
exit: string
toggle: string
}
expand: {
title: string
all: string
none: string
toggle: string
}
timeScale: {
title: string
hour: string
day: string
week: string
month: string
quarter: string
year: string
zoomIn: string
zoomOut: string
}
locale: {
title: string
zhCN: string
enUS: string
}
theme: {
title: string
light: string
dark: string
}
navigation: {
title: string
today: string
taskIdPlaceholder: string
go: string
}
}
propsControl: {
sectionTitle: string
locale: {
title: string
}
theme: {
title: string
}
timeScale: {
title: string
}
fullscreen: {
title: string
true: string
false: string
}
expandAll: {
title: string
true: string
false: string
}
}
}
} }
const localeMessages: Record<LocaleKey, LocaleMessages> = { const localeMessages: Record<LocaleKey, LocaleMessages> = {
'zh-CN': zhCN, 'zh-CN': zhCN as any,
'en-US': enUS, 'en-US': enUS as any,
} }
const currentLocale = ref<LocaleKey>('zh-CN') const currentLocale = ref<LocaleKey>('zh-CN')
+26
View File
@@ -466,5 +466,31 @@
"🎉 Added: New property in GanttChart - enableLinkAnchor to control the relationship line anchor points of Taskbar", "🎉 Added: New property in GanttChart - enableLinkAnchor to control the relationship line anchor points of Taskbar",
"<span style=\"font-weight: bold; color: #f00;\">Special thanks to YQ6494@gitee for their use and suggestions</span>" "<span style=\"font-weight: bold; color: #f00;\">Special thanks to YQ6494@gitee for their use and suggestions</span>"
] ]
},
{
"version": "1.8.0",
"date": "2026-01-17",
"notes": [
"🎉 新增:GanttChart允许展示实际Taskbar",
"🎉 新增:TaskDrawer增加实际开始日期和实际结束日期的属性",
"🎉 新增:TaskDrawer增加当前Task状态:待处理、进行中、已完成、已逾期",
"🎉 新增:GanttChart增加Taskbar不同状态下背景色预设属性pendingTaskBackgroundColor、delayTaskBackgroundColor、completeTaskBackgroundColor、ongoingTaskBackgroundColor",
"🎉 新增:Task对象增加barColor属性,允许自定义对应Taskbar的背景色,优先级高于GanttChart预设背景色",
"🎉 新增:Taskbar增加气泡提示框",
"🎉 新增:GanttChart增加showActualTaskbar属性用于控制是否展示实际Taskbar,默认值为false,需要时手动打开",
"🎉 新增:GanttChart增加enableTaskbarTooltip属性用于控制是否展示Taskbar的气泡提示框,默认值为true,需要时手动关闭",
"🎉 优化:Task对象assignee以及avatar属性允许接收数组,标记多为负责人",
"<span style=\"font-weight: bold; color: #f00;\">特别感谢 yunlbs/fhjfhj/jakepan@gitee的使用及建议</span>",
"🎉 Added: GanttChart allows displaying actual Taskbar",
"🎉 Added: TaskDrawer adds properties for actual start date and actual end date",
"🎉 Added: TaskDrawer adds current Task status: Pending, Ongoing, Completed, Overdue",
"🎉 Added: GanttChart adds preset background color properties for Taskbar in different statuses: pendingTaskBackgroundColor, delayTaskBackgroundColor, completeTaskBackgroundColor, ongoingTaskBackgroundColor",
"🎉 Added: Task object adds barColor property to allow customization of the corresponding Taskbar background color, with higher priority than GanttChart preset background colors",
"🎉 Added: Taskbar adds tooltip",
"🎉 Added: GanttChart adds showActualTaskbar property to control whether to show actual Taskbar",
"🎉 Added: GanttChart adds enableTaskbarTooltip property to control whether to show Taskbar tooltip",
"🎉 Optimized: The assignee and avatar properties of the Task object now accept arrays to mark multiple assignees",
"<span style=\"font-weight: bold; color: #f00;\">Special thanks to yunlbs/fhjfhj/jakepan@gitee for their use and suggestions</span>"
]
} }
] ]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 248 KiB

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "jordium-gantt-vue3", "name": "jordium-gantt-vue3",
"version": "1.7.2", "version": "1.8.0",
"type": "module", "type": "module",
"main": "npm-package/dist/jordium-gantt-vue3.cjs.js", "main": "npm-package/dist/jordium-gantt-vue3.cjs.js",
"module": "npm-package/dist/jordium-gantt-vue3.es.js", "module": "npm-package/dist/jordium-gantt-vue3.es.js",
+29
View File
@@ -59,6 +59,8 @@ const props = withDefaults(defineProps<Props>(), {
enableTaskListContextMenu: true, enableTaskListContextMenu: true,
enableTaskBarContextMenu: true, enableTaskBarContextMenu: true,
enableLinkAnchor: true, enableLinkAnchor: true,
showActualTaskbar: false,
enableTaskBarTooltip: true,
fullscreen: false, fullscreen: false,
expandAll: true, expandAll: true,
locale: 'zh-CN', locale: 'zh-CN',
@@ -201,6 +203,21 @@ interface Props {
// LinkAnchor true // LinkAnchor true
// false TaskBar / // false TaskBar /
enableLinkAnchor?: boolean enableLinkAnchor?: boolean
// TaskBar true
// false TaskBar
enableTaskBarTooltip?: boolean
// false
// true actualStartDate
showActualTaskbar?: boolean
// Task.barColor
// 使
pendingTaskBackgroundColor?: string
// 使
delayTaskBackgroundColor?: string
// 100%使
completeTaskBackgroundColor?: string
// 使
ongoingTaskBackgroundColor?: string
// //
fullscreen?: boolean fullscreen?: boolean
// / // /
@@ -1201,6 +1218,8 @@ const milestonesForTimeline = computed((): Milestone[] => {
startDate: task.startDate, // startDate: task.startDate, //
endDate: task.endDate, endDate: task.endDate,
assignee: task.assignee, assignee: task.assignee,
assigneeName: task.assigneeName,
avatar: task.avatar,
type: task.type || 'milestone', type: task.type || 'milestone',
icon: task.icon, icon: task.icon,
description: task.description, description: task.description,
@@ -2808,6 +2827,12 @@ defineExpose({
:working-hours="props.workingHours" :working-hours="props.workingHours"
:task-bar-config="props.taskBarConfig" :task-bar-config="props.taskBarConfig"
:allow-drag-and-resize="props.allowDragAndResize" :allow-drag-and-resize="props.allowDragAndResize"
:show-actual-taskbar="props.showActualTaskbar"
:enable-task-bar-tooltip="props.enableTaskBarTooltip"
:pending-task-background-color="props.pendingTaskBackgroundColor"
:delay-task-background-color="props.delayTaskBackgroundColor"
:complete-task-background-color="props.completeTaskBackgroundColor"
:ongoing-task-background-color="props.ongoingTaskBackgroundColor"
:use-default-drawer="props.useDefaultDrawer" :use-default-drawer="props.useDefaultDrawer"
:use-default-milestone-dialog="props.useDefaultMilestoneDialog" :use-default-milestone-dialog="props.useDefaultMilestoneDialog"
:on-milestone-save="handleMilestoneSave" :on-milestone-save="handleMilestoneSave"
@@ -2861,6 +2886,10 @@ defineExpose({
:task="taskDrawerTask" :task="taskDrawerTask"
:is-edit="taskDrawerEditMode" :is-edit="taskDrawerEditMode"
:assignee-options="props.assigneeOptions" :assignee-options="props.assigneeOptions"
:pending-task-background-color="props.pendingTaskBackgroundColor"
:delay-task-background-color="props.delayTaskBackgroundColor"
:complete-task-background-color="props.completeTaskBackgroundColor"
:ongoing-task-background-color="props.ongoingTaskBackgroundColor"
@submit="handleTaskDrawerSubmit" @submit="handleTaskDrawerSubmit"
@close="taskDrawerVisible = false" @close="taskDrawerVisible = false"
@start-timer="handleStartTimer" @start-timer="handleStartTimer"
+1 -1
View File
@@ -509,7 +509,7 @@ defineExpose({
top: `${offsetTop}px`, top: `${offsetTop}px`,
width: `${width}px`, width: `${width}px`,
height: `${height}px`, height: `${height}px`,
zIndex: highlightedTaskId !== null ? 1001 : 25, zIndex: highlightedTaskId !== null ? 2000 : 500,
pointerEvents: 'none', pointerEvents: 'none',
}" }"
/> />
File diff suppressed because it is too large Load Diff
+148 -7
View File
@@ -21,6 +21,10 @@ interface Props {
isEdit?: boolean isEdit?: boolean
onDelete?: (task: Task) => void onDelete?: (task: Task) => void
assigneeOptions?: AssigneeOption[] assigneeOptions?: AssigneeOption[]
pendingTaskBackgroundColor?: string
delayTaskBackgroundColor?: string
completeTaskBackgroundColor?: string
ongoingTaskBackgroundColor?: string
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -35,6 +39,10 @@ const props = withDefaults(defineProps<Props>(), {
{ value: 'zhaoliu', label: '赵六' }, { value: 'zhaoliu', label: '赵六' },
{ value: 'qianqi', label: '钱七' }, { value: 'qianqi', label: '钱七' },
], ],
pendingTaskBackgroundColor: '#c0c4cc',
delayTaskBackgroundColor: '#f56c6c',
completeTaskBackgroundColor: '#67c23a',
ongoingTaskBackgroundColor: '#409eff',
}) })
const emit = defineEmits<{ const emit = defineEmits<{
@@ -146,6 +154,8 @@ const formData = reactive<Task>({
assignee: '', assignee: '',
startDate: '', startDate: '',
endDate: '', endDate: '',
actualStartDate: '',
actualEndDate: '',
predecessor: [], predecessor: [],
estimatedHours: 0, estimatedHours: 0,
actualHours: 0, actualHours: 0,
@@ -334,6 +344,8 @@ const errors = reactive({
type: '', type: '',
startDate: '', startDate: '',
endDate: '', endDate: '',
actualStartDate: '',
actualEndDate: '',
}) })
// visible // visible
@@ -397,6 +409,8 @@ const resetForm = () => {
assignee: '', assignee: '',
startDate: '', startDate: '',
endDate: '', endDate: '',
actualStartDate: '',
actualEndDate: '',
predecessor: [], predecessor: [],
estimatedHours: 0, estimatedHours: 0,
actualHours: 0, actualHours: 0,
@@ -408,6 +422,9 @@ const resetForm = () => {
timerEndTime: undefined, timerEndTime: undefined,
timerElapsedTime: 0, timerElapsedTime: 0,
children: undefined, children: undefined,
bgColor: undefined,
avatar: undefined,
barColor: undefined,
}) })
// //
@@ -434,14 +451,19 @@ const validateForm = (): boolean => {
isValid = false isValid = false
} }
if (!formData.startDate) { if (!formData.startDate) {
errors.startDate = t.value.startDateRequired errors.startDate = t.value.plannedStartDateRequired
isValid = false isValid = false
} }
if (!formData.endDate) { if (!formData.endDate) {
errors.endDate = t.value.endDateRequired errors.endDate = t.value.plannedEndDateRequired
isValid = false isValid = false
} else if (formData.startDate && new Date(formData.endDate) < new Date(formData.startDate)) { } else if (formData.startDate && new Date(formData.endDate) < new Date(formData.startDate)) {
errors.endDate = t.value.endDateInvalid errors.endDate = t.value.plannedEndDateInvalid
isValid = false
}
//
if (formData.actualStartDate && formData.actualEndDate && new Date(formData.actualEndDate) < new Date(formData.actualStartDate)) {
errors.actualEndDate = t.value.actualEndDateInvalid
isValid = false isValid = false
} }
return isValid return isValid
@@ -654,6 +676,71 @@ const handleAssigneeChanged = (event: Event) => {
formData.assigneeName = selected.label formData.assigneeName = selected.label
} }
} }
//
const taskStatus = computed(() => {
if (!props.task) return { text: '', color: '', bgColor: '', borderColor: '' }
const task = props.task
const progress = task.progress || 0
const now = new Date()
const endDate = task.endDate ? new Date(task.endDate) : null
const startDate = task.startDate ? new Date(task.startDate) : null
const actualStartDate = task.actualStartDate
// 0
if (!actualStartDate && progress === 0) {
const color = props.pendingTaskBackgroundColor
return {
text: t.value.statusPending,
color: color,
bgColor: color + '20', // 20%
borderColor: color,
}
}
//
if (progress >= 100) {
const color = props.completeTaskBackgroundColor
return {
text: t.value.statusCompleted,
color: color,
bgColor: color + '20',
borderColor: color,
}
}
//
if (endDate && now > endDate && progress < 100) {
const color = props.delayTaskBackgroundColor
return {
text: t.value.statusDelayed,
color: color,
bgColor: color + '20',
borderColor: color,
}
}
//
if (startDate && now >= startDate && progress < 100) {
const color = props.ongoingTaskBackgroundColor
return {
text: t.value.statusOngoing,
color: color,
bgColor: color + '20',
borderColor: color,
}
}
//
const color = props.pendingTaskBackgroundColor
return {
text: t.value.statusPending,
color: color,
bgColor: color + '20',
borderColor: color,
}
})
</script> </script>
<template> <template>
@@ -763,6 +850,18 @@ const handleAssigneeChanged = (event: Event) => {
</span> </span>
</div> </div>
<div style="flex: 1"></div> <div style="flex: 1"></div>
<!-- Status Badge -->
<div
v-if="isEdit && props.task"
class="status-badge"
:style="{
backgroundColor: taskStatus.bgColor,
color: taskStatus.color,
border: `1px solid ${taskStatus.borderColor}`,
}"
>
{{ taskStatus.text }}
</div>
<button class="drawer-close-btn" type="button" @click="handleClose"> <button class="drawer-close-btn" type="button" @click="handleClose">
<svg <svg
class="close-icon" class="close-icon"
@@ -844,14 +943,14 @@ const handleAssigneeChanged = (event: Event) => {
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label class="form-label" for="task-start-date"> <label class="form-label" for="task-start-date">
{{ t.startDate }} <span class="required">*</span></label {{ t.plannedStartDate }} <span class="required">*</span></label
> >
<DatePicker <DatePicker
id="task-start-date" id="task-start-date"
v-model="formData.startDate" v-model="formData.startDate"
:type="'datetime' as any" :type="'datetime' as any"
value-format="YYYY-MM-DD HH:mm" value-format="YYYY-MM-DD HH:mm"
:placeholder="t.startDateRequired" :placeholder="t.plannedStartDateRequired"
:class="{ error: errors.startDate }" :class="{ error: errors.startDate }"
/> />
<span v-if="errors.startDate" class="error-text">{{ errors.startDate }}</span> <span v-if="errors.startDate" class="error-text">{{ errors.startDate }}</span>
@@ -859,20 +958,52 @@ const handleAssigneeChanged = (event: Event) => {
<div class="form-group"> <div class="form-group">
<label class="form-label" for="task-end-date"> <label class="form-label" for="task-end-date">
{{ t.endDate }} <span class="required">*</span></label {{ t.plannedEndDate }} <span class="required">*</span></label
> >
<DatePicker <DatePicker
id="task-end-date" id="task-end-date"
v-model="formData.endDate" v-model="formData.endDate"
:type="'datetime' as any" :type="'datetime' as any"
value-format="YYYY-MM-DD HH:mm" value-format="YYYY-MM-DD HH:mm"
:placeholder="t.endDateRequired" :placeholder="t.plannedEndDateRequired"
:class="{ error: errors.endDate }" :class="{ error: errors.endDate }"
/> />
<span v-if="errors.endDate" class="error-text">{{ errors.endDate }}</span> <span v-if="errors.endDate" class="error-text">{{ errors.endDate }}</span>
</div> </div>
</div> </div>
<div class="form-row">
<div class="form-group">
<label class="form-label" for="task-actual-start-date">
{{ t.actualStartDate }}</label
>
<DatePicker
id="task-actual-start-date"
v-model="formData.actualStartDate"
:type="'datetime' as any"
value-format="YYYY-MM-DD HH:mm"
:placeholder="t.actualStartDate"
:class="{ error: errors.actualStartDate }"
/>
<span v-if="errors.actualStartDate" class="error-text">{{ errors.actualStartDate }}</span>
</div>
<div class="form-group">
<label class="form-label" for="task-actual-end-date">
{{ t.actualEndDate }}</label
>
<DatePicker
id="task-actual-end-date"
v-model="formData.actualEndDate"
:type="'datetime' as any"
value-format="YYYY-MM-DD HH:mm"
:placeholder="t.actualEndDate"
:class="{ error: errors.actualEndDate }"
/>
<span v-if="errors.actualEndDate" class="error-text">{{ errors.actualEndDate }}</span>
</div>
</div>
<MultiSelectPredecessor <MultiSelectPredecessor
v-model="formData.predecessor" v-model="formData.predecessor"
:tasks="allTasks" :tasks="allTasks"
@@ -1068,6 +1199,15 @@ const handleAssigneeChanged = (event: Event) => {
color: var(--gantt-text-primary, #303133); color: var(--gantt-text-primary, #303133);
} }
.status-badge {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
margin-left: 12px;
}
.drawer-close-btn { .drawer-close-btn {
background: none; background: none;
border: none; border: none;
@@ -1075,6 +1215,7 @@ const handleAssigneeChanged = (event: Event) => {
padding: 4px; padding: 4px;
color: var(--gantt-text-muted, #909399); color: var(--gantt-text-muted, #909399);
transition: color 0.2s; transition: color 0.2s;
margin-left: 12px;
} }
.drawer-close-btn:hover { .drawer-close-btn:hover {
+112 -4
View File
@@ -226,6 +226,59 @@ const slotPayload = computed(() => ({
daysText: daysText.value, daysText: daysText.value,
progressClass: progressClass.value, progressClass: progressClass.value,
})) }))
// - barColor
const leftBorderColor = computed(() => {
// barColor使
if (props.task.barColor) {
return props.task.barColor
}
// null使CSS
return null
})
//
const customBorderStyle = computed(() => {
if (leftBorderColor.value) {
return {
borderLeftColor: leftBorderColor.value
}
}
return {}
})
// assignee
const assigneeDisplayData = computed(() => {
const task = props.task
const avatar = task.avatar
const assignee = task.assignee
// avatar
let avatarList: string[] = []
if (avatar) {
avatarList = Array.isArray(avatar) ? avatar : [avatar]
}
// assignee
let assigneeList: string[] = []
if (assignee) {
assigneeList = Array.isArray(assignee) ? assignee : [assignee]
}
// avatarassignee
const displayAvatars = avatarList.length > 0
? avatarList.map(url => ({ type: 'image', url }))
: assigneeList.map(name => ({ type: 'text', name }))
//
const nameText = assigneeList.join('\n') || '-'
return {
avatars: displayAvatars,
nameText,
hasMultiple: displayAvatars.length > 1
}
})
</script> </script>
<template> <template>
@@ -243,7 +296,7 @@ const slotPayload = computed(() => ({
'task-type-milestone': isMilestoneTask, 'task-type-milestone': isMilestoneTask,
[customRowClass]: customRowClass, [customRowClass]: customRowClass,
}" }"
:style="customRowStyle" :style="[customRowStyle, customBorderStyle]"
@click="handleRowClick" @click="handleRowClick"
@dblclick="handleTaskRowDoubleClick" @dblclick="handleTaskRowDoubleClick"
@mousedown="handleMouseDown" @mousedown="handleMouseDown"
@@ -373,10 +426,25 @@ const slotPayload = computed(() => ({
<!-- 负责人列 --> <!-- 负责人列 -->
<template v-else-if="column.key === 'assignee'"> <template v-else-if="column.key === 'assignee'">
<div class="assignee-info"> <div class="assignee-info">
<div class="avatar"> <!-- 多头像容器 -->
{{ props.task.assignee ? props.task.assignee.charAt(0) : '-' }} <div class="assignee-avatars-container">
<div
v-for="(avatarItem, idx) in assigneeDisplayData.avatars"
:key="idx"
class="avatar"
:style="{
zIndex: idx + 1,
marginLeft: idx > 0 ? '-8px' : '0'
}"
>
<!-- 图片头像 -->
<img v-if="avatarItem.type === 'image'" :src="(avatarItem as any).url" :alt="`avatar-${idx}`" />
<!-- 文字头像 -->
<span v-else class="avatar-text">{{ (avatarItem as any).name.charAt(0).toUpperCase() }}</span>
</div> </div>
<span class="assignee-name">{{ props.task.assignee || '-' }}</span> </div>
<!-- 名称显示支持换行和超出显示... -->
<span class="assignee-name" :title="assigneeDisplayData.nameText">{{ assigneeDisplayData.nameText }}</span>
</div> </div>
</template> </template>
@@ -614,9 +682,29 @@ const slotPayload = computed(() => ({
gap: 8px; gap: 8px;
} }
/* 多头像容器 */
.assignee-avatars-container {
display: flex;
align-items: center;
position: relative;
}
.assignee-avatars-container .avatar {
position: relative;
transition: transform 0.2s ease;
cursor: pointer;
}
.assignee-avatars-container .avatar:hover {
transform: translateY(-2px) scale(1.1);
z-index: 999 !important;
}
.avatar { .avatar {
min-width: 25px; min-width: 25px;
min-height: 25px; min-height: 25px;
width: 25px;
height: 25px;
border-radius: 50%; border-radius: 50%;
background: var(--gantt-primary); background: var(--gantt-primary);
color: var(--gantt-text-white); color: var(--gantt-text-white);
@@ -627,11 +715,31 @@ const slotPayload = computed(() => ({
font-weight: 500; font-weight: 500;
border: 2px solid var(--gantt-border-medium); border: 2px solid var(--gantt-border-medium);
box-sizing: border-box; box-sizing: border-box;
flex-shrink: 0;
}
.avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.avatar .avatar-text {
font-size: 11px;
font-weight: 600;
} }
.assignee-name { .assignee-name {
font-size: 14px; font-size: 14px;
color: var(--gantt-text-secondary); color: var(--gantt-text-secondary);
white-space: pre-line;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.4;
} }
.progress-value { .progress-value {
+21 -5
View File
@@ -37,6 +37,15 @@ interface Props {
taskBarConfig?: TaskBarConfig taskBarConfig?: TaskBarConfig
// true // true
allowDragAndResize?: boolean allowDragAndResize?: boolean
// false
showActualTaskbar?: boolean
// TaskBar true
enableTaskBarTooltip?: boolean
//
pendingTaskBackgroundColor?: string
delayTaskBackgroundColor?: string
completeTaskBackgroundColor?: string
ongoingTaskBackgroundColor?: string
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@@ -55,6 +64,12 @@ const props = withDefaults(defineProps<Props>(), {
}), }),
taskBarConfig: undefined, taskBarConfig: undefined,
allowDragAndResize: true, allowDragAndResize: true,
showActualTaskbar: false,
enableTaskBarTooltip: true,
pendingTaskBackgroundColor: undefined,
delayTaskBackgroundColor: undefined,
completeTaskBackgroundColor: undefined,
ongoingTaskBackgroundColor: undefined,
}) })
// emits // emits
@@ -1049,11 +1064,6 @@ const hasCircularDependency = (taskId: number, targetId: number): boolean => {
// //
const createLink = (sourceTask: Task, targetTask: Task, mode: 'predecessor' | 'successor') => { const createLink = (sourceTask: Task, targetTask: Task, mode: 'predecessor' | 'successor') => {
console.log('----> hello:', {
mode,
sourceTask,
targetTask,
})
if (mode === 'predecessor') { if (mode === 'predecessor') {
// mode='predecessor' anchor // mode='predecessor' anchor
// anchor sourceTask targetTask // anchor sourceTask targetTask
@@ -4478,6 +4488,12 @@ const handleAddSuccessor = (task: Task) => {
:current-time-scale="currentTimeScale" :current-time-scale="currentTimeScale"
:task-bar-config="props.taskBarConfig" :task-bar-config="props.taskBarConfig"
:allow-drag-and-resize="props.allowDragAndResize && !isInHighlightMode" :allow-drag-and-resize="props.allowDragAndResize && !isInHighlightMode"
:show-actual-taskbar="props.showActualTaskbar"
:enable-task-bar-tooltip="props.enableTaskBarTooltip"
:pending-task-background-color="props.pendingTaskBackgroundColor"
:delay-task-background-color="props.delayTaskBackgroundColor"
:complete-task-background-color="props.completeTaskBackgroundColor"
:ongoing-task-background-color="props.ongoingTaskBackgroundColor"
:is-highlighted="highlightedTaskIds.has(task.id)" :is-highlighted="highlightedTaskIds.has(task.id)"
:is-primary-highlight="highlightedTaskId === task.id" :is-primary-highlight="highlightedTaskId === task.id"
:is-in-highlight-mode="isInHighlightMode" :is-in-highlight-mode="isInHighlightMode"
+34
View File
@@ -13,6 +13,10 @@ const messages = {
assignee: '分配人', assignee: '分配人',
startDate: '开始日期', startDate: '开始日期',
endDate: '结束日期', endDate: '结束日期',
plannedStartDate: '预计开始日期',
plannedEndDate: '预计结束日期',
actualStartDate: '实际开始日期',
actualEndDate: '实际结束日期',
estimatedHours: '预计工时(hr)', estimatedHours: '预计工时(hr)',
actualHours: '实际工时(hr)', actualHours: '实际工时(hr)',
progress: '进度', progress: '进度',
@@ -26,6 +30,10 @@ const messages = {
assignee: '分配人', assignee: '分配人',
startDate: '开始日期', startDate: '开始日期',
endDate: '结束日期', endDate: '结束日期',
plannedStartDate: '预计开始日期',
plannedEndDate: '预计结束日期',
actualStartDate: '实际开始日期',
actualEndDate: '实际结束日期',
estimatedHours: '预计工时(hr)', estimatedHours: '预计工时(hr)',
actualHours: '实际工时(hr)', actualHours: '实际工时(hr)',
progress: '进度(%)', progress: '进度(%)',
@@ -119,6 +127,15 @@ const messages = {
startDateRequired: '开始日期不能为空', startDateRequired: '开始日期不能为空',
endDateRequired: '结束日期不能为空', endDateRequired: '结束日期不能为空',
endDateInvalid: '结束日期不能早于开始日期', endDateInvalid: '结束日期不能早于开始日期',
plannedStartDateRequired: '预计开始日期不能为空',
plannedEndDateRequired: '预计结束日期不能为空',
plannedEndDateInvalid: '预计结束日期不能早于预计开始日期',
actualEndDateInvalid: '实际结束日期不能早于实际开始日期',
// 任务状态
statusPending: '待处理',
statusOngoing: '进行中',
statusDelayed: '已逾期',
statusCompleted: '已完成',
// 新建里程碑对话框 // 新建里程碑对话框
newMilestone: '新建里程碑', newMilestone: '新建里程碑',
editMilestone: '编辑里程碑', editMilestone: '编辑里程碑',
@@ -279,6 +296,10 @@ const messages = {
assignee: 'Assignee', assignee: 'Assignee',
startDate: 'Start Date', startDate: 'Start Date',
endDate: 'End Date', endDate: 'End Date',
plannedStartDate: 'Planned Start Date',
plannedEndDate: 'Planned End Date',
actualStartDate: 'Actual Start Date',
actualEndDate: 'Actual End Date',
estimatedHours: 'Est. Hours', estimatedHours: 'Est. Hours',
actualHours: 'Act. Hours', actualHours: 'Act. Hours',
progress: 'Progress', progress: 'Progress',
@@ -291,6 +312,10 @@ const messages = {
assignee: 'Assignee', assignee: 'Assignee',
startDate: 'Start Date', startDate: 'Start Date',
endDate: 'End Date', endDate: 'End Date',
plannedStartDate: 'Planned Start Date',
plannedEndDate: 'Planned End Date',
actualStartDate: 'Actual Start Date',
actualEndDate: 'Actual End Date',
estimatedHours: 'Est. Hours', estimatedHours: 'Est. Hours',
actualHours: 'Act. Hours', actualHours: 'Act. Hours',
progress: 'Progress (%)', progress: 'Progress (%)',
@@ -382,6 +407,15 @@ const messages = {
startDateRequired: 'Start date is required', startDateRequired: 'Start date is required',
endDateRequired: 'End date is required', endDateRequired: 'End date is required',
endDateInvalid: 'End date cannot be earlier than start date', endDateInvalid: 'End date cannot be earlier than start date',
plannedStartDateRequired: 'Planned start date is required',
plannedEndDateRequired: 'Planned end date is required',
plannedEndDateInvalid: 'Planned end date cannot be earlier than planned start date',
actualEndDateInvalid: 'Actual end date cannot be earlier than actual start date',
// Task status
statusPending: 'Pending',
statusOngoing: 'Ongoing',
statusDelayed: 'Delayed',
statusCompleted: 'Completed',
// 新建里程碑对话框 // 新建里程碑对话框
newMilestone: 'New Milestone', newMilestone: 'New Milestone',
editMilestone: 'Edit Milestone', editMilestone: 'Edit Milestone',
+3 -1
View File
@@ -4,7 +4,9 @@ export interface Milestone {
name: string name: string
startDate: string startDate: string
endDate?: string endDate?: string
assignee?: string assignee?: string | string[] // 支持单个或多个负责人
assigneeName?: string | string[] // 支持单个或多个负责人姓名
avatar?: string | string[] // 支持单个或多个头像
type: string type: string
icon?: string icon?: string
description?: string description?: string
+6 -2
View File
@@ -3,11 +3,13 @@ export interface Task {
id: number id: number
name: string name: string
predecessor?: number[] // 前置任务ID数组 predecessor?: number[] // 前置任务ID数组
assignee?: string // 记录唯一键值,如用户ID或用户名 assignee?: string | string[] // 记录唯一键值,如用户ID或用户名,支持单个或多个
assigneeName?: string // 任务负责人名称 assigneeName?: string // 任务负责人名称
avatar?: string // 任务负责人头像URL avatar?: string | string[] // 任务负责人头像URL,支持单个或多个头像数组
startDate?: string startDate?: string
endDate?: string endDate?: string
actualStartDate?: string // 实际开始日期
actualEndDate?: string // 实际结束日期
progress?: number progress?: number
estimatedHours?: number estimatedHours?: number
actualHours?: number actualHours?: number
@@ -27,6 +29,8 @@ export interface Task {
timerElapsedTime?: number timerElapsedTime?: number
// 权限控制 // 权限控制
isEditable?: boolean // 是否可编辑(可拖拽、拉伸),默认为true isEditable?: boolean // 是否可编辑(可拖拽、拉伸),默认为true
// 自定义样式
barColor?: string // 自定义TaskBar颜色,如 '#ff5733',若不设置则使用默认颜色方案
// 支持自定义属性 - 使用 unknown 允许任意类型 // 支持自定义属性 - 使用 unknown 允许任意类型
[key: string]: unknown [key: string]: unknown
} }