v1.4.6 - bugfix
This commit is contained in:
@@ -5,6 +5,12 @@ 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.4.6] - 2025-12-09
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 修复:内置TaskDrawer中负责人列表可以外部初始化
|
||||||
|
- Fixed: The assignee list in the built-in TaskDrawer can be initialized externally
|
||||||
|
|
||||||
## [1.4.5] - 2025-12-06
|
## [1.4.5] - 2025-12-06
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
+107
-8
@@ -23,7 +23,8 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="./README.md">中文</a> |
|
<a href="./README.md">中文</a> |
|
||||||
<a href="./README-EN.md">English</a>
|
<a href="./README-EN.md">English</a> |
|
||||||
|
<a href="./CHANGELOG.md">Release Notes</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">A modern Vue 3 Gantt chart component library providing complete solutions for project management and task scheduling</p>
|
<p align="center">A modern Vue 3 Gantt chart component library providing complete solutions for project management and task scheduling</p>
|
||||||
@@ -188,8 +189,9 @@ npm run dev
|
|||||||
| `useDefaultDrawer` | `boolean` | `true` | Whether to use the built-in task edit drawer (TaskDrawer) |
|
| `useDefaultDrawer` | `boolean` | `true` | Whether to use the built-in task edit drawer (TaskDrawer) |
|
||||||
| `useDefaultMilestoneDialog` | `boolean` | `true` | Whether to use the built-in milestone edit dialog (MilestoneDialog) |
|
| `useDefaultMilestoneDialog` | `boolean` | `true` | Whether to use the built-in milestone edit dialog (MilestoneDialog) |
|
||||||
| `autoSortByStartDate` | `boolean` | `false` | Whether to automatically sort tasks by start date |
|
| `autoSortByStartDate` | `boolean` | `false` | Whether to automatically sort tasks by start date |
|
||||||
| `allowDragAndResize` | `boolean` | `true` | Whether to allow dragging and resizing tasks/milestones |
|
| `allowDragAndResize` | `boolean` | `true` | Whether to allow dragging and resizing tasks/milestones |
|
||||||
| `enableTaskRowMove` | `boolean` | `false` | Whether to alloww dragging and dropping TaskRow
|
| `enableTaskRowMove` | `boolean` | `false` | Whether to allow dragging and dropping TaskRow |
|
||||||
|
| `assigneeOptions` | `Array<{ key?: string \| number; value: string \| number; label: string }>` | `[]` | Assignee dropdown options in task edit drawer |
|
||||||
|
|
||||||
#### Configuration Object Props
|
#### Configuration Object Props
|
||||||
|
|
||||||
@@ -251,7 +253,7 @@ For complete event documentation, see:
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
<GanttChart :tasks="tasks" />
|
<GanttChart :tasks="tasks" :assignee-options="assigneeOptions" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -269,6 +271,12 @@ const tasks = ref([
|
|||||||
progress: 100,
|
progress: 100,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'alice', label: 'Alice' },
|
||||||
|
{ value: 'bob', label: 'Bob' },
|
||||||
|
{ value: 'charlie', label: 'Charlie' },
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -277,7 +285,7 @@ const tasks = ref([
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
<GanttChart :tasks="tasks" :milestones="milestones" />
|
<GanttChart :tasks="tasks" :milestones="milestones" :assignee-options="assigneeOptions" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -305,6 +313,12 @@ const milestones = ref([
|
|||||||
icon: 'diamond',
|
icon: 'diamond',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'alice', label: 'Alice' },
|
||||||
|
{ value: 'bob', label: 'Bob' },
|
||||||
|
{ value: 'charlie', label: 'Charlie' },
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -325,6 +339,7 @@ const milestones = ref([
|
|||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:milestones="milestones"
|
:milestones="milestones"
|
||||||
:show-toolbar="false"
|
:show-toolbar="false"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@task-added="handleTaskAdded"
|
@task-added="handleTaskAdded"
|
||||||
@milestone-saved="handleMilestoneSaved"
|
@milestone-saved="handleMilestoneSaved"
|
||||||
/>
|
/>
|
||||||
@@ -340,6 +355,12 @@ import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
|
|||||||
const tasks = ref([])
|
const tasks = ref([])
|
||||||
const milestones = ref([])
|
const milestones = ref([])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'alice', label: 'Alice' },
|
||||||
|
{ value: 'bob', label: 'Bob' },
|
||||||
|
{ value: 'charlie', label: 'Charlie' },
|
||||||
|
])
|
||||||
|
|
||||||
const addTask = () => {
|
const addTask = () => {
|
||||||
const newTask = {
|
const newTask = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
@@ -387,7 +408,8 @@ 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 |
|
| `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 |
|
| `avatar` | `string` | - | - | Avatar URL of task assignee |
|
||||||
| `estimatedHours` | `number` | - | - | Estimated hours |
|
| `estimatedHours` | `number` | - | - | Estimated hours |
|
||||||
| `actualHours` | `number` | - | - | Actual hours |
|
| `actualHours` | `number` | - | - | Actual hours |
|
||||||
@@ -426,7 +448,8 @@ Tasks are the core elements of the Gantt chart. The component provides complete
|
|||||||
| `taskBarConfig` | `TaskBarConfig` | `{}` | Task bar style configuration, see [TaskBarConfig Configuration](#taskbarconfig-configuration) |
|
| `taskBarConfig` | `TaskBarConfig` | `{}` | Task bar style configuration, see [TaskBarConfig Configuration](#taskbarconfig-configuration) |
|
||||||
| `taskListConfig` | `TaskListConfig` | `undefined` | Task list configuration, see [TaskListConfig Configuration](#tasklistconfig-configuration) |
|
| `taskListConfig` | `TaskListConfig` | `undefined` | Task list configuration, see [TaskListConfig Configuration](#tasklistconfig-configuration) |
|
||||||
| `autoSortByStartDate` | `boolean` | `false` | Whether to automatically sort tasks by start date |
|
| `autoSortByStartDate` | `boolean` | `false` | Whether to automatically sort tasks by start date |
|
||||||
| `enableTaskRowMove` | `boolean` | `false` | Whether to alloww dragging and dropping TaskRow
|
| `enableTaskRowMove` | `boolean` | `false` | Whether to alloww dragging and dropping TaskRow |
|
||||||
|
| `assigneeOptions` | `Array<{ key?: string \| number; value: string \| number; label: string }>` | `[]` | Assignee dropdown options in task edit drawer |
|
||||||
|
|
||||||
**Configuration Notes**:
|
**Configuration Notes**:
|
||||||
|
|
||||||
@@ -467,6 +490,7 @@ Tasks are the core elements of the Gantt chart. The component provides complete
|
|||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@add-task="handleAddTask"
|
@add-task="handleAddTask"
|
||||||
@task-added="handleTaskAdded"
|
@task-added="handleTaskAdded"
|
||||||
@task-updated="handleTaskUpdated"
|
@task-updated="handleTaskUpdated"
|
||||||
@@ -504,6 +528,12 @@ const tasks = ref<Task[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'alice', label: 'Alice' },
|
||||||
|
{ value: 'bob', label: 'Bob' },
|
||||||
|
{ value: 'charlie', label: 'Charlie' },
|
||||||
|
])
|
||||||
|
|
||||||
// Toolbar "Add Task" button click event
|
// Toolbar "Add Task" button click event
|
||||||
const handleAddTask = () => {
|
const handleAddTask = () => {
|
||||||
console.log('Preparing to add task...')
|
console.log('Preparing to add task...')
|
||||||
@@ -556,6 +586,7 @@ Tasks can configure predecessors via the `predecessor` field, and the component
|
|||||||
<template>
|
<template>
|
||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@predecessor-added="handlePredecessorAdded"
|
@predecessor-added="handlePredecessorAdded"
|
||||||
@successor-added="handleSuccessorAdded"
|
@successor-added="handleSuccessorAdded"
|
||||||
/>
|
/>
|
||||||
@@ -618,6 +649,12 @@ const tasks = ref<Task[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'alice', label: 'Alice' },
|
||||||
|
{ value: 'bob', label: 'Bob' },
|
||||||
|
{ value: 'charlie', label: 'Charlie' },
|
||||||
|
])
|
||||||
|
|
||||||
// Triggered when adding predecessor via context menu
|
// Triggered when adding predecessor via context menu
|
||||||
const handlePredecessorAdded = (event: { targetTask: Task; newTask: Task }) => {
|
const handlePredecessorAdded = (event: { targetTask: Task; newTask: Task }) => {
|
||||||
console.log(`Task [${event.targetTask.name}] added predecessor [${event.newTask.name}]`)
|
console.log(`Task [${event.targetTask.name}] added predecessor [${event.newTask.name}]`)
|
||||||
@@ -671,6 +708,7 @@ Suitable for scenarios requiring complete custom control bar:
|
|||||||
:show-toolbar="false"
|
:show-toolbar="false"
|
||||||
:use-default-drawer="true"
|
:use-default-drawer="true"
|
||||||
:use-default-milestone-dialog="true"
|
:use-default-milestone-dialog="true"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@add-task="handleAddTask"
|
@add-task="handleAddTask"
|
||||||
@add-milestone="handleAddMilestone"
|
@add-milestone="handleAddMilestone"
|
||||||
@task-added="handleTaskAdded"
|
@task-added="handleTaskAdded"
|
||||||
@@ -686,6 +724,12 @@ import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
|
|||||||
const tasks = ref([])
|
const tasks = ref([])
|
||||||
const milestones = ref([])
|
const milestones = ref([])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'alice', label: 'Alice' },
|
||||||
|
{ value: 'bob', label: 'Bob' },
|
||||||
|
{ value: 'charlie', label: 'Charlie' },
|
||||||
|
])
|
||||||
|
|
||||||
// Custom button triggers event (component will respond and open built-in editor)
|
// Custom button triggers event (component will respond and open built-in editor)
|
||||||
const triggerAddTask = () => {
|
const triggerAddTask = () => {
|
||||||
// Directly trigger component's add-task event
|
// Directly trigger component's add-task event
|
||||||
@@ -729,6 +773,7 @@ Allow users to adjust task hierarchy and order by dragging TaskRow:
|
|||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:enable-task-row-move="true"
|
:enable-task-row-move="true"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@task-row-moved="handleTaskRowMoved"
|
@task-row-moved="handleTaskRowMoved"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -765,6 +810,12 @@ const tasks = ref<Task[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'alice', label: 'Alice' },
|
||||||
|
{ value: 'bob', label: 'Bob' },
|
||||||
|
{ value: 'charlie', label: 'Charlie' },
|
||||||
|
])
|
||||||
|
|
||||||
// Task row drag completed event (optional)
|
// Task row drag completed event (optional)
|
||||||
const handleTaskRowMoved = async (payload: {
|
const handleTaskRowMoved = async (payload: {
|
||||||
draggedTask: Task
|
draggedTask: Task
|
||||||
@@ -1248,6 +1299,54 @@ const handleDelete = () => {
|
|||||||
|
|
||||||
This section details the configuration options and extension capabilities of the GanttChart component, including Component Configuration, Theme & Internationalization, and Custom Extensions.
|
This section details the configuration options and extension capabilities of the GanttChart component, including Component Configuration, Theme & Internationalization, and Custom Extensions.
|
||||||
|
|
||||||
|
### Task Type Definition
|
||||||
|
|
||||||
|
Task type (`type` field) is used to distinguish different types of tasks, and the component internally executes different logic based on the type.
|
||||||
|
|
||||||
|
#### Built-in Task Types
|
||||||
|
|
||||||
|
| Type | Description | Default |
|
||||||
|
| ------- | ------------- | ------- |
|
||||||
|
| `story` | User Story | - |
|
||||||
|
| `task` | Regular Task | ✅ |
|
||||||
|
| `bug` | Bug/Issue | - |
|
||||||
|
|
||||||
|
#### Feature Differences
|
||||||
|
|
||||||
|
Different task types have different functional characteristics in the component:
|
||||||
|
|
||||||
|
| Feature | story | task | bug |
|
||||||
|
| -------------------- | ----- | ---- | --- |
|
||||||
|
| Can be parent task | ✅ | ✅ | ❌ |
|
||||||
|
| Can be predecessor | ❌ | ✅ | ❌ |
|
||||||
|
| Timer support | ❌ | ✅ | ✅ |
|
||||||
|
| Auto parent task | ✅ | ❌ | ❌ |
|
||||||
|
| Special delete hint | ✅ | ❌ | ❌ |
|
||||||
|
|
||||||
|
#### Important Notes
|
||||||
|
|
||||||
|
> ⚠️ **Important**
|
||||||
|
>
|
||||||
|
> 1. Task type values are used for internal component logic, **do not modify** these enum values arbitrarily
|
||||||
|
> 2. When customizing TaskDrawer, you must maintain these three enum values: `story`, `task`, `bug`
|
||||||
|
> 3. For additional business labels, use custom property fields such as: `customType`, `category`, `label`, etc.
|
||||||
|
|
||||||
|
**Example: Using Custom Labels**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const tasks = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Requirements Analysis',
|
||||||
|
type: 'task', // Keep built-in component type
|
||||||
|
customType: 'requirement', // Custom business type
|
||||||
|
category: 'analysis', // Custom category
|
||||||
|
startDate: '2025-01-01',
|
||||||
|
endDate: '2025-01-10',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
### Component Configuration
|
### Component Configuration
|
||||||
|
|
||||||
#### ToolbarConfig (Toolbar Configuration)
|
#### ToolbarConfig (Toolbar Configuration)
|
||||||
@@ -2199,5 +2298,5 @@ View the complete [Contributors list](./CONTRIBUTORS.md)。
|
|||||||
---
|
---
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<sub>If this project helps you, please give it a ⭐️ to support it!</sub>
|
If this project helps you, please give it a ⭐️ to support it!
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="./README.md">中文</a> |
|
<a href="./README.md">中文</a> |
|
||||||
<a href="./README-EN.md">English</a>
|
<a href="./README-EN.md">English</a> |
|
||||||
|
<a href="./CHANGELOG.md">更新日志</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">现代化的 Vue 3 甘特图组件库,为项目管理和任务调度提供完整解决方案</p>
|
<p align="center">现代化的 Vue 3 甘特图组件库,为项目管理和任务调度提供完整解决方案</p>
|
||||||
@@ -178,16 +179,17 @@ npm run dev
|
|||||||
|
|
||||||
#### 基础属性
|
#### 基础属性
|
||||||
|
|
||||||
| 属性名 | 类型 | 默认值 | 说明 |
|
| 属性名 | 类型 | 默认值 | 说明 |
|
||||||
| --------------------------- | --------- | ------- | -------------------------------------------------------------- |
|
| --------------------------- | ----------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------- |
|
||||||
| `tasks` | `Task[]` | `[]` | 任务数据数组 |
|
| `tasks` | `Task[]` | `[]` | 任务数据数组 |
|
||||||
| `milestones` | `Task[]` | `[]` | 里程碑数据数组(注意:类型为 Task[],需设置 type='milestone') |
|
| `milestones` | `Task[]` | `[]` | 里程碑数据数组(注意:类型为 Task[],需设置 type='milestone') |
|
||||||
| `showToolbar` | `boolean` | `true` | 是否显示工具栏 |
|
| `showToolbar` | `boolean` | `true` | 是否显示工具栏 |
|
||||||
| `useDefaultDrawer` | `boolean` | `true` | 是否使用内置任务编辑抽屉(TaskDrawer) |
|
| `useDefaultDrawer` | `boolean` | `true` | 是否使用内置任务编辑抽屉(TaskDrawer) |
|
||||||
| `useDefaultMilestoneDialog` | `boolean` | `true` | 是否使用内置里程碑编辑对话框(MilestoneDialog) |
|
| `useDefaultMilestoneDialog` | `boolean` | `true` | 是否使用内置里程碑编辑对话框(MilestoneDialog) |
|
||||||
| `autoSortByStartDate` | `boolean` | `false` | 是否根据开始时间自动排序任务 |
|
| `autoSortByStartDate` | `boolean` | `false` | 是否根据开始时间自动排序任务 |
|
||||||
| `allowDragAndResize` | `boolean` | `true` | 是否允许拖拽和调整任务/里程碑大小 |
|
| `allowDragAndResize` | `boolean` | `true` | 是否允许拖拽和调整任务/里程碑大小 |
|
||||||
| `enableTaskRowMove` | `boolean` | `false` | 是否允许拖拽和摆放TaskRow |
|
| `enableTaskRowMove` | `boolean` | `false` | 是否允许拖拽和摆放TaskRow |
|
||||||
|
| `assigneeOptions` | `Array<{ key?: string \| number; value: string \| number; label: string }>` | `[]` | 任务编辑抽屉中负责人下拉菜单的选项列表 |
|
||||||
|
|
||||||
#### 配置对象属性
|
#### 配置对象属性
|
||||||
|
|
||||||
@@ -249,7 +251,7 @@ npm run dev
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
<GanttChart :tasks="tasks" />
|
<GanttChart :tasks="tasks" :assignee-options="assigneeOptions" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -267,6 +269,12 @@ const tasks = ref([
|
|||||||
progress: 100,
|
progress: 100,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -275,7 +283,7 @@ const tasks = ref([
|
|||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
<GanttChart :tasks="tasks" :milestones="milestones" />
|
<GanttChart :tasks="tasks" :milestones="milestones" :assignee-options="assigneeOptions" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -303,6 +311,12 @@ const milestones = ref([
|
|||||||
icon: 'diamond',
|
icon: 'diamond',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -323,6 +337,7 @@ const milestones = ref([
|
|||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:milestones="milestones"
|
:milestones="milestones"
|
||||||
:show-toolbar="false"
|
:show-toolbar="false"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@task-added="handleTaskAdded"
|
@task-added="handleTaskAdded"
|
||||||
@milestone-saved="handleMilestoneSaved"
|
@milestone-saved="handleMilestoneSaved"
|
||||||
/>
|
/>
|
||||||
@@ -338,6 +353,12 @@ import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
|
|||||||
const tasks = ref([])
|
const tasks = ref([])
|
||||||
const milestones = ref([])
|
const milestones = ref([])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
|
|
||||||
const addTask = () => {
|
const addTask = () => {
|
||||||
const newTask = {
|
const newTask = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
@@ -385,7 +406,8 @@ const handleMilestoneSaved = milestone => {
|
|||||||
| `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` | - | - | 任务负责人,用作负责人下拉菜单的值绑定 |
|
||||||
|
| `assigneeName` | `string` | - | - | 任务负责人姓名,自动从绑定的数据集`assigneeOptions`中获取Label作为显示,如果需要自定义,可以在GanttChart回调事件`task-added`中自定义信息 |
|
||||||
| `avatar` | `string` | - | - | 任务负责人头像 URL |
|
| `avatar` | `string` | - | - | 任务负责人头像 URL |
|
||||||
| `estimatedHours` | `number` | - | - | 预估工时(小时) |
|
| `estimatedHours` | `number` | - | - | 预估工时(小时) |
|
||||||
| `actualHours` | `number` | - | - | 实际工时(小时) |
|
| `actualHours` | `number` | - | - | 实际工时(小时) |
|
||||||
@@ -424,7 +446,8 @@ const handleMilestoneSaved = milestone => {
|
|||||||
| `taskBarConfig` | `TaskBarConfig` | `{}` | 任务条样式配置,详见 [TaskBarConfig 配置](#taskbarconfig-配置) |
|
| `taskBarConfig` | `TaskBarConfig` | `{}` | 任务条样式配置,详见 [TaskBarConfig 配置](#taskbarconfig-配置) |
|
||||||
| `taskListConfig` | `TaskListConfig` | `undefined` | 任务列表配置,详见 [TaskListConfig 配置](#tasklistconfig-配置) |
|
| `taskListConfig` | `TaskListConfig` | `undefined` | 任务列表配置,详见 [TaskListConfig 配置](#tasklistconfig-配置) |
|
||||||
| `autoSortByStartDate` | `boolean` | `false` | 是否根据开始时间自动排序任务 |
|
| `autoSortByStartDate` | `boolean` | `false` | 是否根据开始时间自动排序任务 |
|
||||||
| `enableTaskRowMove` | `boolean` | `false` | 是否允许拖拽和摆放TaskRow
|
| `enableTaskRowMove` | `boolean` | `false` | 是否允许拖拽和摆放TaskRow |
|
||||||
|
| `assigneeOptions` | `Array<{ key?: string \| number; value: string \| number; label: string }>` | `[]` | 任务编辑抽屉中负责人下拉菜单的选项列表 |
|
||||||
|
|
||||||
**配置说明**:
|
**配置说明**:
|
||||||
|
|
||||||
@@ -465,6 +488,7 @@ const handleMilestoneSaved = milestone => {
|
|||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@add-task="handleAddTask"
|
@add-task="handleAddTask"
|
||||||
@task-added="handleTaskAdded"
|
@task-added="handleTaskAdded"
|
||||||
@task-updated="handleTaskUpdated"
|
@task-updated="handleTaskUpdated"
|
||||||
@@ -502,6 +526,12 @@ const tasks = ref<Task[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
|
|
||||||
// 工具栏"添加任务"按钮点击事件
|
// 工具栏"添加任务"按钮点击事件
|
||||||
const handleAddTask = () => {
|
const handleAddTask = () => {
|
||||||
console.log('准备新增任务...')
|
console.log('准备新增任务...')
|
||||||
@@ -554,6 +584,7 @@ const handleTaskDragEnd = (task: Task) => {
|
|||||||
<template>
|
<template>
|
||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@predecessor-added="handlePredecessorAdded"
|
@predecessor-added="handlePredecessorAdded"
|
||||||
@successor-added="handleSuccessorAdded"
|
@successor-added="handleSuccessorAdded"
|
||||||
/>
|
/>
|
||||||
@@ -616,6 +647,12 @@ const tasks = ref<Task[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
|
|
||||||
// 通过右键菜单添加前置任务时触发
|
// 通过右键菜单添加前置任务时触发
|
||||||
const handlePredecessorAdded = (event: { targetTask: Task; newTask: Task }) => {
|
const handlePredecessorAdded = (event: { targetTask: Task; newTask: Task }) => {
|
||||||
console.log(`任务 [${event.targetTask.name}] 添加了前置任务 [${event.newTask.name}]`)
|
console.log(`任务 [${event.targetTask.name}] 添加了前置任务 [${event.newTask.name}]`)
|
||||||
@@ -669,6 +706,7 @@ const handleSuccessorAdded = (event: { targetTask: Task; newTask: Task }) => {
|
|||||||
:show-toolbar="false"
|
:show-toolbar="false"
|
||||||
:use-default-drawer="true"
|
:use-default-drawer="true"
|
||||||
:use-default-milestone-dialog="true"
|
:use-default-milestone-dialog="true"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@add-task="handleAddTask"
|
@add-task="handleAddTask"
|
||||||
@add-milestone="handleAddMilestone"
|
@add-milestone="handleAddMilestone"
|
||||||
@task-added="handleTaskAdded"
|
@task-added="handleTaskAdded"
|
||||||
@@ -684,6 +722,12 @@ import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
|
|||||||
const tasks = ref([])
|
const tasks = ref([])
|
||||||
const milestones = ref([])
|
const milestones = ref([])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
|
|
||||||
// 自定义按钮触发事件(组件会响应并打开内置编辑器)
|
// 自定义按钮触发事件(组件会响应并打开内置编辑器)
|
||||||
const triggerAddTask = () => {
|
const triggerAddTask = () => {
|
||||||
// 直接触发组件的 add-task 事件
|
// 直接触发组件的 add-task 事件
|
||||||
@@ -727,6 +771,7 @@ const handleTaskAdded = e => {
|
|||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:enable-task-row-move="true"
|
:enable-task-row-move="true"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@task-row-moved="handleTaskRowMoved"
|
@task-row-moved="handleTaskRowMoved"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -763,6 +808,12 @@ const tasks = ref<Task[]>([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
|
|
||||||
// 任务行拖拽完成事件(可选)
|
// 任务行拖拽完成事件(可选)
|
||||||
const handleTaskRowMoved = async (payload: {
|
const handleTaskRowMoved = async (payload: {
|
||||||
draggedTask: Task
|
draggedTask: Task
|
||||||
@@ -1239,6 +1290,54 @@ const handleDelete = () => {
|
|||||||
|
|
||||||
本章节详细介绍 GanttChart 组件的配置选项和扩展能力,包括组件配置、主题与国际化、自定义扩展三个部分。
|
本章节详细介绍 GanttChart 组件的配置选项和扩展能力,包括组件配置、主题与国际化、自定义扩展三个部分。
|
||||||
|
|
||||||
|
### 任务类型定义
|
||||||
|
|
||||||
|
任务类型(`type` 字段)用于区分不同类型的任务,组件内部会根据类型执行不同的逻辑判断。
|
||||||
|
|
||||||
|
#### 内置任务类型
|
||||||
|
|
||||||
|
| 类型值 | 说明 | 默认值 |
|
||||||
|
| ------- | ---------- | ------ |
|
||||||
|
| `story` | 用户故事 | - |
|
||||||
|
| `task` | 普通任务 | ✅ |
|
||||||
|
| `bug` | 缺陷/问题 | - |
|
||||||
|
|
||||||
|
#### 功能区分
|
||||||
|
|
||||||
|
不同任务类型在组件中具有不同的功能特性:
|
||||||
|
|
||||||
|
| 功能 | story | task | bug |
|
||||||
|
| ---------------- | ----- | ---- | --- |
|
||||||
|
| 可作为上级任务 | ✅ | ✅ | ❌ |
|
||||||
|
| 可作为前置任务 | ❌ | ✅ | ❌ |
|
||||||
|
| 支持计时器 | ❌ | ✅ | ✅ |
|
||||||
|
| 自动视为父任务 | ✅ | ❌ | ❌ |
|
||||||
|
| 删除时特殊提示 | ✅ | ❌ | ❌ |
|
||||||
|
|
||||||
|
#### 注意事项
|
||||||
|
|
||||||
|
> ⚠️ **重要提示**
|
||||||
|
>
|
||||||
|
> 1. 任务类型值为组件内置判断使用,**请勿随意修改**这些枚举值
|
||||||
|
> 2. 客制化 TaskDrawer 时,必须保持 `story`、`task`、`bug` 这三个枚举值
|
||||||
|
> 3. 如需添加其他业务标签,建议使用自定义属性字段,例如:`customType`、`category`、`label` 等
|
||||||
|
|
||||||
|
**示例:使用自定义标签**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const tasks = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '需求分析',
|
||||||
|
type: 'task', // 保持组件内置类型
|
||||||
|
customType: 'requirement', // 自定义业务类型
|
||||||
|
category: 'analysis', // 自定义分类
|
||||||
|
startDate: '2025-01-01',
|
||||||
|
endDate: '2025-01-10',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
### 组件配置
|
### 组件配置
|
||||||
|
|
||||||
#### ToolbarConfig(工具栏配置)
|
#### ToolbarConfig(工具栏配置)
|
||||||
@@ -2190,5 +2289,5 @@ jordium-gantt-vue3/
|
|||||||
---
|
---
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<sub>如果这个项目对你有帮助,请给一个 ⭐️ 支持一下!</sub>
|
如果这个项目对你有帮助,请给一个 ⭐️ 支持一下!
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -187,6 +187,18 @@ const allowDragAndResize = ref(true)
|
|||||||
// 控制是否启用TaskRow拖拽移动
|
// 控制是否启用TaskRow拖拽移动
|
||||||
const enableTaskRowMove = ref(true)
|
const enableTaskRowMove = ref(true)
|
||||||
|
|
||||||
|
// 指派人员选项列表
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
{ value: 'zhaoliu', label: '赵六' },
|
||||||
|
{ value: 'qianqi', label: '钱七' },
|
||||||
|
{ key: 'user_sunba', value: 'sunba', label: '孙八' }, // 示例:使用自定义 key
|
||||||
|
{ value: 'zhoujiu', label: '周九' },
|
||||||
|
{ value: 'wushi', label: '吴十' },
|
||||||
|
])
|
||||||
|
|
||||||
// TaskBar配置
|
// TaskBar配置
|
||||||
const taskBarOptions = ref({
|
const taskBarOptions = ref({
|
||||||
showAvatar: true,
|
showAvatar: true,
|
||||||
@@ -1088,6 +1100,7 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
:use-default-milestone-dialog="true"
|
:use-default-milestone-dialog="true"
|
||||||
:allow-drag-and-resize="allowDragAndResize"
|
:allow-drag-and-resize="allowDragAndResize"
|
||||||
:enable-task-row-move="enableTaskRowMove"
|
:enable-task-row-move="enableTaskRowMove"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
:on-export-csv="handleCustomCsvExport"
|
:on-export-csv="handleCustomCsvExport"
|
||||||
:on-language-change="handleLanguageChange"
|
:on-language-change="handleLanguageChange"
|
||||||
:on-theme-change="handleThemeChange"
|
:on-theme-change="handleThemeChange"
|
||||||
|
|||||||
@@ -342,5 +342,17 @@
|
|||||||
"Optimized: Task item move and drag-and-drop functionality in the task list",
|
"Optimized: Task item move and drag-and-drop functionality in the task list",
|
||||||
"<span style=\"font-weight: bold; color: #f00;\">Special thanks to jun201607@github for their valuable use and feedback</span>"
|
"<span style=\"font-weight: bold; color: #f00;\">Special thanks to jun201607@github for their valuable use and feedback</span>"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.4.6",
|
||||||
|
"date": "2025-12-09",
|
||||||
|
"notes": [
|
||||||
|
"修复:内置TaskDrawer中负责人列表可以外部初始化",
|
||||||
|
"修复:未设置startDate和endDate时,任务无法正确显示的问题",
|
||||||
|
"<span style=\"font-weight: bold; color: #f00;\">特别感谢 yue-xiaochuan & YQ6494@gitee的使用及反馈的宝贵意见</span>",
|
||||||
|
"Fixed: The assignee list in the built-in TaskDrawer can be initialized externally",
|
||||||
|
"Fixed: The issue where tasks could not be displayed correctly when startDate and endDate were not set",
|
||||||
|
"<span style=\"font-weight: bold; color: #f00;\">Special thanks to yue-xiaochuan & YQ6494@gitee for their valuable use and feedback</span>"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ const availableColumns = ref<TaskListColumnConfig[]>([
|
|||||||
{ key: 'progress', label: '进度', visible: true },
|
{ key: 'progress', label: '进度', visible: true },
|
||||||
{ key: 'department', label: '部门', visible: true, width: 200 },
|
{ key: 'department', label: '部门', visible: true, width: 200 },
|
||||||
{ key: 'departmentCode', label: '部门编号', visible: true },
|
{ key: 'departmentCode', label: '部门编号', visible: true },
|
||||||
|
{ key: 'assigneeName', label: '负责人', visible: true },
|
||||||
|
])
|
||||||
|
|
||||||
|
// 使用内置TaskDrawer组件的时候,可以传入负责人选项
|
||||||
|
// 请先设置GanttChart组件属性use-default-drawer="true"
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
])
|
])
|
||||||
|
|
||||||
// TaskList宽度配置示例
|
// TaskList宽度配置示例
|
||||||
@@ -140,16 +149,16 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
newParent: Task | null
|
newParent: Task | null
|
||||||
}) => {
|
}) => {
|
||||||
const { draggedTask, targetTask, position, oldParent, newParent } = payload
|
const { draggedTask, targetTask, position, oldParent, newParent } = payload
|
||||||
|
|
||||||
// 组件已自动完成任务移动、parentId更新和TaskList/Timeline同步
|
// 组件已自动完成任务移动、parentId更新和TaskList/Timeline同步
|
||||||
// 监听此事件为完全可选,仅用于:
|
// 监听此事件为完全可选,仅用于:
|
||||||
|
|
||||||
// 1. 显示自定义提示消息
|
// 1. 显示自定义提示消息
|
||||||
const oldParentName = oldParent?.name || '根目录'
|
const oldParentName = oldParent?.name || '根目录'
|
||||||
const newParentName = newParent?.name || '根目录'
|
const newParentName = newParent?.name || '根目录'
|
||||||
const positionText = position === 'after' ? '在目标任务之后' : '作为目标任务的子任务'
|
const positionText = position === 'after' ? '在目标任务之后' : '作为目标任务的子任务'
|
||||||
alert(`任务 [${draggedTask.name}] 已从 [${oldParentName}] 移动到 [${newParentName}] (${positionText})`, 'success')
|
alert(`任务 [${draggedTask.name}] 已从 [${oldParentName}] 移动到 [${newParentName}] (${positionText})`, 'success')
|
||||||
|
|
||||||
// 2. 调用后端 API 保存新的任务层级关系
|
// 2. 调用后端 API 保存新的任务层级关系
|
||||||
try {
|
try {
|
||||||
await api.updateTaskHierarchy({
|
await api.updateTaskHierarchy({
|
||||||
@@ -163,17 +172,36 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
console.error('保存任务层级失败:', error)
|
console.error('保存任务层级失败:', error)
|
||||||
alert('保存失败,请刷新页面')
|
alert('保存失败,请刷新页面')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 触发其他业务逻辑(如更新关联数据、记录操作日志等)
|
// 3. 触发其他业务逻辑(如更新关联数据、记录操作日志等)
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 任务添加后回调
|
||||||
|
const onTaskAdded = (res) => {
|
||||||
|
const addedTask = tasks.value.find(t => t.id === res.task.id);
|
||||||
|
if (addedTask) {
|
||||||
|
// 使用addedTask.assignee去查找assigneeOptions的label进行赋值
|
||||||
|
const assigneeOption = assigneeOptions.value.find(option => option.value === addedTask.assignee);
|
||||||
|
if (assigneeOption) {
|
||||||
|
addedTask.assigneeName = assigneeOption.label;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 使用addedTask.assignee去查找assigneeOptions的label进行赋值
|
||||||
|
const assigneeOption = assigneeOptions.value.find(option => option.value === res.task.assignee);
|
||||||
|
if (assigneeOption) {
|
||||||
|
res.task.assigneeName = assigneeOption.label;
|
||||||
|
}
|
||||||
|
tasks.value.push(res.task);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:milestones="milestones"
|
:milestones="milestones"
|
||||||
:task-list-config="taskListConfig"
|
:task-list-config="taskListConfig"
|
||||||
:toolbar-config="toolbarConfig"
|
:toolbar-config="toolbarConfig"
|
||||||
@@ -182,12 +210,13 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
:locale-messages="customMessages"
|
:locale-messages="customMessages"
|
||||||
:allow-drag-and-resize="true"
|
:allow-drag-and-resize="true"
|
||||||
:enable-task-row-move="true"
|
:enable-task-row-move="true"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
@task-row-moved="handleTaskRowMoved"
|
@task-row-moved="handleTaskRowMoved"
|
||||||
@add-task="showAddTaskDrawer = true"
|
|
||||||
@add-milestone="showAddMilestoneDialog = true"
|
@add-milestone="showAddMilestoneDialog = true"
|
||||||
@task-double-click="onTaskDblclick"
|
@task-double-click="onTaskDblclick"
|
||||||
@task-click="onTaskClick"
|
@task-click="onTaskClick"
|
||||||
@milestone-double-click="onMilestoneDblclick"
|
@milestone-double-click="onMilestoneDblclick"
|
||||||
|
@task-added="onTaskAdded"
|
||||||
>
|
>
|
||||||
</GanttChart>
|
</GanttChart>
|
||||||
</div>
|
</div>
|
||||||
@@ -196,7 +225,7 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
<button class="btn btn-primary" @click="showAddTaskDrawer = true">添加任务</button>
|
<button class="btn btn-primary" @click="showAddTaskDrawer = true">添加任务</button>
|
||||||
<button class="btn btn-primary" @click="showAddMilestoneDialog = true">添加里程碑</button>
|
<button class="btn btn-primary" @click="showAddMilestoneDialog = true">添加里程碑</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 自定义抽屉组件 (原生HTML替代 el-drawer) -->
|
<!-- 自定义抽屉组件 (原生HTML替代 el-drawer) -->
|
||||||
<div v-if="showAddTaskDrawer" class="drawer-overlay" @click="showAddTaskDrawer = false">
|
<div v-if="showAddTaskDrawer" class="drawer-overlay" @click="showAddTaskDrawer = false">
|
||||||
<div class="drawer-container" @click.stop>
|
<div class="drawer-container" @click.stop>
|
||||||
@@ -204,24 +233,24 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
<h3>自定义添加任务组件</h3>
|
<h3>自定义添加任务组件</h3>
|
||||||
<button class="close-btn" @click="showAddTaskDrawer = false">×</button>
|
<button class="close-btn" @click="showAddTaskDrawer = false">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-body">
|
<div class="drawer-body">
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label>任务名称:</label>
|
<label>任务名称:</label>
|
||||||
<input v-model="newTask.name" type="text" placeholder="请输入任务名称" />
|
<input v-model="newTask.name" type="text" placeholder="请输入任务名称" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label>开始日期:</label>
|
<label>开始日期:</label>
|
||||||
<input v-model="newTask.startDate" type="date" />
|
<input v-model="newTask.startDate" type="date" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label>结束日期:</label>
|
<label>结束日期:</label>
|
||||||
<input v-model="newTask.endDate" type="date" />
|
<input v-model="newTask.endDate" type="date" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="drawer-footer">
|
<div class="drawer-footer">
|
||||||
<button class="btn btn-primary" @click="addTask">确定</button>
|
<button class="btn btn-primary" @click="addTask">确定</button>
|
||||||
<button class="btn btn-default" @click="showAddTaskDrawer = false">取消</button>
|
<button class="btn btn-default" @click="showAddTaskDrawer = false">取消</button>
|
||||||
@@ -230,9 +259,9 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 自定义Dialog组件基于element plus -->
|
<!-- 自定义Dialog组件基于element plus -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
title="自定义添加里程碑组件 - Element Plus"
|
title="自定义添加里程碑组件 - Element Plus"
|
||||||
v-model="showAddMilestoneDialog"
|
v-model="showAddMilestoneDialog"
|
||||||
width="400px"
|
width="400px"
|
||||||
@close="newTask = { name: '', startDate: '', endDate: '' }"
|
@close="newTask = { name: '', startDate: '', endDate: '' }"
|
||||||
>
|
>
|
||||||
@@ -252,7 +281,7 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
<el-button @click="addMilestone">确定</el-button>
|
<el-button @click="addMilestone">确定</el-button>
|
||||||
<el-button @click="showAddMilestoneDialog = false">取消</el-button>
|
<el-button @click="showAddMilestoneDialog = false">取消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -403,4 +432,4 @@ const handleTaskRowMoved = async (payload: {
|
|||||||
from { transform: translateX(100%); }
|
from { transform: translateX(100%); }
|
||||||
to { transform: translateX(0); }
|
to { transform: translateX(0); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+211
-148
@@ -1,150 +1,3 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { GanttChart } from 'jordium-gantt-vue3'
|
|
||||||
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
|
|
||||||
|
|
||||||
// 定义类型接口
|
|
||||||
interface TaskListColumnConfig {
|
|
||||||
key: string;
|
|
||||||
label: string;
|
|
||||||
visible: boolean;
|
|
||||||
width?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ToolbarConfig {
|
|
||||||
showAddTask?: boolean;
|
|
||||||
showAddMilestone?: boolean;
|
|
||||||
showTodayLocate?: boolean;
|
|
||||||
showExportCsv?: boolean;
|
|
||||||
showExportPdf?: boolean;
|
|
||||||
showLanguage?: boolean;
|
|
||||||
showTheme?: boolean;
|
|
||||||
showFullscreen?: boolean;
|
|
||||||
showTimeScale?: boolean;
|
|
||||||
timeScaleDimensions?: string[];
|
|
||||||
defaultTimeScale?: string;
|
|
||||||
showExpandCollapse?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tasks = ref([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: '项目启动',
|
|
||||||
startDate: '2025-10-30',
|
|
||||||
endDate: '2025-11-5',
|
|
||||||
progress: 100,
|
|
||||||
department: '管理部',
|
|
||||||
departmentCode: 'D001',
|
|
||||||
type: 'task',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const milestones = ref([
|
|
||||||
{
|
|
||||||
id: 101,
|
|
||||||
name: '项目立项',
|
|
||||||
startDate: '2025-10-29',
|
|
||||||
type: 'milestone',
|
|
||||||
icon: 'diamond',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const customMessages = {
|
|
||||||
'zh-CN': {
|
|
||||||
department: '部门',
|
|
||||||
departmentCode: '部门编号',
|
|
||||||
},
|
|
||||||
'en-US': {
|
|
||||||
department: 'Department',
|
|
||||||
departmentCode: 'Department Code',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// const tasks = ref([])
|
|
||||||
|
|
||||||
// const milestones = ref([])
|
|
||||||
const showAddTaskDrawer = ref(false)
|
|
||||||
const showAddMilestoneDialog = ref(false)
|
|
||||||
|
|
||||||
// 定义可动态配置的列
|
|
||||||
const availableColumns = ref<TaskListColumnConfig[]>([
|
|
||||||
{ key: 'startDate', label: '开始日期', visible: true },
|
|
||||||
{ key: 'endDate', label: '结束日期', visible: true },
|
|
||||||
{ key: 'progress', label: '进度', visible: true },
|
|
||||||
{ key: 'department', label: '部门', visible: true, width: 120 },
|
|
||||||
{ key: 'departmentCode', label: '部门编号', visible: true },
|
|
||||||
])
|
|
||||||
|
|
||||||
// TaskList宽度配置示例
|
|
||||||
const taskListConfig = {
|
|
||||||
defaultWidth: '50%', // 默认展开宽度50%
|
|
||||||
minWidth: '300px', // 最小宽度300px(默认280px)
|
|
||||||
maxWidth: '1200px', // 最大宽度1200px(默认1160px)
|
|
||||||
columns: availableColumns.value,
|
|
||||||
}
|
|
||||||
|
|
||||||
// toolbar配置示例
|
|
||||||
const toolbarConfig: ToolbarConfig = {
|
|
||||||
showAddTask: true, // 显示添加任务按钮
|
|
||||||
showAddMilestone: true, // 显示添加里程碑按钮
|
|
||||||
showTodayLocate: true, // 显示定位到今天按钮
|
|
||||||
showExportCsv: true, // 显示导出CSV按钮
|
|
||||||
showExportPdf: true, // 显示导出PDF按钮
|
|
||||||
showLanguage: true, // 显示语言切换按钮
|
|
||||||
showTheme: true, // 显示主题切换按钮
|
|
||||||
showFullscreen: true, // 显示全屏按钮
|
|
||||||
showTimeScale: true, // 显示时间刻度按钮组
|
|
||||||
timeScaleDimensions: [ // 显示所有时间刻度维度
|
|
||||||
'hour', 'day', 'week', 'month', 'quarter', 'year',
|
|
||||||
],
|
|
||||||
defaultTimeScale: 'week', // 默认选中周视图
|
|
||||||
showExpandCollapse: false, // 显示展开/折叠按钮
|
|
||||||
}
|
|
||||||
|
|
||||||
const newTask = ref({
|
|
||||||
name: '',
|
|
||||||
startDate: '',
|
|
||||||
endDate: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const addTask = () => {
|
|
||||||
tasks.value.push({
|
|
||||||
id: tasks.value.length + 1,
|
|
||||||
name: newTask.value.name,
|
|
||||||
startDate: newTask.value.startDate,
|
|
||||||
endDate: newTask.value.endDate,
|
|
||||||
progress: 0,
|
|
||||||
department: '未分配',
|
|
||||||
departmentCode: 'D000',
|
|
||||||
type: 'task',
|
|
||||||
})
|
|
||||||
newTask.value = { name: '', startDate: '', endDate: '' }
|
|
||||||
showAddTaskDrawer.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const addMilestone = () => {
|
|
||||||
milestones.value.push({
|
|
||||||
id: milestones.value.length + 101,
|
|
||||||
name: newTask.value.name,
|
|
||||||
startDate: newTask.value.startDate,
|
|
||||||
type: 'milestone',
|
|
||||||
icon: 'diamond',
|
|
||||||
})
|
|
||||||
console.log('milestones: ', milestones.value)
|
|
||||||
newTask.value = { name: '', startDate: '', endDate: '' }
|
|
||||||
showAddMilestoneDialog.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTaskDblclick = (task: any) => {
|
|
||||||
alert(`双击任务: ${task.name}`)
|
|
||||||
}
|
|
||||||
const onTaskClick = (task: any) => {
|
|
||||||
alert(`单击任务: ${task.name}`)
|
|
||||||
}
|
|
||||||
const onMilestoneDblclick = (milestone: any) => {
|
|
||||||
alert(`双击里程碑: ${milestone.name}`)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div style="height: 600px;">
|
<div style="height: 600px;">
|
||||||
@@ -157,11 +10,15 @@ const onMilestoneDblclick = (milestone: any) => {
|
|||||||
:use-default-milestone-dialog="false"
|
:use-default-milestone-dialog="false"
|
||||||
:locale-messages="customMessages"
|
:locale-messages="customMessages"
|
||||||
:allow-drag-and-resize="true"
|
:allow-drag-and-resize="true"
|
||||||
|
:enable-task-row-move="true"
|
||||||
|
:assignee-options="assigneeOptions"
|
||||||
|
@task-row-moved="handleTaskRowMoved"
|
||||||
@add-task="showAddTaskDrawer = true"
|
@add-task="showAddTaskDrawer = true"
|
||||||
@add-milestone="showAddMilestoneDialog = true"
|
@add-milestone="showAddMilestoneDialog = true"
|
||||||
@task-double-click="onTaskDblclick"
|
@task-double-click="onTaskDblclick"
|
||||||
@task-click="onTaskClick"
|
@task-click="onTaskClick"
|
||||||
@milestone-double-click="onMilestoneDblclick"
|
@milestone-double-click="onMilestoneDblclick"
|
||||||
|
@task-added="onTaskAdded"
|
||||||
>
|
>
|
||||||
</GanttChart>
|
</GanttChart>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,8 +62,8 @@ const onMilestoneDblclick = (milestone: any) => {
|
|||||||
|
|
||||||
<!-- 自定义Dialog组件基于element plus -->
|
<!-- 自定义Dialog组件基于element plus -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showAddMilestoneDialog"
|
|
||||||
title="自定义添加里程碑组件 - Element Plus"
|
title="自定义添加里程碑组件 - Element Plus"
|
||||||
|
v-model="showAddMilestoneDialog"
|
||||||
width="400px"
|
width="400px"
|
||||||
@close="newTask = { name: '', startDate: '', endDate: '' }"
|
@close="newTask = { name: '', startDate: '', endDate: '' }"
|
||||||
>
|
>
|
||||||
@@ -230,6 +87,212 @@ const onMilestoneDblclick = (milestone: any) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { GanttChart, Task } from 'jordium-gantt-vue3'
|
||||||
|
import 'jordium-gantt-vue3/dist/assets/jordium-gantt-vue3.css'
|
||||||
|
|
||||||
|
// 定义类型接口
|
||||||
|
interface TaskListColumnConfig {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
visible: boolean;
|
||||||
|
width?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolbarConfig {
|
||||||
|
showAddTask?: boolean;
|
||||||
|
showAddMilestone?: boolean;
|
||||||
|
showTodayLocate?: boolean;
|
||||||
|
showExportCsv?: boolean;
|
||||||
|
showExportPdf?: boolean;
|
||||||
|
showLanguage?: boolean;
|
||||||
|
showTheme?: boolean;
|
||||||
|
showFullscreen?: boolean;
|
||||||
|
showTimeScale?: boolean;
|
||||||
|
timeScaleDimensions?: string[];
|
||||||
|
defaultTimeScale?: string;
|
||||||
|
showExpandCollapse?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tasks = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '项目启动',
|
||||||
|
startDate: '2025-10-30',
|
||||||
|
endDate: '2025-11-5',
|
||||||
|
progress: 100,
|
||||||
|
department: '管理部',
|
||||||
|
departmentCode: 'D001',
|
||||||
|
type: 'task',
|
||||||
|
assignee: '',
|
||||||
|
assigneeName: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const milestones = ref([
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
name: '项目立项',
|
||||||
|
startDate: '2025-10-29',
|
||||||
|
type: 'milestone',
|
||||||
|
icon: 'diamond'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const customMessages = {
|
||||||
|
'zh-CN': {
|
||||||
|
department: '部门',
|
||||||
|
departmentCode: '部门编号',
|
||||||
|
},
|
||||||
|
'en-US': {
|
||||||
|
department: 'Department',
|
||||||
|
departmentCode: 'Department Code',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const tasks = ref([])
|
||||||
|
|
||||||
|
// const milestones = ref([])
|
||||||
|
const showAddTaskDrawer = ref(false);
|
||||||
|
const showAddMilestoneDialog = ref(false);
|
||||||
|
|
||||||
|
// 定义可动态配置的列
|
||||||
|
const availableColumns = ref<TaskListColumnConfig[]>([
|
||||||
|
{ key: 'startDate', label: '开始日期', visible: true },
|
||||||
|
{ key: 'endDate', label: '结束日期', visible: true },
|
||||||
|
{ key: 'progress', label: '进度', visible: true },
|
||||||
|
{ key: 'department', label: '部门', visible: true, width: 120 },
|
||||||
|
{ key: 'departmentCode', label: '部门编号', visible: true },
|
||||||
|
{ key: 'assigneeName', label: '负责人', visible: true },
|
||||||
|
])
|
||||||
|
|
||||||
|
// 自定义负责人列表
|
||||||
|
const assigneeOptions = ref([
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
])
|
||||||
|
|
||||||
|
// TaskList宽度配置示例
|
||||||
|
const taskListConfig = {
|
||||||
|
defaultWidth: '50%', // 默认展开宽度50%
|
||||||
|
minWidth: '300px', // 最小宽度300px(默认280px)
|
||||||
|
maxWidth: '1200px', // 最大宽度1200px(默认1160px)
|
||||||
|
columns: availableColumns.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// toolbar配置示例
|
||||||
|
const toolbarConfig: ToolbarConfig = {
|
||||||
|
showAddTask: true, // 显示添加任务按钮
|
||||||
|
showAddMilestone: true, // 显示添加里程碑按钮
|
||||||
|
showTodayLocate: true, // 显示定位到今天按钮
|
||||||
|
showExportCsv: true, // 显示导出CSV按钮
|
||||||
|
showExportPdf: true, // 显示导出PDF按钮
|
||||||
|
showLanguage: true, // 显示语言切换按钮
|
||||||
|
showTheme: true, // 显示主题切换按钮
|
||||||
|
showFullscreen: true, // 显示全屏按钮
|
||||||
|
showTimeScale: true, // 显示时间刻度按钮组
|
||||||
|
timeScaleDimensions: [ // 显示所有时间刻度维度
|
||||||
|
'hour', 'day', 'week', 'month', 'quarter', 'year'
|
||||||
|
],
|
||||||
|
defaultTimeScale: 'week', // 默认选中周视图
|
||||||
|
showExpandCollapse: false // 显示展开/折叠按钮
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const newTask = ref({
|
||||||
|
name: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const addTask = () => {
|
||||||
|
tasks.value.push({
|
||||||
|
id: tasks.value.length + 1,
|
||||||
|
name: newTask.value.name,
|
||||||
|
startDate: newTask.value.startDate,
|
||||||
|
endDate: newTask.value.endDate,
|
||||||
|
progress: 0,
|
||||||
|
department: '未分配',
|
||||||
|
departmentCode: 'D000',
|
||||||
|
assignee: '',
|
||||||
|
assigneeName: '',
|
||||||
|
type: 'task',
|
||||||
|
});
|
||||||
|
newTask.value = { name: '', startDate: '', endDate: '' };
|
||||||
|
showAddTaskDrawer.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addMilestone = () => {
|
||||||
|
milestones.value.push({
|
||||||
|
id: milestones.value.length + 101,
|
||||||
|
name: newTask.value.name,
|
||||||
|
startDate: newTask.value.startDate,
|
||||||
|
type: 'milestone',
|
||||||
|
icon: 'diamond'
|
||||||
|
});
|
||||||
|
console.log('milestones: ', milestones.value)
|
||||||
|
newTask.value = { name: '', startDate: '', endDate: '' };
|
||||||
|
showAddMilestoneDialog.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTaskDblclick = (task: any) => {
|
||||||
|
alert(`双击任务: ${task.name}`)
|
||||||
|
}
|
||||||
|
const onTaskClick = (task: any) => {
|
||||||
|
alert(`单击任务: ${task.name}`)
|
||||||
|
}
|
||||||
|
const onMilestoneDblclick = (milestone: any) => {
|
||||||
|
alert(`双击里程碑: ${milestone.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务行拖拽完成事件
|
||||||
|
const handleTaskRowMoved = (payload: {
|
||||||
|
draggedTask: Task
|
||||||
|
targetTask: Task
|
||||||
|
position: 'after' | 'child'
|
||||||
|
}) => {
|
||||||
|
const { draggedTask, targetTask, position } = payload
|
||||||
|
|
||||||
|
alert(`任务 [${draggedTask.name}] 被拖拽到任务 [${targetTask.name}] ${position === 'after' ? '之后' : '下方作为子任务'}`)
|
||||||
|
|
||||||
|
// 组件已自动更新任务的层级关系和位置
|
||||||
|
// position === 'after': 任务被放置在目标任务之后(同级)
|
||||||
|
// position === 'child': 任务被放置为目标任务的子任务(第一个子任务位置)
|
||||||
|
|
||||||
|
// 这里可以:
|
||||||
|
// 1. 显示确认对话框,让用户确认是否移动
|
||||||
|
// 2. 调用后端 API 保存新的任务层级关系
|
||||||
|
// 3. 更新相关的依赖关系
|
||||||
|
|
||||||
|
// 示例:调用后端 API
|
||||||
|
// await api.updateTaskHierarchy({
|
||||||
|
// taskId: draggedTask.id,
|
||||||
|
// targetTaskId: targetTask.id,
|
||||||
|
// position: position
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务添加后回调
|
||||||
|
const onTaskAdded = (res) => {
|
||||||
|
const addedTask = tasks.value.find(t => t.id === res.task.id);
|
||||||
|
if (addedTask) {
|
||||||
|
// 使用addedTask.assignee去查找assigneeOptions的label进行赋值
|
||||||
|
const assigneeOption = assigneeOptions.value.find(option => option.value === addedTask.assignee);
|
||||||
|
if (assigneeOption) {
|
||||||
|
addedTask.assigneeName = assigneeOption.label;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 使用addedTask.assignee去查找assigneeOptions的label进行赋值
|
||||||
|
const assigneeOption = assigneeOptions.value.find(option => option.value === res.task.assignee);
|
||||||
|
if (assigneeOption) {
|
||||||
|
res.task.assigneeName = assigneeOption.label;
|
||||||
|
}
|
||||||
|
tasks.value.push(res.task);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 抽屉遮罩层 */
|
/* 抽屉遮罩层 */
|
||||||
.drawer-overlay {
|
.drawer-overlay {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jordium-gantt-vue3",
|
"name": "jordium-gantt-vue3",
|
||||||
"version": "1.4.5",
|
"version": "1.4.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/jordium-gantt-vue3.cjs.js",
|
"main": "dist/jordium-gantt-vue3.cjs.js",
|
||||||
"module": "dist/jordium-gantt-vue3.es.js",
|
"module": "dist/jordium-gantt-vue3.es.js",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
autoSortByStartDate: false,
|
autoSortByStartDate: false,
|
||||||
allowDragAndResize: true,
|
allowDragAndResize: true,
|
||||||
enableTaskRowMove: false,
|
enableTaskRowMove: false,
|
||||||
|
assigneeOptions: () => [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
@@ -128,6 +129,10 @@ interface Props {
|
|||||||
allowDragAndResize?: boolean
|
allowDragAndResize?: boolean
|
||||||
// 是否启用TaskRow拖拽移动功能(默认为 false)
|
// 是否启用TaskRow拖拽移动功能(默认为 false)
|
||||||
enableTaskRowMove?: boolean
|
enableTaskRowMove?: boolean
|
||||||
|
// 指派人员选项列表(用于TaskDrawer中的assignee下拉菜单)
|
||||||
|
// 格式:{ key?: string | number, value: string | number, label: string }
|
||||||
|
// key 为可选项,若不存在则使用 value 作为选项的唯一标识
|
||||||
|
assigneeOptions?: Array<{ key?: string | number; value: string | number; label: string }>
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskList的固定总长度(所有列的最小宽度之和 + 边框等额外空间)
|
// TaskList的固定总长度(所有列的最小宽度之和 + 边框等额外空间)
|
||||||
@@ -2328,6 +2333,7 @@ function handleMilestoneDialogDelete(milestoneId: number) {
|
|||||||
v-model:visible="taskDrawerVisible"
|
v-model:visible="taskDrawerVisible"
|
||||||
:task="taskDrawerTask"
|
:task="taskDrawerTask"
|
||||||
:is-edit="taskDrawerEditMode"
|
:is-edit="taskDrawerEditMode"
|
||||||
|
:assignee-options="props.assigneeOptions"
|
||||||
@submit="handleTaskDrawerSubmit"
|
@submit="handleTaskDrawerSubmit"
|
||||||
@close="taskDrawerVisible = false"
|
@close="taskDrawerVisible = false"
|
||||||
@start-timer="handleStartTimer"
|
@start-timer="handleStartTimer"
|
||||||
|
|||||||
+48
-15
@@ -243,7 +243,26 @@ const taskBarStyle = computed(() => {
|
|||||||
const startDate = createLocalDate(currentStartDate)
|
const startDate = createLocalDate(currentStartDate)
|
||||||
const endDate = createLocalDate(currentEndDate)
|
const endDate = createLocalDate(currentEndDate)
|
||||||
const baseStart = parsedBaseStartDate.value
|
const baseStart = parsedBaseStartDate.value
|
||||||
if (!startDate || !endDate || !baseStart) {
|
|
||||||
|
// 如果startDate和endDate都不存在,返回0宽度(实际不会渲染,由shouldRenderTaskBar控制)
|
||||||
|
if (!startDate && !endDate) {
|
||||||
|
return {
|
||||||
|
left: '0px',
|
||||||
|
width: '0px',
|
||||||
|
height: `${props.rowHeight - 10}px`,
|
||||||
|
top: '4px',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染时使用有效日期:如果只有startDate或endDate,用它来计算开始和结束位置
|
||||||
|
// 注意:不修改原始的startDate/endDate变量,只用于渲染计算
|
||||||
|
const renderStartDate = startDate || endDate
|
||||||
|
const renderEndDate = endDate || startDate
|
||||||
|
const renderBaseStart = baseStart
|
||||||
|
|
||||||
|
// 安全检查:renderStartDate和renderEndDate必定存在(因为上面已经检查过)
|
||||||
|
// 但baseStart可能不存在,如果不存在则无法计算位置
|
||||||
|
if (!renderStartDate || !renderEndDate || !renderBaseStart) {
|
||||||
return {
|
return {
|
||||||
left: '0px',
|
left: '0px',
|
||||||
width: '0px',
|
width: '0px',
|
||||||
@@ -258,12 +277,12 @@ const taskBarStyle = computed(() => {
|
|||||||
// 小时视图:按分钟精确计算位置(需要考虑时间部分)
|
// 小时视图:按分钟精确计算位置(需要考虑时间部分)
|
||||||
if (props.currentTimeScale === TimelineScale.HOUR) {
|
if (props.currentTimeScale === TimelineScale.HOUR) {
|
||||||
// 确保 baseStart 是当天的 00:00:00
|
// 确保 baseStart 是当天的 00:00:00
|
||||||
const baseStartOfDay = new Date(baseStart)
|
const baseStartOfDay = new Date(renderBaseStart)
|
||||||
baseStartOfDay.setHours(0, 0, 0, 0)
|
baseStartOfDay.setHours(0, 0, 0, 0)
|
||||||
|
|
||||||
// 处理没有时间部分的日期字符串
|
// 处理没有时间部分的日期字符串
|
||||||
let adjustedStartDate = startDate
|
let adjustedStartDate = renderStartDate
|
||||||
let adjustedEndDate = endDate
|
let adjustedEndDate = renderEndDate
|
||||||
|
|
||||||
// 检查原始日期字符串是否包含时间部分
|
// 检查原始日期字符串是否包含时间部分
|
||||||
const originalStartStr = currentStartDate || props.task.startDate
|
const originalStartStr = currentStartDate || props.task.startDate
|
||||||
@@ -274,13 +293,13 @@ const taskBarStyle = computed(() => {
|
|||||||
typeof originalStartStr === 'string' &&
|
typeof originalStartStr === 'string' &&
|
||||||
/^\d{4}-\d{2}-\d{2}$/.test(originalStartStr.trim())
|
/^\d{4}-\d{2}-\d{2}$/.test(originalStartStr.trim())
|
||||||
) {
|
) {
|
||||||
adjustedStartDate = new Date(startDate)
|
adjustedStartDate = new Date(renderStartDate)
|
||||||
adjustedStartDate.setHours(0, 0, 0, 0)
|
adjustedStartDate.setHours(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果endDate没有时间部分(格式为YYYY-MM-DD),设置为次日00:00
|
// 如果endDate没有时间部分(格式为YYYY-MM-DD),设置为次日00:00
|
||||||
if (typeof originalEndStr === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(originalEndStr.trim())) {
|
if (typeof originalEndStr === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(originalEndStr.trim())) {
|
||||||
adjustedEndDate = new Date(endDate)
|
adjustedEndDate = new Date(renderEndDate)
|
||||||
adjustedEndDate.setDate(adjustedEndDate.getDate() + 1)
|
adjustedEndDate.setDate(adjustedEndDate.getDate() + 1)
|
||||||
adjustedEndDate.setHours(0, 0, 0, 0)
|
adjustedEndDate.setHours(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
@@ -299,15 +318,19 @@ const taskBarStyle = computed(() => {
|
|||||||
|
|
||||||
// 将日期标准化为当天的00:00:00,忽略时间部分
|
// 将日期标准化为当天的00:00:00,忽略时间部分
|
||||||
const startDateOnly = new Date(
|
const startDateOnly = new Date(
|
||||||
startDate.getFullYear(),
|
renderStartDate.getFullYear(),
|
||||||
startDate.getMonth(),
|
renderStartDate.getMonth(),
|
||||||
startDate.getDate(),
|
renderStartDate.getDate(),
|
||||||
|
)
|
||||||
|
const endDateOnly = new Date(
|
||||||
|
renderEndDate.getFullYear(),
|
||||||
|
renderEndDate.getMonth(),
|
||||||
|
renderEndDate.getDate(),
|
||||||
)
|
)
|
||||||
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate())
|
|
||||||
const baseStartOnly = new Date(
|
const baseStartOnly = new Date(
|
||||||
baseStart.getFullYear(),
|
renderBaseStart.getFullYear(),
|
||||||
baseStart.getMonth(),
|
renderBaseStart.getMonth(),
|
||||||
baseStart.getDate(),
|
renderBaseStart.getDate(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -429,6 +452,15 @@ const parsedEndDate = computed(() => createLocalDate(props.task.endDate || ''))
|
|||||||
// 缓存解析后的基准开始日期
|
// 缓存解析后的基准开始日期
|
||||||
const parsedBaseStartDate = computed(() => createLocalDate(props.startDate))
|
const parsedBaseStartDate = computed(() => createLocalDate(props.startDate))
|
||||||
|
|
||||||
|
// 判断是否应该渲染TaskBar:只考虑startDate和endDate,都不存在时不渲染
|
||||||
|
const shouldRenderTaskBar = computed(() => {
|
||||||
|
const currentStartDate = tempTaskData.value?.startDate || props.task.startDate
|
||||||
|
const currentEndDate = tempTaskData.value?.endDate || props.task.endDate
|
||||||
|
|
||||||
|
// 只要startDate或endDate有一个存在就渲染
|
||||||
|
return !!(currentStartDate || currentEndDate)
|
||||||
|
})
|
||||||
|
|
||||||
// 计算任务状态和颜色
|
// 计算任务状态和颜色
|
||||||
const taskStatus = computed(() => {
|
const taskStatus = computed(() => {
|
||||||
// 父级任务(Story类型)使用与新建按钮一致的配色
|
// 父级任务(Story类型)使用与新建按钮一致的配色
|
||||||
@@ -2240,6 +2272,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
v-if="shouldRenderTaskBar"
|
||||||
ref="barRef"
|
ref="barRef"
|
||||||
class="task-bar"
|
class="task-bar"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -2336,8 +2369,8 @@ onUnmounted(() => {
|
|||||||
<!-- 图片头像 -->
|
<!-- 图片头像 -->
|
||||||
<img v-if="task.avatar" :src="task.avatar" :alt="task.assignee || 'avatar'" />
|
<img v-if="task.avatar" :src="task.avatar" :alt="task.assignee || 'avatar'" />
|
||||||
<!-- 文字头像(负责人首字母) -->
|
<!-- 文字头像(负责人首字母) -->
|
||||||
<span v-else-if="task.assignee" class="avatar-text">
|
<span v-else-if="task.assigneeName" class="avatar-text">
|
||||||
{{ task.assignee.charAt(0).toUpperCase() }}
|
{{ task.assigneeName.charAt(0).toUpperCase() }}
|
||||||
</span>
|
</span>
|
||||||
<!-- 默认灰色用户图标 -->
|
<!-- 默认灰色用户图标 -->
|
||||||
<svg v-else class="avatar-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg v-else class="avatar-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -9,11 +9,18 @@ import ConfirmTimerDialog from './ConfirmTimerDialog.vue'
|
|||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
import '../styles/app.css'
|
import '../styles/app.css'
|
||||||
|
|
||||||
|
interface AssigneeOption {
|
||||||
|
key?: string | number
|
||||||
|
value: string | number
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
task?: Task | null
|
task?: Task | null
|
||||||
isEdit?: boolean
|
isEdit?: boolean
|
||||||
onDelete?: (task: Task) => void
|
onDelete?: (task: Task) => void
|
||||||
|
assigneeOptions?: AssigneeOption[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@@ -21,6 +28,13 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
task: null,
|
task: null,
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
onDelete: undefined,
|
onDelete: undefined,
|
||||||
|
assigneeOptions: () => [
|
||||||
|
{ value: 'zhangsan', label: '张三' },
|
||||||
|
{ value: 'lisi', label: '李四' },
|
||||||
|
{ value: 'wangwu', label: '王五' },
|
||||||
|
{ value: 'zhaoliu', label: '赵六' },
|
||||||
|
{ value: 'qianqi', label: '钱七' },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -628,6 +642,17 @@ function confirmTimer(desc: string) {
|
|||||||
// desc 可用于后续业务
|
// desc 可用于后续业务
|
||||||
handleStartTimer(desc)
|
handleStartTimer(desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理负责人变更
|
||||||
|
const handleAssigneeChanged = (value: string) => {
|
||||||
|
// 通过value过滤props.assigneeOptions获取对应的label
|
||||||
|
const selected = props.assigneeOptions?.find(option => option.value === value)
|
||||||
|
if (selected) {
|
||||||
|
// 这里可以根据需要处理选中的负责人信息
|
||||||
|
// 例如,可以将负责人名称存储在formData中
|
||||||
|
formData.assigneeName = selected.label
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -788,13 +813,15 @@ function confirmTimer(desc: string) {
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label" for="task-assignee">{{ t.assignee }}</label>
|
<label class="form-label" for="task-assignee">{{ t.assignee }}</label>
|
||||||
<select id="task-assignee" v-model="formData.assignee" class="form-select">
|
<select id="task-assignee" v-model="formData.assignee" class="form-select" @change="handleAssigneeChanged">
|
||||||
<option value="">{{ t.selectAssignee }}</option>
|
<option value="">{{ t.selectAssignee }}</option>
|
||||||
<option value="张三">张三</option>
|
<option
|
||||||
<option value="李四">李四</option>
|
v-for="assignee in props.assigneeOptions"
|
||||||
<option value="王五">王五</option>
|
:key="assignee.key ?? assignee.value"
|
||||||
<option value="赵六">赵六</option>
|
:value="assignee.value"
|
||||||
<option value="钱七">钱七</option>
|
>
|
||||||
|
{{ assignee.label }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export interface Task {
|
|||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
predecessor?: number[] // 前置任务ID数组
|
predecessor?: number[] // 前置任务ID数组
|
||||||
assignee?: string
|
assignee?: string // 记录唯一键值,如用户ID或用户名
|
||||||
|
assigneeName?: string // 任务负责人名称
|
||||||
avatar?: string // 任务负责人头像URL
|
avatar?: string // 任务负责人头像URL
|
||||||
startDate?: string
|
startDate?: string
|
||||||
endDate?: string
|
endDate?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user