diff --git a/CHANGELOG.md b/CHANGELOG.md index 1650bc1..143878b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.1] - 2025-10-26 + +### Fixed +- TaskList客制化列展示问题修复 +- TaskList客制化列数据动态绑定问题修复 +- SonarQube代码质量检查问题修改 +- MTaskList Custom Column Display Issue Fix +- TaskList Custom Column Data Dynamic Binding Issue Fix +- SonarQube code quality inspection issue modification + ## [1.4.0] - 2025-10-11 ### Added diff --git a/demo/App.vue b/demo/App.vue index c5023fb..7d79204 100644 --- a/demo/App.vue +++ b/demo/App.vue @@ -53,6 +53,7 @@ const availableColumns = ref([ { key: 'estimatedHours', label: '预估工时', visible: true }, { key: 'actualHours', label: '实际工时', visible: true }, { key: 'progress', label: '进度', visible: true }, + { key: 'custom', label: '自定义列', visible: true, width: 120 }, // 添加默认宽度120px ]) // TaskList宽度配置 diff --git a/demo/data.json b/demo/data.json index b9f2fc1..6cceab7 100644 --- a/demo/data.json +++ b/demo/data.json @@ -11,6 +11,7 @@ "actualHours": 3942.75, "type": "story", "description": "ADX-2024新药完整临床试验计划,从I期到III期临床试验", + "custom": 180000, "children": [ { "id": 1100, @@ -24,6 +25,7 @@ "type": "story", "parentId": 1000, "description": "评估ADX-2024在健康志愿者和患者中的安全性、耐受性和药代动力学特征", + "custom": "test11111", "children": [ { "id": 1101, @@ -36,7 +38,8 @@ "actualHours": 480, "type": "task", "parentId": 1100, - "description": "完成I期试验方案设计、伦理委员会审批及监管部门申报" + "description": "完成I期试验方案设计、伦理委员会审批及监管部门申报", + "custom": "test11111" }, { "id": 1102, @@ -50,7 +53,8 @@ "type": "task", "predecessor": [1101], "parentId": 1100, - "description": "招募24名健康志愿者,完成医学筛选和入组评估" + "description": "招募24名健康志愿者,完成医学筛选和入组评估", + "custom": "test11111" }, { "id": 1103, @@ -64,7 +68,8 @@ "type": "task", "predecessor": [1102], "parentId": 1100, - "description": "按照剂量递增方案给药,密切监测不良反应和药代动力学参数" + "description": "按照剂量递增方案给药,密切监测不良反应和药代动力学参数", + "custom": "test11111" } ], "collapsed": false @@ -81,6 +86,7 @@ "type": "story", "parentId": 1000, "description": "在目标患者群体中评估ADX-2024的初步疗效和进一步的安全性", + "custom": "test11111", "children": [ { "id": 1201, @@ -94,7 +100,8 @@ "type": "task", "predecessor": [1103], "parentId": 1200, - "description": "启动5个临床中心,完成研究者培训和质量体系建立" + "description": "启动5个临床中心,完成研究者培训和质量体系建立", + "custom": "test11111" }, { "id": 1202, @@ -108,7 +115,8 @@ "type": "task", "predecessor": [1201], "parentId": 1200, - "description": "计划入组120名患者,采用双盲随机对照设计" + "description": "计划入组120名患者,采用双盲随机对照设计", + "custom": "test11111" }, { "id": 1203, @@ -122,7 +130,8 @@ "type": "task", "predecessor": [1202], "parentId": 1200, - "description": "定期评估患者疗效指标,收集安全性数据,进行中期分析" + "description": "定期评估患者疗效指标,收集安全性数据,进行中期分析", + "custom": "test11111" } ], "collapsed": false diff --git a/demo/version-history.json b/demo/version-history.json index 29fcc10..6e05b8e 100644 --- a/demo/version-history.json +++ b/demo/version-history.json @@ -216,5 +216,19 @@ "特别感谢贡献者 @qiuchengw", "Special thanks to contributor @qiuchengw" ] + }, + { + "version": "1.4.1", + "date": "2025-10-26", + "notes": [ + "TaskList客制化列展示问题修复", + "TaskList Custom Column Display Issue Fix", + "TaskList客制化列数据动态绑定问题修复", + "TaskList Custom Column Data Dynamic Binding Issue Fix", + "SonarQube代码质量检查问题修改", + "SonarQube code quality inspection issue modification", + "特别感谢 @Guduxwa", + "Special thanks to @Guduxwa" + ] } ] diff --git a/package.json b/package.json index 4ddaedc..701b02f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jordium-gantt-vue3", - "version": "1.4.0", + "version": "1.4.1", "type": "module", "main": "dist/jordium-gantt-vue3.cjs.js", "module": "dist/jordium-gantt-vue3.es.js", diff --git a/src/components/TaskBar.vue b/src/components/TaskBar.vue index e7e73dd..7d4ef92 100644 --- a/src/components/TaskBar.vue +++ b/src/components/TaskBar.vue @@ -244,13 +244,13 @@ const taskBarStyle = computed(() => { const startDateOnly = new Date( startDate.getFullYear(), startDate.getMonth(), - startDate.getDate() + startDate.getDate(), ) const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate()) const baseStartOnly = new Date( baseStart.getFullYear(), baseStart.getMonth(), - baseStart.getDate() + baseStart.getDate(), ) if (props.currentTimeScale === TimelineScale.YEAR) { @@ -272,7 +272,7 @@ const taskBarStyle = computed(() => { const startPosition = calculatePositionFromTimelineData( startDateOnly, props.timelineData, - props.currentTimeScale + props.currentTimeScale, ) // 计算结束位置:为结束日期添加一天来获取正确的结束位置 const nextDay = new Date(endDateOnly) @@ -280,7 +280,7 @@ const taskBarStyle = computed(() => { let endPosition = calculatePositionFromTimelineData( nextDay, props.timelineData, - props.currentTimeScale + props.currentTimeScale, ) // 如果结束日期+1天超出范围,使用结束日期的位置+一天的宽度 @@ -295,7 +295,7 @@ const taskBarStyle = computed(() => { calculatePositionFromTimelineData( endDateOnly, props.timelineData, - props.currentTimeScale + props.currentTimeScale, ) + dayWidth } @@ -304,7 +304,7 @@ const taskBarStyle = computed(() => { } else { // 日视图:基于日期的简单计算 const startDiff = Math.floor( - (startDateOnly.getTime() - baseStartOnly.getTime()) / (1000 * 60 * 60 * 24) + (startDateOnly.getTime() - baseStartOnly.getTime()) / (1000 * 60 * 60 * 24), ) // 计算持续天数(基于日期,忽略时间) @@ -501,7 +501,7 @@ const handleMouseMove = (e: MouseEvent) => { mouseX: e.clientX, isDragging: isDragging.value || isResizingLeft.value || isResizingRight.value, }, - }) + }), ) // 更新拖拽提示框位置 @@ -766,7 +766,7 @@ const handleMouseMove = (e: MouseEvent) => { const newDurationDays = newWidth / props.dayWidth const newEndDate = addDaysToLocalDate( props.startDate, - resizeStartLeft.value / props.dayWidth + newDurationDays - 1 + resizeStartLeft.value / props.dayWidth + newDurationDays - 1, ) // 只更新临时数据,不触发事件 @@ -798,7 +798,7 @@ const handleMouseUp = () => { mouseX: 0, isDragging: false, }, - }) + }), ) // 如果有临时数据,说明发生了拖拽或拉伸,提交数据更新 @@ -882,7 +882,7 @@ watch( reportBarPosition() }) }, - { deep: true } + { deep: true }, ) // 处理TaskBar双击事件 @@ -1171,7 +1171,7 @@ watch( } }, 200) } - } + }, ) // 监听外部hideBubbles属性变化,确保Timeline的容器变化能及时反应 @@ -1184,7 +1184,7 @@ watch( // 强制重新计算bubbleIndicator,确保容器宽度变化后正确显示半圆 }) } - } + }, ) // 监听TaskBar可见性变化,只在滚动时实现重新出现动画 @@ -1193,7 +1193,7 @@ watch( () => { // TaskBar重新出现时,不需要动画效果 // 半圆会自然消失,TaskBar会立即显示 - } + }, ) // 监听页面缩放和大小变化,重新计算气泡位置 @@ -1353,13 +1353,13 @@ const calculateYearViewPosition = (targetDate: Date, baseStartDate: Date): numbe const daysInHalfYear = month <= 6 ? Math.floor( - (new Date(targetYear, 6, 1).getTime() - new Date(targetYear, 0, 1).getTime()) / - (1000 * 60 * 60 * 24) - ) + (new Date(targetYear, 6, 1).getTime() - new Date(targetYear, 0, 1).getTime()) / + (1000 * 60 * 60 * 24), + ) : Math.floor( - (new Date(targetYear + 1, 0, 1).getTime() - new Date(targetYear, 6, 1).getTime()) / - (1000 * 60 * 60 * 24) - ) + (new Date(targetYear + 1, 0, 1).getTime() - new Date(targetYear, 6, 1).getTime()) / + (1000 * 60 * 60 * 24), + ) const dayPositionInHalfYear = (dayOffset / daysInHalfYear) * halfYearWidth position += dayPositionInHalfYear @@ -1383,7 +1383,7 @@ const calculatePositionFromTimelineData = ( subDays: Array<{ date: Date; dayOfWeek?: number }> }> }>, - timeScale: TimelineScale + timeScale: TimelineScale, ) => { let cumulativePosition = 0 @@ -1401,11 +1401,11 @@ const calculatePositionFromTimelineData = ( // 找到目标日期所在的季度 const quarterWidth = 60 const daysInQuarter = Math.ceil( - (quarterEnd.getTime() - quarterStart.getTime()) / (1000 * 60 * 60 * 24) + (quarterEnd.getTime() - quarterStart.getTime()) / (1000 * 60 * 60 * 24), ) const dayWidth = quarterWidth / daysInQuarter const dayInQuarter = Math.ceil( - (targetDate.getTime() - quarterStart.getTime()) / (1000 * 60 * 60 * 24) + (targetDate.getTime() - quarterStart.getTime()) / (1000 * 60 * 60 * 24), ) return cumulativePosition + dayInQuarter * dayWidth } diff --git a/src/components/TaskDrawer.vue b/src/components/TaskDrawer.vue index e9b5dbf..7636486 100644 --- a/src/components/TaskDrawer.vue +++ b/src/components/TaskDrawer.vue @@ -68,7 +68,7 @@ watch( () => [props.task?.isTimerRunning, props.task?.timerStartTime, props.task?.timerElapsedTime], () => { updateTimer() - } + }, ) // 计时器本地状态,保证点击后UI立即切换 @@ -84,7 +84,7 @@ watch( timerInterval.value = null } }, - { immediate: true } + { immediate: true }, ) // 修正计时器每秒递增逻辑,保证计时器正常跳动 @@ -102,7 +102,7 @@ watch( updateTimer() } }, - { immediate: true } + { immediate: true }, ) onUnmounted(() => { @@ -153,7 +153,7 @@ const availableParentTasks = computed(() => { .filter( task => task.id !== props.task?.id && // 排除当前任务自己 - (task.type === 'story' || task.type === 'task') // 只显示story和task类型 + (task.type === 'story' || task.type === 'task'), // 只显示story和task类型 ) .map(task => ({ ...task, @@ -346,7 +346,7 @@ watch( // 抽屉显示时重新请求任务数据,确保前置任务列表是最新的 window.dispatchEvent(new CustomEvent('request-task-list')) } - } + }, ) // 监听 isVisible 变化,同步到父组件 @@ -363,7 +363,7 @@ watch( formData.parentId = newTask.parentId ?? undefined } }, - { immediate: true } + { immediate: true }, ) // 重置表单 @@ -548,7 +548,7 @@ watch( newValue => { progressDisplayValue.value = (newValue || 0).toString() }, - { immediate: true } + { immediate: true }, ) // 修正计时器首次启动不跳动问题:每次打开抽屉时重置 timerElapsed,且 timerStartTime 为空时立即赋值 @@ -566,7 +566,7 @@ watch( } } }, - { immediate: true } + { immediate: true }, ) const handleStartTimer = (desc?: string) => { diff --git a/src/components/TaskRow.vue b/src/components/TaskRow.vue index 4affab3..6f94284 100644 --- a/src/components/TaskRow.vue +++ b/src/components/TaskRow.vue @@ -67,7 +67,7 @@ const isStoryTask = computed(() => props.task.type === 'story') const isMilestoneGroup = computed(() => props.task.type === 'milestone-group') const isMilestoneTask = computed(() => props.task.type === 'milestone') const isParentTask = computed( - () => isStoryTask.value || hasChildren.value || isMilestoneGroup.value + () => isStoryTask.value || hasChildren.value || isMilestoneGroup.value, ) function handleToggle() { emit('toggle', props.task) @@ -221,7 +221,7 @@ watch( updateTimer() } }, - { immediate: true } + { immediate: true }, ) // 右键菜单相关状态 @@ -464,6 +464,11 @@ onUnmounted(() => { {{ props.task.progress != null ? props.task.progress + '%' : '-' }} + + + diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index 480f21f..6af142d 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -354,7 +354,7 @@ const optimizedTimelineData = computed(() => { const currentDay = new Date(day.year, day.month - 1, day.day) currentDay.setHours(0, 0, 0, 0) const daysDiff = Math.floor( - (currentDay.getTime() - dayStart.getTime()) / (1000 * 60 * 60 * 24) + (currentDay.getTime() - dayStart.getTime()) / (1000 * 60 * 60 * 24), ) const totalHourOffset = daysDiff * 24 @@ -455,7 +455,7 @@ const computeAllMilestonesPositions = () => { if (!isNaN(milestoneDate.getTime())) { const startDiff = Math.floor( (milestoneDate.getTime() - timelineConfig.value.startDate.getTime()) / - (1000 * 60 * 60 * 24) + (1000 * 60 * 60 * 24), ) const left = startDiff * 30 + 30 / 2 - 12 // 30是dayWidth,12是图标半径 @@ -489,7 +489,7 @@ const computeAllMilestonesPositions = () => { if (!isNaN(milestoneDate.getTime())) { const startDiff = Math.floor( (milestoneDate.getTime() - timelineConfig.value.startDate.getTime()) / - (1000 * 60 * 60 * 24) + (1000 * 60 * 60 * 24), ) const left = startDiff * 30 + 30 / 2 - 12 @@ -592,7 +592,7 @@ const handleTaskRowHover = (taskId: number | null) => { window.dispatchEvent( new CustomEvent('timeline-task-hover', { detail: taskId, - }) + }), ) } @@ -644,7 +644,7 @@ const handleMilestoneIconChange = (milestoneId: number, icon: string) => { window.dispatchEvent( new CustomEvent('milestone-icon-changed', { detail: { milestoneId, icon }, - }) + }), ) } @@ -662,7 +662,7 @@ const handleMilestoneSave = (updatedMilestone: Milestone) => { window.dispatchEvent( new CustomEvent('milestone-data-updated', { detail: { milestone: updatedMilestone }, - }) + }), ) } @@ -675,14 +675,14 @@ const handleMilestoneDelete = (milestoneId: number) => { window.dispatchEvent( new CustomEvent('milestone-deleted', { detail: { milestoneId }, - }) + }), ) // 广播里程碑数据变化事件,确保Timeline重新渲染 window.dispatchEvent( new CustomEvent('milestone-data-changed', { detail: { milestoneId }, - }) + }), ) } @@ -697,7 +697,7 @@ const handleMilestoneUpdate = (updatedMilestone: Milestone) => { window.dispatchEvent( new CustomEvent('milestone-data-updated', { detail: { milestone: updatedMilestone }, - }) + }), ) } @@ -1017,7 +1017,7 @@ watch( clearTimelineCache() timelineData.value = generateTimelineData() } - } + }, ) // 保证每次时间轴数据变化后都自动居中今日(仅初始化和外部props变更时触发,不因任务/里程碑变更触发) @@ -1032,7 +1032,7 @@ watch( }) } }, - { deep: true } + { deep: true }, ) // 将今日定位到时间线中间位置 @@ -1057,13 +1057,13 @@ const scrollToTodayCenter = (retry = 0) => { startNormalized = new Date( yearRange.startDate.getFullYear(), yearRange.startDate.getMonth(), - yearRange.startDate.getDate() + yearRange.startDate.getDate(), ) } else { startNormalized = new Date( timelineStart.getFullYear(), timelineStart.getMonth(), - timelineStart.getDate() + timelineStart.getDate(), ) } @@ -1136,7 +1136,7 @@ const scrollToTodayCenter = (retry = 0) => { } const dayOffset = Math.floor( - (todayNormalized.getTime() - startOfQuarter.getTime()) / (1000 * 60 * 60 * 24) + (todayNormalized.getTime() - startOfQuarter.getTime()) / (1000 * 60 * 60 * 24), ) const daysInQuarter = Math.floor((endOfQuarter.getTime() - startOfQuarter.getTime()) / (1000 * 60 * 60 * 24)) + 1 @@ -1193,7 +1193,7 @@ const getTodayLinePositionInYearView = computed(() => { const startNormalized = new Date( yearRange.startDate.getFullYear(), yearRange.startDate.getMonth(), - yearRange.startDate.getDate() + yearRange.startDate.getDate(), ) const startYear = startNormalized.getFullYear() @@ -1287,7 +1287,7 @@ const scrollToToday = () => { const startNormalized = new Date( timelineStart.getFullYear(), timelineStart.getMonth(), - timelineStart.getDate() + timelineStart.getDate(), ) // 计算今天距离时间线开始日期的天数 @@ -1338,7 +1338,7 @@ const updateTask = (updatedTask: Task) => { window.dispatchEvent( new CustomEvent('task-updated', { detail: updatedTask, - }) + }), ) } @@ -1398,7 +1398,7 @@ const handleTaskBarContextMenu = (event: { task: Task; position: { x: number; y: window.dispatchEvent( new CustomEvent('context-menu', { detail: event, - }) + }), ) } @@ -1481,7 +1481,7 @@ onMounted(() => { // 监听TaskList的垂直滚动事件 window.addEventListener( 'task-list-vertical-scroll', - handleTaskListVerticalScroll as EventListener + handleTaskListVerticalScroll as EventListener, ) // 监听语言变化 window.addEventListener('locale-changed', handleLocaleChange as EventListener) @@ -1491,7 +1491,7 @@ onMounted(() => { // 监听Timeline容器resize事件(TaskList切换等) window.addEventListener( 'timeline-container-resized', - handleTimelineContainerResized as EventListener + handleTimelineContainerResized as EventListener, ) // 监听里程碑点击定位事件 @@ -1581,7 +1581,7 @@ const handleTimelineBodyScroll = (event: Event) => { window.dispatchEvent( new CustomEvent('timeline-vertical-scroll', { detail: { scrollTop }, - }) + }), ) } } @@ -1594,7 +1594,7 @@ watch( updateSvgSize() }) }, - { immediate: true } + { immediate: true }, ) // 拖拽滑动相关状态 @@ -1778,7 +1778,7 @@ const startAutoScroll = (direction: 'left' | 'right') => { window.dispatchEvent( new CustomEvent('timeline-auto-scroll', { detail: { scrollDelta: newScrollLeft - currentScrollLeft }, - }) + }), ) autoScrollTimer = window.setTimeout(scroll, 16) // 约60fps @@ -1832,14 +1832,14 @@ onUnmounted(() => { window.removeEventListener('task-list-hover', handleTaskListHover as EventListener) window.removeEventListener( 'task-list-vertical-scroll', - handleTaskListVerticalScroll as EventListener + handleTaskListVerticalScroll as EventListener, ) window.removeEventListener('locale-changed', handleLocaleChange as EventListener) window.removeEventListener('splitter-drag-start', handleSplitterDragStart as EventListener) window.removeEventListener('splitter-drag-end', handleSplitterDragEnd as EventListener) window.removeEventListener( 'timeline-container-resized', - handleTimelineContainerResized as EventListener + handleTimelineContainerResized as EventListener, ) window.removeEventListener('milestone-click-locate', handleMilestoneClickLocate as EventListener) window.removeEventListener('drag-boundary-check', handleDragBoundaryCheck as EventListener) @@ -1950,7 +1950,7 @@ watch( } }) }, - { deep: true } + { deep: true }, ) // 处理里程碑点击定位事件 diff --git a/src/models/classes/Task.ts b/src/models/classes/Task.ts index e5f92cd..353ca0a 100644 --- a/src/models/classes/Task.ts +++ b/src/models/classes/Task.ts @@ -23,4 +23,6 @@ export interface Task { timerEndTime?: number // 结束计时时间 timerStartDesc?: string // 计时开始时填写的描述 timerElapsedTime?: number + // 支持自定义属性 - 使用 unknown 允许任意类型 + [key: string]: unknown } diff --git a/src/styles/list.css b/src/styles/list.css index b14b3ca..024e418 100644 --- a/src/styles/list.css +++ b/src/styles/list.css @@ -8,6 +8,10 @@ border-right: 1px solid var(--gantt-border-light); box-sizing: border-box; overflow: hidden; + /* 为没有特定样式的列设置默认宽度 */ + flex: 1 0 120px; + min-width: 120px; + max-width: 120px; } .col:last-child {