v1.8.0 - Add actual Taskbar component
24
CHANGELOG.md
@@ -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/),
|
||||
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
|
||||
|
||||
### Added
|
||||
|
||||
14
README-EN.md
@@ -209,7 +209,13 @@ npm run dev
|
||||
| `timeScale`  | `'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'` | `'week'` | Time scale (reactive). Timeline scale will follow changes |
|
||||
| `fullscreen`  | `boolean` | `false` | Fullscreen state control (reactive). Component's fullscreen state will follow changes |
|
||||
| `expandAll`  | `boolean` | `true` | Expand/collapse all tasks (reactive). All tasks' expand state will follow changes |
|
||||
| `enableLinkAnchor`  | `boolean` | `true` | Whether to enable Link Anchor,Default: true |
|
||||
| `enableLinkAnchor`  | `boolean` | `true` | Whether to enable Link Anchor,Default: true |
|
||||
| `pendingTaskBackgroundColor`  | `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`  | `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`  | `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`  | `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`  | `boolean` | `false` | Whether to display actual TaskBar (shows actual execution progress below planned TaskBar) |
|
||||
| `enableTaskbarTooltip`  | `boolean` | `true` | Whether to enable TaskBar hover tooltip (shows task details on mouse hover) |
|
||||
|
||||
#### 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' |
|
||||
| `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 |
|
||||
| `assignee` | `string` | - | - | Task assignee, used as the value binding for the assignee dropdown menu |
|
||||
| `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 |
|
||||
| `avatar` | `string` | - | - | Avatar URL of task assignee |
|
||||
| `assignee` | `string` \| `string[]`  | - | - | Task assignee, used as the value binding for the assignee dropdown menu. Supports single assignee (string) or multiple assignees (string array) |
|
||||
| `assigneeName` | `string` \| `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. Supports single name (string) or multiple names (string array) |
|
||||
| `avatar` | `string` \| `string[]`  | - | - | Avatar URL of task assignee. Supports single avatar (string) or multiple avatars (string array) |
|
||||
| `estimatedHours` | `number` | - | - | Estimated hours |
|
||||
| `actualHours` | `number` | - | - | Actual hours |
|
||||
| `parentId` | `number` | - | - | Parent task ID, used for task grouping |
|
||||
|
||||
14
README.md
@@ -207,7 +207,13 @@ npm run dev
|
||||
| `timeScale`  | `'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'` | `'week'` | 时间刻度(响应式)。设置后时间线刻度将跟随变化 |
|
||||
| `fullscreen`  | `boolean` | `false` | 全屏状态控制(响应式)。设置后组件全屏状态将跟随变化 |
|
||||
| `expandAll`  | `boolean` | `true` | 展开/收起所有任务(响应式)。设置后所有任务的展开状态将跟随变化 |
|
||||
| `enableLinkAnchor`  | `boolean` | `true` | 是否开启Taskbar的关系线锚点,默认值: true |
|
||||
| `enableLinkAnchor`  | `boolean` | `true` | 是否开启Taskbar的关系线锚点,默认值: true |
|
||||
| `pendingTaskBackgroundColor`  | `string` | `'#409eff'` | 待处理任务的TaskBar背景色。支持十六进制颜色值(如 `'#409eff'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
|
||||
| `delayTaskBackgroundColor`  | `string` | `'#f56c6c'` | 已逾期任务的TaskBar背景色。支持十六进制颜色值(如 `'#f56c6c'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
|
||||
| `completeTaskBackgroundColor`  | `string` | `'#909399'` | 已完成任务的TaskBar背景色。支持十六进制颜色值(如 `'#909399'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
|
||||
| `ongoingTaskBackgroundColor`  | `string` | `'#e6a23c'` | 进行中任务的TaskBar背景色。支持十六进制颜色值(如 `'#e6a23c'`)。**优先级**:高于系统默认,低于 Task 对象的 `barColor` 属性 |
|
||||
| `showActualTaskbar`  | `boolean` | `false` | 是否显示实际TaskBar(在计划TaskBar下方显示实际执行进度) |
|
||||
| `enableTaskbarTooltip`  | `boolean` | `true` | 是否启用TaskBar悬停提示框(鼠标悬停显示任务详情) |
|
||||
|
||||
#### TaskListColumn 属性
|
||||
|
||||
@@ -616,9 +622,9 @@ const propsExpandAll = ref(false)
|
||||
| `endDate` | `string` | - | - | 结束日期,格式:'YYYY-MM-DD' 或 'YYYY-MM-DD HH:mm' |
|
||||
| `progress` | `number` | - | `0` | 任务进度,范围 0-100 |
|
||||
| `predecessor` | `number[]` | - | - | 前置任务 ID 数组,标准格式:`[1, 2, 3]`<br/>**兼容格式**:也支持字符串 `'1,2,3'` 或字符串数组 `['1', '2', '3']`,组件会自动解析 |
|
||||
| `assignee` | `string` | - | - | 任务负责人,用作负责人下拉菜单的值绑定 |
|
||||
| `assigneeName` | `string` | - | - | 任务负责人姓名,自动从绑定的数据集`assigneeOptions`中获取Label作为显示,如果需要自定义,可以在GanttChart回调事件`task-added`中自定义信息 |
|
||||
| `avatar` | `string` | - | - | 任务负责人头像 URL |
|
||||
| `assignee` | `string` \| `string[]`  | - | - | 任务负责人,用作负责人下拉菜单的值绑定。支持单个负责人(字符串)或多个负责人(字符串数组) |
|
||||
| `assigneeName` | `string` \| `string[]`  | - | - | 任务负责人姓名,自动从绑定的数据集`assigneeOptions`中获取Label作为显示,如果需要自定义,可以在GanttChart回调事件`task-added`中自定义信息。支持单个姓名(字符串)或多个姓名(字符串数组) |
|
||||
| `avatar` | `string` \| `string[]`  | - | - | 任务负责人头像 URL。支持单个头像(字符串)或多个头像(字符串数组) |
|
||||
| `estimatedHours` | `number` | - | - | 预估工时(小时) |
|
||||
| `actualHours` | `number` | - | - | 实际工时(小时) |
|
||||
| `parentId` | `number` | - | - | 父任务 ID,用于任务分组 |
|
||||
|
||||
125
demo/App.vue
@@ -25,6 +25,9 @@ const { locale: demoLocale, messages: demoMessages, setLocale: setDemoLocale, fo
|
||||
// Tool Settings 多语言
|
||||
const ts = computed(() => demoMessages.value.toolSettings || {})
|
||||
|
||||
// TaskBar Config 多语言
|
||||
const taskBarConfigMessages = computed(() => demoMessages.value.taskBarConfig || {})
|
||||
|
||||
// GanttChart ref
|
||||
const gantt = ref<InstanceType<typeof import('../src/components/GanttChart.vue').default> | null>(null)
|
||||
|
||||
@@ -242,6 +245,13 @@ const taskBarOptions = ref({
|
||||
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>(() => ({
|
||||
showAvatar: taskBarOptions.value.showAvatar,
|
||||
showTitle: taskBarOptions.value.showTitle,
|
||||
@@ -1302,6 +1312,53 @@ const handleCustomMenuAction = (action: string, task: Task) => {
|
||||
<input v-model="taskBarOptions.showProgress" type="checkbox" />
|
||||
<span class="taskbar-label">{{ t.taskBarConfig.display.showProgress }}</span>
|
||||
</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>
|
||||
|
||||
@@ -1324,11 +1381,11 @@ const handleCustomMenuAction = (action: string, task: Task) => {
|
||||
<label class="taskbar-control">
|
||||
<input v-model="enableTaskRowMove" type="checkbox" />
|
||||
<span class="taskbar-label">
|
||||
启用TaskRow拖拽移动
|
||||
{{ taskBarConfigMessages.enableTaskRowMove?.label }}
|
||||
</span>
|
||||
</label>
|
||||
<span class="control-hint">
|
||||
允许通过拖拽TaskRow来调整任务的层级和顺序
|
||||
{{ taskBarConfigMessages.enableTaskRowMove?.hint }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
@@ -1819,12 +1876,16 @@ const handleCustomMenuAction = (action: string, task: Task) => {
|
||||
:allow-drag-and-resize="allowDragAndResize"
|
||||
:enable-task-row-move="enableTaskRowMove"
|
||||
:assignee-options="assigneeOptions"
|
||||
:task-list-row-class-name="getTaskRowClassName"
|
||||
:task-list-row-style="getTaskRowStyle"
|
||||
:on-export-csv="handleCustomCsvExport"
|
||||
:on-language-change="handleLanguageChange"
|
||||
:on-theme-change="handleThemeChange"
|
||||
: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-deleted="handleMilestoneDeleted"
|
||||
@milestone-icon-changed="handleMilestoneIconChanged"
|
||||
@@ -2528,6 +2589,62 @@ const handleCustomMenuAction = (action: string, task: Task) => {
|
||||
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-advanced-controls {
|
||||
display: flex;
|
||||
|
||||
@@ -27,21 +27,25 @@
|
||||
"description": "评估ADX-2024在健康志愿者和患者中的安全性、耐受性和药代动力学特征",
|
||||
"custom": "test11111",
|
||||
"priority": "高",
|
||||
"barColor": "#7B3FF2",
|
||||
"children": [
|
||||
{
|
||||
"id": 1101,
|
||||
"name": "试验方案设计与<span style='font-weight: bold;color:red;'>伦理审查</span>",
|
||||
"assignee": "方案设计师 李明",
|
||||
"avatar": "50-1.jpg",
|
||||
"avatar": ["50-1.jpg", "50-2.jpg"],
|
||||
"startDate": "2025-01-01",
|
||||
"endDate": "2025-02-28",
|
||||
"actualStartDate": "2025-01-03",
|
||||
"actualEndDate": "2025-02-25",
|
||||
"progress": 100,
|
||||
"estimatedHours": 464,
|
||||
"actualHours": 480,
|
||||
"type": "task",
|
||||
"parentId": 1100,
|
||||
"description": "完成I期试验方案设计、伦理委员会审批及监管部门申报",
|
||||
"custom": "test11111"
|
||||
"custom": "test11111",
|
||||
"barColor": "#2E7D6B"
|
||||
},
|
||||
{
|
||||
"id": 1102,
|
||||
@@ -50,6 +54,8 @@
|
||||
"avatar": "50-2.jpg",
|
||||
"startDate": "2025-03-01",
|
||||
"endDate": "2025-04-30",
|
||||
"actualStartDate": "2025-03-05",
|
||||
"actualEndDate": "2025-04-28",
|
||||
"progress": 90,
|
||||
"estimatedHours": 1392.5,
|
||||
"actualHours": 1250.25,
|
||||
@@ -57,15 +63,17 @@
|
||||
"predecessor": [1101],
|
||||
"parentId": 1100,
|
||||
"description": "招募24名健康志愿者,完成医学筛选和入组评估",
|
||||
"custom": "test11111"
|
||||
"custom": "test11111",
|
||||
"barColor": "#FF4D4F"
|
||||
},
|
||||
{
|
||||
"id": 1103,
|
||||
"name": "药物给药与安全性监测",
|
||||
"assignee": "临床医生 Dr. Liu",
|
||||
"avatar": "50-3.jpg",
|
||||
"avatar": ["50-3.jpg", "50-2.jpg"],
|
||||
"startDate": "2025-05-01",
|
||||
"endDate": "2025-08-31",
|
||||
"actualStartDate": "2025-05-10",
|
||||
"progress": 40,
|
||||
"estimatedHours": 3400,
|
||||
"actualHours": 1687,
|
||||
@@ -73,7 +81,8 @@
|
||||
"predecessor": [1102],
|
||||
"parentId": 1100,
|
||||
"description": "按照剂量递增方案给药,密切监测不良反应和药代动力学参数",
|
||||
"custom": "test11111"
|
||||
"custom": "test11111",
|
||||
"barColor": "#7B3FF2"
|
||||
}
|
||||
],
|
||||
"collapsed": false
|
||||
@@ -91,14 +100,16 @@
|
||||
"parentId": 1000,
|
||||
"description": "在目标患者群体中评估ADX-2024的初步疗效和进一步的安全性",
|
||||
"custom": "test11111",
|
||||
"barColor": "#3F6DF6",
|
||||
"children": [
|
||||
{
|
||||
"id": 1201,
|
||||
"name": "多中心试验<span style='font-weight: bold; color: blue;'>启动</span>",
|
||||
"assignee": "项目经理 王芳",
|
||||
"avatar": "50-10.jpg",
|
||||
"avatar": ["50-10.jpg", "50-2.jpg"],
|
||||
"startDate": "2025-09-01",
|
||||
"endDate": "2025-11-30",
|
||||
"actualStartDate": "2025-09-01",
|
||||
"progress": 25,
|
||||
"estimatedHours": 2160,
|
||||
"actualHours": 324,
|
||||
@@ -106,13 +117,14 @@
|
||||
"predecessor": [1103],
|
||||
"parentId": 1200,
|
||||
"description": "启动5个临床中心,完成研究者培训和质量体系建立",
|
||||
"custom": "test11111"
|
||||
"custom": "test11111",
|
||||
"barColor": "#3F6DF6"
|
||||
},
|
||||
{
|
||||
"id": 1202,
|
||||
"name": "患者入组与随机化",
|
||||
"assignee": "数据管理员 陈静",
|
||||
"avatar": "50-6.jpg",
|
||||
"avatar": ["50-6.jpg", "50-10.jpg"],
|
||||
"startDate": "2025-12-01",
|
||||
"endDate": "2026-03-31",
|
||||
"progress": 0,
|
||||
@@ -138,7 +150,8 @@
|
||||
"predecessor": [1202],
|
||||
"parentId": 1200,
|
||||
"description": "定期评估患者疗效指标,收集安全性数据,进行中期分析",
|
||||
"custom": "test11111"
|
||||
"custom": "test11111",
|
||||
"barColor": "#FFA43A"
|
||||
}
|
||||
],
|
||||
"collapsed": false
|
||||
|
||||
@@ -111,5 +111,21 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,5 +111,21 @@
|
||||
"false": "假"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskBarConfig": {
|
||||
"display": {
|
||||
"showActualTaskBar": "显示实际TaskBar"
|
||||
},
|
||||
"colorConfig": {
|
||||
"title": "自定义任务状态背景色",
|
||||
"pendingTask": "待处理任务",
|
||||
"delayTask": "逾期任务",
|
||||
"completeTask": "已完成任务",
|
||||
"ongoingTask": "进行中任务"
|
||||
},
|
||||
"enableTaskRowMove": {
|
||||
"label": "启用TaskRow拖拽移动",
|
||||
"hint": "允许通过拖拽TaskRow来调整任务的层级和顺序"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,114 @@ interface LocaleMessages {
|
||||
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> = {
|
||||
'zh-CN': zhCN,
|
||||
'en-US': enUS,
|
||||
'zh-CN': zhCN as any,
|
||||
'en-US': enUS as any,
|
||||
}
|
||||
|
||||
const currentLocale = ref<LocaleKey>('zh-CN')
|
||||
|
||||
@@ -466,5 +466,31 @@
|
||||
"🎉 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>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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>"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 248 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jordium-gantt-vue3",
|
||||
"version": "1.7.2",
|
||||
"version": "1.8.0",
|
||||
"type": "module",
|
||||
"main": "npm-package/dist/jordium-gantt-vue3.cjs.js",
|
||||
"module": "npm-package/dist/jordium-gantt-vue3.es.js",
|
||||
|
||||
@@ -59,6 +59,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
enableTaskListContextMenu: true,
|
||||
enableTaskBarContextMenu: true,
|
||||
enableLinkAnchor: true,
|
||||
showActualTaskbar: false,
|
||||
enableTaskBarTooltip: true,
|
||||
fullscreen: false,
|
||||
expandAll: true,
|
||||
locale: 'zh-CN',
|
||||
@@ -201,6 +203,21 @@ interface Props {
|
||||
// 是否启用 LinkAnchor 连接触点功能(默认为 true)
|
||||
// 当设置为 false 时,TaskBar 上不显示前置/后置任务的连接触点
|
||||
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
|
||||
// 展开/收起所有任务(响应式)
|
||||
@@ -1201,6 +1218,8 @@ const milestonesForTimeline = computed((): Milestone[] => {
|
||||
startDate: task.startDate, // 此时已确保非空
|
||||
endDate: task.endDate,
|
||||
assignee: task.assignee,
|
||||
assigneeName: task.assigneeName,
|
||||
avatar: task.avatar,
|
||||
type: task.type || 'milestone',
|
||||
icon: task.icon,
|
||||
description: task.description,
|
||||
@@ -2808,6 +2827,12 @@ defineExpose({
|
||||
:working-hours="props.workingHours"
|
||||
:task-bar-config="props.taskBarConfig"
|
||||
: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-milestone-dialog="props.useDefaultMilestoneDialog"
|
||||
:on-milestone-save="handleMilestoneSave"
|
||||
@@ -2861,6 +2886,10 @@ defineExpose({
|
||||
:task="taskDrawerTask"
|
||||
:is-edit="taskDrawerEditMode"
|
||||
: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"
|
||||
@close="taskDrawerVisible = false"
|
||||
@start-timer="handleStartTimer"
|
||||
|
||||
@@ -509,7 +509,7 @@ defineExpose({
|
||||
top: `${offsetTop}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
zIndex: highlightedTaskId !== null ? 1001 : 25,
|
||||
zIndex: highlightedTaskId !== null ? 2000 : 500,
|
||||
pointerEvents: 'none',
|
||||
}"
|
||||
/>
|
||||
|
||||
@@ -21,6 +21,10 @@ interface Props {
|
||||
isEdit?: boolean
|
||||
onDelete?: (task: Task) => void
|
||||
assigneeOptions?: AssigneeOption[]
|
||||
pendingTaskBackgroundColor?: string
|
||||
delayTaskBackgroundColor?: string
|
||||
completeTaskBackgroundColor?: string
|
||||
ongoingTaskBackgroundColor?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -35,6 +39,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
{ value: 'zhaoliu', label: '赵六' },
|
||||
{ value: 'qianqi', label: '钱七' },
|
||||
],
|
||||
pendingTaskBackgroundColor: '#c0c4cc',
|
||||
delayTaskBackgroundColor: '#f56c6c',
|
||||
completeTaskBackgroundColor: '#67c23a',
|
||||
ongoingTaskBackgroundColor: '#409eff',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -146,6 +154,8 @@ const formData = reactive<Task>({
|
||||
assignee: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
actualStartDate: '',
|
||||
actualEndDate: '',
|
||||
predecessor: [],
|
||||
estimatedHours: 0,
|
||||
actualHours: 0,
|
||||
@@ -334,6 +344,8 @@ const errors = reactive({
|
||||
type: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
actualStartDate: '',
|
||||
actualEndDate: '',
|
||||
})
|
||||
|
||||
// 监听 visible 属性变化
|
||||
@@ -397,6 +409,8 @@ const resetForm = () => {
|
||||
assignee: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
actualStartDate: '',
|
||||
actualEndDate: '',
|
||||
predecessor: [],
|
||||
estimatedHours: 0,
|
||||
actualHours: 0,
|
||||
@@ -408,6 +422,9 @@ const resetForm = () => {
|
||||
timerEndTime: undefined,
|
||||
timerElapsedTime: 0,
|
||||
children: undefined,
|
||||
bgColor: undefined,
|
||||
avatar: undefined,
|
||||
barColor: undefined,
|
||||
})
|
||||
|
||||
// 清除错误信息
|
||||
@@ -434,14 +451,19 @@ const validateForm = (): boolean => {
|
||||
isValid = false
|
||||
}
|
||||
if (!formData.startDate) {
|
||||
errors.startDate = t.value.startDateRequired
|
||||
errors.startDate = t.value.plannedStartDateRequired
|
||||
isValid = false
|
||||
}
|
||||
if (!formData.endDate) {
|
||||
errors.endDate = t.value.endDateRequired
|
||||
errors.endDate = t.value.plannedEndDateRequired
|
||||
isValid = false
|
||||
} 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
|
||||
}
|
||||
return isValid
|
||||
@@ -654,6 +676,71 @@ const handleAssigneeChanged = (event: Event) => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -763,6 +850,18 @@ const handleAssigneeChanged = (event: Event) => {
|
||||
</span>
|
||||
</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">
|
||||
<svg
|
||||
class="close-icon"
|
||||
@@ -844,14 +943,14 @@ const handleAssigneeChanged = (event: Event) => {
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="task-start-date">
|
||||
{{ t.startDate }} <span class="required">*</span></label
|
||||
{{ t.plannedStartDate }} <span class="required">*</span></label
|
||||
>
|
||||
<DatePicker
|
||||
id="task-start-date"
|
||||
v-model="formData.startDate"
|
||||
:type="'datetime' as any"
|
||||
value-format="YYYY-MM-DD HH:mm"
|
||||
:placeholder="t.startDateRequired"
|
||||
:placeholder="t.plannedStartDateRequired"
|
||||
:class="{ error: errors.startDate }"
|
||||
/>
|
||||
<span v-if="errors.startDate" class="error-text">{{ errors.startDate }}</span>
|
||||
@@ -859,20 +958,52 @@ const handleAssigneeChanged = (event: Event) => {
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="task-end-date">
|
||||
{{ t.endDate }} <span class="required">*</span></label
|
||||
{{ t.plannedEndDate }} <span class="required">*</span></label
|
||||
>
|
||||
<DatePicker
|
||||
id="task-end-date"
|
||||
v-model="formData.endDate"
|
||||
:type="'datetime' as any"
|
||||
value-format="YYYY-MM-DD HH:mm"
|
||||
:placeholder="t.endDateRequired"
|
||||
:placeholder="t.plannedEndDateRequired"
|
||||
:class="{ error: errors.endDate }"
|
||||
/>
|
||||
<span v-if="errors.endDate" class="error-text">{{ errors.endDate }}</span>
|
||||
</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
|
||||
v-model="formData.predecessor"
|
||||
:tasks="allTasks"
|
||||
@@ -1068,6 +1199,15 @@ const handleAssigneeChanged = (event: Event) => {
|
||||
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 {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -1075,6 +1215,7 @@ const handleAssigneeChanged = (event: Event) => {
|
||||
padding: 4px;
|
||||
color: var(--gantt-text-muted, #909399);
|
||||
transition: color 0.2s;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.drawer-close-btn:hover {
|
||||
|
||||
@@ -226,6 +226,59 @@ const slotPayload = computed(() => ({
|
||||
daysText: daysText.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]
|
||||
}
|
||||
|
||||
// 如果没有avatar,从assignee生成文字头像
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -243,7 +296,7 @@ const slotPayload = computed(() => ({
|
||||
'task-type-milestone': isMilestoneTask,
|
||||
[customRowClass]: customRowClass,
|
||||
}"
|
||||
:style="customRowStyle"
|
||||
:style="[customRowStyle, customBorderStyle]"
|
||||
@click="handleRowClick"
|
||||
@dblclick="handleTaskRowDoubleClick"
|
||||
@mousedown="handleMouseDown"
|
||||
@@ -373,10 +426,25 @@ const slotPayload = computed(() => ({
|
||||
<!-- 负责人列 -->
|
||||
<template v-else-if="column.key === 'assignee'">
|
||||
<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>
|
||||
<!-- 名称显示,支持换行和超出显示... -->
|
||||
<span class="assignee-name" :title="assigneeDisplayData.nameText">{{ assigneeDisplayData.nameText }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -614,9 +682,29 @@ const slotPayload = computed(() => ({
|
||||
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 {
|
||||
min-width: 25px;
|
||||
min-height: 25px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: var(--gantt-primary);
|
||||
color: var(--gantt-text-white);
|
||||
@@ -627,11 +715,31 @@ const slotPayload = computed(() => ({
|
||||
font-weight: 500;
|
||||
border: 2px solid var(--gantt-border-medium);
|
||||
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 {
|
||||
font-size: 14px;
|
||||
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 {
|
||||
|
||||
@@ -37,6 +37,15 @@ interface Props {
|
||||
taskBarConfig?: TaskBarConfig
|
||||
// 是否允许拖拽和拉伸(默认为 true)
|
||||
allowDragAndResize?: boolean
|
||||
// 是否显示实际任务条(默认为 false)
|
||||
showActualTaskbar?: boolean
|
||||
// 是否启用 TaskBar 气泡提示框(默认为 true)
|
||||
enableTaskBarTooltip?: boolean
|
||||
// 自定义任务状态背景色
|
||||
pendingTaskBackgroundColor?: string
|
||||
delayTaskBackgroundColor?: string
|
||||
completeTaskBackgroundColor?: string
|
||||
ongoingTaskBackgroundColor?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -55,6 +64,12 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
}),
|
||||
taskBarConfig: undefined,
|
||||
allowDragAndResize: true,
|
||||
showActualTaskbar: false,
|
||||
enableTaskBarTooltip: true,
|
||||
pendingTaskBackgroundColor: undefined,
|
||||
delayTaskBackgroundColor: undefined,
|
||||
completeTaskBackgroundColor: undefined,
|
||||
ongoingTaskBackgroundColor: undefined,
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
@@ -1049,11 +1064,6 @@ const hasCircularDependency = (taskId: number, targetId: number): boolean => {
|
||||
|
||||
// 创建连接
|
||||
const createLink = (sourceTask: Task, targetTask: Task, mode: 'predecessor' | 'successor') => {
|
||||
console.log('----> hello:', {
|
||||
mode,
|
||||
sourceTask,
|
||||
targetTask,
|
||||
})
|
||||
if (mode === 'predecessor') {
|
||||
// mode='predecessor' 表示从左侧 anchor 拖出
|
||||
// 从左侧 anchor 拖到右侧:sourceTask 依赖 targetTask
|
||||
@@ -4478,6 +4488,12 @@ const handleAddSuccessor = (task: Task) => {
|
||||
:current-time-scale="currentTimeScale"
|
||||
:task-bar-config="props.taskBarConfig"
|
||||
: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-primary-highlight="highlightedTaskId === task.id"
|
||||
:is-in-highlight-mode="isInHighlightMode"
|
||||
|
||||
@@ -13,6 +13,10 @@ const messages = {
|
||||
assignee: '分配人',
|
||||
startDate: '开始日期',
|
||||
endDate: '结束日期',
|
||||
plannedStartDate: '预计开始日期',
|
||||
plannedEndDate: '预计结束日期',
|
||||
actualStartDate: '实际开始日期',
|
||||
actualEndDate: '实际结束日期',
|
||||
estimatedHours: '预计工时(hr)',
|
||||
actualHours: '实际工时(hr)',
|
||||
progress: '进度',
|
||||
@@ -26,6 +30,10 @@ const messages = {
|
||||
assignee: '分配人',
|
||||
startDate: '开始日期',
|
||||
endDate: '结束日期',
|
||||
plannedStartDate: '预计开始日期',
|
||||
plannedEndDate: '预计结束日期',
|
||||
actualStartDate: '实际开始日期',
|
||||
actualEndDate: '实际结束日期',
|
||||
estimatedHours: '预计工时(hr)',
|
||||
actualHours: '实际工时(hr)',
|
||||
progress: '进度(%)',
|
||||
@@ -119,6 +127,15 @@ const messages = {
|
||||
startDateRequired: '开始日期不能为空',
|
||||
endDateRequired: '结束日期不能为空',
|
||||
endDateInvalid: '结束日期不能早于开始日期',
|
||||
plannedStartDateRequired: '预计开始日期不能为空',
|
||||
plannedEndDateRequired: '预计结束日期不能为空',
|
||||
plannedEndDateInvalid: '预计结束日期不能早于预计开始日期',
|
||||
actualEndDateInvalid: '实际结束日期不能早于实际开始日期',
|
||||
// 任务状态
|
||||
statusPending: '待处理',
|
||||
statusOngoing: '进行中',
|
||||
statusDelayed: '已逾期',
|
||||
statusCompleted: '已完成',
|
||||
// 新建里程碑对话框
|
||||
newMilestone: '新建里程碑',
|
||||
editMilestone: '编辑里程碑',
|
||||
@@ -279,6 +296,10 @@ const messages = {
|
||||
assignee: 'Assignee',
|
||||
startDate: 'Start Date',
|
||||
endDate: 'End Date',
|
||||
plannedStartDate: 'Planned Start Date',
|
||||
plannedEndDate: 'Planned End Date',
|
||||
actualStartDate: 'Actual Start Date',
|
||||
actualEndDate: 'Actual End Date',
|
||||
estimatedHours: 'Est. Hours',
|
||||
actualHours: 'Act. Hours',
|
||||
progress: 'Progress',
|
||||
@@ -291,6 +312,10 @@ const messages = {
|
||||
assignee: 'Assignee',
|
||||
startDate: 'Start Date',
|
||||
endDate: 'End Date',
|
||||
plannedStartDate: 'Planned Start Date',
|
||||
plannedEndDate: 'Planned End Date',
|
||||
actualStartDate: 'Actual Start Date',
|
||||
actualEndDate: 'Actual End Date',
|
||||
estimatedHours: 'Est. Hours',
|
||||
actualHours: 'Act. Hours',
|
||||
progress: 'Progress (%)',
|
||||
@@ -382,6 +407,15 @@ const messages = {
|
||||
startDateRequired: 'Start date is required',
|
||||
endDateRequired: 'End date is required',
|
||||
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',
|
||||
editMilestone: 'Edit Milestone',
|
||||
|
||||
@@ -4,7 +4,9 @@ export interface Milestone {
|
||||
name: string
|
||||
startDate: string
|
||||
endDate?: string
|
||||
assignee?: string
|
||||
assignee?: string | string[] // 支持单个或多个负责人
|
||||
assigneeName?: string | string[] // 支持单个或多个负责人姓名
|
||||
avatar?: string | string[] // 支持单个或多个头像
|
||||
type: string
|
||||
icon?: string
|
||||
description?: string
|
||||
|
||||
@@ -3,11 +3,13 @@ export interface Task {
|
||||
id: number
|
||||
name: string
|
||||
predecessor?: number[] // 前置任务ID数组
|
||||
assignee?: string // 记录唯一键值,如用户ID或用户名
|
||||
assignee?: string | string[] // 记录唯一键值,如用户ID或用户名,支持单个或多个
|
||||
assigneeName?: string // 任务负责人名称
|
||||
avatar?: string // 任务负责人头像URL
|
||||
avatar?: string | string[] // 任务负责人头像URL,支持单个或多个头像数组
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
actualStartDate?: string // 实际开始日期
|
||||
actualEndDate?: string // 实际结束日期
|
||||
progress?: number
|
||||
estimatedHours?: number
|
||||
actualHours?: number
|
||||
@@ -27,6 +29,8 @@ export interface Task {
|
||||
timerElapsedTime?: number
|
||||
// 权限控制
|
||||
isEditable?: boolean // 是否可编辑(可拖拽、拉伸),默认为true
|
||||
// 自定义样式
|
||||
barColor?: string // 自定义TaskBar颜色,如 '#ff5733',若不设置则使用默认颜色方案
|
||||
// 支持自定义属性 - 使用 unknown 允许任意类型
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||