diff --git a/src/hooks/useOptimisticMutation.ts b/src/hooks/useOptimisticMutation.ts new file mode 100644 index 0000000..29bd0b6 --- /dev/null +++ b/src/hooks/useOptimisticMutation.ts @@ -0,0 +1,41 @@ +import { ref } from 'vue'; +import { message } from 'ant-design-vue'; + +interface OptimisticOptions { + /** 调用前乐观修改本地数据 */ + onOptimistic: () => void; + /** 失败时回滚本地数据 */ + onRollback: () => void; + /** 失败提示消息 */ + errorMsg: string; + /** 成功回调 */ + onSuccess?: () => void; +} + +export function useOptimisticMutation() { + const loading = ref(false); + + async function execute( + apiCall: () => Promise, + options: OptimisticOptions + ): Promise { + const { onOptimistic, onRollback, errorMsg, onSuccess } = options; + + onOptimistic(); + + try { + const result = await apiCall(); + onSuccess?.(); + return result; + } catch (e: any) { + console.error('[OptimisticMutation] 操作失败', e); + message.error(errorMsg); + onRollback(); + throw e; + } finally { + loading.value = false; + } + } + + return { execute, loading }; +} diff --git a/src/hooks/useVirtualScroll.ts b/src/hooks/useVirtualScroll.ts new file mode 100644 index 0000000..a4689ee --- /dev/null +++ b/src/hooks/useVirtualScroll.ts @@ -0,0 +1,67 @@ +import { ref, computed, type Ref } from 'vue'; + +export interface VirtualScrollOptions { + items: Ref; + itemHeight: number; + buffer: number; + containerRef: Ref; +} + +export function useVirtualScroll(options: VirtualScrollOptions) { + const { items, itemHeight, buffer, containerRef } = options; + + const scrollTop = ref(0); + const containerHeight = ref(0); + + const totalHeight = computed(() => items.value.length * itemHeight); + + const startIndex = computed(() => { + const idx = Math.floor(scrollTop.value / itemHeight) - buffer; + return Math.max(0, idx); + }); + + const endIndex = computed(() => { + const visible = Math.ceil(containerHeight.value / itemHeight); + const idx = Math.floor(scrollTop.value / itemHeight) + visible + buffer; + return Math.min(items.value.length, idx); + }); + + const visibleItems = computed(() => { + return items.value.slice(startIndex.value, endIndex.value).map((item, idx) => ({ + item, + index: startIndex.value + idx, + style: { + position: 'absolute' as const, + top: `${(startIndex.value + idx) * itemHeight}px`, + width: '100%', + height: `${itemHeight}px`, + }, + })); + }); + + const containerStyle = computed(() => ({ + height: `${totalHeight.value}px`, + position: 'relative' as const, + overflow: 'hidden', + })); + + function onScroll(event: Event) { + const target = event.target as HTMLElement; + scrollTop.value = target.scrollTop; + } + + function updateContainer() { + if (containerRef.value) { + containerHeight.value = containerRef.value.clientHeight; + } + } + + return { + visibleItems, + containerStyle, + onScroll, + updateContainer, + totalHeight, + scrollTop, + }; +} diff --git a/src/layouts/default/header/index.vue b/src/layouts/default/header/index.vue index ab12ff5..7fb1fa7 100644 --- a/src/layouts/default/header/index.vue +++ b/src/layouts/default/header/index.vue @@ -16,6 +16,7 @@ :class="[prefixCls, `${prefixCls}--${getHeaderTheme}`, 'headerIntroductionClass']" > + 机密级 {{ t('layout.header.welcomeIn') }} {{ title }} @@ -256,6 +257,13 @@ gap: 10px; } + .header-secrecy-level { + color: #ffffff; + font-weight: bold; + font-size: 20px; + letter-spacing: 2px; + } + .header-logo { height: 32px; width: auto; diff --git a/src/views/tasklist/TaskList.api.ts b/src/views/tasklist/TaskList.api.ts index c7a2339..cb7aa76 100644 --- a/src/views/tasklist/TaskList.api.ts +++ b/src/views/tasklist/TaskList.api.ts @@ -106,11 +106,14 @@ export const renameGroup = (params) => defHttp.post({ url: Api.renameGroup, para export const renameTaskList = (params) => defHttp.post({ url: Api.renameTaskList, params }); -export const getMyOwnLists = () => defHttp.get({ url: Api.myOwnLists }); +export const getMyOwnLists = (params?: { pageNo?: number; pageSize?: number; keyword?: string }) => + defHttp.get({ url: Api.myOwnLists, params }); -export const getAllLists = () => defHttp.get({ url: Api.allLists }); +export const getAllLists = (params?: { pageNo?: number; pageSize?: number; keyword?: string }) => + defHttp.get({ url: Api.allLists, params }); -export const getMyCollabLists = () => defHttp.get({ url: Api.myCollabLists }); +export const getMyCollabLists = (params?: { pageNo?: number; pageSize?: number; keyword?: string }) => + defHttp.get({ url: Api.myCollabLists, params }); export const addTask = (params) => defHttp.post({ url: Api.addTask, params }); @@ -146,10 +149,13 @@ export const loadSubTasks = (params) => defHttp.get({ url: Api.loadSubTasks, par export const listByMainId = (params) => defHttp.get({ url: Api.listByMainId, params }); -export const listAllByMainId = (params) => defHttp.get({ url: Api.listAllByMainId, params }); +export const listAllByMainId = (params: { mainId: string }) => + defHttp.get({ url: Api.listAllByMainId, params }); -export const myResponsibleTasks = () => defHttp.get({ url: Api.myResponsibleTasks }); +export const myResponsibleTasks = (params?: { pageNo?: number; pageSize?: number }) => + defHttp.get({ url: Api.myResponsibleTasks, params }); -export const myFollowedTasks = () => defHttp.get({ url: Api.myFollowedTasks }); +export const myFollowedTasks = (params?: { pageNo?: number; pageSize?: number }) => + defHttp.get({ url: Api.myFollowedTasks, params }); export const getCurrentUserSecurityLevel = () => defHttp.get({ url: Api.getCurrentUserSecurityLevel }); diff --git a/src/views/tasklist/components/TaskContent.vue b/src/views/tasklist/components/TaskContent.vue index ca354f5..047adf4 100644 --- a/src/views/tasklist/components/TaskContent.vue +++ b/src/views/tasklist/components/TaskContent.vue @@ -16,6 +16,7 @@ placeholder="搜索计划名称" class="list-search-input" allow-clear + @search="onSearch" /> @@ -265,7 +266,7 @@
@@ -435,14 +443,14 @@
-
+
+
@@ -258,7 +264,7 @@ 'overdue-date': field.key === 'endTime' && isOverdue(ft.task), }" > - {{ field.key === 'startTime' ? ft.task.startTime : ft.task.endTime }} + {{ (field.key === 'startTime' ? ft.task.startTime : ft.task.endTime).substring(0, 10) }}
+

暂无事项

+
+ + 加载更多 +