czh-20260630-优化计划,增加分页、滚动加载等

This commit is contained in:
zhihao
2026-06-30 16:15:06 +08:00
parent e20fa25000
commit fd3bfea809
8 changed files with 599 additions and 123 deletions
+216 -67
View File
@@ -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);
}
}
}