czh-20260630-优化计划,增加分页、滚动加载等
This commit is contained in:
+216
-67
@@ -32,6 +32,8 @@
|
||||
:show-list-group-view="showListGroupView"
|
||||
:favorite-id-map="favoriteIdMap"
|
||||
:loading="pageLoading"
|
||||
:has-more="showListGroupView ? listViewHasMore : false"
|
||||
:loading-more="showListGroupView ? loadingMore : false"
|
||||
@create-task="onCreateTask"
|
||||
@toggle-task="onToggleTask"
|
||||
@task-click="onTaskClick"
|
||||
@@ -55,6 +57,8 @@
|
||||
@rename-group="onRenameTaskGroup"
|
||||
@delete-group="onDeleteTaskGroup"
|
||||
@create-list="onCreateListModal('')"
|
||||
@load-more="onLoadMoreListViews"
|
||||
@search-keyword="onSearchKeywordChange"
|
||||
/>
|
||||
<TaskFlatView
|
||||
v-else
|
||||
@@ -67,6 +71,9 @@
|
||||
:editable="flatViewEditable"
|
||||
:task-edit-map="flatTaskEditMap"
|
||||
:loading="pageLoading"
|
||||
:has-more="hasMore"
|
||||
:loading-more="loadingMore"
|
||||
:total-count="totalCount"
|
||||
@toggle-task="onToggleTask"
|
||||
@task-click="onTaskClick"
|
||||
@update-task-field="onUpdateTaskField"
|
||||
@@ -76,6 +83,7 @@
|
||||
@update:sort-direction="(v) => (sortDirection = v)"
|
||||
@update:visible-fields="(v) => (visibleFields = v)"
|
||||
@load-subtasks="onLoadSubtasks"
|
||||
@load-more="loadMoreFlatTasks"
|
||||
/>
|
||||
<CreateTaskModal @register="registerTaskModal" @success="onTaskCreated" />
|
||||
<CreateListModal @register="registerListModal" @success="onListCreated" />
|
||||
@@ -168,6 +176,19 @@
|
||||
const visibleFields = ref<FieldKey[]>([...DEFAULT_VISIBLE_FIELDS]);
|
||||
const currentDrawerTaskId = ref<string>('');
|
||||
const pageLoading = ref(false);
|
||||
const loadingMore = ref(false);
|
||||
const pageNo = ref(1);
|
||||
const pageSize = 50;
|
||||
const totalCount = ref(0);
|
||||
const hasMore = computed(() => flatTasks.value.length < totalCount.value);
|
||||
const searchKeyword = ref('');
|
||||
const listViewPageNo = ref(1);
|
||||
const listViewPageSize = 30;
|
||||
const listViewTotalCount = ref(0);
|
||||
const listViewHasMore = computed(() => {
|
||||
const allLists = quickAccessGroups.value.flatMap((g) => g.taskLists);
|
||||
return allLists.length < listViewTotalCount.value;
|
||||
});
|
||||
|
||||
const [registerTaskModal, { openModal: openTaskModal }] = useModal();
|
||||
const [registerListModal, { openModal: openListModal }] = useModal();
|
||||
@@ -366,8 +387,7 @@
|
||||
|
||||
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 data: any[] = (await listAllByMainId({ mainId: listId })) || [];
|
||||
const list = findListById(listId);
|
||||
if (!list) return;
|
||||
|
||||
@@ -393,58 +413,72 @@
|
||||
defaultGroupRealIdMap.value[listId] = gr.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultGroup) {
|
||||
defaultGroup = {
|
||||
id: '__default__',
|
||||
name: '默认分组',
|
||||
taskListId: listId,
|
||||
collapsed: false,
|
||||
tasks: [],
|
||||
sortOrder: 0,
|
||||
};
|
||||
defaultGroup = { id: '__default__', name: '默认分组', taskListId: listId, collapsed: false, tasks: [], sortOrder: 0 };
|
||||
}
|
||||
|
||||
const groupIds = new Set(groupRecords.map((g: any) => g.id));
|
||||
const newTasks = taskRecords.map((tr: any) => buildTaskItem(tr));
|
||||
|
||||
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!;
|
||||
for (const taskItem of newTasks) {
|
||||
taskItem.groupId = resolveGroupId(taskItem, groupIds, groupMap, defaultGroup!);
|
||||
taskItem.groupName = groupMap.get(taskItem.groupId)?.name || defaultGroup!.name;
|
||||
const targetGroup = groupMap.get(taskItem.groupId) || defaultGroup!;
|
||||
targetGroup.tasks = targetGroup.tasks.filter((t) => t.id !== taskItem.id);
|
||||
targetGroup.tasks.push(taskItem);
|
||||
}
|
||||
|
||||
const resultGroups = Array.from(groupMap.values());
|
||||
resultGroups.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
|
||||
list.groups = resultGroups;
|
||||
list.groups = Array.from(groupMap.values()).sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载任务失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveGroupId(taskItem: TaskItem, groupIds: Set<string>, groupMap: Map<string, TaskGroup>, defaultGroup: TaskGroup): string {
|
||||
if (!taskItem.pid) return defaultGroup.id;
|
||||
if (groupIds.has(taskItem.pid)) return taskItem.pid;
|
||||
return defaultGroup.id;
|
||||
}
|
||||
|
||||
async function loadMoreFlatTasks() {
|
||||
if (!hasMore.value || loadingMore.value) return;
|
||||
loadingMore.value = true;
|
||||
try {
|
||||
pageNo.value++;
|
||||
if (currentView.value === 'my-tasks') {
|
||||
const res: any = await myResponsibleTasks({ pageNo: pageNo.value, pageSize });
|
||||
const data: any[] = Array.isArray(res?.records) ? res.records : Array.isArray(res?.result?.records) ? res.result.records : Array.isArray(res?.result) ? res.result : [];
|
||||
totalCount.value = res?.total || res?.result?.total || data.length;
|
||||
flatTasks.value = [...flatTasks.value, ...data.map(buildFlatTaskItem)];
|
||||
} else if (currentView.value === 'followed') {
|
||||
const res: any = await myFollowedTasks({ pageNo: pageNo.value, pageSize });
|
||||
const data: any[] = Array.isArray(res?.records) ? res.records : Array.isArray(res?.result?.records) ? res.result.records : Array.isArray(res?.result) ? res.result : [];
|
||||
totalCount.value = res?.total || res?.result?.total || data.length;
|
||||
const items = data.map(buildFlatTaskItem);
|
||||
flatTasks.value = [...flatTasks.value, ...items];
|
||||
const editMap = new Map(flatTaskEditMap.value);
|
||||
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;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载更多任务失败', e);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onViewSelect(view: ViewType) {
|
||||
currentView.value = view;
|
||||
showListGroupView.value = false;
|
||||
currentListId.value = '';
|
||||
flatTasks.value = [];
|
||||
pageNo.value = 1;
|
||||
totalCount.value = 0;
|
||||
groupDimension.value = 'none';
|
||||
sortField.value = 'createTime';
|
||||
sortDirection.value = 'asc';
|
||||
@@ -455,13 +489,17 @@
|
||||
flatViewTitle.value = '我的事项';
|
||||
flatViewEditable.value = true;
|
||||
flatTaskEditMap.value = new Map();
|
||||
const data: any[] = await myResponsibleTasks();
|
||||
flatTasks.value = (data || []).map(buildFlatTaskItem);
|
||||
const res: any = await myResponsibleTasks({ pageNo: 1, pageSize });
|
||||
const data: any[] = Array.isArray(res?.records) ? res.records : Array.isArray(res?.result?.records) ? res.result.records : Array.isArray(res?.result) ? res.result : [];
|
||||
totalCount.value = res?.total || res?.result?.total || data.length;
|
||||
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 res: any = await myFollowedTasks({ pageNo: 1, pageSize });
|
||||
const data: any[] = Array.isArray(res?.records) ? res.records : Array.isArray(res?.result?.records) ? res.result.records : Array.isArray(res?.result) ? res.result : [];
|
||||
totalCount.value = res?.total || res?.result?.total || data.length;
|
||||
const items = data.map(buildFlatTaskItem);
|
||||
const editMap = new Map<string, boolean>();
|
||||
const userId = userStore.getUserInfo?.id || '';
|
||||
for (const item of items) {
|
||||
@@ -604,18 +642,23 @@
|
||||
currentListId.value = '';
|
||||
groupDimension.value = 'custom';
|
||||
flatTasks.value = [];
|
||||
listViewPageNo.value = 1;
|
||||
pageLoading.value = true;
|
||||
|
||||
try {
|
||||
const kw = searchKeyword.value || undefined;
|
||||
if (tabKey === 'all') {
|
||||
const data: TaskList[] = await getAllLists();
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
||||
const res: any = await getAllLists({ pageNo: 1, pageSize: listViewPageSize, keyword: kw });
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(res?.records || []);
|
||||
listViewTotalCount.value = res?.total || 0;
|
||||
} else if (tabKey === 'own') {
|
||||
const data: TaskList[] = await getMyOwnLists();
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
||||
const res: any = await getMyOwnLists({ pageNo: 1, pageSize: listViewPageSize, keyword: kw });
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(res?.records || []);
|
||||
listViewTotalCount.value = res?.total || 0;
|
||||
} else if (tabKey === 'collab') {
|
||||
const data: TaskList[] = await getMyCollabLists();
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
||||
const res: any = await getMyCollabLists({ pageNo: 1, pageSize: listViewPageSize, keyword: kw });
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(res?.records || []);
|
||||
listViewTotalCount.value = res?.total || 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载清单失败', e);
|
||||
@@ -624,6 +667,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
function onSearchKeywordChange(keyword: string) {
|
||||
searchKeyword.value = keyword;
|
||||
if (searchTimer) clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
listViewPageNo.value = 1;
|
||||
onQuickAccessTabClick(getCurrentTabKey());
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function getCurrentTabKey(): string {
|
||||
const tabKeyMap: Record<string, string> = {
|
||||
'all-tasks': 'all',
|
||||
created: 'own',
|
||||
assigned: 'collab',
|
||||
};
|
||||
return tabKeyMap[currentView.value] || 'all';
|
||||
}
|
||||
|
||||
async function onLoadMoreListViews() {
|
||||
if (!listViewHasMore.value || loadingMore.value) return;
|
||||
loadingMore.value = true;
|
||||
listViewPageNo.value++;
|
||||
const tabKeyMap: Record<string, string> = {
|
||||
'all-tasks': 'all',
|
||||
created: 'own',
|
||||
assigned: 'collab',
|
||||
};
|
||||
const tabKey = tabKeyMap[currentView.value] || '';
|
||||
try {
|
||||
const kw = searchKeyword.value || undefined;
|
||||
let res: any;
|
||||
if (tabKey === 'all') {
|
||||
res = await getAllLists({ pageNo: listViewPageNo.value, pageSize: listViewPageSize, keyword: kw });
|
||||
} else if (tabKey === 'own') {
|
||||
res = await getMyOwnLists({ pageNo: listViewPageNo.value, pageSize: listViewPageSize, keyword: kw });
|
||||
} else if (tabKey === 'collab') {
|
||||
res = await getMyCollabLists({ pageNo: listViewPageNo.value, pageSize: listViewPageSize, keyword: kw });
|
||||
}
|
||||
if (res?.records) {
|
||||
const existingIds = new Set(quickAccessGroups.value.flatMap((g) => g.taskLists).map((l) => l.id));
|
||||
const newLists = (res.records as TaskList[]).filter((item) => !existingIds.has(item.id));
|
||||
if (newLists.length > 0) {
|
||||
quickAccessGroups.value = [...quickAccessGroups.value, ...wrapAsVirtualGroupsWithDetails(newLists)];
|
||||
}
|
||||
listViewTotalCount.value = res?.total || 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载更多清单失败', e);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function wrapAsVirtualGroupsWithDetails(lists: TaskList[]): TaskListGroup[] {
|
||||
return lists.map((item: TaskList) => ({
|
||||
id: `_virtual_${item.id}`,
|
||||
@@ -663,9 +760,21 @@
|
||||
type: '1',
|
||||
pid: groupId && groupId !== '__default__' ? groupId : '',
|
||||
};
|
||||
await addTask(params);
|
||||
const created: any = await addTask(params);
|
||||
if (currentListId.value) {
|
||||
await loadTasks(currentListId.value);
|
||||
const newTask = buildTaskItem(created?.result || created || {});
|
||||
if (newTask.id) {
|
||||
newTask.groupId = groupId || '__default__';
|
||||
newTask.groupName = groupId && groupId !== '__default__'
|
||||
? (currentList.value?.groups?.find((g: TaskGroup) => g.id === groupId)?.name || '')
|
||||
: (currentList.value?.groups?.[0]?.name || '');
|
||||
const list = findListById(currentListId.value);
|
||||
if (list) {
|
||||
const targetGroup = list.groups.find((g: TaskGroup) => g.id === (groupId || list.groups[0]?.id));
|
||||
if (targetGroup) targetGroup.tasks.unshift(newTask);
|
||||
else if (list.groups.length > 0) list.groups[0].tasks.unshift(newTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 创建任务失败', e);
|
||||
@@ -697,15 +806,16 @@
|
||||
|
||||
async function refreshQuickAccessView() {
|
||||
try {
|
||||
const viewMap: Record<string, () => Promise<TaskList[]>> = {
|
||||
const viewMap: Record<string, (params?: any) => Promise<any>> = {
|
||||
'all-tasks': getAllLists,
|
||||
created: getMyOwnLists,
|
||||
assigned: getMyCollabLists,
|
||||
};
|
||||
const fetcher = viewMap[currentView.value];
|
||||
if (fetcher) {
|
||||
const data = await fetcher();
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
||||
const res = await fetcher({ pageNo: 1, pageSize: listViewPageSize });
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(res?.records || []);
|
||||
listViewTotalCount.value = res?.total || 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 刷新计划列表失败', e);
|
||||
@@ -981,18 +1091,33 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 乐观更新
|
||||
let oldValue: any = undefined;
|
||||
if (isFlatView.value) {
|
||||
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
||||
if (idx >= 0) {
|
||||
oldValue = (flatTasks.value[idx] as any)[field];
|
||||
(flatTasks.value[idx] as any)[field] = value;
|
||||
}
|
||||
} else {
|
||||
const found = findTaskInGroups(taskId);
|
||||
if (found) {
|
||||
oldValue = (found.task as any)[field];
|
||||
(found.task as any)[field] = value;
|
||||
}
|
||||
}
|
||||
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);
|
||||
// 回滚
|
||||
if (isFlatView.value) {
|
||||
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
||||
if (idx >= 0) (flatTasks.value[idx] as any)[field] = oldValue;
|
||||
} else {
|
||||
const found = findTaskInGroups(taskId);
|
||||
if (found) (found.task as any)[field] = oldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1008,18 +1133,33 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
const oldFields: Record<string, any> = {};
|
||||
if (isFlatView.value) {
|
||||
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
||||
if (idx >= 0) {
|
||||
for (const f of Object.keys(fields)) oldFields[f] = (flatTasks.value[idx] as any)[f];
|
||||
flatTasks.value[idx] = { ...flatTasks.value[idx], ...fields };
|
||||
}
|
||||
} else {
|
||||
const found = findTaskInGroups(taskId);
|
||||
if (found) {
|
||||
for (const f of Object.keys(fields)) oldFields[f] = (found.task as any)[f];
|
||||
Object.assign(found.task, fields);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await editTask({ id: taskId, ...fields });
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 批量更新任务字段失败', e);
|
||||
if (isFlatView.value) {
|
||||
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
||||
if (idx >= 0) {
|
||||
flatTasks.value[idx] = { ...flatTasks.value[idx], ...fields };
|
||||
for (const f of Object.keys(oldFields)) (flatTasks.value[idx] as any)[f] = oldFields[f];
|
||||
}
|
||||
} else if (currentListId.value) {
|
||||
await loadTasks(currentListId.value);
|
||||
} else {
|
||||
const found = findTaskInGroups(taskId);
|
||||
if (found) Object.assign(found.task, oldFields);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 批量更新任务字段失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1134,15 +1274,24 @@
|
||||
}
|
||||
|
||||
async function onDeleteTask(taskId: string) {
|
||||
let removed: { idx: number; item: TaskItem } | null = null;
|
||||
if (isFlatView.value) {
|
||||
const idx = flatTasks.value.findIndex((t) => t.id === taskId);
|
||||
if (idx >= 0) {
|
||||
removed = { idx, item: flatTasks.value[idx] };
|
||||
flatTasks.value.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await deleteTaskApi({ id: taskId }, () => {});
|
||||
if (isFlatView.value) {
|
||||
flatTasks.value = flatTasks.value.filter((t) => t.id !== taskId);
|
||||
} else if (currentListId.value) {
|
||||
if (!isFlatView.value && currentListId.value) {
|
||||
await loadTasks(currentListId.value);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 删除任务失败', e);
|
||||
if (removed) {
|
||||
flatTasks.value.splice(removed.idx, 0, removed.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user