1230 lines
40 KiB
Vue
1230 lines
40 KiB
Vue
<!-- eslint-disable vue/multi-word-component-names -->
|
|
<template>
|
|
<div class="task-page">
|
|
<TaskSidebar
|
|
:task-list-groups="taskListGroups"
|
|
:current-view="currentView"
|
|
:current-list-id="currentListId"
|
|
:favorite-id-map="favoriteIdMap"
|
|
:permission-map="permissionMap"
|
|
@view-select="onViewSelect"
|
|
@list-select="onListSelect"
|
|
@create-list-modal="onCreateListModal"
|
|
@create-group="onCreateGroup"
|
|
@rename-list="onRenameList"
|
|
@delete-list="onDeleteList"
|
|
@rename-group="onRenameGroup"
|
|
@delete-group="onDeleteGroup"
|
|
@quick-access-tab-click="onQuickAccessTabClick"
|
|
@move-list="onMoveList"
|
|
@move-group="onMoveGroup"
|
|
@remove-favorite="onRemoveFavorite"
|
|
/>
|
|
<TaskContent
|
|
v-if="!isFlatView"
|
|
:current-list="currentList"
|
|
:task-list-groups="showListGroupView ? quickAccessGroups : taskListGroups"
|
|
:status-filter="statusFilter"
|
|
:group-dimension="groupDimension"
|
|
:sort-field="sortField"
|
|
:sort-direction="sortDirection"
|
|
:visible-fields="visibleFields"
|
|
:show-list-group-view="showListGroupView"
|
|
:favorite-id-map="favoriteIdMap"
|
|
:loading="pageLoading"
|
|
@create-task="onCreateTask"
|
|
@toggle-task="onToggleTask"
|
|
@task-click="onTaskClick"
|
|
@add-group="onAddGroup"
|
|
@update-task-field="onUpdateTaskField"
|
|
@update-task-fields="onUpdateTaskFields"
|
|
@update:status-filter="(v) => (statusFilter = v)"
|
|
@update:group-dimension="(v) => (groupDimension = v)"
|
|
@update:sort-field="(v) => (sortField = v)"
|
|
@update:sort-direction="(v) => (sortDirection = v)"
|
|
@update:visible-fields="(v) => (visibleFields = v)"
|
|
@list-select="onListSelect"
|
|
@open-collaborator="onOpenCollaborator"
|
|
@open-collaborator-for-list="onOpenCollaboratorForList"
|
|
@add-to-favorites="onAddToFavorites"
|
|
@remove-from-favorites="onRemoveFromFavorites"
|
|
@move-task="onMoveTask"
|
|
@move-task-group="onMoveTaskGroup"
|
|
@rename-list="onRenameList"
|
|
@delete-list="onDeleteList"
|
|
@rename-group="onRenameTaskGroup"
|
|
@delete-group="onDeleteTaskGroup"
|
|
@create-list="onCreateListModal('')"
|
|
/>
|
|
<TaskFlatView
|
|
v-else
|
|
:title="flatViewTitle"
|
|
:tasks="flatTasks"
|
|
:status-filter="statusFilter"
|
|
:sort-field="sortField"
|
|
:sort-direction="sortDirection"
|
|
:visible-fields="visibleFields"
|
|
:editable="flatViewEditable"
|
|
:task-edit-map="flatTaskEditMap"
|
|
:loading="pageLoading"
|
|
@toggle-task="onToggleTask"
|
|
@task-click="onTaskClick"
|
|
@update-task-field="onUpdateTaskField"
|
|
@update-task-fields="onUpdateTaskFields"
|
|
@update:status-filter="(v) => (statusFilter = v)"
|
|
@update:sort-field="(v) => (sortField = v)"
|
|
@update:sort-direction="(v) => (sortDirection = v)"
|
|
@update:visible-fields="(v) => (visibleFields = v)"
|
|
@load-subtasks="onLoadSubtasks"
|
|
/>
|
|
<CreateTaskModal @register="registerTaskModal" @success="onTaskCreated" />
|
|
<CreateListModal @register="registerListModal" @success="onListCreated" />
|
|
<TaskDetailDrawer
|
|
@register="registerDetailDrawer"
|
|
@save-task="onSaveTask"
|
|
@create-sub-task="onCreateSubTask"
|
|
@toggle-sub-task="onToggleSubTask"
|
|
@toggle-task="onToggleTask"
|
|
@navigate-task="onNavigateTask"
|
|
@delete-task="onDeleteTask"
|
|
@delete-sub-task="onDeleteSubTaskRecord"
|
|
@update-sub-task-field="onUpdateSubTaskField"
|
|
@update-sub-task-fields="onUpdateSubTaskFields"
|
|
@follow-task="onFollowTask"
|
|
@unfollow-task="onUnfollowTask"
|
|
@move-task="onMoveTask"
|
|
/>
|
|
<CollaboratorModal ref="collaboratorModalRef" :task-list-id="collaboratorTargetListId || currentListId" :is-owner="collaboratorTargetListId ? isOwnerOfTargetList : isCurrentOwner" :secret-level="collaboratorTargetList ? collaboratorTargetList.secretLevel || 1 : currentList?.secretLevel || 1" :secret-text="collaboratorTargetList ? collaboratorTargetList.secretText || '非密' : currentList?.secretText || '非密'" @changed="onCollaboratorsChanged" />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" name="tasklist-index-page" setup>
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { message } from 'ant-design-vue';
|
|
import { useModal } from '/@/components/Modal';
|
|
import { useDrawer } from '/@/components/Drawer';
|
|
import { useUserStore } from '/@/store/modules/user';
|
|
import TaskSidebar from './components/TaskSidebar.vue';
|
|
import TaskContent from './components/TaskContent.vue';
|
|
import TaskFlatView from './components/TaskFlatView.vue';
|
|
import CreateTaskModal from './components/CreateTaskModal.vue';
|
|
import CreateListModal from './components/CreateListModal.vue';
|
|
import TaskDetailDrawer from './components/TaskDetailDrawer.vue';
|
|
import CollaboratorModal from './components/CollaboratorModal.vue';
|
|
import {
|
|
getMyFavorites,
|
|
addTaskListGroup,
|
|
addTaskList,
|
|
removeFavorite,
|
|
removeFavoriteGroup,
|
|
deleteTaskListApi,
|
|
renameTaskList,
|
|
renameGroup,
|
|
getMyOwnLists,
|
|
getMyCollabLists,
|
|
getAllLists,
|
|
moveTaskList,
|
|
moveGroup,
|
|
getMyPermission,
|
|
getCollaborators,
|
|
addToFavorites,
|
|
addTask,
|
|
editTask,
|
|
deleteTask as deleteTaskApi,
|
|
toggleTaskStatus,
|
|
followTask as followTaskApi,
|
|
unfollowTask as unfollowTaskApi,
|
|
listAllByMainId,
|
|
myResponsibleTasks,
|
|
myFollowedTasks,
|
|
moveTask,
|
|
moveTaskGroup,
|
|
getCurrentUserSecurityLevel,
|
|
} from './TaskList.api';
|
|
import { DEFAULT_VISIBLE_FIELDS } from './types';
|
|
import { isOverdue, checkUserSecLevel } from './utils/taskUtils';
|
|
import type {
|
|
TaskListGroup,
|
|
StatusFilter,
|
|
GroupDimension,
|
|
SortField,
|
|
SortDirection,
|
|
FieldKey,
|
|
TaskList,
|
|
ViewType,
|
|
TaskItem,
|
|
TaskGroup,
|
|
} from './types';
|
|
|
|
const taskListGroups = ref<TaskListGroup[]>([]);
|
|
const quickAccessGroups = ref<TaskListGroup[]>([]);
|
|
const currentView = ref<ViewType>('my-tasks');
|
|
const currentListId = ref<string>('');
|
|
const statusFilter = ref<StatusFilter>('all');
|
|
const groupDimension = ref<GroupDimension>('custom');
|
|
const sortField = ref<SortField>('sortOrder');
|
|
const sortDirection = ref<SortDirection>('asc');
|
|
const showListGroupView = ref(false);
|
|
const visibleFields = ref<FieldKey[]>([...DEFAULT_VISIBLE_FIELDS]);
|
|
const currentDrawerTaskId = ref<string>('');
|
|
const pageLoading = ref(false);
|
|
|
|
const [registerTaskModal, { openModal: openTaskModal }] = useModal();
|
|
const [registerListModal, { openModal: openListModal }] = useModal();
|
|
const [registerDetailDrawer, { openDrawer: openDetailDrawer }] = useDrawer();
|
|
|
|
const collaboratorModalRef = ref<InstanceType<typeof CollaboratorModal> | null>(null);
|
|
const collaboratorTargetListId = ref('');
|
|
const defaultGroupRealIdMap = ref<Record<string, string>>({});
|
|
|
|
const userStore = useUserStore();
|
|
|
|
const currentUserSecurityLevel = ref<number>(3);
|
|
|
|
const flatTasks = ref<TaskItem[]>([]);
|
|
const flatViewTitle = ref('');
|
|
const flatViewEditable = ref(false);
|
|
const flatTaskEditMap = ref<Map<string, boolean>>(new Map());
|
|
|
|
const isFlatView = computed(() => currentView.value === 'my-tasks' || currentView.value === 'followed');
|
|
|
|
const favoriteIdMap = ref(new Map<string, string>());
|
|
const permissionMap = ref(new Map<string, string>());
|
|
|
|
async function loadFavorites() {
|
|
try {
|
|
const data: any[] = await getMyFavorites();
|
|
const newFavMap = new Map<string, string>();
|
|
const newPermMap = new Map<string, string>();
|
|
for (const fav of data) {
|
|
if (fav.type === '1' && fav.mainId) {
|
|
newFavMap.set(fav.mainId, fav.id);
|
|
if (fav.permission) {
|
|
newPermMap.set(fav.mainId, fav.permission);
|
|
}
|
|
}
|
|
}
|
|
favoriteIdMap.value = newFavMap;
|
|
permissionMap.value = newPermMap;
|
|
taskListGroups.value = buildTree(data);
|
|
} catch (e) {
|
|
console.error('[TaskList] 加载收藏列表失败', e);
|
|
}
|
|
}
|
|
|
|
function buildTree(favorites: any[]): TaskListGroup[] {
|
|
const groups: TaskListGroup[] = [];
|
|
const ungroupedLists: TaskList[] = [];
|
|
|
|
for (const fav of favorites) {
|
|
if (fav.type === '0') {
|
|
groups.push({
|
|
id: fav.id,
|
|
name: fav.tasklistName || '',
|
|
pid: '0',
|
|
hasChild: fav.hasChild || '0',
|
|
type: 0,
|
|
sortOrder: fav.sortOrder || 0,
|
|
delFlag: 0,
|
|
taskLists: [],
|
|
});
|
|
} else if (fav.type === '1') {
|
|
const list: TaskList = {
|
|
id: fav.mainId,
|
|
name: fav.tasklistName || '',
|
|
pid: fav.pid || '',
|
|
hasChild: '0',
|
|
type: 1,
|
|
sortOrder: fav.sortOrder || 0,
|
|
delFlag: 0,
|
|
groups: [],
|
|
secretLevel: fav.secretLevel,
|
|
secretText: fav.secretText,
|
|
};
|
|
|
|
if (fav.pid) {
|
|
const parentGroup = groups.find((g) => g.id === fav.pid);
|
|
if (parentGroup) {
|
|
parentGroup.taskLists.push(list);
|
|
} else {
|
|
ungroupedLists.push(list);
|
|
}
|
|
} else {
|
|
ungroupedLists.push(list);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ungroupedLists.length > 0) {
|
|
const virtualGroup: TaskListGroup = {
|
|
id: '__ungrouped__',
|
|
name: '__ungrouped__',
|
|
pid: '0',
|
|
hasChild: '0',
|
|
type: 0,
|
|
sortOrder: -1,
|
|
delFlag: 0,
|
|
taskLists: ungroupedLists,
|
|
};
|
|
groups.unshift(virtualGroup);
|
|
}
|
|
|
|
groups.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
|
|
|
return groups;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const level = await getCurrentUserSecurityLevel();
|
|
currentUserSecurityLevel.value = level || 3;
|
|
console.log('[TaskList] 当前用户密级:', currentUserSecurityLevel.value);
|
|
} catch (e) {
|
|
console.error('[TaskList] 获取用户密级失败', e);
|
|
}
|
|
loadFavorites();
|
|
onQuickAccessTabClick('all');
|
|
});
|
|
|
|
function findListInGroups(groups: TaskListGroup[], listId: string): TaskList | null {
|
|
for (const group of groups) {
|
|
for (const list of group.taskLists) {
|
|
if (list.id === listId) return list;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const currentList = computed<TaskList | null>(() => {
|
|
if (!currentListId.value) return null;
|
|
return findListInGroups(taskListGroups.value, currentListId.value) || findListInGroups(quickAccessGroups.value, currentListId.value);
|
|
});
|
|
|
|
const isCurrentOwner = computed(() => currentList.value?.myPermission === '1');
|
|
|
|
async function loadPermissionForList(listId: string) {
|
|
const silentOpts = { successMessageMode: 'none' as const };
|
|
try {
|
|
const perm = await getMyPermission({ taskListId: listId }, silentOpts);
|
|
const list = findListById(listId);
|
|
if (list) {
|
|
list.myPermission = perm || '';
|
|
}
|
|
const collabs: any[] = await getCollaborators({ taskListId: listId }, silentOpts);
|
|
const names = collabs.map((c: any) => c.username).filter(Boolean);
|
|
if (list) {
|
|
list.collaboratorNames = names.join(',');
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 获取权限失败', e);
|
|
}
|
|
}
|
|
|
|
function buildTaskItem(tr: any): TaskItem {
|
|
return {
|
|
id: tr.id,
|
|
taskName: tr.taskName || '',
|
|
taskDesc: tr.taskDesc || '',
|
|
completed: tr.taskStatus === 1,
|
|
priority: tr.priority || '',
|
|
assigneeId: tr.assigneeId || '',
|
|
assigneeName: tr.assigneeName || '',
|
|
assignId: tr.assignId || '',
|
|
assignName: tr.assignName || '',
|
|
participantId: tr.participantId || '',
|
|
participantName: tr.participantName || '',
|
|
followersId: tr.followersId || '',
|
|
followersName: tr.followersName || '',
|
|
createBy: tr.createByName || tr.createBy_dictText || tr.createBy || '',
|
|
startTime: tr.startTime ? tr.startTime.split(' ')[0] : '',
|
|
endTime: tr.endTime ? tr.endTime.split(' ')[0] : '',
|
|
completeTime: tr.completeTime ? tr.completeTime.split(' ')[0] : '',
|
|
createTime: tr.createTime ? tr.createTime.split(' ')[0] : '',
|
|
updateTime: tr.updateTime ? tr.updateTime.split(' ')[0] : '',
|
|
type: tr.type || '',
|
|
subTaskCount: tr.subTaskCount || 0,
|
|
completedSubTaskCount: tr.completedSubTaskCount || 0,
|
|
remark: tr.remark || '',
|
|
groupId: '',
|
|
groupName: '',
|
|
childCount: tr.subTaskCount || 0,
|
|
completedChildCount: tr.completedSubTaskCount || 0,
|
|
pid: tr.pid || '',
|
|
hasChild: tr.hasChild || '0',
|
|
sortOrder: tr.sortOrder ?? 0,
|
|
isDefault: tr.isDefault,
|
|
};
|
|
}
|
|
|
|
function buildFlatTaskItem(tr: any): TaskItem {
|
|
const base = buildTaskItem(tr);
|
|
return {
|
|
...base,
|
|
listName: tr.listName || '',
|
|
mainId: tr.mainId || '',
|
|
myPermission: tr.myPermission || '',
|
|
};
|
|
}
|
|
|
|
async function loadTasks(listId: string) {
|
|
try {
|
|
const res = await listAllByMainId({ mainId: listId });
|
|
const data: any[] = Array.isArray(res) ? res : Array.isArray(res?.result) ? res.result : [];
|
|
const list = findListById(listId);
|
|
if (!list) return;
|
|
|
|
const groupRecords = data.filter((d: any) => d.type === '0');
|
|
const taskRecords = data.filter((d: any) => d.type === '1');
|
|
|
|
const groupMap = new Map<string, TaskGroup>();
|
|
let defaultGroup: TaskGroup | null = null;
|
|
|
|
for (const gr of groupRecords) {
|
|
const tg: TaskGroup = {
|
|
id: gr.id,
|
|
name: gr.taskName || '',
|
|
taskListId: listId,
|
|
collapsed: false,
|
|
tasks: [],
|
|
isDefault: gr.isDefault,
|
|
sortOrder: gr.sortOrder ?? 0,
|
|
};
|
|
groupMap.set(gr.id, tg);
|
|
if (gr.isDefault === 1) {
|
|
defaultGroup = tg;
|
|
defaultGroupRealIdMap.value[listId] = gr.id;
|
|
}
|
|
}
|
|
|
|
if (!defaultGroup) {
|
|
defaultGroup = {
|
|
id: '__default__',
|
|
name: '默认分组',
|
|
taskListId: listId,
|
|
collapsed: false,
|
|
tasks: [],
|
|
sortOrder: 0,
|
|
};
|
|
}
|
|
|
|
const groupIds = new Set(groupRecords.map((g: any) => g.id));
|
|
|
|
const allTaskItems = new Map<string, TaskItem>();
|
|
for (const tr of taskRecords) {
|
|
allTaskItems.set(tr.id, buildTaskItem(tr));
|
|
}
|
|
|
|
function getGroupForTask(taskId: string): { groupId: string; groupName: string } {
|
|
const item = allTaskItems.get(taskId);
|
|
if (!item) return { groupId: defaultGroup!.id, groupName: defaultGroup!.name };
|
|
if (!item.pid) return { groupId: defaultGroup!.id, groupName: defaultGroup!.name };
|
|
if (groupIds.has(item.pid)) {
|
|
const tg = groupMap.get(item.pid);
|
|
return { groupId: tg!.id, groupName: tg!.name };
|
|
}
|
|
return getGroupForTask(item.pid);
|
|
}
|
|
|
|
for (const [id, taskItem] of allTaskItems) {
|
|
const { groupId, groupName } = getGroupForTask(id);
|
|
taskItem.groupId = groupId;
|
|
taskItem.groupName = groupName;
|
|
const targetGroup = groupMap.get(groupId) || defaultGroup!;
|
|
targetGroup.tasks.push(taskItem);
|
|
}
|
|
|
|
const resultGroups = Array.from(groupMap.values());
|
|
resultGroups.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
|
|
|
list.groups = resultGroups;
|
|
} catch (e) {
|
|
console.error('[TaskList] 加载任务失败', e);
|
|
}
|
|
}
|
|
|
|
async function onViewSelect(view: ViewType) {
|
|
currentView.value = view;
|
|
showListGroupView.value = false;
|
|
currentListId.value = '';
|
|
flatTasks.value = [];
|
|
groupDimension.value = 'none';
|
|
sortField.value = 'createTime';
|
|
sortDirection.value = 'asc';
|
|
pageLoading.value = true;
|
|
|
|
try {
|
|
if (view === 'my-tasks') {
|
|
flatViewTitle.value = '我的事项';
|
|
flatViewEditable.value = true;
|
|
flatTaskEditMap.value = new Map();
|
|
const data: any[] = await myResponsibleTasks();
|
|
flatTasks.value = (data || []).map(buildFlatTaskItem);
|
|
} else if (view === 'followed') {
|
|
flatViewTitle.value = '关注事项';
|
|
flatViewEditable.value = false;
|
|
const data: any[] = await myFollowedTasks();
|
|
const items = (data || []).map(buildFlatTaskItem);
|
|
const editMap = new Map<string, boolean>();
|
|
const userId = userStore.getUserInfo?.id || '';
|
|
for (const item of items) {
|
|
const isAssignee = item.assigneeId?.includes(userId);
|
|
const hasListPerm = item.myPermission === '1' || item.myPermission === '2';
|
|
editMap.set(item.id, !!isAssignee || hasListPerm);
|
|
}
|
|
flatTaskEditMap.value = editMap;
|
|
flatTasks.value = items;
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 加载视图失败', e);
|
|
} finally {
|
|
pageLoading.value = false;
|
|
}
|
|
}
|
|
|
|
async function onListSelect(listId: string) {
|
|
currentView.value = '';
|
|
currentListId.value = listId;
|
|
showListGroupView.value = false;
|
|
groupDimension.value = 'custom';
|
|
pageLoading.value = true;
|
|
try {
|
|
loadPermissionForList(listId);
|
|
await loadTasks(listId);
|
|
} finally {
|
|
pageLoading.value = false;
|
|
}
|
|
}
|
|
|
|
function onOpenCollaborator() {
|
|
collaboratorTargetListId.value = '';
|
|
collaboratorModalRef.value?.open();
|
|
}
|
|
|
|
const collaboratorTargetList = computed(() => {
|
|
if (!collaboratorTargetListId.value) return null;
|
|
for (const group of quickAccessGroups.value) {
|
|
for (const list of group.taskLists) {
|
|
if (list.id === collaboratorTargetListId.value) return list;
|
|
}
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const isOwnerOfTargetList = computed(() => {
|
|
return collaboratorTargetList.value?.myPermission === '1';
|
|
});
|
|
|
|
function onOpenCollaboratorForList(listId: string) {
|
|
const list = findListInQuickAccess(listId);
|
|
if (list && list.myPermission !== '1') {
|
|
message.warning('仅所有者可管理协作者');
|
|
return;
|
|
}
|
|
collaboratorTargetListId.value = listId;
|
|
collaboratorModalRef.value?.open();
|
|
}
|
|
|
|
function findListInQuickAccess(listId: string): TaskList | null {
|
|
for (const group of quickAccessGroups.value) {
|
|
for (const list of group.taskLists) {
|
|
if (list.id === listId) return list;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function onCollaboratorsChanged(collaboratorNames?: string) {
|
|
const targetId = collaboratorTargetListId.value || currentListId.value;
|
|
if (targetId && collaboratorNames !== undefined) {
|
|
updateListCollaboratorNames(targetId, collaboratorNames);
|
|
}
|
|
if (targetId) {
|
|
await loadPermissionForList(targetId);
|
|
}
|
|
collaboratorTargetListId.value = '';
|
|
}
|
|
|
|
function updateListCollaboratorNames(listId: string, names: string) {
|
|
for (const group of quickAccessGroups.value) {
|
|
const idx = group.taskLists.findIndex((l) => l.id === listId);
|
|
if (idx >= 0) {
|
|
group.taskLists[idx] = { ...group.taskLists[idx], collaboratorNames: names };
|
|
return;
|
|
}
|
|
}
|
|
if (currentList.value && currentList.value.id === listId) {
|
|
currentList.value = { ...currentList.value, collaboratorNames: names };
|
|
}
|
|
for (const group of taskListGroups.value) {
|
|
const idx = group.taskLists.findIndex((l) => l.id === listId);
|
|
if (idx >= 0) {
|
|
group.taskLists[idx] = { ...group.taskLists[idx], collaboratorNames: names };
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function onAddToFavorites(taskListId: string, pid?: string) {
|
|
try {
|
|
await addToFavorites({ taskListId, pid: pid || undefined });
|
|
await loadFavorites();
|
|
} catch (e) {
|
|
console.error('[TaskList] 添加收藏失败', e);
|
|
}
|
|
}
|
|
|
|
async function onRemoveFromFavorites(favoriteId: string) {
|
|
try {
|
|
await removeFavorite({ favoriteId });
|
|
await loadFavorites();
|
|
} catch (e) {
|
|
console.error('[TaskList] 取消收藏失败', e);
|
|
}
|
|
}
|
|
|
|
async function onRemoveFavorite(listId: string) {
|
|
try {
|
|
const favId = favoriteIdMap.value.get(listId);
|
|
if (favId) {
|
|
await removeFavorite({ favoriteId: favId });
|
|
const isCurrent = currentListId.value === listId;
|
|
await loadFavorites();
|
|
if (isCurrent) {
|
|
const firstAvailable = findFirstAvailableList();
|
|
currentListId.value = firstAvailable?.id || '';
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 从收藏移除失败', e);
|
|
}
|
|
}
|
|
|
|
async function onQuickAccessTabClick(tabKey: string) {
|
|
const quickAccessViewMap: Record<string, ViewType> = {
|
|
all: 'all-tasks',
|
|
own: 'created',
|
|
collab: 'assigned',
|
|
};
|
|
currentView.value = quickAccessViewMap[tabKey] || currentView.value;
|
|
showListGroupView.value = true;
|
|
currentListId.value = '';
|
|
groupDimension.value = 'custom';
|
|
flatTasks.value = [];
|
|
pageLoading.value = true;
|
|
|
|
try {
|
|
if (tabKey === 'all') {
|
|
const data: TaskList[] = await getAllLists();
|
|
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
|
} else if (tabKey === 'own') {
|
|
const data: TaskList[] = await getMyOwnLists();
|
|
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
|
} else if (tabKey === 'collab') {
|
|
const data: TaskList[] = await getMyCollabLists();
|
|
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 加载清单失败', e);
|
|
} finally {
|
|
pageLoading.value = false;
|
|
}
|
|
}
|
|
|
|
function wrapAsVirtualGroupsWithDetails(lists: TaskList[]): TaskListGroup[] {
|
|
return lists.map((item: TaskList) => ({
|
|
id: `_virtual_${item.id}`,
|
|
name: item.tasklistName || '',
|
|
pid: '0',
|
|
hasChild: '0',
|
|
type: 0,
|
|
sortOrder: 0,
|
|
delFlag: 0,
|
|
taskLists: [
|
|
{
|
|
id: item.id,
|
|
name: item.tasklistName || '',
|
|
pid: '',
|
|
hasChild: '0',
|
|
type: 1,
|
|
sortOrder: 0,
|
|
delFlag: 0,
|
|
groups: [],
|
|
ownerName: item.ownerName || '',
|
|
collaboratorNames: item.collaboratorNames || '',
|
|
createTimeStr: item.createTimeStr || '',
|
|
secretLevel: (item as any).secretLevel,
|
|
secretText: (item as any).secretText,
|
|
myPermission: item.myPermission,
|
|
},
|
|
],
|
|
}));
|
|
}
|
|
|
|
async function onCreateTask(groupId: string, taskName: string) {
|
|
if (taskName && taskName.trim()) {
|
|
try {
|
|
const params: any = {
|
|
mainId: currentListId.value,
|
|
taskName: taskName.trim(),
|
|
type: '1',
|
|
pid: groupId && groupId !== '__default__' ? groupId : '',
|
|
};
|
|
await addTask(params);
|
|
if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 创建任务失败', e);
|
|
}
|
|
} else {
|
|
openTaskModal(true, { groupId, mainId: currentListId.value, secretLevel: currentList.value?.secretLevel || 1, secretText: currentList.value?.secretText || '非密' });
|
|
}
|
|
}
|
|
|
|
function onCreateListModal(groupId: string) {
|
|
openListModal(true, {
|
|
groupId,
|
|
userSecurityLevel: currentUserSecurityLevel.value,
|
|
});
|
|
}
|
|
|
|
async function onListCreated(listId?: string) {
|
|
if (listId) {
|
|
await addToFavorites({ taskListId: listId });
|
|
}
|
|
await loadFavorites();
|
|
if (showListGroupView.value) {
|
|
refreshQuickAccessView();
|
|
}
|
|
}
|
|
|
|
async function refreshQuickAccessView() {
|
|
try {
|
|
const viewMap: Record<string, () => Promise<TaskList[]>> = {
|
|
'all-tasks': getAllLists,
|
|
created: getMyOwnLists,
|
|
assigned: getMyCollabLists,
|
|
};
|
|
const fetcher = viewMap[currentView.value];
|
|
if (fetcher) {
|
|
const data = await fetcher();
|
|
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 刷新计划列表失败', e);
|
|
}
|
|
}
|
|
|
|
async function onTaskCreated() {
|
|
if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
}
|
|
|
|
async function onCreateGroup(name: string) {
|
|
const groupName = name?.trim() || '新建分组';
|
|
try {
|
|
const newId = await addTaskListGroup({ tasklistName: groupName });
|
|
const maxSort = taskListGroups.value.reduce((max, g) => Math.max(max, g.sortOrder ?? 0), 0);
|
|
const newGroup: TaskListGroup = {
|
|
id: newId,
|
|
name: groupName,
|
|
pid: '0',
|
|
hasChild: '0',
|
|
type: 0,
|
|
sortOrder: maxSort + 1,
|
|
delFlag: 0,
|
|
taskLists: [],
|
|
};
|
|
taskListGroups.value.push(newGroup);
|
|
} catch (e) {
|
|
console.error('[TaskList] 创建分组失败', e);
|
|
}
|
|
}
|
|
|
|
async function onAddGroup(listId: string, groupName: string) {
|
|
if (!groupName.trim() || !listId) return;
|
|
try {
|
|
await addTask({
|
|
mainId: listId,
|
|
taskName: groupName.trim(),
|
|
type: '0',
|
|
});
|
|
if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 创建任务分组失败', e);
|
|
}
|
|
}
|
|
|
|
async function onRenameGroup(groupId: string, newName: string) {
|
|
try {
|
|
await renameGroup({ groupId, newName });
|
|
await loadFavorites();
|
|
} catch (e) {
|
|
console.error('[TaskList] 重命名分组失败', e);
|
|
}
|
|
}
|
|
|
|
async function onDeleteGroup(groupId: string) {
|
|
try {
|
|
await removeFavoriteGroup({ groupId });
|
|
await loadFavorites();
|
|
const hasCurrentList = taskListGroups.value.some((g) => g.taskLists.some((l) => l.id === currentListId.value));
|
|
if (!hasCurrentList) {
|
|
const firstAvailable = findFirstAvailableList();
|
|
currentListId.value = firstAvailable?.id || '';
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 移除分组失败', e);
|
|
}
|
|
}
|
|
|
|
async function onMoveList(favoriteId: string, targetGroupId: string, sortOrder: number) {
|
|
try {
|
|
await moveTaskList({ favoriteId, targetGroupId: targetGroupId || '', sortOrder });
|
|
} catch (e) {
|
|
console.error('[TaskList] 移动清单失败', e);
|
|
}
|
|
}
|
|
|
|
async function onMoveGroup(groupId: string, sortOrder: number) {
|
|
try {
|
|
await moveGroup({ groupId, sortOrder });
|
|
} catch (e) {
|
|
console.error('[TaskList] 移动分组失败', e);
|
|
}
|
|
}
|
|
|
|
async function onMoveTask(taskId: string, targetPid: string, targetSortOrder: number) {
|
|
try {
|
|
await moveTask({ taskId, targetPid, targetSortOrder });
|
|
} catch (e) {
|
|
console.error('[TaskList] 移动任务失败', e);
|
|
} finally {
|
|
if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function onMoveTaskGroup(taskGroupId: string, targetSortOrder: number) {
|
|
try {
|
|
await moveTaskGroup({ taskGroupId, targetSortOrder });
|
|
} catch (e) {
|
|
console.error('[TaskList] 移动任务分组失败', e);
|
|
} finally {
|
|
if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function onDeleteTaskGroup(groupId: string) {
|
|
if (groupId === '__default__') return;
|
|
try {
|
|
await deleteTaskApi({ id: groupId }, () => {});
|
|
if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 删除任务分组失败', e);
|
|
}
|
|
}
|
|
|
|
async function onRenameTaskGroup(groupId: string, newName: string) {
|
|
if (!newName.trim()) return;
|
|
const realGroupId = groupId === '__default__' ? defaultGroupRealIdMap.value[currentListId.value] : groupId;
|
|
if (!realGroupId) return;
|
|
try {
|
|
await editTask({ id: realGroupId, taskName: newName.trim() });
|
|
if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 重命名任务分组失败', e);
|
|
}
|
|
}
|
|
|
|
async function onToggleTask(taskId: string) {
|
|
try {
|
|
await toggleTaskStatus({ id: taskId });
|
|
if (isFlatView.value) {
|
|
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
|
if (idx >= 0) {
|
|
flatTasks.value[idx] = { ...flatTasks.value[idx], completed: !flatTasks.value[idx].completed };
|
|
}
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 切换任务状态失败', e);
|
|
}
|
|
}
|
|
|
|
function getAllTasks(): TaskItem[] {
|
|
if (isFlatView.value) return flatTasks.value;
|
|
const allTasks: TaskItem[] = [];
|
|
for (const group of taskListGroups.value) {
|
|
for (const list of group.taskLists) {
|
|
for (const taskGroup of list.groups) {
|
|
allTasks.push(...taskGroup.tasks);
|
|
}
|
|
}
|
|
}
|
|
return allTasks;
|
|
}
|
|
|
|
function onTaskClick(taskId: string) {
|
|
currentDrawerTaskId.value = taskId;
|
|
const allTasks = getAllTasks();
|
|
const task = allTasks.find((t) => t.id === taskId);
|
|
if (task) {
|
|
const readOnly = isFlatView.value ? !canEditFlatTask(task) : currentList.value?.myPermission === '3';
|
|
openDetailDrawer(true, { task, allTasks, readOnly, secretLevel: currentList.value?.secretLevel || 1 });
|
|
}
|
|
}
|
|
|
|
function canEditFlatTask(task: TaskItem): boolean {
|
|
if (flatViewEditable.value) return true;
|
|
return flatTaskEditMap.value.has(task.id);
|
|
}
|
|
|
|
async function refreshFlatViewTasks() {
|
|
if (currentView.value === 'my-tasks') {
|
|
const data: any[] = await myResponsibleTasks();
|
|
flatTasks.value = (data || []).map(buildFlatTaskItem);
|
|
} else if (currentView.value === 'followed') {
|
|
const data: any[] = await myFollowedTasks();
|
|
const items = (data || []).map(buildFlatTaskItem);
|
|
const editMap = new Map<string, boolean>();
|
|
const userId = userStore.getUserInfo?.id || '';
|
|
for (const item of items) {
|
|
const isAssignee = item.assigneeId?.includes(userId);
|
|
const hasListPerm = item.myPermission === '1' || item.myPermission === '2';
|
|
editMap.set(item.id, !!isAssignee || hasListPerm);
|
|
}
|
|
flatTaskEditMap.value = editMap;
|
|
flatTasks.value = items;
|
|
}
|
|
}
|
|
|
|
async function onLoadSubtasks(taskId: string, mainId: string) {
|
|
try {
|
|
const res: any = await listAllByMainId({ mainId });
|
|
const records: any[] = Array.isArray(res) ? res : Array.isArray(res?.result) ? res.result : [];
|
|
const taskRecords = records.filter((r: any) => r.type === '1' && r.pid === taskId);
|
|
if (taskRecords.length === 0) return;
|
|
const newChildren = taskRecords.map((tr: any) => buildFlatTaskItem(tr));
|
|
flatTasks.value = [...flatTasks.value, ...newChildren];
|
|
if (currentView.value === 'followed') {
|
|
const editMap = new Map(flatTaskEditMap.value);
|
|
const userId = userStore.getUserInfo?.id || '';
|
|
for (const child of newChildren) {
|
|
const isAssignee = child.assigneeId?.includes(userId);
|
|
const hasListPerm = child.myPermission === '1' || child.myPermission === '2';
|
|
if (isAssignee || hasListPerm) {
|
|
editMap.set(child.id, true);
|
|
}
|
|
}
|
|
flatTaskEditMap.value = editMap;
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 加载子任务失败', e);
|
|
}
|
|
}
|
|
|
|
function refreshDrawer() {
|
|
if (!currentDrawerTaskId.value) return;
|
|
const allTasks = getAllTasks();
|
|
const task = allTasks.find((t) => t.id === currentDrawerTaskId.value);
|
|
if (task) {
|
|
const readOnly = isFlatView.value ? !canEditFlatTask(task) : currentList.value?.myPermission === '3';
|
|
openDetailDrawer(true, { task, allTasks, readOnly, secretLevel: currentList.value?.secretLevel || 1 });
|
|
}
|
|
}
|
|
|
|
async function onUpdateTaskField(taskId: string, field: string, value: any) {
|
|
const userFields = ['assigneeId', 'participantId', 'followersId'];
|
|
if (userFields.includes(field) && value) {
|
|
const secretLevel = currentList.value?.secretLevel || 1;
|
|
const { ok, invalidNames } = await checkUserSecLevel(value, secretLevel);
|
|
if (!ok) {
|
|
message.warning(`以下人员的密级不满足当前计划(${currentList.value?.secretText || '非密'})要求:${invalidNames.join('、')}`);
|
|
return;
|
|
}
|
|
}
|
|
try {
|
|
await editTask({ id: taskId, [field]: value });
|
|
if (isFlatView.value) {
|
|
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
|
if (idx >= 0) {
|
|
flatTasks.value[idx] = { ...flatTasks.value[idx], [field]: value };
|
|
}
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 更新任务字段失败', e);
|
|
}
|
|
}
|
|
|
|
async function onUpdateTaskFields(taskId: string, fields: Record<string, any>) {
|
|
const userFields = ['assigneeId', 'participantId', 'followersId'];
|
|
for (const f of userFields) {
|
|
if (fields[f]) {
|
|
const secretLevel = currentList.value?.secretLevel || 1;
|
|
const { ok, invalidNames } = await checkUserSecLevel(fields[f], secretLevel);
|
|
if (!ok) {
|
|
message.warning(`以下人员的密级不满足当前计划(${currentList.value?.secretText || '非密'})要求:${invalidNames.join('、')}`);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
await editTask({ id: taskId, ...fields });
|
|
if (isFlatView.value) {
|
|
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
|
if (idx >= 0) {
|
|
flatTasks.value[idx] = { ...flatTasks.value[idx], ...fields };
|
|
}
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 批量更新任务字段失败', e);
|
|
}
|
|
}
|
|
|
|
async function onSaveTask(updatedTask: TaskItem) {
|
|
const secretLevel = currentList.value?.secretLevel || 1;
|
|
const secretText = currentList.value?.secretText || '非密';
|
|
const userIdFields = [
|
|
{ value: updatedTask.assigneeId, label: '负责人' },
|
|
{ value: updatedTask.participantId, label: '参与人' },
|
|
{ value: updatedTask.followersId, label: '关注人' },
|
|
];
|
|
for (const { value } of userIdFields) {
|
|
if (value) {
|
|
const { ok, invalidNames } = await checkUserSecLevel(value, secretLevel);
|
|
if (!ok) {
|
|
message.warning(`以下人员的密级不满足当前计划(${secretText})要求:${invalidNames.join('、')}`);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
await editTask({
|
|
id: updatedTask.id,
|
|
taskName: updatedTask.taskName,
|
|
taskDesc: updatedTask.taskDesc,
|
|
priority: updatedTask.priority,
|
|
assigneeId: updatedTask.assigneeId,
|
|
assigneeName: updatedTask.assigneeName,
|
|
participantId: updatedTask.participantId,
|
|
participantName: updatedTask.participantName,
|
|
followersId: updatedTask.followersId,
|
|
followersName: updatedTask.followersName,
|
|
startTime: updatedTask.startTime || null,
|
|
endTime: updatedTask.endTime || null,
|
|
type: updatedTask.type,
|
|
remark: updatedTask.remark,
|
|
});
|
|
if (isFlatView.value) {
|
|
const idx = flatTasks.value.findIndex((t) => t.id === updatedTask.id);
|
|
if (idx >= 0) {
|
|
flatTasks.value[idx] = { ...flatTasks.value[idx], ...updatedTask };
|
|
}
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 保存任务失败', e);
|
|
}
|
|
}
|
|
|
|
async function onCreateSubTask(parentTaskId: string, taskName: string) {
|
|
try {
|
|
const mainId = isFlatView.value ? flatTasks.value.find((t) => t.id === parentTaskId)?.mainId || '' : currentListId.value;
|
|
await addTask({ mainId, taskName, type: '1', pid: parentTaskId });
|
|
if (isFlatView.value) {
|
|
await refreshFlatViewTasks();
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
refreshDrawer();
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 创建子任务失败', e);
|
|
}
|
|
}
|
|
|
|
async function onToggleSubTask(taskId: string) {
|
|
try {
|
|
await toggleTaskStatus({ id: taskId });
|
|
if (isFlatView.value) {
|
|
await refreshFlatViewTasks();
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
refreshDrawer();
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 切换子任务状态失败', e);
|
|
}
|
|
}
|
|
|
|
function onNavigateTask(taskId: string) {
|
|
currentDrawerTaskId.value = taskId;
|
|
const allTasks = getAllTasks();
|
|
const task = allTasks.find((t) => t.id === taskId);
|
|
if (task) {
|
|
openDetailDrawer(true, { task, allTasks });
|
|
}
|
|
}
|
|
|
|
async function onDeleteTask(taskId: string) {
|
|
try {
|
|
await deleteTaskApi({ id: taskId }, () => {});
|
|
if (isFlatView.value) {
|
|
flatTasks.value = flatTasks.value.filter((t) => t.id !== taskId);
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 删除任务失败', e);
|
|
}
|
|
}
|
|
|
|
async function onDeleteSubTaskRecord(taskId: string) {
|
|
try {
|
|
await deleteTaskApi({ id: taskId }, () => {});
|
|
if (isFlatView.value) {
|
|
await refreshFlatViewTasks();
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
refreshDrawer();
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 删除子任务失败', e);
|
|
}
|
|
}
|
|
|
|
async function onUpdateSubTaskField(taskId: string, field: string, value: any) {
|
|
try {
|
|
await editTask({ id: taskId, [field]: value || '' });
|
|
if (isFlatView.value) {
|
|
await refreshFlatViewTasks();
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
refreshDrawer();
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 更新子任务字段失败', e);
|
|
}
|
|
}
|
|
|
|
async function onUpdateSubTaskFields(taskId: string, fields: Record<string, any>) {
|
|
try {
|
|
await editTask({ id: taskId, ...fields });
|
|
if (isFlatView.value) {
|
|
await refreshFlatViewTasks();
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
refreshDrawer();
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 批量更新子任务字段失败', e);
|
|
}
|
|
}
|
|
|
|
async function onFollowTask(taskId: string) {
|
|
try {
|
|
await followTaskApi({ id: taskId });
|
|
if (isFlatView.value) {
|
|
refreshFlatViewTasks();
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 关注任务失败', e);
|
|
}
|
|
}
|
|
|
|
async function onUnfollowTask(taskId: string) {
|
|
try {
|
|
await unfollowTaskApi({ id: taskId });
|
|
if (isFlatView.value) {
|
|
refreshFlatViewTasks();
|
|
} else if (currentListId.value) {
|
|
await loadTasks(currentListId.value);
|
|
}
|
|
} catch (e) {
|
|
console.error('[TaskList] 取消关注失败', e);
|
|
}
|
|
}
|
|
|
|
async function onRenameList(listId: string, newName: string) {
|
|
const list = findListById(listId);
|
|
if (list) {
|
|
list.name = newName;
|
|
}
|
|
try {
|
|
await renameTaskList({ taskListId: listId, newName });
|
|
} catch (e) {
|
|
console.error('[TaskList] 重命名清单失败', e);
|
|
}
|
|
}
|
|
|
|
async function onDeleteList(listId: string) {
|
|
try {
|
|
if (showListGroupView.value) {
|
|
const favId = favoriteIdMap.value.get(listId);
|
|
if (favId) {
|
|
await removeFavorite({ favoriteId: favId });
|
|
}
|
|
} else {
|
|
await deleteTaskListApi({ id: listId });
|
|
}
|
|
if (currentListId.value === listId) {
|
|
const firstAvailable = findFirstAvailableList();
|
|
currentListId.value = firstAvailable?.id || '';
|
|
}
|
|
await loadFavorites();
|
|
} catch (e) {
|
|
console.error('[TaskList] 删除清单失败', e);
|
|
}
|
|
}
|
|
|
|
function findListById(listId: string): TaskList | null {
|
|
for (const group of taskListGroups.value) {
|
|
for (const list of group.taskLists) {
|
|
if (list.id === listId) return list;
|
|
}
|
|
}
|
|
for (const group of quickAccessGroups.value) {
|
|
for (const list of group.taskLists) {
|
|
if (list.id === listId) return list;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function findFirstAvailableList(): TaskList | null {
|
|
for (const group of taskListGroups.value) {
|
|
for (const list of group.taskLists) {
|
|
if (list.delFlag !== 1) return list;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.task-page {
|
|
display: flex;
|
|
height: calc(100vh - 120px);
|
|
background: #f5f5f5;
|
|
overflow: hidden;
|
|
}
|
|
</style>
|