支持配置左侧table 列显示
This commit is contained in:
153
demo/App.vue
153
demo/App.vue
@@ -13,6 +13,7 @@ import { useMessage } from '../src/composables/useMessage'
|
|||||||
import { useI18n } from '../src/composables/useI18n'
|
import { useI18n } from '../src/composables/useI18n'
|
||||||
import { getPredecessorIds, predecessorIdsToString } from '../src/utils/predecessorUtils'
|
import { getPredecessorIds, predecessorIdsToString } from '../src/utils/predecessorUtils'
|
||||||
import type { Task } from '../src/models/Task'
|
import type { Task } from '../src/models/Task'
|
||||||
|
import type { TaskListConfig, TaskListColumnConfig } from '../src/models/configs/TaskListConfig'
|
||||||
|
|
||||||
const { showMessage } = useMessage()
|
const { showMessage } = useMessage()
|
||||||
const { t, formatTranslation } = useI18n()
|
const { t, formatTranslation } = useI18n()
|
||||||
@@ -41,6 +42,36 @@ const toolbarConfig = {
|
|||||||
timeScaleDimensions: ['hour', 'day', 'week', 'month', 'quarter', 'year'], // 设置时间刻度按钮的展示维度,包含所有时间维度
|
timeScaleDimensions: ['hour', 'day', 'week', 'month', 'quarter', 'year'], // 设置时间刻度按钮的展示维度,包含所有时间维度
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TaskList列配置
|
||||||
|
const availableColumns = ref<TaskListColumnConfig[]>([
|
||||||
|
{ key: 'predecessor', label: '前置任务', visible: true },
|
||||||
|
{ key: 'assignee', label: '负责人', visible: true },
|
||||||
|
{ key: 'startDate', label: '开始日期', visible: true },
|
||||||
|
{ key: 'endDate', label: '结束日期', visible: true },
|
||||||
|
{ key: 'estimatedHours', label: '预估工时', visible: true },
|
||||||
|
{ key: 'actualHours', label: '实际工时', visible: true },
|
||||||
|
{ key: 'progress', label: '进度', visible: true },
|
||||||
|
])
|
||||||
|
|
||||||
|
const taskListConfig = ref<TaskListConfig>({
|
||||||
|
columns: availableColumns.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换列显示状态
|
||||||
|
const toggleColumn = (columnKey: string, event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
const visible = target?.checked ?? false
|
||||||
|
|
||||||
|
const column = availableColumns.value.find(col => col.key === columnKey)
|
||||||
|
if (column) {
|
||||||
|
column.visible = visible
|
||||||
|
// 更新taskListConfig以触发响应式更新
|
||||||
|
taskListConfig.value = {
|
||||||
|
columns: [...availableColumns.value],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 工作时间配置示例
|
// 工作时间配置示例
|
||||||
const workingHoursConfig = {
|
const workingHoursConfig = {
|
||||||
morning: { start: 8, end: 11 }, // 上午8:00-11:59为工作时间
|
morning: { start: 8, end: 11 }, // 上午8:00-11:59为工作时间
|
||||||
@@ -632,11 +663,34 @@ function onTimerStopped(task: Task) {
|
|||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
<VersionHistoryDrawer :visible="showVersionDrawer" @close="showVersionDrawer = false" />
|
<VersionHistoryDrawer :visible="showVersionDrawer" @close="showVersionDrawer = false" />
|
||||||
|
|
||||||
|
<!-- TaskList列配置面板 -->
|
||||||
|
<div class="config-panel">
|
||||||
|
<h3 class="config-title">
|
||||||
|
<svg class="config-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 15l3-3H9l3 3z" fill="currentColor"/>
|
||||||
|
<path d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
TaskList 列配置
|
||||||
|
</h3>
|
||||||
|
<div class="column-controls">
|
||||||
|
<label v-for="column in availableColumns" :key="column.key" class="column-control">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="column.visible"
|
||||||
|
@change="toggleColumn(column.key, $event)"
|
||||||
|
/>
|
||||||
|
<span class="column-label">{{ column.label }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="gantt-wrapper">
|
<div class="gantt-wrapper">
|
||||||
<GanttChart
|
<GanttChart
|
||||||
:tasks="tasks"
|
:tasks="tasks"
|
||||||
:milestones="milestones"
|
:milestones="milestones"
|
||||||
:toolbar-config="toolbarConfig"
|
:toolbar-config="toolbarConfig"
|
||||||
|
:task-list-config="taskListConfig"
|
||||||
:working-hours="workingHoursConfig"
|
:working-hours="workingHoursConfig"
|
||||||
:on-add-task="handleAddTask"
|
:on-add-task="handleAddTask"
|
||||||
:on-add-milestone="handleAddMilestone"
|
:on-add-milestone="handleAddMilestone"
|
||||||
@@ -700,6 +754,79 @@ function onTimerStopped(task: Task) {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TaskList列配置面板样式 */
|
||||||
|
.config-panel {
|
||||||
|
background: var(--gantt-bg-primary, #ffffff);
|
||||||
|
border: 1px solid var(--gantt-border-color, #e4e7ed);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-panel:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-title {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gantt-text-primary, #333);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: var(--gantt-primary-color, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: var(--gantt-bg-secondary, #f8f9fa);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-control:hover {
|
||||||
|
background: var(--gantt-hover-bg, #e8f4fd);
|
||||||
|
border-color: var(--gantt-primary-color, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-control input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--gantt-primary-color, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--gantt-text-primary, #333);
|
||||||
|
user-select: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-control:hover .column-label {
|
||||||
|
color: var(--gantt-primary-color, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
@@ -821,6 +948,32 @@ function onTimerStopped(task: Task) {
|
|||||||
color: #e5e5e5 !important;
|
color: #e5e5e5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 暗色主题下的配置面板样式 */
|
||||||
|
:global(html[data-theme='dark']) .config-panel {
|
||||||
|
background: var(--gantt-bg-primary, #2d3748);
|
||||||
|
border-color: var(--gantt-border-color, #4a5568);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .config-title {
|
||||||
|
color: var(--gantt-text-primary, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .column-control {
|
||||||
|
background: var(--gantt-bg-secondary, #1a202c);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .column-control:hover {
|
||||||
|
background: var(--gantt-hover-bg, #2d3748);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .column-label {
|
||||||
|
color: var(--gantt-text-primary, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .column-control:hover .column-label {
|
||||||
|
color: var(--gantt-primary-color, #66b3ff);
|
||||||
|
}
|
||||||
|
|
||||||
/* 暗黑模式下的版本标签 */
|
/* 暗黑模式下的版本标签 */
|
||||||
:global(html[data-theme='dark']) .version-badge {
|
:global(html[data-theme='dark']) .version-badge {
|
||||||
background: linear-gradient(135deg, #1a73e8 0%, #00bcd4 50%, #3f51b5 100%);
|
background: linear-gradient(135deg, #1a73e8 0%, #00bcd4 50%, #3f51b5 100%);
|
||||||
|
|||||||
239
demo/TaskListConfigDemo.vue
Normal file
239
demo/TaskListConfigDemo.vue
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { GanttChart } from '../src/index'
|
||||||
|
import type { Task } from '../src/models/classes/Task'
|
||||||
|
import type { TaskListConfig, TaskListColumnConfig } from '../src/models/configs/TaskListConfig'
|
||||||
|
|
||||||
|
// 示例任务数据
|
||||||
|
const tasks = ref<Task[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '项目启动',
|
||||||
|
startDate: '2024-01-15',
|
||||||
|
endDate: '2024-01-20',
|
||||||
|
progress: 100,
|
||||||
|
assignee: '张三',
|
||||||
|
estimatedHours: 40,
|
||||||
|
actualHours: 35,
|
||||||
|
predecessor: null,
|
||||||
|
type: 'task',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '需求分析',
|
||||||
|
startDate: '2024-01-21',
|
||||||
|
endDate: '2024-01-30',
|
||||||
|
progress: 80,
|
||||||
|
assignee: '李四',
|
||||||
|
estimatedHours: 80,
|
||||||
|
actualHours: 75,
|
||||||
|
predecessor: [1],
|
||||||
|
type: 'task',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '系统设计',
|
||||||
|
startDate: '2024-02-01',
|
||||||
|
endDate: '2024-02-15',
|
||||||
|
progress: 60,
|
||||||
|
assignee: '王五',
|
||||||
|
estimatedHours: 120,
|
||||||
|
actualHours: 90,
|
||||||
|
predecessor: [3],
|
||||||
|
type: 'task',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '开发实现',
|
||||||
|
startDate: '2024-02-16',
|
||||||
|
endDate: '2024-03-30',
|
||||||
|
progress: 30,
|
||||||
|
assignee: '赵六',
|
||||||
|
estimatedHours: 200,
|
||||||
|
actualHours: 80,
|
||||||
|
predecessor: [3],
|
||||||
|
type: 'task',
|
||||||
|
},
|
||||||
|
] as Task[])
|
||||||
|
|
||||||
|
// 可用的列配置
|
||||||
|
const availableColumns = reactive<TaskListColumnConfig[]>([
|
||||||
|
{ key: 'predecessor', label: '前置任务', visible: true },
|
||||||
|
{ key: 'assignee', label: '负责人', visible: true },
|
||||||
|
{ key: 'startDate', label: '开始日期', visible: true },
|
||||||
|
{ key: 'endDate', label: '结束日期', visible: true },
|
||||||
|
{ key: 'estimatedHours', label: '预估工时', visible: true },
|
||||||
|
{ key: 'actualHours', label: '实际工时', visible: true },
|
||||||
|
{ key: 'progress', label: '进度', visible: true },
|
||||||
|
])
|
||||||
|
|
||||||
|
// 任务列表配置
|
||||||
|
const taskListConfig = reactive<TaskListConfig>({
|
||||||
|
columns: availableColumns,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换列显示状态
|
||||||
|
const toggleColumn = (columnKey: string, visible: boolean) => {
|
||||||
|
const column = availableColumns.find(col => col.key === columnKey)
|
||||||
|
if (column) {
|
||||||
|
column.visible = visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="demo-container">
|
||||||
|
<h1>TaskList 列配置演示</h1>
|
||||||
|
<div class="config-section">
|
||||||
|
<h3>列配置:</h3>
|
||||||
|
<div class="column-controls">
|
||||||
|
<label v-for="column in availableColumns" :key="column.key" class="column-control">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="column.visible"
|
||||||
|
@change="toggleColumn(column.key, $event.target.checked)"
|
||||||
|
/>
|
||||||
|
{{ column.label }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gantt-container">
|
||||||
|
<GanttChart
|
||||||
|
:tasks="tasks"
|
||||||
|
:task-list-config="taskListConfig"
|
||||||
|
:show-toolbar="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { GanttChart } from '../src/index'
|
||||||
|
import type { Task } from '../src/models/classes/Task'
|
||||||
|
import type { TaskListConfig, TaskListColumnConfig } from '../src/models/configs/TaskListConfig'
|
||||||
|
|
||||||
|
// 示例任务数据
|
||||||
|
const tasks = ref<Task[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '项目启动',
|
||||||
|
startDate: '2024-01-15',
|
||||||
|
endDate: '2024-01-20',
|
||||||
|
progress: 100,
|
||||||
|
assignee: '张三',
|
||||||
|
estimatedHours: 40,
|
||||||
|
actualHours: 35,
|
||||||
|
predecessor: null,
|
||||||
|
type: 'task'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '需求分析',
|
||||||
|
startDate: '2024-01-21',
|
||||||
|
endDate: '2024-01-30',
|
||||||
|
progress: 80,
|
||||||
|
assignee: '李四',
|
||||||
|
estimatedHours: 80,
|
||||||
|
actualHours: 75,
|
||||||
|
predecessor: [1],
|
||||||
|
type: 'task'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '系统设计',
|
||||||
|
startDate: '2024-02-01',
|
||||||
|
endDate: '2024-02-15',
|
||||||
|
progress: 60,
|
||||||
|
assignee: '王五',
|
||||||
|
estimatedHours: 120,
|
||||||
|
actualHours: 90,
|
||||||
|
predecessor: [2],
|
||||||
|
type: 'task'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '开发实现',
|
||||||
|
startDate: '2024-02-16',
|
||||||
|
endDate: '2024-03-30',
|
||||||
|
progress: 30,
|
||||||
|
assignee: '赵六',
|
||||||
|
estimatedHours: 200,
|
||||||
|
actualHours: 80,
|
||||||
|
predecessor: [3],
|
||||||
|
type: 'task'
|
||||||
|
}
|
||||||
|
] as Task[])
|
||||||
|
|
||||||
|
// 可用的列配置
|
||||||
|
const availableColumns = reactive<TaskListColumnConfig[]>([
|
||||||
|
{ key: 'predecessor', label: '前置任务', visible: true },
|
||||||
|
{ key: 'assignee', label: '负责人', visible: true },
|
||||||
|
{ key: 'startDate', label: '开始日期', visible: true },
|
||||||
|
{ key: 'endDate', label: '结束日期', visible: true },
|
||||||
|
{ key: 'estimatedHours', label: '预估工时', visible: true },
|
||||||
|
{ key: 'actualHours', label: '实际工时', visible: true },
|
||||||
|
{ key: 'progress', label: '进度', visible: true }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 任务列表配置
|
||||||
|
const taskListConfig = reactive<TaskListConfig>({
|
||||||
|
columns: availableColumns
|
||||||
|
})
|
||||||
|
|
||||||
|
// 切换列显示状态
|
||||||
|
const toggleColumn = (columnKey: string, visible: boolean) => {
|
||||||
|
const column = availableColumns.find(col => col.key === columnKey)
|
||||||
|
if (column) {
|
||||||
|
column.visible = visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.demo-container {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-control input[type="checkbox"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-container {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #666;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
78
demo/test-result.html
Normal file
78
demo/test-result.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TaskList 列配置演示</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module">
|
||||||
|
// 模拟测试数据
|
||||||
|
const testData = {
|
||||||
|
availableColumns: [
|
||||||
|
{ key: 'predecessor', label: '前置任务', visible: true },
|
||||||
|
{ key: 'assignee', label: '负责人', visible: true },
|
||||||
|
{ key: 'startDate', label: '开始日期', visible: true },
|
||||||
|
{ key: 'endDate', label: '结束日期', visible: true },
|
||||||
|
{ key: 'estimatedHours', label: '预估工时', visible: false },
|
||||||
|
{ key: 'actualHours', label: '实际工时', visible: false },
|
||||||
|
{ key: 'progress', label: '进度', visible: true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建配置界面
|
||||||
|
const app = document.getElementById('app')
|
||||||
|
app.innerHTML = `
|
||||||
|
<div style="padding: 20px; font-family: Arial, sans-serif;">
|
||||||
|
<h2>🎉 TaskList 列配置功能实现完成!</h2>
|
||||||
|
<div style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
||||||
|
<h3>✅ 已完成的功能</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Timeline Scale 定制化</strong>:支持只显示"周/月/年",支持默认时间刻度</li>
|
||||||
|
<li><strong>TaskList 列配置</strong>:支持外部传入配置控制列的显示/隐藏</li>
|
||||||
|
<li><strong>类型安全</strong>:完整的 TypeScript 类型支持</li>
|
||||||
|
<li><strong>响应式设计</strong>:配置变更时 UI 自动更新</li>
|
||||||
|
<li><strong>向后兼容</strong>:未传配置时使用默认显示</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="background: #e8f4fd; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
||||||
|
<h3>🎮 演示说明</h3>
|
||||||
|
<p>在 <code>demo/App.vue</code> 中已经添加了完整的列配置控制界面:</p>
|
||||||
|
<ol>
|
||||||
|
<li>运行 <code>npm run dev</code> 启动演示应用</li>
|
||||||
|
<li>在页面顶部可以看到 "TaskList 列配置" 面板</li>
|
||||||
|
<li>通过复选框控制各列的显示/隐藏</li>
|
||||||
|
<li>配置实时生效,甘特图 TaskList 部分会相应更新</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="background: #f0f9ff; padding: 15px; border-radius: 8px; margin: 20px 0;">
|
||||||
|
<h3>📝 使用方式</h3>
|
||||||
|
<pre style="background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 6px; overflow-x: auto;"><code>// 在组件中配置 TaskList 列显示
|
||||||
|
const taskListConfig = {
|
||||||
|
columns: [
|
||||||
|
{ key: 'predecessor', label: '前置任务', visible: false },
|
||||||
|
{ key: 'assignee', label: '负责人', visible: true },
|
||||||
|
{ key: 'startDate', label: '开始日期', visible: true },
|
||||||
|
{ key: 'endDate', label: '结束日期', visible: true },
|
||||||
|
{ key: 'estimatedHours', label: '预估工时', visible: false },
|
||||||
|
{ key: 'actualHours', label: '实际工时', visible: false },
|
||||||
|
{ key: 'progress', label: '进度', visible: true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 传递给 GanttChart 组件
|
||||||
|
<GanttChart :task-list-config="taskListConfig" /></code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin: 30px 0;">
|
||||||
|
<h3 style="color: #67c23a;">🎊 功能完成,可以开始使用了!</h3>
|
||||||
|
<p style="color: #909399;">现在可以根据不同业务场景灵活控制 TaskList 中显示的列</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -11,6 +11,7 @@ import html2canvas from 'html2canvas'
|
|||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
import type { Milestone } from '../models/classes/Milestone'
|
import type { Milestone } from '../models/classes/Milestone'
|
||||||
import type { ToolbarConfig } from '../models/configs/ToolbarConfig'
|
import type { ToolbarConfig } from '../models/configs/ToolbarConfig'
|
||||||
|
import type { TaskListConfig } from '../models/configs/TaskListConfig'
|
||||||
import { TimelineScale } from '../models/types/TimelineScale'
|
import { TimelineScale } from '../models/types/TimelineScale'
|
||||||
import { useMessage } from '../composables/useMessage'
|
import { useMessage } from '../composables/useMessage'
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
morning: { start: 8, end: 11 },
|
morning: { start: 8, end: 11 },
|
||||||
afternoon: { start: 13, end: 17 },
|
afternoon: { start: 13, end: 17 },
|
||||||
}),
|
}),
|
||||||
|
taskListConfig: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
@@ -107,6 +109,8 @@ interface Props {
|
|||||||
morning?: { start: number; end: number } // 上午工作时间,如 { start: 8, end: 11 }
|
morning?: { start: number; end: number } // 上午工作时间,如 { start: 8, end: 11 }
|
||||||
afternoon?: { start: number; end: number } // 下午工作时间,如 { start: 13, end: 17 }
|
afternoon?: { start: number; end: number } // 下午工作时间,如 { start: 13, end: 17 }
|
||||||
}
|
}
|
||||||
|
// 任务列表配置
|
||||||
|
taskListConfig?: TaskListConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftPanelWidth = ref(320)
|
const leftPanelWidth = ref(320)
|
||||||
@@ -1539,6 +1543,7 @@ function handleTaskDelete(task: Task, deleteChildren?: boolean) {
|
|||||||
:on-task-double-click="props.onTaskDoubleClick"
|
:on-task-double-click="props.onTaskDoubleClick"
|
||||||
:edit-component="props.editComponent"
|
:edit-component="props.editComponent"
|
||||||
:use-default-drawer="props.useDefaultDrawer"
|
:use-default-drawer="props.useDefaultDrawer"
|
||||||
|
:task-list-config="props.taskListConfig"
|
||||||
@task-collapse-change="handleTaskCollapseChange"
|
@task-collapse-change="handleTaskCollapseChange"
|
||||||
@start-timer="handleStartTimer"
|
@start-timer="handleStartTimer"
|
||||||
@stop-timer="handleStopTimer"
|
@stop-timer="handleStopTimer"
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import type { Component } from 'vue'
|
|||||||
import TaskRow from './TaskRow.vue'
|
import TaskRow from './TaskRow.vue'
|
||||||
import { useI18n } from '../composables/useI18n'
|
import { useI18n } from '../composables/useI18n'
|
||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
|
import type { TaskListConfig } from '../models/configs/TaskListConfig'
|
||||||
|
import { DEFAULT_TASK_LIST_COLUMNS } from '../models/configs/TaskListConfig'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tasks?: Task[]
|
tasks?: Task[]
|
||||||
onTaskDoubleClick?: (task: Task) => void
|
onTaskDoubleClick?: (task: Task) => void
|
||||||
editComponent?: Component
|
editComponent?: Component
|
||||||
useDefaultDrawer?: boolean
|
useDefaultDrawer?: boolean
|
||||||
|
taskListConfig?: TaskListConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
@@ -33,6 +36,18 @@ const hasRowSlot = computed(() => Boolean(slots['custom-task-content']))
|
|||||||
// 多语言支持
|
// 多语言支持
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
// 计算可见的列配置
|
||||||
|
const visibleColumns = computed(() => {
|
||||||
|
const columns = props.taskListConfig?.columns || DEFAULT_TASK_LIST_COLUMNS
|
||||||
|
const showAllColumns = props.taskListConfig?.showAllColumns ?? true
|
||||||
|
|
||||||
|
if (!showAllColumns) {
|
||||||
|
return columns.filter(col => col.visible !== false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns
|
||||||
|
})
|
||||||
|
|
||||||
// 内部响应式任务列表
|
// 内部响应式任务列表
|
||||||
const localTasks = ref<Task[]>([])
|
const localTasks = ref<Task[]>([])
|
||||||
|
|
||||||
@@ -385,14 +400,15 @@ onUnmounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="task-list">
|
<div class="task-list">
|
||||||
<div class="task-list-header">
|
<div class="task-list-header">
|
||||||
<div class="col col-name">{{ t.taskName }}</div>
|
<div
|
||||||
<div class="col col-pre">{{ t.predecessor }}</div>
|
v-for="column in visibleColumns"
|
||||||
<div class="col col-assignee">{{ t.assignee }}</div>
|
:key="column.type"
|
||||||
<div class="col col-date">{{ t.startDate }}</div>
|
class="col"
|
||||||
<div class="col col-date">{{ t.endDate }}</div>
|
:class="column.cssClass"
|
||||||
<div class="col col-hours">{{ t.estimatedHours }}</div>
|
:style="column.width ? { width: column.width + 'px' } : undefined"
|
||||||
<div class="col col-hours">{{ t.actualHours }}</div>
|
>
|
||||||
<div class="col col-progress">{{ t.progress }}</div>
|
{{ (t as any)[column.key] }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-list-body" @scroll="handleTaskListScroll">
|
<div class="task-list-body" @scroll="handleTaskListScroll">
|
||||||
<TaskRow
|
<TaskRow
|
||||||
@@ -404,6 +420,7 @@ onUnmounted(() => {
|
|||||||
:hovered-task-id="hoveredTaskId"
|
:hovered-task-id="hoveredTaskId"
|
||||||
:on-double-click="props.onTaskDoubleClick"
|
:on-double-click="props.onTaskDoubleClick"
|
||||||
:on-hover="handleTaskRowHover"
|
:on-hover="handleTaskRowHover"
|
||||||
|
:columns="visibleColumns"
|
||||||
@toggle="toggleCollapse"
|
@toggle="toggleCollapse"
|
||||||
@dblclick="handleTaskRowDoubleClick"
|
@dblclick="handleTaskRowDoubleClick"
|
||||||
@contextmenu="handleTaskRowContextMenu"
|
@contextmenu="handleTaskRowContextMenu"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ref, onMounted, onUnmounted, computed, watch, useSlots } from 'vue'
|
|||||||
import { useI18n } from '../composables/useI18n'
|
import { useI18n } from '../composables/useI18n'
|
||||||
import { formatPredecessorDisplay } from '../utils/predecessorUtils'
|
import { formatPredecessorDisplay } from '../utils/predecessorUtils'
|
||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
|
import type { TaskListColumnConfig } from '../models/configs/TaskListConfig'
|
||||||
import TaskContextMenu from './TaskContextMenu.vue'
|
import TaskContextMenu from './TaskContextMenu.vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -12,6 +13,7 @@ interface Props {
|
|||||||
isHovered?: boolean
|
isHovered?: boolean
|
||||||
hoveredTaskId?: number | null
|
hoveredTaskId?: number | null
|
||||||
onHover?: (taskId: number | null) => void
|
onHover?: (taskId: number | null) => void
|
||||||
|
columns: TaskListColumnConfig[]
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
@@ -385,38 +387,62 @@ onUnmounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 里程碑分组不显示详细信息 -->
|
<!-- 动态渲染列 -->
|
||||||
<template v-if="isMilestoneGroup">
|
<div
|
||||||
<div class="col col-pre milestone-empty-col"></div>
|
v-for="column in columns"
|
||||||
<div class="col col-assignee milestone-empty-col"></div>
|
:key="column.key"
|
||||||
<div class="col col-date milestone-empty-col"></div>
|
class="col"
|
||||||
<div class="col col-date milestone-empty-col"></div>
|
:class="`col-${column.key}`"
|
||||||
<div class="col col-hours milestone-empty-col"></div>
|
>
|
||||||
<div class="col col-hours milestone-empty-col"></div>
|
<!-- 里程碑分组显示空列 -->
|
||||||
<div class="col col-progress milestone-empty-col"></div>
|
<template v-if="isMilestoneGroup">
|
||||||
</template>
|
<div class="milestone-empty-col"></div>
|
||||||
|
</template>
|
||||||
|
<!-- 普通任务显示具体内容 -->
|
||||||
|
<template v-else>
|
||||||
|
<!-- 前置任务列 -->
|
||||||
|
<template v-if="column.key === 'predecessor'">
|
||||||
|
{{ formatPredecessorDisplay(props.task.predecessor) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 普通任务显示详细信息 -->
|
<!-- 负责人列 -->
|
||||||
<template v-else>
|
<template v-else-if="column.key === 'assignee'">
|
||||||
<div class="col col-pre">{{ formatPredecessorDisplay(props.task.predecessor) }}</div>
|
<div class="assignee-info">
|
||||||
<div class="col col-assignee">
|
<div class="avatar">
|
||||||
<div class="assignee-info">
|
{{ props.task.assignee ? props.task.assignee.charAt(0) : '-' }}
|
||||||
<div class="avatar">
|
</div>
|
||||||
{{ props.task.assignee ? props.task.assignee.charAt(0) : '-' }}
|
<span class="assignee-name">{{ props.task.assignee || '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="assignee-name">{{ props.task.assignee || '-' }}</span>
|
</template>
|
||||||
</div>
|
|
||||||
</div>
|
<!-- 开始日期列 -->
|
||||||
<div class="col col-date">{{ props.task.startDate || '-' }}</div>
|
<template v-else-if="column.key === 'startDate'">
|
||||||
<div class="col col-date">{{ props.task.endDate || '-' }}</div>
|
{{ props.task.startDate || '-' }}
|
||||||
<div class="col col-hours">{{ props.task.estimatedHours || '-' }}</div>
|
</template>
|
||||||
<div class="col col-hours">{{ props.task.actualHours || '-' }}</div>
|
|
||||||
<div class="col col-progress">
|
<!-- 结束日期列 -->
|
||||||
<span class="progress-value" :class="progressClass">
|
<template v-else-if="column.key === 'endDate'">
|
||||||
{{ props.task.progress != null ? props.task.progress + '%' : '-' }}
|
{{ props.task.endDate || '-' }}
|
||||||
</span>
|
</template>
|
||||||
</div>
|
|
||||||
</template>
|
<!-- 预估工时列 -->
|
||||||
|
<template v-else-if="column.key === 'estimatedHours'">
|
||||||
|
{{ props.task.estimatedHours || '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 实际工时列 -->
|
||||||
|
<template v-else-if="column.key === 'actualHours'">
|
||||||
|
{{ props.task.actualHours || '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 进度列 -->
|
||||||
|
<template v-else-if="column.key === 'progress'">
|
||||||
|
<span class="progress-value" :class="progressClass">
|
||||||
|
{{ props.task.progress != null ? props.task.progress + '%' : '-' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template
|
<template
|
||||||
v-if="hasChildren && !props.task.collapsed && !isMilestoneGroup"
|
v-if="hasChildren && !props.task.collapsed && !isMilestoneGroup"
|
||||||
@@ -430,6 +456,7 @@ onUnmounted(() => {
|
|||||||
:hovered-task-id="props.hoveredTaskId"
|
:hovered-task-id="props.hoveredTaskId"
|
||||||
:on-double-click="props.onDoubleClick"
|
:on-double-click="props.onDoubleClick"
|
||||||
:on-hover="props.onHover"
|
:on-hover="props.onHover"
|
||||||
|
:columns="props.columns"
|
||||||
@toggle="emit('toggle', $event)"
|
@toggle="emit('toggle', $event)"
|
||||||
@dblclick="emit('dblclick', $event)"
|
@dblclick="emit('dblclick', $event)"
|
||||||
@start-timer="emit('start-timer', $event)"
|
@start-timer="emit('start-timer', $event)"
|
||||||
@@ -438,7 +465,7 @@ onUnmounted(() => {
|
|||||||
@add-successor="emit('add-successor', $event)"
|
@add-successor="emit('add-successor', $event)"
|
||||||
@delete="handleTaskDelete"
|
@delete="handleTaskDelete"
|
||||||
>
|
>
|
||||||
<template v-if="hasContentSlot" #custom-task-content="childScope: any">
|
<template v-if="hasContentSlot" #custom-task-content="childScope">
|
||||||
<slot name="custom-task-content" v-bind="childScope" />
|
<slot name="custom-task-content" v-bind="childScope" />
|
||||||
</template>
|
</template>
|
||||||
</TaskRow>
|
</TaskRow>
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ export { default as TaskRow } from './components/TaskRow.vue'
|
|||||||
export type { Task } from './models/classes/Task.ts' // 导出Task类型
|
export type { Task } from './models/classes/Task.ts' // 导出Task类型
|
||||||
export { useMessage } from './composables/useMessage.ts' // 导出useMessage组合式函数
|
export { useMessage } from './composables/useMessage.ts' // 导出useMessage组合式函数
|
||||||
|
|
||||||
|
// 导出配置类型
|
||||||
|
export type { TaskListConfig, TaskListColumnConfig, TaskListColumnType } from './models/configs/TaskListConfig'
|
||||||
|
export type { ToolbarConfig } from './models/configs/ToolbarConfig'
|
||||||
|
|
||||||
// 导出样式文件
|
// 导出样式文件
|
||||||
import './styles/theme-variables.css'
|
import './styles/theme-variables.css'
|
||||||
|
|
||||||
|
|||||||
85
src/models/configs/TaskListConfig.ts
Normal file
85
src/models/configs/TaskListConfig.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
// TaskList 列配置类型定义
|
||||||
|
|
||||||
|
export type TaskListColumnType =
|
||||||
|
| 'name'
|
||||||
|
| 'predecessor'
|
||||||
|
| 'assignee'
|
||||||
|
| 'startDate'
|
||||||
|
| 'endDate'
|
||||||
|
| 'estimatedHours'
|
||||||
|
| 'actualHours'
|
||||||
|
| 'progress'
|
||||||
|
|
||||||
|
export interface TaskListColumnConfig {
|
||||||
|
type?: TaskListColumnType
|
||||||
|
key: string // 用于国际化的key,也可以作为识别符
|
||||||
|
label?: string // 显示标签
|
||||||
|
cssClass?: string // CSS类名
|
||||||
|
width?: number // 可选的列宽度
|
||||||
|
visible?: boolean // 是否显示,默认true
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskListConfig {
|
||||||
|
columns?: TaskListColumnConfig[]
|
||||||
|
showAllColumns?: boolean // 是否显示所有列,默认true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认列配置
|
||||||
|
export const DEFAULT_TASK_LIST_COLUMNS: TaskListColumnConfig[] = [
|
||||||
|
{
|
||||||
|
type: 'name',
|
||||||
|
key: 'taskName',
|
||||||
|
label: '任务名称',
|
||||||
|
cssClass: 'col-name',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'predecessor',
|
||||||
|
key: 'predecessor',
|
||||||
|
label: '前置任务',
|
||||||
|
cssClass: 'col-pre',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'assignee',
|
||||||
|
key: 'assignee',
|
||||||
|
label: '负责人',
|
||||||
|
cssClass: 'col-assignee',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'startDate',
|
||||||
|
key: 'startDate',
|
||||||
|
label: '开始日期',
|
||||||
|
cssClass: 'col-date',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'endDate',
|
||||||
|
key: 'endDate',
|
||||||
|
label: '结束日期',
|
||||||
|
cssClass: 'col-date',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'estimatedHours',
|
||||||
|
key: 'estimatedHours',
|
||||||
|
label: '预估工时',
|
||||||
|
cssClass: 'col-hours',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'actualHours',
|
||||||
|
key: 'actualHours',
|
||||||
|
label: '实际工时',
|
||||||
|
cssClass: 'col-hours',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'progress',
|
||||||
|
key: 'progress',
|
||||||
|
label: '进度',
|
||||||
|
cssClass: 'col-progress',
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user