v1.0.9 - Add Story and sub-tasks deletion logic; l18n updates; GanttConfirmDialog updates

This commit is contained in:
LINING-PC\lining
2025-07-04 21:41:06 +08:00
parent 06c049f49c
commit 94ee66cbb4
15 changed files with 651 additions and 292 deletions
+9
View File
@@ -5,6 +5,15 @@ 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.0.9] - 2025-07-04
### Changed
- 更新多语言标签
### Added
- 增加Story删除时选择是否删除子任务的选项
- 删除Story或Task时同时删除前后的关系线路
## [1.0.6] - 2025-07-03
### Changed
+195 -19
View File
@@ -9,9 +9,11 @@ import packageInfo from '../package.json'
import '../src/styles/theme-variables.css'
import VersionHistoryDrawer from './VersionHistoryDrawer.vue'
import { useMessage } from '../src/composables/useMessage'
import { useI18n } from '../src/composables/useI18n'
import type { Task } from '../src/models/Task'
const { showMessage } = useMessage()
const { t, formatTranslation } = useI18n()
const tasks = ref<Task[]>([])
const milestones = ref<Task[]>([])
@@ -42,7 +44,7 @@ const toolbarConfig = {
// 自定义CSV导出处理器(可选)
const handleCustomCsvExport = () => {
showMessage('自定义CSV导出被调用', 'info', { closable: true })
showMessage(t.value.customCsvExportCalled, 'info', { closable: true })
// 这里可以实现自定义的CSV导出逻辑
// 例如:添加额外的数据处理、格式化、或发送到服务器等
@@ -70,11 +72,17 @@ const handleAddMilestone = () => {
}
const handleLanguageChange = (lang: 'zh' | 'en') => {
showMessage(`语言切换到:${lang}`, 'info', { closable: true })
const languageText = lang === 'zh' ? '中文' : 'English'
showMessage(formatTranslation('languageSwitchedTo', { language: languageText }), 'info', {
closable: true,
})
}
const handleThemeChange = (isDark: boolean) => {
showMessage(`主题切换到:${isDark ? '暗黑模式' : '明亮模式'}`, 'info', { closable: true })
const themeText = isDark ? t.value.darkModeText : t.value.lightModeText
showMessage(formatTranslation('themeSwitchedTo', { theme: themeText }), 'info', {
closable: true,
})
}
// 里程碑保存处理器示例
@@ -104,7 +112,7 @@ const handleMilestoneDelete = async (milestoneId: number) => {
const milestoneIndex = milestones.value.findIndex(m => m.id === milestoneId)
if (milestoneIndex !== -1) {
milestones.value.splice(milestoneIndex, 1)
showMessage('里程碑删除成功', 'success', { closable: false })
showMessage(t.value.milestoneDeleteSuccess, 'success', { closable: false })
// 等待DOM更新完成
await nextTick()
@@ -146,7 +154,9 @@ const handleTaskUpdate = (updatedTask: Task) => {
const originalTask = findOriginalTask(tasks.value)
if (!originalTask) {
showMessage(`未找到要更新的任务,ID ${updatedTask.id}`, 'warning', { closable: true })
showMessage(formatTranslation('taskNotFound', { id: updatedTask.id }), 'warning', {
closable: true,
})
return
}
@@ -203,9 +213,11 @@ const handleTaskUpdate = (updatedTask: Task) => {
}
if (!addToParentChildren(tasks.value)) {
showMessage(`未找到新父任务,ID ${taskToAdd.parentId},将作为顶级任务添加`, 'warning', {
closable: true,
})
showMessage(
formatTranslation('newParentTaskNotFound', { parentId: taskToAdd.parentId }),
'warning',
{ closable: true },
)
tasks.value.push(taskToAdd)
}
} else {
@@ -237,7 +249,9 @@ const handleTaskUpdate = (updatedTask: Task) => {
}
if (!updateTaskInPlace(tasks.value)) {
showMessage(`就地更新失败,未找到任务,ID ${updatedTask.id}`, 'warning', { closable: true })
showMessage(formatTranslation('inPlaceUpdateFailed', { id: updatedTask.id }), 'warning', {
closable: true,
})
}
}
}
@@ -285,7 +299,6 @@ const handleTaskAdd = (newTask: Task) => {
// 尝试添加到父任务的children中
if (!addToParentChildren(tasks.value)) {
console.warn('未找到父任务,ID', newTask.parentId, ',将作为顶级任务添加')
// 如果没找到父任务,作为顶级任务添加
tasks.value.push({ ...newTask })
}
@@ -297,13 +310,16 @@ const handleTaskAdd = (newTask: Task) => {
// 任务删除处理器
const handleTaskDelete = (taskToDelete: Task) => {
// 收集要删除的所有任务ID(包括子任务)
const deletedTaskIds = collectAllTaskIds(taskToDelete)
// 递归查找和删除任务(支持嵌套结构)
const deleteTaskFromArray = (taskArray: Task[]): boolean => {
for (let i = 0; i < taskArray.length; i++) {
if (taskArray[i].id === taskToDelete.id) {
// 找到任务,删除它
taskArray.splice(i, 1)
showMessage('已删除任务', 'success', { closable: false })
showMessage(t.value.taskDeletedSuccess, 'success', { closable: false })
return true
}
@@ -317,8 +333,116 @@ const handleTaskDelete = (taskToDelete: Task) => {
return false
}
if (!deleteTaskFromArray(tasks.value)) {
showMessage(`未找到要删除的任务,ID ${taskToDelete.id}`, 'warning', { closable: true })
if (deleteTaskFromArray(tasks.value)) {
// 删除成功后,清理predecessor依赖关系
cleanupPredecessorReferences(deletedTaskIds)
} else {
showMessage(formatTranslation('taskToDeleteNotFound', { id: taskToDelete.id }), 'warning', {
closable: true,
})
}
}
// 删除story及其所有子任务
const handleStoryDeleteWithChildren = (storyToDelete: Task) => {
// 收集要删除的所有任务ID(story及其所有子任务)
const deletedTaskIds = collectAllTaskIds(storyToDelete)
// 递归查找和删除story(包含其所有子任务)
const deleteStoryFromArray = (taskArray: Task[]): boolean => {
for (let i = 0; i < taskArray.length; i++) {
if (taskArray[i].id === storyToDelete.id) {
// 找到story,直接删除它(包含所有子任务)
taskArray.splice(i, 1)
showMessage(
formatTranslation('storyDeleteAllSuccess', { name: storyToDelete.name }),
'success',
{
closable: false,
},
)
return true
}
// 如果有子任务,递归查找
if (taskArray[i].children && taskArray[i].children.length > 0) {
if (deleteStoryFromArray(taskArray[i].children!)) {
return true
}
}
}
return false
}
if (deleteStoryFromArray(tasks.value)) {
// 删除成功后,清理predecessor依赖关系
cleanupPredecessorReferences(deletedTaskIds)
} else {
showMessage(formatTranslation('storyNotFound', { id: storyToDelete.id }), 'warning', {
closable: true,
})
}
}
// 仅删除story,保留子任务
const handleStoryDeleteOnly = (storyToDelete: Task) => {
// 递归查找story并处理其子任务
const deleteStoryOnlyFromArray = (taskArray: Task[]): boolean => {
for (let i = 0; i < taskArray.length; i++) {
if (taskArray[i].id === storyToDelete.id) {
// 找到story
const childrenToPromote = taskArray[i].children || []
// 步骤a: 克隆并升级子任务
const upgradedChildren: Task[] = []
if (childrenToPromote.length > 0) {
childrenToPromote.forEach(child => {
// 深度克隆子任务
const clonedChild = JSON.parse(JSON.stringify(child))
// 升级:移除parentId,使其成为顶级任务
delete clonedChild.parentId
upgradedChildren.push(clonedChild)
})
}
// 步骤b: 删除story数据
taskArray.splice(i, 1)
// 将升级后的子任务push到tasks集合顶层
if (upgradedChildren.length > 0) {
tasks.value.push(...upgradedChildren)
}
showMessage(
formatTranslation('storyDeleteOnlySuccess', {
name: storyToDelete.name,
count: upgradedChildren.length,
}),
'success',
{
closable: false,
},
)
return true
}
// 如果有子任务,递归查找
if (taskArray[i].children && taskArray[i].children.length > 0) {
if (deleteStoryOnlyFromArray(taskArray[i].children!)) {
return true
}
}
}
return false
}
if (deleteStoryOnlyFromArray(tasks.value)) {
// 删除成功后,清理story的predecessor依赖关系(只清理story本身的ID,不影响子任务)
cleanupPredecessorReferences([storyToDelete.id])
} else {
showMessage(formatTranslation('storyNotFound', { id: storyToDelete.id }), 'warning', {
closable: true,
})
}
}
@@ -328,7 +452,9 @@ const handleMilestoneIconChange = (milestoneId: number, icon: string) => {
if (milestoneIndex !== -1) {
milestones.value[milestoneIndex].icon = icon
} else {
showMessage(`未找到要更新图标的里程碑,ID ${milestoneId}`, 'warning', { closable: true })
showMessage(formatTranslation('milestoneIconUpdateNotFound', { id: milestoneId }), 'warning', {
closable: true,
})
}
}
@@ -350,10 +476,16 @@ const handleTaskDrawerClose = () => {
isEditMode.value = false
}
const handleTaskDrawerDelete = (taskId: number) => {
const taskToDelete = tasks.value.find(t => t.id === taskId)
if (taskToDelete) {
handleTaskDelete(taskToDelete)
const handleTaskDrawerDelete = (task: Task, deleteChildren?: boolean) => {
if (task.type === 'story' && deleteChildren === true) {
// 删除story及其所有子任务
handleStoryDeleteWithChildren(task)
} else if (task.type === 'story' && deleteChildren === false) {
// 仅删除story,保留子任务
handleStoryDeleteOnly(task)
} else {
// 普通任务删除
handleTaskDelete(task)
}
showTaskDrawer.value = false
}
@@ -411,6 +543,50 @@ function findTaskDeep(taskArray: Task[], id: number): Task | null {
}
return null
}
// 清理被删除任务的predecessor依赖关系
const cleanupPredecessorReferences = (deletedTaskIds: number[]) => {
// 递归清理所有任务(包括嵌套的子任务)的predecessor
const cleanupTaskArray = (taskArray: Task[]) => {
taskArray.forEach(task => {
if (task.predecessor) {
// 将predecessor字符串解析为ID数组
const predecessorIds = task.predecessor
.split(',')
.map(id => parseInt(id.trim()))
.filter(id => !isNaN(id))
// 过滤掉被删除的任务ID
const validPredecessorIds = predecessorIds.filter(id => !deletedTaskIds.includes(id))
// 更新predecessor属性
if (validPredecessorIds.length === 0) {
delete task.predecessor
} else {
task.predecessor = validPredecessorIds.join(',')
}
}
// 递归处理子任务
if (task.children && task.children.length > 0) {
cleanupTaskArray(task.children)
}
})
}
cleanupTaskArray(tasks.value)
}
// 收集被删除任务及其所有子任务的ID
const collectAllTaskIds = (task: Task): number[] => {
const ids = [task.id]
if (task.children && task.children.length > 0) {
task.children.forEach(child => {
ids.push(...collectAllTaskIds(child))
})
}
return ids
}
</script>
<template>
@@ -459,7 +635,7 @@ function findTaskDeep(taskArray: Task[], id: number): Task | null {
:on-milestone-delete="handleMilestoneDelete"
:on-task-update="handleTaskUpdate"
:on-task-add="handleTaskAdd"
:on-task-delete="handleTaskDelete"
:on-task-delete="handleTaskDrawerDelete"
:on-milestone-icon-change="handleMilestoneIconChange"
@taskbar-drag-end="handleTaskbarDragOrResizeEnd"
@taskbar-resize-end="handleTaskbarDragOrResizeEnd"
+9
View File
@@ -115,5 +115,14 @@
"Story进度根据子任务集进度改编重新计算",
"同时保留Story进度单独的调整方式"
]
},
{
"version": "1.0.9",
"date": "2025-07-04",
"notes": [
"更新多语言标签",
"增加Story删除时选择是否删除子任务的选项",
"删除Story或Task时同时删除前后的关系线路"
]
}
]
+3 -2
View File
@@ -1,12 +1,13 @@
{
"name": "jordium-gantt-vue3",
"version": "1.0.0",
"version": "1.0.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jordium-gantt-vue3",
"version": "1.0.0",
"version": "1.0.9",
"license": "MIT",
"dependencies": {
"date-fns": "^4.1.0",
"html2canvas": "^1.4.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jordium-gantt-vue3",
"version": "1.0.6",
"version": "1.0.9",
"type": "module",
"main": "dist/jordium-gantt-vue3.cjs.js",
"module": "dist/jordium-gantt-vue3.es.js",
+195 -19
View File
@@ -9,9 +9,11 @@ import packageInfo from '../package.json'
import '../src/styles/theme-variables.css'
import VersionHistoryDrawer from './VersionHistoryDrawer.vue'
import { useMessage } from '../src/composables/useMessage'
import { useI18n } from '../src/composables/useI18n'
import type { Task } from '../src/models/Task'
const { showMessage } = useMessage()
const { t, formatTranslation } = useI18n()
const tasks = ref<Task[]>([])
const milestones = ref<Task[]>([])
@@ -42,7 +44,7 @@ const toolbarConfig = {
// 自定义CSV导出处理器(可选)
const handleCustomCsvExport = () => {
showMessage('自定义CSV导出被调用', 'info', { closable: true })
showMessage(t.value.customCsvExportCalled, 'info', { closable: true })
// 这里可以实现自定义的CSV导出逻辑
// 例如:添加额外的数据处理、格式化、或发送到服务器等
@@ -70,11 +72,17 @@ const handleAddMilestone = () => {
}
const handleLanguageChange = (lang: 'zh' | 'en') => {
showMessage(`语言切换到:${lang}`, 'info', { closable: true })
const languageText = lang === 'zh' ? '中文' : 'English'
showMessage(formatTranslation('languageSwitchedTo', { language: languageText }), 'info', {
closable: true,
})
}
const handleThemeChange = (isDark: boolean) => {
showMessage(`主题切换到:${isDark ? '暗黑模式' : '明亮模式'}`, 'info', { closable: true })
const themeText = isDark ? t.value.darkModeText : t.value.lightModeText
showMessage(formatTranslation('themeSwitchedTo', { theme: themeText }), 'info', {
closable: true,
})
}
// 里程碑保存处理器示例
@@ -104,7 +112,7 @@ const handleMilestoneDelete = async (milestoneId: number) => {
const milestoneIndex = milestones.value.findIndex(m => m.id === milestoneId)
if (milestoneIndex !== -1) {
milestones.value.splice(milestoneIndex, 1)
showMessage('里程碑删除成功', 'success', { closable: false })
showMessage(t.value.milestoneDeleteSuccess, 'success', { closable: false })
// 等待DOM更新完成
await nextTick()
@@ -146,7 +154,9 @@ const handleTaskUpdate = (updatedTask: Task) => {
const originalTask = findOriginalTask(tasks.value)
if (!originalTask) {
showMessage(`未找到要更新的任务,ID ${updatedTask.id}`, 'warning', { closable: true })
showMessage(formatTranslation('taskNotFound', { id: updatedTask.id }), 'warning', {
closable: true,
})
return
}
@@ -203,9 +213,11 @@ const handleTaskUpdate = (updatedTask: Task) => {
}
if (!addToParentChildren(tasks.value)) {
showMessage(`未找到新父任务,ID ${taskToAdd.parentId},将作为顶级任务添加`, 'warning', {
closable: true,
})
showMessage(
formatTranslation('newParentTaskNotFound', { parentId: taskToAdd.parentId }),
'warning',
{ closable: true },
)
tasks.value.push(taskToAdd)
}
} else {
@@ -237,7 +249,9 @@ const handleTaskUpdate = (updatedTask: Task) => {
}
if (!updateTaskInPlace(tasks.value)) {
showMessage(`就地更新失败,未找到任务,ID ${updatedTask.id}`, 'warning', { closable: true })
showMessage(formatTranslation('inPlaceUpdateFailed', { id: updatedTask.id }), 'warning', {
closable: true,
})
}
}
}
@@ -285,7 +299,6 @@ const handleTaskAdd = (newTask: Task) => {
// 尝试添加到父任务的children中
if (!addToParentChildren(tasks.value)) {
console.warn('未找到父任务,ID', newTask.parentId, ',将作为顶级任务添加')
// 如果没找到父任务,作为顶级任务添加
tasks.value.push({ ...newTask })
}
@@ -297,13 +310,16 @@ const handleTaskAdd = (newTask: Task) => {
// 任务删除处理器
const handleTaskDelete = (taskToDelete: Task) => {
// 收集要删除的所有任务ID(包括子任务)
const deletedTaskIds = collectAllTaskIds(taskToDelete)
// 递归查找和删除任务(支持嵌套结构)
const deleteTaskFromArray = (taskArray: Task[]): boolean => {
for (let i = 0; i < taskArray.length; i++) {
if (taskArray[i].id === taskToDelete.id) {
// 找到任务,删除它
taskArray.splice(i, 1)
showMessage('已删除任务', 'success', { closable: false })
showMessage(t.value.taskDeletedSuccess, 'success', { closable: false })
return true
}
@@ -317,8 +333,116 @@ const handleTaskDelete = (taskToDelete: Task) => {
return false
}
if (!deleteTaskFromArray(tasks.value)) {
showMessage(`未找到要删除的任务,ID ${taskToDelete.id}`, 'warning', { closable: true })
if (deleteTaskFromArray(tasks.value)) {
// 删除成功后,清理predecessor依赖关系
cleanupPredecessorReferences(deletedTaskIds)
} else {
showMessage(formatTranslation('taskToDeleteNotFound', { id: taskToDelete.id }), 'warning', {
closable: true,
})
}
}
// 删除story及其所有子任务
const handleStoryDeleteWithChildren = (storyToDelete: Task) => {
// 收集要删除的所有任务ID(story及其所有子任务)
const deletedTaskIds = collectAllTaskIds(storyToDelete)
// 递归查找和删除story(包含其所有子任务)
const deleteStoryFromArray = (taskArray: Task[]): boolean => {
for (let i = 0; i < taskArray.length; i++) {
if (taskArray[i].id === storyToDelete.id) {
// 找到story,直接删除它(包含所有子任务)
taskArray.splice(i, 1)
showMessage(
formatTranslation('storyDeleteAllSuccess', { name: storyToDelete.name }),
'success',
{
closable: false,
},
)
return true
}
// 如果有子任务,递归查找
if (taskArray[i].children && taskArray[i].children.length > 0) {
if (deleteStoryFromArray(taskArray[i].children!)) {
return true
}
}
}
return false
}
if (deleteStoryFromArray(tasks.value)) {
// 删除成功后,清理predecessor依赖关系
cleanupPredecessorReferences(deletedTaskIds)
} else {
showMessage(formatTranslation('storyNotFound', { id: storyToDelete.id }), 'warning', {
closable: true,
})
}
}
// 仅删除story,保留子任务
const handleStoryDeleteOnly = (storyToDelete: Task) => {
// 递归查找story并处理其子任务
const deleteStoryOnlyFromArray = (taskArray: Task[]): boolean => {
for (let i = 0; i < taskArray.length; i++) {
if (taskArray[i].id === storyToDelete.id) {
// 找到story
const childrenToPromote = taskArray[i].children || []
// 步骤a: 克隆并升级子任务
const upgradedChildren: Task[] = []
if (childrenToPromote.length > 0) {
childrenToPromote.forEach(child => {
// 深度克隆子任务
const clonedChild = JSON.parse(JSON.stringify(child))
// 升级:移除parentId,使其成为顶级任务
delete clonedChild.parentId
upgradedChildren.push(clonedChild)
})
}
// 步骤b: 删除story数据
taskArray.splice(i, 1)
// 将升级后的子任务push到tasks集合顶层
if (upgradedChildren.length > 0) {
tasks.value.push(...upgradedChildren)
}
showMessage(
formatTranslation('storyDeleteOnlySuccess', {
name: storyToDelete.name,
count: upgradedChildren.length,
}),
'success',
{
closable: false,
},
)
return true
}
// 如果有子任务,递归查找
if (taskArray[i].children && taskArray[i].children.length > 0) {
if (deleteStoryOnlyFromArray(taskArray[i].children!)) {
return true
}
}
}
return false
}
if (deleteStoryOnlyFromArray(tasks.value)) {
// 删除成功后,清理story的predecessor依赖关系(只清理story本身的ID,不影响子任务)
cleanupPredecessorReferences([storyToDelete.id])
} else {
showMessage(formatTranslation('storyNotFound', { id: storyToDelete.id }), 'warning', {
closable: true,
})
}
}
@@ -328,7 +452,9 @@ const handleMilestoneIconChange = (milestoneId: number, icon: string) => {
if (milestoneIndex !== -1) {
milestones.value[milestoneIndex].icon = icon
} else {
showMessage(`未找到要更新图标的里程碑,ID ${milestoneId}`, 'warning', { closable: true })
showMessage(formatTranslation('milestoneIconUpdateNotFound', { id: milestoneId }), 'warning', {
closable: true,
})
}
}
@@ -350,10 +476,16 @@ const handleTaskDrawerClose = () => {
isEditMode.value = false
}
const handleTaskDrawerDelete = (taskId: number) => {
const taskToDelete = tasks.value.find(t => t.id === taskId)
if (taskToDelete) {
handleTaskDelete(taskToDelete)
const handleTaskDrawerDelete = (task: Task, deleteChildren?: boolean) => {
if (task.type === 'story' && deleteChildren === true) {
// 删除story及其所有子任务
handleStoryDeleteWithChildren(task)
} else if (task.type === 'story' && deleteChildren === false) {
// 仅删除story,保留子任务
handleStoryDeleteOnly(task)
} else {
// 普通任务删除
handleTaskDelete(task)
}
showTaskDrawer.value = false
}
@@ -411,6 +543,50 @@ function findTaskDeep(taskArray: Task[], id: number): Task | null {
}
return null
}
// 清理被删除任务的predecessor依赖关系
const cleanupPredecessorReferences = (deletedTaskIds: number[]) => {
// 递归清理所有任务(包括嵌套的子任务)的predecessor
const cleanupTaskArray = (taskArray: Task[]) => {
taskArray.forEach(task => {
if (task.predecessor) {
// 将predecessor字符串解析为ID数组
const predecessorIds = task.predecessor
.split(',')
.map(id => parseInt(id.trim()))
.filter(id => !isNaN(id))
// 过滤掉被删除的任务ID
const validPredecessorIds = predecessorIds.filter(id => !deletedTaskIds.includes(id))
// 更新predecessor属性
if (validPredecessorIds.length === 0) {
delete task.predecessor
} else {
task.predecessor = validPredecessorIds.join(',')
}
}
// 递归处理子任务
if (task.children && task.children.length > 0) {
cleanupTaskArray(task.children)
}
})
}
cleanupTaskArray(tasks.value)
}
// 收集被删除任务及其所有子任务的ID
const collectAllTaskIds = (task: Task): number[] => {
const ids = [task.id]
if (task.children && task.children.length > 0) {
task.children.forEach(child => {
ids.push(...collectAllTaskIds(child))
})
}
return ids
}
</script>
<template>
@@ -459,7 +635,7 @@ function findTaskDeep(taskArray: Task[], id: number): Task | null {
:on-milestone-delete="handleMilestoneDelete"
:on-task-update="handleTaskUpdate"
:on-task-add="handleTaskAdd"
:on-task-delete="handleTaskDelete"
:on-task-delete="handleTaskDrawerDelete"
:on-milestone-icon-change="handleMilestoneIconChange"
@taskbar-drag-end="handleTaskbarDragOrResizeEnd"
@taskbar-resize-end="handleTaskbarDragOrResizeEnd"
+39 -227
View File
@@ -8,9 +8,6 @@
"name": "packagedemo",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"jordium-gantt-vue3": "^1.0.1"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
@@ -30,6 +27,7 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -38,6 +36,7 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -46,6 +45,7 @@
"version": "7.27.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz",
"integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==",
"dev": true,
"dependencies": {
"@babel/types": "^7.27.7"
},
@@ -56,18 +56,11 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.27.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz",
"integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
@@ -617,7 +610,9 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz",
"integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw=="
"integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==",
"dev": true,
"peer": true
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -938,24 +933,12 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true
},
"node_modules/@types/semver": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
"integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
"dev": true
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"optional": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
@@ -1195,6 +1178,7 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz",
"integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.27.5",
"@vue/shared": "3.5.17",
@@ -1207,6 +1191,7 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz",
"integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==",
"dev": true,
"dependencies": {
"@vue/compiler-core": "3.5.17",
"@vue/shared": "3.5.17"
@@ -1216,6 +1201,8 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz",
"integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/parser": "^7.27.5",
"@vue/compiler-core": "3.5.17",
@@ -1232,6 +1219,8 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz",
"integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.17",
"@vue/shared": "3.5.17"
@@ -1313,6 +1302,8 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz",
"integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/shared": "3.5.17"
}
@@ -1321,6 +1312,8 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz",
"integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.17",
"@vue/shared": "3.5.17"
@@ -1330,6 +1323,8 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz",
"integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.17",
"@vue/runtime-core": "3.5.17",
@@ -1341,6 +1336,8 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz",
"integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.17",
"@vue/shared": "3.5.17"
@@ -1352,7 +1349,8 @@
"node_modules/@vue/shared": {
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz",
"integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg=="
"integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==",
"dev": true
},
"node_modules/@vue/tsconfig": {
"version": "0.7.0",
@@ -1454,31 +1452,12 @@
"node": ">=8"
}
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"bin": {
"atob": "bin/atob.js"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -1506,17 +1485,6 @@
"node": ">=8"
}
},
"node_modules/btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1526,25 +1494,6 @@
"node": ">=6"
}
},
"node_modules/canvg": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1585,17 +1534,6 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/core-js": {
"version": "3.43.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz",
"integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==",
"hasInstallScript": true,
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -1610,14 +1548,6 @@
"node": ">= 8"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1633,16 +1563,9 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"peer": true
},
"node_modules/de-indent": {
"version": "1.0.2",
@@ -1697,19 +1620,11 @@
"node": ">=6.0.0"
}
},
"node_modules/dompurify": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
"optional": true,
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
},
@@ -1992,7 +1907,8 @@
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
"node_modules/esutils": {
"version": "2.0.3",
@@ -2064,11 +1980,6 @@
"reusify": "^1.0.4"
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -2263,18 +2174,6 @@
"he": "bin/he"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2371,20 +2270,6 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/jordium-gantt-vue3": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/jordium-gantt-vue3/-/jordium-gantt-vue3-1.0.1.tgz",
"integrity": "sha512-LaFzGN6LZnlSGhUxp8sEKK1YCtQX4NoE4V0yKBOIiUhF22ykJjtHXkQnOu5jH/Wtm0WR+WhQC/AOzA+WLl+VUA==",
"dependencies": {
"date-fns": "^4.1.0",
"html2canvas": "^1.4.1",
"jspdf": "^3.0.1",
"vue": "^3.5.13"
},
"peerDependencies": {
"vue": "^3.5.13"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2415,23 +2300,6 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
"node_modules/jspdf": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz",
"integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==",
"dependencies": {
"@babel/runtime": "^7.26.7",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"fflate": "^0.8.1"
},
"optionalDependencies": {
"canvg": "^3.0.11",
"core-js": "^3.6.0",
"dompurify": "^3.2.4",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -2485,6 +2353,8 @@
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
@@ -2542,6 +2412,7 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
@@ -2683,16 +2554,11 @@
"node": ">=8"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -2710,6 +2576,7 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"type": "opencollective",
@@ -2811,21 +2678,6 @@
}
]
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"optional": true,
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -2845,15 +2697,6 @@
"node": ">=0.10.0"
}
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"optional": true,
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -2978,19 +2821,11 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"optional": true,
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
@@ -3027,15 +2862,6 @@
"node": ">=8"
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/synckit": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz",
@@ -3051,14 +2877,6 @@
"url": "https://opencollective.com/synckit"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -3159,7 +2977,7 @@
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true,
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3183,14 +3001,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
@@ -3301,6 +3111,8 @@
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz",
"integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.17",
"@vue/compiler-sfc": "3.5.17",
-3
View File
@@ -19,9 +19,6 @@
],
"author": "Jordium.com",
"license": "MIT",
"dependencies": {
"jordium-gantt-vue3": "^1.0.1"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
+34
View File
@@ -90,5 +90,39 @@
"version": "1.0.2",
"date": "2025-07-01",
"notes": ["升级README文档", "增加贡献说明", "增加贡献者列表"]
},
{
"version": "1.0.3",
"date": "2025-07-02",
"notes": ["增加Github Pages", "修改README, 添加GIthub在线Demo入口", "移除大体积静态资源"]
},
{
"version": "1.0.4",
"date": "2025-07-02",
"notes": ["修改NPM徽章CDN"]
},
{
"version": "1.0.5",
"date": "2025-07-02",
"notes": ["更新README中所有NPM徽章CDN地址"]
},
{
"version": "1.0.6",
"date": "2025-07-03",
"notes": [
"更新TaskDrawer组件的Progressbar样式",
"可通过拖拽进度条和文本修改的方式更改任务的进度",
"Story进度根据子任务集进度改编重新计算",
"同时保留Story进度单独的调整方式"
]
},
{
"version": "1.0.9",
"date": "2025-07-04",
"notes": [
"更新多语言标签",
"增加Story删除时选择是否删除子任务的选项",
"删除Story或Task时同时删除前后的关系线路"
]
}
]
+1 -1
View File
@@ -51,7 +51,7 @@ interface Props {
// 使TaskDrawer
useDefaultDrawer?: boolean
// API
onTaskDelete?: (task: Task) => void
onTaskDelete?: (task: Task, deleteChildren?: boolean) => void
// API
onMilestoneSave?: (milestone: Task) => void
// API
+34 -8
View File
@@ -7,10 +7,15 @@ const props = defineProps({
message: { type: String, default: '' },
confirmText: { type: String, default: '确认' },
cancelText: { type: String, default: '取消' },
yesText: { type: String, default: '是' },
noText: { type: String, default: '否' },
type: { type: String, default: 'confirm-cancel' }, // 'confirm-cancel' | 'yes-no-cancel'
})
const emit = defineEmits(['confirm', 'cancel'])
const emit = defineEmits(['confirm', 'cancel', 'yes', 'no'])
const onConfirm = () => emit('confirm')
const onCancel = () => emit('cancel')
const onYes = () => emit('yes')
const onNo = () => emit('no')
</script>
<template>
@@ -23,12 +28,27 @@ const onCancel = () => emit('cancel')
<p>{{ props.message }}</p>
</div>
<div class="gantt-confirm-footer">
<button type="button" class="btn btn-default" @click="onCancel">
{{ props.cancelText }}
</button>
<button type="button" class="btn btn-danger" @click="onConfirm">
{{ props.confirmText }}
</button>
<template v-if="props.type === 'yes-no-cancel'">
<button type="button" class="btn btn-default" @click="onCancel">
{{ props.cancelText }}
</button>
<div class="gantt-confirm-footer-right">
<button type="button" class="btn btn-warning" @click="onNo">
{{ props.noText }}
</button>
<button type="button" class="btn btn-danger" @click="onYes">
{{ props.yesText }}
</button>
</div>
</template>
<template v-else>
<button type="button" class="btn btn-default" @click="onCancel">
{{ props.cancelText }}
</button>
<button type="button" class="btn btn-danger" @click="onConfirm">
{{ props.confirmText }}
</button>
</template>
</div>
</div>
</div>
@@ -73,7 +93,13 @@ const onCancel = () => emit('cancel')
}
.gantt-confirm-footer {
display: flex;
justify-content: flex-end;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.gantt-confirm-footer-right {
display: flex;
gap: 12px;
}
+55 -5
View File
@@ -25,7 +25,7 @@ const emit = defineEmits<{
'update:visible': [value: boolean]
submit: [task: Task]
close: []
delete: [task: Task]
delete: [task: Task, deleteChildren?: boolean]
}>()
const { t } = useI18n()
@@ -35,6 +35,19 @@ const submitting = ref(false)
const isVisible = ref(props.visible)
const showDeleteConfirm = ref(false)
// dialog
const dialogType = computed(() => {
return props.task?.type === 'story' ? 'yes-no-cancel' : 'confirm-cancel'
})
// dialog
const dialogMessage = computed(() => {
if (props.task?.type === 'story') {
return t.value.confirmDeleteStory.replace('{name}', props.task?.name || '')
}
return t.value.confirmDeleteTask.replace('{name}', props.task?.name || '')
})
//
const formData = reactive<Task>({
id: 0, //
@@ -156,7 +169,7 @@ const handleProgressKeydown = (event: KeyboardEvent) => {
]
//
const isNumber = /^[0-9]$/.test(key)
const isNumber = /^\d$/.test(key)
const isAllowedKey = allowedKeys.includes(key)
// Ctrl+ACtrl+CCtrl+VCtrl+X
@@ -167,7 +180,6 @@ const handleProgressKeydown = (event: KeyboardEvent) => {
//
if (!isNumber && !isAllowedKey) {
event.preventDefault()
return
}
}
@@ -311,6 +323,39 @@ const cancelDelete = () => {
showDeleteConfirm.value = false
}
// Story"" - story
const handleDeleteYes = () => {
showDeleteConfirm.value = false
if (props.task && props.isEdit) {
try {
submitting.value = true
emit('delete', props.task, true) // true
handleClose()
} catch (error) {
showMessage(t.value.taskDeleteFailed, 'error')
} finally {
submitting.value = false
}
}
}
// Story"" - story
// Story"" - story
const handleDeleteNo = () => {
showDeleteConfirm.value = false
if (props.task && props.isEdit) {
try {
submitting.value = true
emit('delete', props.task, false) // falsestory
handleClose()
} catch (error) {
showMessage(t.value.taskDeleteFailed, 'error')
} finally {
submitting.value = false
}
}
}
//
const handleTasksChanged = (event: CustomEvent) => {
allTasks.value = event.detail || []
@@ -390,7 +435,7 @@ watch(
<div class="form-group">
<label class="form-label" for="task-assignee">{{ t.assignee }}</label>
<select id="task-assignee" v-model="formData.assignee" class="form-select">
<option value="">请选择负责人</option>
<option value="">{{ t.selectAssignee }}</option>
<option value="张三">张三</option>
<option value="李四">李四</option>
<option value="王五">王五</option>
@@ -546,10 +591,15 @@ watch(
<GanttConfirmDialog
:visible="showDeleteConfirm"
:title="t.delete"
:message="t.confirmDeleteTask.replace('{name}', task?.name || '')"
:type="dialogType"
:message="dialogMessage"
:confirm-text="t.confirm"
:cancel-text="t.cancel"
:yes-text="t.storyDeleteYes"
:no-text="t.storyDeleteNo"
@confirm="confirmDelete"
@yes="handleDeleteYes"
@no="handleDeleteNo"
@cancel="cancelDelete"
/>
</div>
+5 -5
View File
@@ -22,7 +22,7 @@ interface Props {
// 使TaskDrawer
useDefaultDrawer?: boolean
//
onTaskDelete?: (task: Task) => void
onTaskDelete?: (task: Task, deleteChildren?: boolean) => void
//
onMilestoneSave?: (milestone: Task) => void
//
@@ -810,12 +810,12 @@ onUnmounted(() => {
}
})
const handleTaskDelete = (taskId: number) => {
const handleTaskDelete = (taskId: number, deleteChildren?: boolean) => {
//
if (props.onTaskDelete && typeof props.onTaskDelete === 'function') {
const taskToDelete = tasks.value.find(task => task.id === taskId)
if (taskToDelete) {
props.onTaskDelete(taskToDelete)
props.onTaskDelete(taskToDelete, deleteChildren)
}
}
@@ -833,8 +833,8 @@ const handleTaskDelete = (taskId: number) => {
}
// TaskDrawer
const handleDrawerTaskDelete = (task: Task) => {
handleTaskDelete(task.id)
const handleDrawerTaskDelete = (task: Task, deleteChildren?: boolean) => {
handleTaskDelete(task.id, deleteChildren)
}
// API
+60 -2
View File
@@ -119,8 +119,31 @@ const messages = {
taskUpdateSuccess: '任务更新成功',
taskCreateSuccess: '任务创建成功',
confirmDeleteTask: '确定要删除任务"{name}"吗?此操作不可撤销。',
taskDeleteSuccess: '任务删除成功',
taskDeleteFailed: '删除失败,请重试',
// Story删除相关
confirmDeleteStory: '是否删除该需求{name}及其所有的子任务?该操作不可撤回',
storyDeleteYes: '是, 继续',
storyDeleteNo: '不,仅删除需求',
storyDeleteAllSuccess: '已删除需求【{name}】及其所有子任务',
storyDeleteOnlySuccess: '已删除需求【{name}】,{count}个子任务已升级保留',
storyNotFound: '未找到要删除的需求,ID{id}',
// 里程碑删除成功
milestoneDeleteSuccess: '里程碑删除成功',
// 其他删除成功消息
taskDeletedSuccess: '已删除任务',
// 选择器占位符
selectAssignee: '请选择负责人',
// 通用消息
customCsvExportCalled: '自定义CSV导出被调用',
languageSwitchedTo: '语言切换到:{language}',
themeSwitchedTo: '主题切换到:{theme}',
lightModeText: '明亮模式',
darkModeText: '暗黑模式',
taskNotFound: '未找到要更新的任务,ID{id}',
newParentTaskNotFound: '未找到新父任务,ID:{parentId},将作为顶级任务添加',
inPlaceUpdateFailed: '就地更新失败,未找到任务,ID{id}',
taskToDeleteNotFound: '未找到要删除的任务,ID{id}',
milestoneIconUpdateNotFound: '未找到要更新图标的里程碑,ID{id}',
overtime: '超',
overdue: '逾期',
days: '天',
@@ -238,8 +261,33 @@ const messages = {
taskCreateSuccess: 'Task created successfully',
confirmDeleteTask:
'Are you sure you want to delete task "{name}"? This action cannot be undone.',
taskDeleteSuccess: 'Task deleted successfully',
taskDeleteFailed: 'Delete failed, please try again',
// Story deletion related
confirmDeleteStory:
'Do you want to delete the requirement {name} and all its subtasks? This action cannot be undone',
storyDeleteYes: 'Yes, Continue',
storyDeleteNo: 'No, Delete Requirement Only',
storyDeleteAllSuccess: 'Deleted requirement [{name}] and all its subtasks',
storyDeleteOnlySuccess: 'Deleted requirement [{name}], {count} subtasks have been promoted',
storyNotFound: 'Requirement not found, ID: {id}',
// Milestone deletion success
milestoneDeleteSuccess: 'Milestone deleted successfully',
// Other deletion success messages
taskDeletedSuccess: 'Task deleted',
// Selector placeholders
selectAssignee: 'Select assignee',
// Common messages
customCsvExportCalled: 'Custom CSV export called',
languageSwitchedTo: 'Language switched to: {language}',
themeSwitchedTo: 'Theme switched to: {theme}',
lightModeText: 'Light Mode',
darkModeText: 'Dark Mode',
taskNotFound: 'Task not found for update, ID: {id}',
newParentTaskNotFound:
'New parent task not found, ID: {parentId}, will be added as top-level task',
inPlaceUpdateFailed: 'In-place update failed, task not found, ID: {id}',
taskToDeleteNotFound: 'Task to delete not found, ID: {id}',
milestoneIconUpdateNotFound: 'Milestone not found for icon update, ID: {id}',
overtime: 'Over',
overdue: 'Overdue',
days: ' days',
@@ -282,6 +330,15 @@ export function useI18n() {
return typeof translation === 'string' ? translation : key
}
// 格式化带参数的翻译文本
const formatTranslation = (key: string, params: Record<string, string | number>): string => {
let text = getTranslation(key)
Object.keys(params).forEach(param => {
text = text.replace(new RegExp(`{${param}}`, 'g'), String(params[param]))
})
return text
}
// 切换语言
const setLocale = (locale: Locale) => {
currentLocale.value = locale
@@ -305,6 +362,7 @@ export function useI18n() {
return {
t,
getTranslation,
formatTranslation,
locale,
setLocale,
formatYearMonth,
+11
View File
@@ -65,3 +65,14 @@
background: #f78989;
border-color: #f78989;
}
/* 通用 .btn-warning 按钮样式 */
.btn-warning {
background: var(--gantt-warning, #e6a23c);
border-color: var(--gantt-warning, #e6a23c);
color: #fff;
}
.btn-warning:hover:not(:disabled) {
background: #ebb563;
border-color: #ebb563;
}