From 83956f2587f6ca1dffb890a28baf57db432d8b8b Mon Sep 17 00:00:00 2001 From: chengkai3 Date: Sat, 20 Jun 2026 07:15:48 +0800 Subject: [PATCH] =?UTF-8?q?[fix]:[FL-155][=E8=8F=9C=E5=8D=95=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=A1=B5=E9=9D=A2=E6=9C=80=E5=90=8E3=E9=A1=B9?= =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=BC=98=E5=8C=96]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: multica-agent --- memory/2026-06-20.md | 18 ++++++++++ web/src/app/admin/menus/page.tsx | 61 +++++++++++++------------------- web/src/app/globals.css | 2 +- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/memory/2026-06-20.md b/memory/2026-06-20.md index f050697..965a9c9 100644 --- a/memory/2026-06-20.md +++ b/memory/2026-06-20.md @@ -128,3 +128,21 @@ - 风险与关注点: - `/api/v1/system-messages/me` 新增可选 `message_type` 参数,未传参时保持原列表语义;前端不再做本地类型过滤,分页 total 与服务端过滤结果一致。 - 改动影响系统消息列表展示与筛选,不改变创建、删除、标记已读接口字段。 + +# Work Log - 菜单管理页面最终细节对齐(FL-155) + +- 背景: + - 菜单管理页需要补齐启用/禁用 mutation 组织、移动卡片字段列宽和加载完成提示条件三处细节,与用户管理页保持一致。 + +- 本次处理: + - 菜单启用/禁用移除独立 `updateMenuStatusMutation`,改为复用 `updateMenuMutation` 通过 `PATCH /api/v1/admin/menus/{menuId}` 提交 `{ status }`。 + - `.admin-menus-menu-card-field` 标签列宽从 `72px` 调整为 `64px`。 + - 移动卡片“已加载全部”提示条件简化为 `allLoadedMenus.length >= menuTotal && allLoadedMenus.length > 0`。 + +- 验证: + - 基线:`npm --workspace web exec tsc --noEmit` 通过。 + - 修改后:`npm --workspace web exec eslint src/app/admin/menus/page.tsx --max-warnings=0` 通过。 + - 修改后:`npm --workspace web exec tsc --noEmit` 通过。 + +- 风险与关注点: + - 改动仅影响菜单管理页前端数据 mutation 组织和移动端展示条件,不改变接口路径、请求/响应字段或权限语义。 diff --git a/web/src/app/admin/menus/page.tsx b/web/src/app/admin/menus/page.tsx index fdeea33..c74936d 100644 --- a/web/src/app/admin/menus/page.tsx +++ b/web/src/app/admin/menus/page.tsx @@ -351,7 +351,7 @@ export default function AdminMenusPage() { }); const updateMenuMutation = useMutation({ - mutationFn: async ({ menuId, payload }: { menuId: string; payload: MenuMutationPayload }) => { + mutationFn: async ({ menuId, payload }: { menuId: string; payload: Partial }) => { const response = await fetchWithAuth(`/api/v1/admin/menus/${menuId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, @@ -360,18 +360,33 @@ export default function AdminMenusPage() { if (!response.ok) throw new Error(await readApiError(response)); return response.json() as Promise; }, - onMutate: () => { + onMutate: ({ menuId, payload }) => { + if (payload.status && Object.keys(payload).length === 1) { + setUpdatingStatusMenuId(menuId); + } setError(""); setSuccess(""); }, - onSuccess: async () => { - setSuccess("菜单已更新"); - closeDialog(); + onSuccess: async (_, variables) => { + if (variables.payload.status && Object.keys(variables.payload).length === 1) { + setSuccess(variables.payload.status === "enabled" ? "菜单已启用" : "菜单已禁用"); + } else { + setSuccess("菜单已更新"); + closeDialog(); + } await refreshData(); }, - onError: (candidate) => { + onError: (candidate, variables) => { setSuccess(""); - setError(candidate instanceof Error ? candidate.message : "更新菜单失败"); + const fallbackMessage = variables.payload.status && Object.keys(variables.payload).length === 1 + ? "菜单状态更新失败" + : "更新菜单失败"; + setError(candidate instanceof Error ? candidate.message : fallbackMessage); + }, + onSettled: (_data, _error, variables) => { + if (variables?.payload.status && Object.keys(variables.payload).length === 1) { + setUpdatingStatusMenuId(null); + } }, }); @@ -403,32 +418,6 @@ export default function AdminMenusPage() { onSettled: () => setDeletingMenuId(null), }); - const updateMenuStatusMutation = useMutation({ - mutationFn: async ({ menuId, status }: { menuId: string; status: "enabled" | "disabled" }) => { - const response = await fetchWithAuth(`/api/v1/admin/menus/${menuId}`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ status }), - }); - if (!response.ok) throw new Error(await readApiError(response)); - return response.json() as Promise; - }, - onMutate: ({ menuId }) => { - setUpdatingStatusMenuId(menuId); - setError(""); - setSuccess(""); - }, - onSuccess: async (_, variables) => { - setSuccess(variables.status === "enabled" ? "菜单已启用" : "菜单已禁用"); - await refreshData(); - }, - onError: (candidate) => { - setSuccess(""); - setError(candidate instanceof Error ? candidate.message : "菜单状态更新失败"); - }, - onSettled: () => setUpdatingStatusMenuId(null), - }); - const submit = useCallback(async () => { try { const values = await form.validateFields(); @@ -479,8 +468,8 @@ export default function AdminMenusPage() { const updateMenuStatus = useCallback(async (menu: MenuItem) => { const nextStatus: "enabled" | "disabled" = menu.status === "enabled" ? "disabled" : "enabled"; - updateMenuStatusMutation.mutate({ menuId: menu.id, status: nextStatus }); - }, [updateMenuStatusMutation]); + updateMenuMutation.mutate({ menuId: menu.id, payload: { status: nextStatus } }); + }, [updateMenuMutation]); const columns = useMemo>(() => { const base: TableColumnsType = [ @@ -876,7 +865,7 @@ export default function AdminMenusPage() { )} - {!menusQuery.isLoading && !isLoadingMore && allLoadedMenus.length >= menuTotal && allLoadedMenus.length > 0 && ( + {allLoadedMenus.length >= menuTotal && allLoadedMenus.length > 0 && (
已加载全部 {allLoadedMenus.length} 条数据 diff --git a/web/src/app/globals.css b/web/src/app/globals.css index 3719245..9aea051 100644 --- a/web/src/app/globals.css +++ b/web/src/app/globals.css @@ -554,7 +554,7 @@ body { .admin-menus-menu-card-field { display: grid; - grid-template-columns: 72px minmax(0, 1fr); + grid-template-columns: 64px minmax(0, 1fr); gap: 8px; align-items: baseline; }