v1.9.2-rc.1 - tooltip slots / taskList collapse enable etc.

This commit is contained in:
LINING-PC\lining
2026-02-26 22:43:54 +08:00
parent f3b0c55bba
commit 544f6ec2d5
13 changed files with 2494 additions and 160 deletions
+338 -4
View File
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { ref, computed, onMounted, nextTick, watch } from 'vue'
import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue'
// GanttChart 和 TaskListColumn 已经通过 app.use(JordiumGantt) 全局注册,无需导入
// import TaskDrawer from '../src/components/TaskDrawer.vue' // 移除
import MilestoneDialog from '../src/components/MilestoneDialog.vue'
import normalData from './data.json'
import mediumData from './data-100.json'
import largeData from './data-large-1m.json'
import resourcesData from './data-resources.json'
import largeResourcesData from './data-resources-large.json'
@@ -48,6 +49,11 @@ const rawDataSources = [
fileName: 'data.json',
payload: normalData,
},
{
key: 'medium',
fileName: 'data-100.json',
payload: mediumData,
},
{
key: 'large',
fileName: 'data-large-1m.json',
@@ -71,7 +77,7 @@ const dataSourceOptions = computed(() => {
})
})
const currentDataSource = ref<DataSourceKey>('normal')
const currentDataSource = ref<DataSourceKey>('medium')
const dataLoading = ref(false)
const cloneData = <T>(data: T): T => {
@@ -212,7 +218,7 @@ const clickedResource = ref<Resource | null>(null)
// 改为在handleTaskDoubleClick中根据对象类型判断行为
// useDefaultDrawer保持为true,确保新建任务和TaskBar双击能正常工作
const toolbarConfig = {
const toolbarConfig = reactive({
showAddTask: true,
showAddMilestone: true,
showTodayLocate: true,
@@ -225,7 +231,8 @@ const toolbarConfig = {
timeScaleDimensions: ['hour', 'day', 'week', 'month', 'quarter', 'year'], // 设置时间刻度按钮的展示维度,包含所有时间维度
defaultTimeScale: 'week',
showExpandCollapse: true, // 显示全部展开/折叠按钮
}
showViewMode: true, // 显示 Task/Resource 视图切换按钮组
})
// TaskList列渲染模式配置
const taskListColumnRenderMode = ref<'default' | 'declarative'>('default')
@@ -292,6 +299,10 @@ const taskListConfig = computed<TaskListConfig>(() => ({
widthUnit.value === '%' ? `${widthPercentage.value.maxWidth}%` : taskListWidth.value.maxWidth,
}))
// TaskList 展开/收起配置
const enableTaskListCollapsible = ref(true)
const taskListVisible = ref(true)
// 资源列表配置
const resourceListConfig = computed<ResourceListConfig>(() => ({
columns: [
@@ -392,6 +403,8 @@ const taskBarOptions = ref({
// 自定义任务状态背景色
const showActualTaskBar = ref(true)
// Tooltip 自定义 Slot 演示
const useCustomTooltip = ref(true)
const pendingTaskBackgroundColor = ref('#409eff')
const delayTaskBackgroundColor = ref('#f56c6c')
const completeTaskBackgroundColor = ref('#909399')
@@ -467,6 +480,14 @@ watch(propsExpandAll, (newExpandAll) => {
expandStatus.value = newExpandAll
})
// TaskList 显隐 expose 方法
const handleSetTaskListVisible = (visible: boolean) => {
gantt.value?.setTaskListVisible(visible)
}
const handleToggleTaskList = () => {
gantt.value?.toggleTaskList()
}
// 切换配置面板折叠状态
const toggleConfigPanel = () => {
isConfigPanelCollapsed.value = !isConfigPanelCollapsed.value
@@ -1596,6 +1617,40 @@ const handleCustomMenuAction = (action: string, task: Task) => {
</label>
</div>
</div>
<!-- 展开/收起控制 -->
<div class="subsection">
<h5 class="subsection-title">{{ (t.taskListConfig as any).collapsible.title }}</h5>
<!-- enableTaskListCollapsible 开关 -->
<div class="column-control" style="margin-bottom: 10px;">
<label class="taskbar-control">
<input v-model="enableTaskListCollapsible" type="checkbox" />
<span class="taskbar-label">{{ (t.taskListConfig as any).collapsible.enableCollapsible }}</span>
</label>
<div class="config-hint" style="margin-top: 4px; margin-left: 20px;">
{{ (t.taskListConfig as any).collapsible.enableCollapsibleHint }}
</div>
</div>
<!-- taskListVisible 切换仅当 enableTaskListCollapsible=true 时展示 -->
<transition name="section-content">
<div v-show="enableTaskListCollapsible">
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
<span class="width-label" style="flex-shrink: 0;">{{ (t.taskListConfig as any).collapsible.visible }}:</span>
<label class="taskbar-control">
<input v-model="taskListVisible" type="radio" :value="true" />
<span class="taskbar-label">{{ (t.taskListConfig as any).collapsible.visibleExpanded }}</span>
</label>
<label class="taskbar-control">
<input v-model="taskListVisible" type="radio" :value="false" />
<span class="taskbar-label">{{ (t.taskListConfig as any).collapsible.visibleCollapsed }}</span>
</label>
</div>
<div class="config-hint" style="margin-top: 4px;">
{{ (t.taskListConfig as any).collapsible.visibleHint }}
</div>
</div>
</transition>
</div>
</div>
</transition>
</div>
@@ -1660,6 +1715,10 @@ const handleCustomMenuAction = (action: string, task: Task) => {
<input v-model="showActualTaskBar" type="checkbox" />
<span class="taskbar-label">{{ taskBarConfigMessages.display?.showActualTaskBar }}</span>
</label>
<label class="taskbar-control">
<input v-model="useCustomTooltip" type="checkbox" />
<span class="taskbar-label">🎨 自定义 TooltipSlot 演示</span>
</label>
</div>
<!-- 自定义任务状态背景色 -->
@@ -1907,6 +1966,20 @@ const handleCustomMenuAction = (action: string, task: Task) => {
</p>
</div>
<!-- 工具栏配置 - showViewMode -->
<div class="subsection">
<h5 class="subsection-title">🔀 {{ (ts as any).showViewMode?.title || 'Task/Resource 视图切换按钮组' }}</h5>
<div class="column-control">
<label class="taskbar-control">
<input v-model="toolbarConfig.showViewMode" type="checkbox" />
<span class="taskbar-label">{{ (ts as any).showViewMode?.label || '显示视图切换按钮组' }}</span>
</label>
<div class="config-hint" style="margin-top: 4px; margin-left: 20px;">
{{ (ts as any).showViewMode?.hint || '关闭后工具栏不展示 Task/Resource 切换控件' }}
</div>
</div>
</div>
<!-- Expose Methods 控制区域 -->
<div v-show="controlMode === 'expose'" class="subsection">
<h4 class="section-subtitle"> {{ ts.exposeMethods?.sectionTitle || 'Expose 方法控制' }}</h4>
@@ -2041,6 +2114,24 @@ const handleCustomMenuAction = (action: string, task: Task) => {
<button class="tool-button" @click="handleScrollToDate">{{ ts.exposeMethods?.navigation?.go || 'Go' }}</button>
</div>
</div>
<div class="tool-divider"></div>
<!-- TaskList 显隐 Expose 控制 -->
<div class="tool-control-group">
<h5 class="subsection-title">📋 {{ (ts as any).taskListControl?.sectionTitle || 'TaskList 显隐' }}</h5>
<div class="tool-button-group">
<button class="tool-button" @click="handleSetTaskListVisible(true)">
{{ (ts as any).taskListControl?.show || '展开' }}
</button>
<button class="tool-button" @click="handleSetTaskListVisible(false)">
{{ (ts as any).taskListControl?.hide || '收起' }}
</button>
<button class="tool-button primary" @click="handleToggleTaskList">
🔄 {{ (ts as any).taskListControl?.toggle || '切换(动画)' }}
</button>
</div>
</div>
</div>
</div>
@@ -2193,6 +2284,57 @@ const handleCustomMenuAction = (action: string, task: Task) => {
</div>
<p class="prop-info">:expand-all="{{ propsExpandAll }}"</p>
</div>
<div class="tool-divider"></div>
<!-- TaskList 控制 Props -->
<div class="tool-control-group">
<h5 class="subsection-title">📋 {{ (ts as any).taskListControl?.sectionTitle || 'TaskList 可折叠控制' }}</h5>
<!-- enableTaskListCollapsible -->
<div class="tool-button-group">
<button
class="tool-button"
:class="{ primary: enableTaskListCollapsible === true }"
@click="enableTaskListCollapsible = true"
>
{{ (ts as any).taskListControl?.true || '开启' }}
</button>
<button
class="tool-button"
:class="{ primary: enableTaskListCollapsible === false }"
@click="enableTaskListCollapsible = false"
>
{{ (ts as any).taskListControl?.false || '关闭' }}
</button>
</div>
<p class="prop-info">:enable-task-list-collapsible="{{ enableTaskListCollapsible }}"</p>
</div>
<div class="tool-divider"></div>
<div class="tool-control-group">
<h5 class="subsection-title">📋 {{ (ts as any).taskListControl?.sectionTitle1 || 'TaskList 显隐控制' }}</h5>
<!-- taskListVisible -->
<div class="tool-button-group">
<button
class="tool-button"
:class="{ primary: taskListVisible === true }"
:disabled="!enableTaskListCollapsible"
@click="taskListVisible = true"
>
{{ (ts as any).taskListControl?.show || '展开' }}
</button>
<button
class="tool-button"
:class="{ primary: taskListVisible === false }"
:disabled="!enableTaskListCollapsible"
@click="taskListVisible = false"
>
{{ (ts as any).taskListControl?.hide || '收起' }}
</button>
</div>
<p class="prop-info">:task-list-visible="{{ taskListVisible }}"</p>
</div>
</div>
</div>
</div>
@@ -2216,6 +2358,8 @@ const handleCustomMenuAction = (action: string, task: Task) => {
:expand-all="controlMode === 'props' ? propsExpandAll : undefined"
:toolbar-config="toolbarConfig"
:task-list-config="taskListConfig"
:enable-task-list-collapsible="enableTaskListCollapsible"
:task-list-visible="enableTaskListCollapsible ? taskListVisible : undefined"
:task-bar-config="taskBarConfig"
:working-hours="workingHoursConfig"
:use-default-milestone-dialog="true"
@@ -2404,6 +2548,196 @@ const handleCustomMenuAction = (action: string, task: Task) => {
</div>
</template>
</TaskBarContextMenu> -->
<!-- #taskbar-tooltip Slot 示例自定义悬浮 Tooltip -->
<!-- slot 完全替换内置 Tooltip通过 useCustomTooltip 开关切换两种样式 -->
<template #taskbar-tooltip="{ task, taskStatus, resourcePercent }">
<!-- 默认复刻样式slot 接管保持内置风格 -->
<template v-if="!useCustomTooltip">
<div
:style="{
position: 'absolute',
left: '50%',
bottom: '-5px',
transform: 'translateX(-50%)',
width: '0',
height: '0',
borderLeft: '6px solid transparent',
borderRight: '6px solid transparent',
borderTop: `6px solid ${taskStatus.color}`,
borderBottom: '0',
}"
/>
<div style="display: flex; flex-direction: column; gap: 6px;">
<div style="font-weight: 600; font-size: 13px; margin-bottom: 4px; border-bottom: 1px solid rgba(255,255,255,0.3); padding-bottom: 4px;">
{{ task.name }}
</div>
<div style="display: flex; justify-content: space-between; gap: 12px; font-size: 11px;">
<span style="opacity: 0.9;">📅 开始</span>
<span style="font-weight: 500;">{{ task.startDate?.slice(0, 10) ?? '-' }}</span>
</div>
<div style="display: flex; justify-content: space-between; gap: 12px; font-size: 11px;">
<span style="opacity: 0.9;">🏁 结束</span>
<span style="font-weight: 500;">{{ task.endDate?.slice(0, 10) ?? '-' }}</span>
</div>
<div style="display: flex; justify-content: space-between; gap: 12px; font-size: 11px;">
<span style="opacity: 0.9;">📊 进度</span>
<span style="font-weight: 500;">{{ task.progress ?? 0 }}%</span>
</div>
<div style="display: flex; align-items: center; gap: 4px; margin-top: 2px;">
<span
style="
background: rgba(255,255,255,0.25);
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
border: 1px solid rgba(255,255,255,0.4);
"
>
{{ taskStatus.label }}
</span>
<span style="opacity: 0.7; font-size: 10px; margin-left: 4px;"> Slot 接管默认风格</span>
</div>
</div>
</template>
<!-- 🎨 自定义富文本样式完全重设计 -->
<template v-else>
<div
style="
min-width: 230px;
margin: -10px -14px;
border-radius: 6px;
overflow: hidden;
background: #fff;
color: #333;
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
"
>
<!-- 彩色顶栏 -->
<div
:style="{
background: `linear-gradient(135deg, ${taskStatus.color} 0%, ${taskStatus.color}cc 100%)`,
padding: '12px 14px 10px',
color: 'white',
}"
>
<div style="display: flex; align-items: center; gap: 8px;">
<img
v-if="task.avatar && typeof task.avatar === 'string'"
:src="task.avatar"
width="28"
height="28"
style="border-radius: 50%; border: 2px solid rgba(255,255,255,0.6); flex-shrink: 0;"
/>
<div
v-else
:style="{
width: '28px', height: '28px', borderRadius: '50%',
background: 'rgba(255,255,255,0.3)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '14px', flexShrink: '0',
}"
>📋</div>
<div style="flex: 1; min-width: 0;">
<div style="font-weight: 700; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{ task.name }}</div>
<div style="font-size: 10px; opacity: 0.85; margin-top: 2px;">{{ task.assigneeName ?? '未分配' }}</div>
</div>
<span
style="
background: rgba(255,255,255,0.25);
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
white-space: nowrap;
border: 1px solid rgba(255,255,255,0.5);
"
>{{ taskStatus.label }}</span>
</div>
</div>
<!-- 主体内容 -->
<div style="padding: 10px 14px; display: flex; flex-direction: column; gap: 8px;">
<!-- 进度条 -->
<div>
<div style="display: flex; justify-content: space-between; font-size: 11px; color: #555; margin-bottom: 4px;">
<span>完成进度</span>
<span style="font-weight: 600;" :style="{ color: taskStatus.color }">{{ task.progress ?? 0 }}%</span>
</div>
<div style="height: 6px; background: #eee; border-radius: 3px; overflow: hidden;">
<div
:style="{
height: '100%',
width: `${task.progress ?? 0}%`,
background: `linear-gradient(90deg, ${taskStatus.color} 0%, ${taskStatus.color}99 100%)`,
borderRadius: '3px',
transition: 'width 0.3s',
}"
/>
</div>
</div>
<!-- 资源利用率资源视图时 -->
<div v-if="resourcePercent !== 100">
<div style="display: flex; justify-content: space-between; font-size: 11px; color: #555; margin-bottom: 4px;">
<span>资源占用</span>
<span
style="font-weight: 600;"
:style="{ color: resourcePercent > 100 ? '#f56c6c' : '#67c23a' }"
>{{ resourcePercent }}%</span>
</div>
<div style="height: 6px; background: #eee; border-radius: 3px; overflow: hidden;">
<div
:style="{
height: '100%',
width: `${Math.min(resourcePercent, 100)}%`,
background: resourcePercent > 100 ? '#f56c6c' : '#67c23a',
borderRadius: '3px',
}"
/>
</div>
</div>
<!-- 日期信息 -->
<div style="display: flex; flex-direction: column; gap: 4px; font-size: 11px;">
<div style="display: flex; justify-content: space-between; color: #666;">
<span>📅 计划开始</span>
<span style="color: #333; font-weight: 500;">{{ task.startDate?.slice(0, 10) ?? '-' }}</span>
</div>
<div style="display: flex; justify-content: space-between; color: #666;">
<span>🏁 计划结束</span>
<span style="color: #333; font-weight: 500;">{{ task.endDate?.slice(0, 10) ?? '-' }}</span>
</div>
<template v-if="task.actualStartDate || task.actualEndDate">
<div style="border-top: 1px solid #f0f0f0; margin: 2px 0;"/>
<div v-if="task.actualStartDate" style="display: flex; justify-content: space-between; color: #888;">
<span> 实际开始</span>
<span style="color: #555; font-weight: 500;">{{ task.actualStartDate?.slice(0, 10) }}</span>
</div>
<div v-if="task.actualEndDate" style="display: flex; justify-content: space-between; color: #888;">
<span> 实际结束</span>
<span style="color: #555; font-weight: 500;">{{ task.actualEndDate?.slice(0, 10) }}</span>
</div>
</template>
</div>
<!-- 底部标签 -->
<div
style="
margin-top: 2px;
padding-top: 6px;
border-top: 1px solid #f0f0f0;
font-size: 10px;
color: #999;
text-align: right;
"
> 自定义 Slot Tooltip</div>
</div>
</div>
</template>
</template>
</GanttChart>
</div>