czh-20260430-修复任务管理相关bug
This commit is contained in:
@@ -47,22 +47,24 @@
|
||||
<div class="sidebar-favorites-header">
|
||||
<Icon icon="tasklist-task-list|svg" class="sidebar-favorites-icon" />
|
||||
<span class="sidebar-favorites-name">收藏清单</span>
|
||||
<Icon icon="ant-design:plus-outlined" class="sidebar-group-add" @click.stop="startNewList('')" />
|
||||
<a-dropdown :trigger="['click']">
|
||||
<Icon icon="ant-design:plus-outlined" class="sidebar-group-add" @click.stop />
|
||||
<template #overlay>
|
||||
<a-menu @click="onAddMenuClick">
|
||||
<a-menu-item key="new-list">
|
||||
<Icon icon="ant-design:file-add-outlined" class="mr-1" />
|
||||
新建清单
|
||||
</a-menu-item>
|
||||
<a-menu-item key="new-group">
|
||||
<Icon icon="ant-design:folder-add-outlined" class="mr-1" />
|
||||
新建分组
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div class="sidebar-favorites-items">
|
||||
<div v-if="showNewListInput && newListGroupId === ''" class="sidebar-new-item-row">
|
||||
<Icon icon="ant-design:file-text-outlined" class="sidebar-item-icon" style="opacity: 0.4" />
|
||||
<a-input
|
||||
v-model:value="newListName"
|
||||
size="small"
|
||||
placeholder="输入清单名称..."
|
||||
autofocus
|
||||
style="flex: 1"
|
||||
@press-enter="submitNewList"
|
||||
@blur="showNewListInput = false"
|
||||
/>
|
||||
</div>
|
||||
<draggable :model-value="ungroupedTaskLists" group="tasklists" item-key="id" handle=".list-drag-handle" :animation="200" @end="onDragEnd">
|
||||
<draggable :list="localUngroupedTaskLists" group="tasklists" item-key="id" handle=".list-drag-handle" :animation="200" @end="onDragEnd">
|
||||
<template #item="{ element: taskList }">
|
||||
<div
|
||||
v-if="renamingId !== taskList.id"
|
||||
@@ -97,13 +99,25 @@
|
||||
<div v-else class="sidebar-item sidebar-item-renaming">
|
||||
<span class="list-drag-handle-placeholder"></span>
|
||||
<Icon icon="ant-design:file-text-outlined" class="sidebar-item-icon" style="opacity: 0.4" />
|
||||
<a-input v-model:value="renameValue" size="small" style="flex: 1" @press-enter="confirmInlineRename" @blur="confirmInlineRename" />
|
||||
<a-input ref="renameInputRef" v-model:value="renameValue" size="small" style="flex: 1" @press-enter="confirmInlineRename" @blur="confirmInlineRename" />
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div v-if="showNewListInput && newListGroupId === ''" class="sidebar-new-item-row">
|
||||
<Icon icon="ant-design:file-text-outlined" class="sidebar-item-icon" style="opacity: 0.4" />
|
||||
<a-input
|
||||
v-model:value="newListName"
|
||||
size="small"
|
||||
placeholder="输入清单名称..."
|
||||
autofocus
|
||||
style="flex: 1"
|
||||
@press-enter="submitNewList"
|
||||
@blur="showNewListInput = false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<draggable :model-value="userGroups" group="tasklistgroups" item-key="id" :animation="200" drag-class="group-drag-ghost" @start="onGroupDragStart" @end="onGroupDragEnd">
|
||||
<draggable :list="localUserGroups" group="tasklistgroups" item-key="id" :animation="200" drag-class="group-drag-ghost" @start="onGroupDragStart" @end="onGroupDragEnd">
|
||||
<template #item="{ element: group }">
|
||||
<div :data-group-id="group.id" :class="{ 'group-being-dragged': draggingGroupId === group.id }">
|
||||
<div
|
||||
@@ -134,7 +148,7 @@
|
||||
</div>
|
||||
<div v-if="group.delFlag !== 1 && renamingId === group.id" class="sidebar-group-header sidebar-group-renaming" @click.stop>
|
||||
<Icon icon="tasklist-arrow-down|svg" class="group-collapse-arrow" :class="{ collapsed: collapsedGroups[group.id] || draggingGroupId === group.id }" />
|
||||
<a-input v-model:value="renameValue" size="small" style="flex: 1" @press-enter="confirmInlineRename" @blur="confirmInlineRename" />
|
||||
<a-input ref="renameInputRef" v-model:value="renameValue" size="small" style="flex: 1" @press-enter="confirmInlineRename" @blur="confirmInlineRename" />
|
||||
</div>
|
||||
<template v-if="group.delFlag !== 1 && !collapsedGroups[group.id]">
|
||||
<div class="sidebar-group-items" :data-group-id="group.id">
|
||||
@@ -151,7 +165,7 @@
|
||||
/>
|
||||
</div>
|
||||
<draggable
|
||||
:model-value="getGroupTaskLists(group)"
|
||||
:list="getLocalGroupTaskLists(group)"
|
||||
group="tasklists"
|
||||
item-key="id"
|
||||
handle=".list-drag-handle"
|
||||
@@ -192,12 +206,12 @@
|
||||
<div v-else class="sidebar-item sidebar-item-renaming">
|
||||
<span class="list-drag-handle-placeholder"></span>
|
||||
<Icon icon="ant-design:file-text-outlined" class="sidebar-item-icon" style="opacity: 0.4" />
|
||||
<a-input v-model:value="renameValue" size="small" style="flex: 1" @press-enter="confirmInlineRename" @blur="confirmInlineRename" />
|
||||
<a-input ref="renameInputRef" v-model:value="renameValue" size="small" style="flex: 1" @press-enter="confirmInlineRename" @blur="confirmInlineRename" />
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
<div
|
||||
v-if="group.taskLists.filter((l) => l.delFlag !== 1).length === 0 && !(showNewListInput && newListGroupId === group.id)"
|
||||
v-if="getLocalGroupTaskLists(group).length === 0 && !(showNewListInput && newListGroupId === group.id)"
|
||||
class="sidebar-empty-hint"
|
||||
>
|
||||
可拖拽清单加入该分组
|
||||
@@ -229,7 +243,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, watch, nextTick } from 'vue';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
import draggable from 'vuedraggable';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
@@ -253,6 +267,7 @@
|
||||
'create-group': [name: string];
|
||||
'rename-list': [listId: string, newName: string];
|
||||
'delete-list': [listId: string];
|
||||
'remove-favorite': [listId: string];
|
||||
'rename-group': [groupId: string, newName: string];
|
||||
'delete-group': [groupId: string];
|
||||
'quick-access-tab-click': [tabKey: string];
|
||||
@@ -277,14 +292,46 @@
|
||||
return [];
|
||||
});
|
||||
|
||||
function getGroupTaskLists(group: TaskListGroup) {
|
||||
return [...group.taskLists].filter((l) => l.delFlag !== 1).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
}
|
||||
|
||||
const userGroups = computed(() => {
|
||||
return props.taskListGroups.filter((g) => g.id !== '__ungrouped__' && g.delFlag !== 1).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
});
|
||||
|
||||
// 本地可变副本,供 draggable :list 模式直接修改,实现乐观更新
|
||||
const localUngroupedTaskLists = ref<any[]>([]);
|
||||
const localUserGroups = ref<any[]>([]);
|
||||
const localGroupTaskListsMap = ref<Record<string, any[]>>({});
|
||||
|
||||
watch(ungroupedTaskLists, (val) => {
|
||||
localUngroupedTaskLists.value = [...val];
|
||||
}, { immediate: true });
|
||||
|
||||
watch(userGroups, (val) => {
|
||||
localUserGroups.value = val.map((g) => ({
|
||||
...g,
|
||||
taskLists: [...(g.taskLists || [])],
|
||||
}));
|
||||
// 同步分组内清单到 map
|
||||
const newMap: Record<string, any[]> = {};
|
||||
for (const g of val) {
|
||||
newMap[g.id] = [...(g.taskLists || [])].filter((l) => l.delFlag !== 1).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
}
|
||||
localGroupTaskListsMap.value = newMap;
|
||||
}, { immediate: true });
|
||||
|
||||
function getGroupTaskLists(group: any) {
|
||||
return [...group.taskLists].filter((l) => l.delFlag !== 1).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
}
|
||||
|
||||
function getLocalGroupTaskLists(group: any) {
|
||||
if (localGroupTaskListsMap.value[group.id]) {
|
||||
return localGroupTaskListsMap.value[group.id];
|
||||
}
|
||||
// 兜底:如果没有本地缓存,从原始数据计算
|
||||
const list = getGroupTaskLists(group);
|
||||
localGroupTaskListsMap.value[group.id] = list;
|
||||
return list;
|
||||
}
|
||||
|
||||
const hoveredListId = ref('');
|
||||
const collapsedGroups = ref<Record<string, boolean>>({});
|
||||
const showNewGroupInput = ref(false);
|
||||
@@ -295,6 +342,14 @@
|
||||
const renamingId = ref('');
|
||||
const renamingType = ref<'list' | 'group'>('list');
|
||||
const renameValue = ref('');
|
||||
const renameInputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
watch(renamingId, async (val) => {
|
||||
if (val) {
|
||||
await nextTick();
|
||||
renameInputRef.value?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
const draggingGroupId = ref('');
|
||||
const dragStartX = ref(0);
|
||||
@@ -363,6 +418,8 @@
|
||||
const itemEl = evt.item;
|
||||
const groupId = itemEl?.dataset?.groupId || itemEl?.closest('[data-group-id]')?.dataset?.groupId;
|
||||
if (!groupId) return;
|
||||
// 拖拽结束后保持分组折叠状态,防止展开跳动
|
||||
collapsedGroups.value[groupId] = true;
|
||||
const newIndex = evt.newIndex ?? 0;
|
||||
emit('move-group', groupId, newIndex + 1);
|
||||
}
|
||||
@@ -372,6 +429,15 @@
|
||||
return perm === '1' || perm === '2';
|
||||
}
|
||||
|
||||
function onAddMenuClick({ key }: { key: string }) {
|
||||
if (key === 'new-list') {
|
||||
startNewList('');
|
||||
} else if (key === 'new-group') {
|
||||
showNewGroupInput.value = true;
|
||||
newGroupName.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function startNewList(groupId: string) {
|
||||
showNewListInput.value = true;
|
||||
newListGroupId.value = groupId;
|
||||
@@ -397,12 +463,12 @@
|
||||
} else if (key === 'remove') {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '确认删除',
|
||||
content: '删除任务清单后,其下所有任务也将被删除,确定要删除吗?',
|
||||
title: '确认移除',
|
||||
content: '确定从收藏清单中移除吗?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
emit('delete-list', listId);
|
||||
emit('remove-favorite', listId);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user