v1.0.6 - TaskDrawer.Progressbar style update; story progress cal based on sub-tasks' progress;
This commit is contained in:
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.6] - 2025-07-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 更新TaskDrawer组件的Progressbar样式
|
||||||
|
- 可通过拖拽进度条和文本修改的方式更改任务的进度
|
||||||
|
- Story进度根据子任务集进度改编重新计算
|
||||||
|
- 同时保留Story进度单独的调整方式
|
||||||
|
|
||||||
## [1.0.5] - 2025-07-02
|
## [1.0.5] - 2025-07-02
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
+5
-7
@@ -113,14 +113,14 @@ const handleMilestoneDelete = async (milestoneId: number) => {
|
|||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent('milestone-deleted', {
|
new CustomEvent('milestone-deleted', {
|
||||||
detail: { milestoneId },
|
detail: { milestoneId },
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
// 触发强制更新事件,确保Timeline重新渲染
|
// 触发强制更新事件,确保Timeline重新渲染
|
||||||
window.dispatchEvent(
|
window.dispatchEvent(
|
||||||
new CustomEvent('milestone-data-changed', {
|
new CustomEvent('milestone-data-changed', {
|
||||||
detail: { milestones: milestones.value },
|
detail: { milestones: milestones.value },
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,8 +240,6 @@ const handleTaskUpdate = (updatedTask: Task) => {
|
|||||||
showMessage(`就地更新失败,未找到任务,ID: ${updatedTask.id}`, 'warning', { closable: true })
|
showMessage(`就地更新失败,未找到任务,ID: ${updatedTask.id}`, 'warning', { closable: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage('任务更新完成', 'success', { closable: false })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 任务添加处理器
|
// 任务添加处理器
|
||||||
@@ -252,7 +250,7 @@ const handleTaskAdd = (newTask: Task) => {
|
|||||||
const maxId = Math.max(
|
const maxId = Math.max(
|
||||||
...tasks.value.map(t => t.id || 0),
|
...tasks.value.map(t => t.id || 0),
|
||||||
...milestones.value.map(m => m.id || 0),
|
...milestones.value.map(m => m.id || 0),
|
||||||
0
|
0,
|
||||||
)
|
)
|
||||||
newTask.id = maxId + 1
|
newTask.id = maxId + 1
|
||||||
}
|
}
|
||||||
@@ -383,7 +381,7 @@ function handleTaskbarDragOrResizeEnd(newTask) {
|
|||||||
`开始: ${oldTask.startDate} → ${newTask.startDate}\n` +
|
`开始: ${oldTask.startDate} → ${newTask.startDate}\n` +
|
||||||
`结束: ${oldTask.endDate} → ${newTask.endDate}`,
|
`结束: ${oldTask.endDate} → ${newTask.endDate}`,
|
||||||
'info',
|
'info',
|
||||||
{ closable: true }
|
{ closable: true },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
function handleMilestoneDragEnd(newMilestone) {
|
function handleMilestoneDragEnd(newMilestone) {
|
||||||
@@ -393,7 +391,7 @@ function handleMilestoneDragEnd(newMilestone) {
|
|||||||
`里程碑【${newMilestone.name}】\n` +
|
`里程碑【${newMilestone.name}】\n` +
|
||||||
`开始: ${oldMilestone.endDate} → ${newMilestone.startDate}`,
|
`开始: ${oldMilestone.endDate} → ${newMilestone.startDate}`,
|
||||||
'info',
|
'info',
|
||||||
{ closable: true }
|
{ closable: true },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,5 +105,15 @@
|
|||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"date": "2025-07-02",
|
"date": "2025-07-02",
|
||||||
"notes": ["更新README中所有NPM徽章CDN地址"]
|
"notes": ["更新README中所有NPM徽章CDN地址"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "1.0.6",
|
||||||
|
"date": "2025-07-03",
|
||||||
|
"notes": [
|
||||||
|
"更新TaskDrawer组件的Progressbar样式",
|
||||||
|
"可通过拖拽进度条和文本修改的方式更改任务的进度",
|
||||||
|
"Story进度根据子任务集进度改编重新计算",
|
||||||
|
"同时保留Story进度单独的调整方式"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jordium-gantt-vue3",
|
"name": "jordium-gantt-vue3",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/jordium-gantt-vue3.cjs.js",
|
"main": "dist/jordium-gantt-vue3.cjs.js",
|
||||||
"module": "dist/jordium-gantt-vue3.es.js",
|
"module": "dist/jordium-gantt-vue3.es.js",
|
||||||
|
|||||||
+6
-4
@@ -1,11 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, nextTick } from 'vue'
|
import { ref, 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'
|
||||||
import demoData from './data.json'
|
import demoData from './data.json'
|
||||||
import packageInfo from '../package.json'
|
import packageInfo from '../package.json'
|
||||||
// 导入主题变量
|
// 导入主题变量
|
||||||
|
import '../src/styles/theme-variables.css'
|
||||||
import VersionHistoryDrawer from './VersionHistoryDrawer.vue'
|
import VersionHistoryDrawer from './VersionHistoryDrawer.vue'
|
||||||
import { GanttChart, TaskDrawer, MilestoneDialog, useMessage, Task } from 'jordium-gantt-vue3'
|
import { useMessage } from '../src/composables/useMessage'
|
||||||
import 'jordium-gantt-vue3/dist/jordium-gantt-vue3.css'
|
import type { Task } from '../src/models/Task'
|
||||||
|
|
||||||
const { showMessage } = useMessage()
|
const { showMessage } = useMessage()
|
||||||
|
|
||||||
@@ -236,8 +240,6 @@ const handleTaskUpdate = (updatedTask: Task) => {
|
|||||||
showMessage(`就地更新失败,未找到任务,ID: ${updatedTask.id}`, 'warning', { closable: true })
|
showMessage(`就地更新失败,未找到任务,ID: ${updatedTask.id}`, 'warning', { closable: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage('任务更新完成', 'success', { closable: false })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 任务添加处理器
|
// 任务添加处理器
|
||||||
|
|||||||
+17
-8
@@ -20,7 +20,8 @@
|
|||||||
"progress": 100,
|
"progress": 100,
|
||||||
"estimatedHours": 16,
|
"estimatedHours": 16,
|
||||||
"actualHours": 15,
|
"actualHours": 15,
|
||||||
"type": "task"
|
"type": "task",
|
||||||
|
"parentId": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"estimatedHours": 24,
|
"estimatedHours": 24,
|
||||||
"actualHours": 28,
|
"actualHours": 28,
|
||||||
"type": "story",
|
"type": "story",
|
||||||
|
"parentId": 1,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
@@ -43,7 +45,8 @@
|
|||||||
"progress": 80,
|
"progress": 80,
|
||||||
"estimatedHours": 8,
|
"estimatedHours": 8,
|
||||||
"actualHours": 10,
|
"actualHours": 10,
|
||||||
"type": "task"
|
"type": "task",
|
||||||
|
"parentId": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
@@ -55,7 +58,8 @@
|
|||||||
"progress": 0,
|
"progress": 0,
|
||||||
"estimatedHours": 16,
|
"estimatedHours": 16,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task"
|
"type": "task",
|
||||||
|
"parentId": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
@@ -84,7 +88,8 @@
|
|||||||
"estimatedHours": 80,
|
"estimatedHours": 80,
|
||||||
"actualHours": 35,
|
"actualHours": 35,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "5"
|
"predecessor": "5",
|
||||||
|
"parentId": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
@@ -96,7 +101,8 @@
|
|||||||
"estimatedHours": 80,
|
"estimatedHours": 80,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "5"
|
"predecessor": "5",
|
||||||
|
"parentId": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 12,
|
"id": 12,
|
||||||
@@ -108,7 +114,8 @@
|
|||||||
"estimatedHours": 88,
|
"estimatedHours": 88,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "5"
|
"predecessor": "5",
|
||||||
|
"parentId": 7
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
@@ -134,7 +141,8 @@
|
|||||||
"estimatedHours": 48,
|
"estimatedHours": 48,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "7"
|
"predecessor": "7",
|
||||||
|
"parentId": 13
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 15,
|
"id": 15,
|
||||||
@@ -146,7 +154,8 @@
|
|||||||
"estimatedHours": 24,
|
"estimatedHours": 24,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "7"
|
"predecessor": "7",
|
||||||
|
"parentId": 13
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
|
|||||||
@@ -902,13 +902,72 @@ const handleMilestoneSave = (milestone: Task) => {
|
|||||||
// 处理任务更新事件
|
// 处理任务更新事件
|
||||||
const handleTaskUpdate = (event: CustomEvent) => {
|
const handleTaskUpdate = (event: CustomEvent) => {
|
||||||
const updatedTask = event.detail
|
const updatedTask = event.detail
|
||||||
if (props.onTaskUpdate && typeof props.onTaskUpdate === 'function') {
|
|
||||||
|
// 如果更新的是task类型且有parentId,需要同时更新对应的story进度
|
||||||
|
if (updatedTask.type === 'task' && updatedTask.parentId) {
|
||||||
|
const updatedStory = calculateStoryProgress(updatedTask.parentId, updatedTask)
|
||||||
|
|
||||||
|
// 先调用外部的任务更新处理器更新子任务
|
||||||
|
if (props.onTaskUpdate && typeof props.onTaskUpdate === 'function') {
|
||||||
|
props.onTaskUpdate(updatedTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果story进度有变化,也更新story
|
||||||
|
if (updatedStory && props.onTaskUpdate && typeof props.onTaskUpdate === 'function') {
|
||||||
|
props.onTaskUpdate(updatedStory)
|
||||||
|
}
|
||||||
|
} else if (props.onTaskUpdate && typeof props.onTaskUpdate === 'function') {
|
||||||
|
// 普通任务更新
|
||||||
props.onTaskUpdate(updatedTask)
|
props.onTaskUpdate(updatedTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关键:任务更新后强制刷新Timeline时间轴
|
// 关键:任务更新后强制刷新Timeline时间轴
|
||||||
updateTaskTrigger.value++
|
updateTaskTrigger.value++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算story的进度(根据其下所有task的进度计算)
|
||||||
|
const calculateStoryProgress = (storyId: number, updatedTask?: Task): Task | null => {
|
||||||
|
// 获取所有任务的扁平列表
|
||||||
|
const allTasks = [...(props.tasks || [])]
|
||||||
|
const flatTasks: Task[] = []
|
||||||
|
|
||||||
|
const flattenTasks = (tasks: Task[]) => {
|
||||||
|
tasks.forEach(task => {
|
||||||
|
flatTasks.push(task)
|
||||||
|
if (task.children && task.children.length > 0) {
|
||||||
|
flattenTasks(task.children)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
flattenTasks(allTasks)
|
||||||
|
|
||||||
|
// 找到对应的story
|
||||||
|
const storyTask = flatTasks.find(task => task.id === storyId && task.type === 'story')
|
||||||
|
if (!storyTask) return null
|
||||||
|
|
||||||
|
// 获取该story下所有的task
|
||||||
|
let childTasks = flatTasks.filter(task => task.parentId === storyId && task.type === 'task')
|
||||||
|
if (childTasks.length === 0) return null
|
||||||
|
|
||||||
|
// 如果有正在更新的task,使用最新的数据替换旧数据
|
||||||
|
if (updatedTask && updatedTask.type === 'task' && updatedTask.parentId === storyId) {
|
||||||
|
childTasks = childTasks.map(task => (task.id === updatedTask.id ? updatedTask : task))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算平均进度
|
||||||
|
const totalProgress = childTasks.reduce((sum, task) => sum + (task.progress || 0), 0)
|
||||||
|
const avgProgress = Math.round(totalProgress / childTasks.length)
|
||||||
|
|
||||||
|
// 如果进度有变化,返回更新后的story
|
||||||
|
if (storyTask.progress !== avgProgress) {
|
||||||
|
return { ...storyTask, progress: avgProgress }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进度没有变化,返回null
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// 处理任务添加事件
|
// 处理任务添加事件
|
||||||
const handleTaskAdd = (event: CustomEvent) => {
|
const handleTaskAdd = (event: CustomEvent) => {
|
||||||
const newTask = event.detail
|
const newTask = event.detail
|
||||||
|
|||||||
@@ -27,7 +27,19 @@ const dragStartLeft = ref(0)
|
|||||||
const tempMilestoneData = ref<{ startDate?: string } | null>(null)
|
const tempMilestoneData = ref<{ startDate?: string } | null>(null)
|
||||||
|
|
||||||
// 双击事件处理
|
// 双击事件处理
|
||||||
const handleDoubleClick = () => {
|
const handleDoubleClick = (e: MouseEvent) => {
|
||||||
|
// 阻止事件冒泡和默认行为
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
// 清理任何可能残留的拖拽状态
|
||||||
|
isDragging.value = false
|
||||||
|
tempMilestoneData.value = null
|
||||||
|
|
||||||
|
// 移除可能残留的事件监听器
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
|
||||||
if (props.milestone) {
|
if (props.milestone) {
|
||||||
emit('milestone-double-click', props.milestone)
|
emit('milestone-double-click', props.milestone)
|
||||||
} else {
|
} else {
|
||||||
@@ -57,10 +69,11 @@ const formatDateToLocalString = (date: Date): string => {
|
|||||||
|
|
||||||
// 拖拽事件处理
|
// 拖拽事件处理
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
|
// 如果正在双击过程中,不启动拖拽
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
isDragging.value = true
|
// 设置拖拽状态,但不立即开始拖拽
|
||||||
dragStartX.value = e.clientX
|
dragStartX.value = e.clientX
|
||||||
dragStartLeft.value = parseInt(milestoneStyle.value.left)
|
dragStartLeft.value = parseInt(milestoneStyle.value.left)
|
||||||
tempMilestoneData.value = null
|
tempMilestoneData.value = null
|
||||||
@@ -71,8 +84,12 @@ const handleMouseDown = (e: MouseEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (isDragging.value) {
|
const deltaX = e.clientX - dragStartX.value
|
||||||
const deltaX = e.clientX - dragStartX.value
|
|
||||||
|
// 只有在真正移动了一定距离后才开始拖拽(避免意外触发)
|
||||||
|
if (Math.abs(deltaX) > 3) {
|
||||||
|
isDragging.value = true
|
||||||
|
|
||||||
const newLeft = Math.max(0, dragStartLeft.value + deltaX)
|
const newLeft = Math.max(0, dragStartLeft.value + deltaX)
|
||||||
const newStartDate = addDaysToLocalDate(props.startDate, newLeft / props.dayWidth)
|
const newStartDate = addDaysToLocalDate(props.startDate, newLeft / props.dayWidth)
|
||||||
|
|
||||||
@@ -84,17 +101,21 @@ const handleMouseMove = (e: MouseEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
// 如果有临时数据,说明发生了拖拽,提交数据更新
|
// 只有在真正拖拽了(有临时数据)且状态为拖拽中时才触发更新
|
||||||
if (tempMilestoneData.value && props.milestone) {
|
if (isDragging.value && tempMilestoneData.value && props.milestone) {
|
||||||
const updatedMilestone = {
|
const updatedMilestone = {
|
||||||
...props.milestone,
|
...props.milestone,
|
||||||
...tempMilestoneData.value,
|
...tempMilestoneData.value,
|
||||||
}
|
}
|
||||||
emit('update:milestone', updatedMilestone)
|
emit('update:milestone', updatedMilestone)
|
||||||
emit('drag-end', updatedMilestone) // 新增
|
emit('drag-end', updatedMilestone)
|
||||||
tempMilestoneData.value = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置所有拖拽状态
|
||||||
isDragging.value = false
|
isDragging.value = false
|
||||||
|
tempMilestoneData.value = null
|
||||||
|
|
||||||
|
// 移除事件监听器
|
||||||
document.removeEventListener('mousemove', handleMouseMove)
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
document.removeEventListener('mouseup', handleMouseUp)
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
}
|
}
|
||||||
@@ -144,6 +165,11 @@ const milestoneIcon = computed(() => {
|
|||||||
|
|
||||||
// 组件销毁时清理事件监听器
|
// 组件销毁时清理事件监听器
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
// 清理拖拽状态
|
||||||
|
isDragging.value = false
|
||||||
|
tempMilestoneData.value = null
|
||||||
|
|
||||||
|
// 移除事件监听器
|
||||||
document.removeEventListener('mousemove', handleMouseMove)
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
document.removeEventListener('mouseup', handleMouseUp)
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -306,11 +306,17 @@ watch(
|
|||||||
const handleTaskBarDoubleClick = (e: MouseEvent) => {
|
const handleTaskBarDoubleClick = (e: MouseEvent) => {
|
||||||
// 阻止事件冒泡,避免触发拖拽等其他事件
|
// 阻止事件冒泡,避免触发拖拽等其他事件
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
// 如果正在拖拽或调整大小,不触发双击事件
|
// 清理任何可能残留的拖拽状态和临时数据
|
||||||
if (isDragging.value || isResizingLeft.value || isResizingRight.value) {
|
isDragging.value = false
|
||||||
return
|
isResizingLeft.value = false
|
||||||
}
|
isResizingRight.value = false
|
||||||
|
tempTaskData.value = null
|
||||||
|
|
||||||
|
// 移除可能残留的事件监听器
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
|
||||||
// 优先调用外部传入的双击处理器
|
// 优先调用外部传入的双击处理器
|
||||||
if (props.onDoubleClick && typeof props.onDoubleClick === 'function') {
|
if (props.onDoubleClick && typeof props.onDoubleClick === 'function') {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, watch, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, reactive, watch, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { useI18n } from '../composables/useI18n'
|
import { useI18n } from '../composables/useI18n'
|
||||||
|
import { useMessage } from '../composables/useMessage'
|
||||||
import DatePicker from './DatePicker.vue'
|
import DatePicker from './DatePicker.vue'
|
||||||
import GanttConfirmDialog from './GanttConfirmDialog.vue'
|
import GanttConfirmDialog from './GanttConfirmDialog.vue'
|
||||||
import type { Task } from '../models/classes/Task'
|
import type { Task } from '../models/classes/Task'
|
||||||
@@ -28,6 +29,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const { showMessage } = useMessage()
|
||||||
|
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const isVisible = ref(props.visible)
|
const isVisible = ref(props.visible)
|
||||||
@@ -55,7 +57,7 @@ const allTasks = ref<Task[]>([])
|
|||||||
// 获取可作为前置任务的任务列表(只包含type="task"的任务,且不包含当前任务)
|
// 获取可作为前置任务的任务列表(只包含type="task"的任务,且不包含当前任务)
|
||||||
const availablePredecessorTasks = computed(() => {
|
const availablePredecessorTasks = computed(() => {
|
||||||
return allTasks.value.filter(
|
return allTasks.value.filter(
|
||||||
task => task.type === 'task' && task.id !== props.task?.id // 排除当前任务自己
|
task => task.type === 'task' && task.id !== props.task?.id, // 排除当前任务自己
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ const availableParentTasks = computed(() => {
|
|||||||
.filter(
|
.filter(
|
||||||
task =>
|
task =>
|
||||||
task.id !== props.task?.id && // 排除当前任务自己
|
task.id !== props.task?.id && // 排除当前任务自己
|
||||||
(task.type === 'story' || task.type === 'task') // 只显示story和task类型
|
(task.type === 'story' || task.type === 'task'), // 只显示story和task类型
|
||||||
)
|
)
|
||||||
.map(task => ({
|
.map(task => ({
|
||||||
...task,
|
...task,
|
||||||
@@ -191,7 +193,7 @@ watch(
|
|||||||
// 抽屉显示时重新请求任务数据,确保前置任务列表是最新的
|
// 抽屉显示时重新请求任务数据,确保前置任务列表是最新的
|
||||||
window.dispatchEvent(new CustomEvent('request-task-list'))
|
window.dispatchEvent(new CustomEvent('request-task-list'))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听 isVisible 变化,同步到父组件
|
// 监听 isVisible 变化,同步到父组件
|
||||||
@@ -278,8 +280,7 @@ const handleSubmit = async () => {
|
|||||||
showMessage(props.isEdit ? t.value.taskUpdateSuccess : t.value.taskCreateSuccess, 'success')
|
showMessage(props.isEdit ? t.value.taskUpdateSuccess : t.value.taskCreateSuccess, 'success')
|
||||||
handleClose()
|
handleClose()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 记录异常,保证 lint 通过
|
// 处理错误但不在控制台输出
|
||||||
console.error(error)
|
|
||||||
showMessage(t.value.operationFailed, 'error')
|
showMessage(t.value.operationFailed, 'error')
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
@@ -327,42 +328,13 @@ onUnmounted(() => {
|
|||||||
window.removeEventListener('task-list-updated', handleTasksChanged as EventListener)
|
window.removeEventListener('task-list-updated', handleTasksChanged as EventListener)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 简单的消息提示函数
|
|
||||||
const showMessage = (message: string, type: 'success' | 'error') => {
|
|
||||||
// 创建消息元素
|
|
||||||
const messageEl = document.createElement('div')
|
|
||||||
messageEl.className = `message ${type}`
|
|
||||||
messageEl.textContent = message
|
|
||||||
messageEl.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
padding: 12px 20px;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
font-size: 14px;
|
|
||||||
z-index: 9999;
|
|
||||||
background: ${type === 'success' ? '#67c23a' : '#f56c6c'};
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
`
|
|
||||||
|
|
||||||
document.body.appendChild(messageEl)
|
|
||||||
|
|
||||||
// 3秒后自动移除
|
|
||||||
setTimeout(() => {
|
|
||||||
if (messageEl.parentNode) {
|
|
||||||
document.body.removeChild(messageEl)
|
|
||||||
}
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听 formData.progress 变化,同步更新显示值
|
// 监听 formData.progress 变化,同步更新显示值
|
||||||
watch(
|
watch(
|
||||||
() => formData.progress,
|
() => formData.progress,
|
||||||
newValue => {
|
newValue => {
|
||||||
progressDisplayValue.value = (newValue || 0).toString()
|
progressDisplayValue.value = (newValue || 0).toString()
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ function getContainer() {
|
|||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 防重复消息队列,存储最近的消息内容和时间
|
||||||
|
const recentMessages = new Map<string, number>()
|
||||||
|
const DUPLICATE_THRESHOLD = 1000 // 1秒内相同消息视为重复
|
||||||
|
|
||||||
export function useMessage() {
|
export function useMessage() {
|
||||||
/**
|
/**
|
||||||
* @param message 消息内容
|
* @param message 消息内容
|
||||||
@@ -38,12 +42,32 @@ export function useMessage() {
|
|||||||
* @param options 可选项:
|
* @param options 可选项:
|
||||||
* - duration: 自动关闭的毫秒数(默认3000,0表示不自动关闭)
|
* - duration: 自动关闭的毫秒数(默认3000,0表示不自动关闭)
|
||||||
* - closable: 是否显示关闭按钮(true=手动关闭,false=自动关闭)
|
* - closable: 是否显示关闭按钮(true=手动关闭,false=自动关闭)
|
||||||
|
* - allowDuplicate: 是否允许重复消息(默认false)
|
||||||
*/
|
*/
|
||||||
const showMessage = (
|
const showMessage = (
|
||||||
message: string,
|
message: string,
|
||||||
type: MessageType = 'success',
|
type: MessageType = 'success',
|
||||||
options?: { duration?: number; closable?: boolean },
|
options?: { duration?: number; closable?: boolean; allowDuplicate?: boolean },
|
||||||
) => {
|
) => {
|
||||||
|
// 防重复检查
|
||||||
|
const now = Date.now()
|
||||||
|
const messageKey = `${type}:${message}`
|
||||||
|
const lastTime = recentMessages.get(messageKey)
|
||||||
|
|
||||||
|
if (!options?.allowDuplicate && lastTime && now - lastTime < DUPLICATE_THRESHOLD) {
|
||||||
|
// 重复消息,忽略
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录消息时间
|
||||||
|
recentMessages.set(messageKey, now)
|
||||||
|
|
||||||
|
// 清理过期的消息记录
|
||||||
|
for (const [key, time] of recentMessages.entries()) {
|
||||||
|
if (now - time > DUPLICATE_THRESHOLD * 2) {
|
||||||
|
recentMessages.delete(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
const messageEl = document.createElement('div')
|
const messageEl = document.createElement('div')
|
||||||
messageEl.className = `message ${type}`
|
messageEl.className = `message ${type}`
|
||||||
messageEl.textContent = message
|
messageEl.textContent = message
|
||||||
|
|||||||
Reference in New Issue
Block a user