Merge branch 'master' of https://gitee.com/jordium/jordium-gantt-vue3
This commit is contained in:
+454
-13
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||
import GanttChart from '../src/components/GanttChart.vue'
|
||||
// import TaskDrawer from '../src/components/TaskDrawer.vue' // 移除
|
||||
import MilestoneDialog from '../src/components/MilestoneDialog.vue'
|
||||
@@ -8,10 +8,12 @@ import packageInfo from '../package.json'
|
||||
// 导入主题变量
|
||||
import '../src/styles/theme-variables.css'
|
||||
import VersionHistoryDrawer from './VersionHistoryDrawer.vue'
|
||||
import HtmlContent from './HtmlContent.vue'
|
||||
import { useMessage } from '../src/composables/useMessage'
|
||||
import { useI18n } from '../src/composables/useI18n'
|
||||
import { getPredecessorIds, predecessorIdsToString } from '../src/utils/predecessorUtils'
|
||||
import type { Task } from '../src/models/Task'
|
||||
import type { TaskListConfig, TaskListColumnConfig } from '../src/models/configs/TaskListConfig'
|
||||
|
||||
const { showMessage } = useMessage()
|
||||
const { t, formatTranslation } = useI18n()
|
||||
@@ -37,10 +39,54 @@ const toolbarConfig = {
|
||||
showTheme: true,
|
||||
showFullscreen: true,
|
||||
showTimeScale: true, // 控制日|周|月时间刻度按钮组的可见性
|
||||
timeScaleDimensions: ['hour', 'day', 'week', 'month', 'quarter', 'year'], // 设置时间刻度按钮的展示维度,包含所有时间维度
|
||||
timeScaleDimensions: ['day', 'week', 'month', 'quarter', 'year'], // 设置时间刻度按钮的展示维度,包含所有时间维度
|
||||
defaultTimeScale: 'week',
|
||||
showExpandCollapse: true, // 显示全部展开/折叠按钮
|
||||
}
|
||||
|
||||
// 工作时间配置示例
|
||||
// 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 },
|
||||
])
|
||||
|
||||
// TaskList宽度配置
|
||||
const taskListWidth = ref({
|
||||
defaultWidth: 450, // 默认宽度400px(比默认320px更宽)
|
||||
minWidth: 300, // 最小宽度300px(比默认280px略大)
|
||||
maxWidth: 1200, // 最大宽度1200px(比默认1160px略大)
|
||||
})
|
||||
|
||||
const taskListConfig = computed<TaskListConfig>(() => ({
|
||||
columns: availableColumns.value,
|
||||
defaultWidth: taskListWidth.value.defaultWidth,
|
||||
minWidth: taskListWidth.value.minWidth,
|
||||
maxWidth: taskListWidth.value.maxWidth,
|
||||
}))
|
||||
|
||||
// 配置面板折叠状态
|
||||
const isConfigPanelCollapsed = ref(false)
|
||||
|
||||
// 切换配置面板折叠状态
|
||||
const toggleConfigPanel = () => {
|
||||
isConfigPanelCollapsed.value = !isConfigPanelCollapsed.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
|
||||
}
|
||||
} // 工作时间配置示例
|
||||
const workingHoursConfig = {
|
||||
morning: { start: 8, end: 11 }, // 上午8:00-11:59为工作时间
|
||||
afternoon: { start: 13, end: 17 }, // 下午13:00-17:00为工作时间
|
||||
@@ -126,14 +172,14 @@ const handleMilestoneDelete = async (milestoneId: number) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('milestone-deleted', {
|
||||
detail: { milestoneId },
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
// 触发强制更新事件,确保Timeline重新渲染
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('milestone-data-changed', {
|
||||
detail: { milestones: milestones.value },
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -227,7 +273,7 @@ const handleTaskUpdate = (updatedTask: Task) => {
|
||||
showMessage(
|
||||
formatTranslation('newParentTaskNotFound', { parentId: taskToAdd.parentId }),
|
||||
'warning',
|
||||
{ closable: true },
|
||||
{ closable: true }
|
||||
)
|
||||
tasks.value.push(taskToAdd)
|
||||
}
|
||||
@@ -275,7 +321,7 @@ const handleTaskAdd = (newTask: Task) => {
|
||||
const maxId = Math.max(
|
||||
...tasks.value.map(t => t.id || 0),
|
||||
...milestones.value.map(m => m.id || 0),
|
||||
0,
|
||||
0
|
||||
)
|
||||
newTask.id = maxId + 1
|
||||
}
|
||||
@@ -370,7 +416,7 @@ const handleStoryDeleteWithChildren = (storyToDelete: Task) => {
|
||||
'success',
|
||||
{
|
||||
closable: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
@@ -432,7 +478,7 @@ const handleStoryDeleteOnly = (storyToDelete: Task) => {
|
||||
'success',
|
||||
{
|
||||
closable: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
@@ -506,7 +552,7 @@ function handleTaskbarDragOrResizeEnd(newTask) {
|
||||
`开始: ${oldTask.startDate} → ${newTask.startDate}\n` +
|
||||
`结束: ${oldTask.endDate} → ${newTask.endDate}`,
|
||||
'info',
|
||||
{ closable: true },
|
||||
{ closable: true }
|
||||
)
|
||||
}
|
||||
function handleMilestoneDragEnd(newMilestone) {
|
||||
@@ -516,7 +562,7 @@ function handleMilestoneDragEnd(newMilestone) {
|
||||
`里程碑【${newMilestone.name}】\n` +
|
||||
`开始: ${oldMilestone.endDate} → ${newMilestone.startDate}`,
|
||||
'info',
|
||||
{ closable: true },
|
||||
{ closable: true }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -583,7 +629,7 @@ function onTimerStarted(task: Task) {
|
||||
showMessage(
|
||||
`Demo 任务【${task.name}】\n开始计时:${new Date(task.timerStartTime).toLocaleString()}\n计时说明:${task.timerStartDesc ? task.timerStartDesc : ''}`,
|
||||
'info',
|
||||
{ closable: true },
|
||||
{ closable: true }
|
||||
)
|
||||
}
|
||||
function onTimerStopped(task: Task) {
|
||||
@@ -597,6 +643,10 @@ function onTimerStopped(task: Task) {
|
||||
}
|
||||
showMessage(msg, 'info', { closable: true })
|
||||
}
|
||||
|
||||
function taskDebug(item: any) {
|
||||
console.log('Task Debug:', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -631,11 +681,143 @@ function onTimerStopped(task: Task) {
|
||||
</div>
|
||||
</h1>
|
||||
<VersionHistoryDrawer :visible="showVersionDrawer" @close="showVersionDrawer = false" />
|
||||
|
||||
<!-- TaskList配置面板 - 可折叠 -->
|
||||
<div class="config-panel" :class="{ collapsed: isConfigPanelCollapsed }">
|
||||
<div class="config-header" @click="toggleConfigPanel">
|
||||
<h3 class="config-title">
|
||||
<svg
|
||||
class="config-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z" fill="currentColor" />
|
||||
</svg>
|
||||
TaskList 配置
|
||||
</h3>
|
||||
<button class="collapse-button" :class="{ collapsed: isConfigPanelCollapsed }">
|
||||
<svg
|
||||
class="collapse-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7 10l5 5 5-5"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 可折叠内容区域 -->
|
||||
<transition name="config-content">
|
||||
<div v-show="!isConfigPanelCollapsed" class="config-content">
|
||||
<!-- 宽度配置区域 -->
|
||||
<div class="config-section">
|
||||
<h4 class="section-title">
|
||||
<svg
|
||||
class="section-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="3"
|
||||
y="6"
|
||||
width="18"
|
||||
height="12"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M8 12h8M8 9l-2 3 2 3M16 9l2 3-2 3"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
宽度设置
|
||||
</h4>
|
||||
<div class="width-controls">
|
||||
<div class="width-control">
|
||||
<label class="width-label">默认宽度:</label>
|
||||
<input
|
||||
v-model.number="taskListWidth.defaultWidth"
|
||||
type="number"
|
||||
:min="taskListWidth.minWidth"
|
||||
:max="taskListWidth.maxWidth"
|
||||
step="10"
|
||||
class="width-input"
|
||||
/>
|
||||
<span class="width-unit">px</span>
|
||||
</div>
|
||||
<div class="width-control">
|
||||
<label class="width-label">最小宽度:</label>
|
||||
<input
|
||||
v-model.number="taskListWidth.minWidth"
|
||||
type="number"
|
||||
min="280"
|
||||
:max="taskListWidth.defaultWidth"
|
||||
step="10"
|
||||
class="width-input"
|
||||
/>
|
||||
<span class="width-unit">px</span>
|
||||
</div>
|
||||
<div class="width-control">
|
||||
<label class="width-label">最大宽度:</label>
|
||||
<input
|
||||
v-model.number="taskListWidth.maxWidth"
|
||||
type="number"
|
||||
:min="taskListWidth.defaultWidth"
|
||||
max="2000"
|
||||
step="10"
|
||||
class="width-input"
|
||||
/>
|
||||
<span class="width-unit">px</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列配置区域 -->
|
||||
<div class="config-section">
|
||||
<h4 class="section-title">
|
||||
<svg
|
||||
class="section-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z" fill="currentColor" />
|
||||
</svg>
|
||||
列显示
|
||||
</h4>
|
||||
<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>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<div class="gantt-wrapper">
|
||||
<GanttChart
|
||||
:tasks="tasks"
|
||||
:milestones="milestones"
|
||||
:toolbar-config="toolbarConfig"
|
||||
:task-list-config="taskListConfig"
|
||||
:working-hours="workingHoursConfig"
|
||||
:on-add-task="handleAddTask"
|
||||
:on-add-milestone="handleAddMilestone"
|
||||
@@ -666,7 +848,11 @@ function onTimerStopped(task: Task) {
|
||||
@task-deleted="e => showMessage(`Demo 任务[${e.task.name}] 已删除`, 'info')"
|
||||
@task-added="e => showMessage(`Demo 任务[${e.task.name}] 已创建`, 'info')"
|
||||
@task-updated="e => showMessage(`Demo 任务[${e.task.name}] 已更新`, 'info')"
|
||||
/>
|
||||
>
|
||||
<template #custom-task-content="item">
|
||||
<HtmlContent :item="taskDebug(item)" :task="item.task" :type="item.type" />
|
||||
</template>
|
||||
</GanttChart>
|
||||
</div>
|
||||
<div class="license-info">
|
||||
<p>MIT License @JORDIUM.COM</p>
|
||||
@@ -695,6 +881,217 @@ function onTimerStopped(task: Task) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* TaskList列配置面板样式 - 可折叠 */
|
||||
.config-panel {
|
||||
background: var(--gantt-bg-primary, #ffffff);
|
||||
border: 1px solid var(--gantt-border-color, #e4e7ed);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.config-panel:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.config-panel.collapsed {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.config-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s ease;
|
||||
border-bottom: 1px solid var(--gantt-border-color, #e4e7ed);
|
||||
}
|
||||
|
||||
.config-panel.collapsed .config-header {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.config-header:hover {
|
||||
background-color: var(--gantt-hover-bg, #f8f9fa);
|
||||
}
|
||||
|
||||
.config-content {
|
||||
padding: 0 16px 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.collapse-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
color: var(--gantt-text-secondary, #666);
|
||||
}
|
||||
|
||||
.collapse-button:hover {
|
||||
background-color: var(--gantt-hover-bg, #e8f4fd);
|
||||
color: var(--gantt-primary-color, #409eff);
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.collapse-button.collapsed .collapse-icon {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.config-content-enter-active,
|
||||
.config-content-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.config-content-enter-from,
|
||||
.config-content-leave-to {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.config-content-enter-to,
|
||||
.config-content-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.config-title {
|
||||
margin: 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);
|
||||
}
|
||||
|
||||
.config-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.width-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.width-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.width-label {
|
||||
flex: 0 0 80px;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.width-input {
|
||||
flex: 1;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
background: var(--input-background, #fff);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.width-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.width-unit {
|
||||
flex: 0 0 20px;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.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 {
|
||||
margin: 20px 0;
|
||||
font-size: 1.8rem;
|
||||
@@ -816,6 +1213,50 @@ function onTimerStopped(task: Task) {
|
||||
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']) .config-header {
|
||||
border-bottom-color: var(--gantt-border-color, #4a5568);
|
||||
}
|
||||
|
||||
:global(html[data-theme='dark']) .config-header:hover {
|
||||
background-color: var(--gantt-hover-bg, #2d3748);
|
||||
}
|
||||
|
||||
:global(html[data-theme='dark']) .collapse-button {
|
||||
color: var(--gantt-text-secondary, #a0aec0);
|
||||
}
|
||||
|
||||
:global(html[data-theme='dark']) .collapse-button:hover {
|
||||
background-color: var(--gantt-hover-bg, #2d3748);
|
||||
color: var(--gantt-primary-color, #66b3ff);
|
||||
}
|
||||
|
||||
/* 暗黑模式下的版本标签 */
|
||||
:global(html[data-theme='dark']) .version-badge {
|
||||
background: linear-gradient(135deg, #1a73e8 0%, #00bcd4 50%, #3f51b5 100%);
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import type { Task } from '../src/models/Task'
|
||||
|
||||
interface Props {
|
||||
task: Task,
|
||||
type: string, // 'task-row' | 'task-bar'
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
// console.error('props', props)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="html-content-card">
|
||||
<div v-if="type==='task-row'" class="task-row" v-html="task.name" />
|
||||
<div v-else-if="type==='task-bar'" class="task-bar" v-html="task.name" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.html-content-card {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.task-row {
|
||||
flex: 1;
|
||||
padding: 0 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-bar {
|
||||
flex: 1;
|
||||
padding: 0 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
+2
-2
@@ -27,7 +27,7 @@
|
||||
"children": [
|
||||
{
|
||||
"id": 1101,
|
||||
"name": "试验方案设计与伦理审查",
|
||||
"name": "试验方案设计与<span style='font-weight: bold;color:red;'>伦理审查</span>",
|
||||
"assignee": "方案设计师 李明",
|
||||
"startDate": "2025-01-01",
|
||||
"endDate": "2025-02-28",
|
||||
@@ -84,7 +84,7 @@
|
||||
"children": [
|
||||
{
|
||||
"id": 1201,
|
||||
"name": "多中心试验启动",
|
||||
"name": "多中心试验<span style='font-weight: bold; color: blue;'>启动</span>",
|
||||
"assignee": "项目经理 王芳",
|
||||
"startDate": "2025-09-01",
|
||||
"endDate": "2025-11-30",
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user