v1.0.9 - Add Story and sub-tasks deletion logic; l18n updates; GanttConfirmDialog updates
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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+A、Ctrl+C、Ctrl+V、Ctrl+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) // 传递false表示仅删除story
|
||||
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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user