From 0a0e6df66b3df711a550ad69d421fae35aacaf59 Mon Sep 17 00:00:00 2001 From: Chaos Date: Sat, 29 Nov 2025 09:02:00 +0800 Subject: [PATCH] =?UTF-8?q?refactor(settings):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86=E5=92=8C=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整用户管理页面角色数据获取方法,使用 getRolesOptions 替代 getAllRoles - 更新用户表格组件接收的角色数据属性名及类型 - 修改设备管理页面路由路径,从 /device/list 调整为 /devices - 移除调试用 console.log 输出语句 - 添加选项类型 Options 接口定义 - 优化侧边栏导航结构与交互逻辑,支持父级菜单带链接可点击 - 引入日志模块用于 API 请求与响应记录 - 升级依赖包配置,移除 peer 标记 - 微调样式类名增强布局效果和用户体验 --- package-lock.json | 13 -- src/lib/api/httpClient.ts | 23 +-- src/lib/api/services/roleService.ts | 7 +- src/lib/api/services/userService.ts | 4 +- .../components/layout/app/AppHeader.svelte | 4 +- .../components/layout/app/AppSidebar.svelte | 150 ++++++++---------- src/lib/components/table/UserTable.svelte | 17 +- src/lib/log.ts | 43 +++++ src/lib/types/api.ts | 5 + src/routes/app/+layout.svelte | 2 +- src/routes/app/settings/+page.svelte | 3 + .../app/settings/auth/users/+page.server.ts | 4 +- .../app/settings/auth/users/+page.svelte | 28 ++-- .../{device/list => devices}/+page.server.ts | 3 +- .../{device/list => devices}/+page.svelte | 6 +- .../{device => devices}/type/+page.svelte | 0 16 files changed, 170 insertions(+), 142 deletions(-) create mode 100644 src/lib/log.ts rename src/routes/app/settings/{device/list => devices}/+page.server.ts (94%) rename src/routes/app/settings/{device/list => devices}/+page.svelte (79%) rename src/routes/app/settings/{device => devices}/type/+page.svelte (100%) diff --git a/package-lock.json b/package-lock.json index 464eefb..0acde90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1473,7 +1473,6 @@ "integrity": "sha512-/rnwfSWS3qwUSzvHynUTORF9xSJi7PCR9yXkxUOnRrNqyKmCmh3FPHH+E9BbgqxXfTevGXBqgnlh9kMb+9T5XA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -1513,7 +1512,6 @@ "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", @@ -1846,7 +1844,6 @@ "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1897,7 +1894,6 @@ "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", @@ -2116,7 +2112,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2493,7 +2488,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3614,7 +3608,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3642,7 +3635,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3776,7 +3768,6 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3793,7 +3784,6 @@ "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" @@ -4520,7 +4510,6 @@ "integrity": "sha512-d1R+3pFa39LXoHCsxHmV//D2pSFZlEMlnxCVQ54TlrQv+4o5pewJO0/Pc5MUp+j71PJrOrPJHTvREZJHn+ymDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -4718,7 +4707,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4788,7 +4776,6 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/src/lib/api/httpClient.ts b/src/lib/api/httpClient.ts index 2390b27..6b40a24 100644 --- a/src/lib/api/httpClient.ts +++ b/src/lib/api/httpClient.ts @@ -2,6 +2,7 @@ import type { HttpMethod, JsonObject, JsonValue } from '$lib/types/http.ts'; import type { ApiResult } from '$lib/types/api.ts'; +import { log } from '$lib/log.ts'; interface RequestOptions extends Omit { @@ -13,8 +14,6 @@ const API_BASE_URL = import.meta.env.VITE_PUBLIC_API_URL || 'http://localhost:18 const normalizeHeaders = (headers?: HeadersInit):Record =>{ const result:Record = {}; - - console.log('normalizeHeaders', headers); if (!headers){ return result; } @@ -36,7 +35,7 @@ const normalizeHeaders = (headers?: HeadersInit):Record =>{ }) } - console.log('normalizeHeaders result:', result); + return result; } export class HttpError extends Error { @@ -48,7 +47,6 @@ export class HttpError extends Error { this.name = 'HttpError'; this.status = status; this.details = details; - // 保持正确的原型链 if (Error.captureStackTrace) { Error.captureStackTrace(this, HttpError); @@ -63,10 +61,10 @@ const httpRequest = async ( options: RequestOptions = {} ): Promise> => { const fullUrl = `${API_BASE_URL}${url}`; + + log.info('API Request:', method, fullUrl) const { body , headers, ...rest } = options; - - const requestHeaders: Record = normalizeHeaders(headers); let requestBody: BodyInit | undefined; @@ -85,7 +83,7 @@ const httpRequest = async ( try { - + log.debug('API Request Body:', requestBody) const response = await fetch(fullUrl, { method, headers: requestHeaders, @@ -94,24 +92,27 @@ const httpRequest = async ( body: canHaveBody ? requestBody : undefined, ...rest }); - - console.log('response', response); - if (!response.ok) { + let errorDetail; + try { errorDetail = await response.json(); + } catch (e) { console.error('Error parsing JSON:', e); errorDetail = await response.text(); } const message = `HTTP Error ${response.status} (${response.statusText})`; + log.warn(message) throw new HttpError(message, response.status, errorDetail); } const contentType = response.headers.get('Content-Type'); if (contentType && contentType.includes('application/json')) { - return (await response.json()) as ApiResult; + const res = await response.json(); + log.info('API Response:', res) + return (res) as ApiResult; } return { code: 200, msg: 'OK', data: null } ; // 这里的 as any 是为了兼容 T 可能是 null 的情况 diff --git a/src/lib/api/services/roleService.ts b/src/lib/api/services/roleService.ts index 4bb086f..1138e1a 100644 --- a/src/lib/api/services/roleService.ts +++ b/src/lib/api/services/roleService.ts @@ -1,9 +1,10 @@ import { api } from '$lib/api/httpClient.ts'; -import type { RoleResponse } from '$lib/types/user.ts'; +import type { Options } from '$lib/types/api.ts'; + export const roleService = { - getAllRoles: async (token:string) => { - const response = await api.get('/role/', {headers: {Authorization: `${token}`}}); + getRolesOptions: async (token:string) => { + const response = await api.get('/roles/options', {headers: {Authorization: `${token}`}}); if (response.code != 200 || !response.data){ throw new Error(response.msg); } diff --git a/src/lib/api/services/userService.ts b/src/lib/api/services/userService.ts index 190d91a..978269e 100644 --- a/src/lib/api/services/userService.ts +++ b/src/lib/api/services/userService.ts @@ -4,7 +4,7 @@ import type { PageResult } from '$lib/types/dataTable.ts'; export const userService = { getUserProfile: async (token:string) => { - const response = await api.get('/user/profile', {headers: {Authorization: `${token}`}}); + const response = await api.get('/users/me', {headers: {Authorization: `${token}`}}); if (response.code != 200 || !response.data){ throw new Error(response.msg); } @@ -15,7 +15,7 @@ export const userService = { formData.append('pageNum', page.toString()); formData.append('pageSize', size.toString()); const response = await api.get>( - '/user/all', + '/users', { body: formData, headers: {Authorization: `${token}`} diff --git a/src/lib/components/layout/app/AppHeader.svelte b/src/lib/components/layout/app/AppHeader.svelte index 743ee3e..f822a07 100644 --- a/src/lib/components/layout/app/AppHeader.svelte +++ b/src/lib/components/layout/app/AppHeader.svelte @@ -6,7 +6,7 @@ -
+
@@ -32,7 +32,7 @@ > {#if page.data.user.avatar} Avatar diff --git a/src/lib/components/layout/app/AppSidebar.svelte b/src/lib/components/layout/app/AppSidebar.svelte index ddfead5..b7216bc 100644 --- a/src/lib/components/layout/app/AppSidebar.svelte +++ b/src/lib/components/layout/app/AppSidebar.svelte @@ -6,12 +6,9 @@ import Icon from '$lib/components/icon/Icon.svelte'; import type { NavItem, ProcessedNavItem } from '$lib/types/layout'; import { TOAST_KEY, type ToastState } from '$lib/stores/toast.svelte.ts'; - // import { SIDEBAR_KEY, SidebarState } from '$lib/stores/sidebar.svelte.ts'; import { enhance } from '$app/forms'; - // const sidebarState = getContext(SIDEBAR_KEY); - - // 1. 模拟数据:包含三层结构 + // 1. 模拟数据:现在给父级 "device" 也加上了 href const rawNavItems: NavItem[] = [ { id: 'dashboard', @@ -29,7 +26,7 @@ id: 'settings', label: '系统设置', icon: 'settings', - href: '/app/settings', + href: '/app/settings', // 父级带链接 subItems: [ { id: 'auth', @@ -55,20 +52,16 @@ ] }, { - id: "device", + id: 'device', label: '设备管理', icon: 'laptop-settings', + href: '/app/settings/devices', // 【修改点】父级现在有链接了,指向列表页 subItems: [ { - id: 'device', - label: '设备管理', - href: '/app/settings/device/list' - }, - { - id: 'type', + id: 'device-type', label: '类型管理', - href: '/app/settings/device/type' - }, + href: '/app/settings/devices/type' + } ] }, { @@ -93,64 +86,50 @@ ]; /** - - * 递归计算高亮状态 (强类型版本) - + * 递归计算高亮状态 (逻辑修复版) + * 修复了当 href 为 undefined 时的潜在报错,并增强了激活判断 */ - function processNavItems(items: NavItem[], currentPath: string): ProcessedNavItem[] { return items.map((item) => { - const isSelfActive = - item.href === '/' ? currentPath === '/' : currentPath.startsWith(item.href); + // 安全获取 href + const href = item.href || ''; + + // 判断自身是否激活 + // 如果 href 存在,且 (是根路径全等 OR 当前路径以 href 开头) + const isSelfActive = href + ? (href === '/' ? currentPath === '/' : currentPath.startsWith(href)) + : false; let processedSubItems: ProcessedNavItem[] | undefined = undefined; - let isChildActive = false; if (item.subItems) { - // 递归调用 - processedSubItems = processNavItems(item.subItems, currentPath); - - // 检查子项激活状态 - + // 只要有一个子项激活,或者子项的子项激活,父级就算 ChildActive isChildActive = processedSubItems.some((sub) => sub.isActive || sub.isChildActive); } return { ...item, - - subItems: processedSubItems, // 这里类型现在是 ProcessedNavItem[] - + subItems: processedSubItems, isActive: isSelfActive, - isChildActive: isChildActive }; }); } - // 使用 $derived 动态计算,类型自动推断为 ProcessedNavItem[] - + // 使用 $derived 动态计算 let navItems = $derived(processNavItems(rawNavItems, page.url.pathname)); - // 获取 Toast 以便提示用户 - + // Toast & Logout 逻辑 const toast = getContext(TOAST_KEY); - // 处理提交结果的回调 - const handleLogout = () => { toast.info('正在退出登录...'); - return async ({ result, update }) => { - // result.type 可能是 'redirect', 'success', 'failure' - if (result.type === 'redirect') { toast.success('您已安全退出'); } - - // update() 会触发默认行为(也就是执行 redirect 跳转) - await update(); }; }; @@ -158,44 +137,64 @@ let logoutForm: HTMLFormElement; - - + {#snippet menuItem(item: ProcessedNavItem)}
  • {#if item.subItems && item.subItems.length > 0} -
    - + +
    + {#if item.icon} {/if} - {item.label} + + {#if item.href} + + e.stopPropagation()} + > + {item.label} + + {:else} + + {item.label} + {/if}
      {#each item.subItems as subItem (subItem.id)} - - {@render menuItem(subItem)} {/each}
    {:else} - + + {#if item.icon} {:else} - - {/if} - {item.label} {/if}
  • {/snippet} +
    +
    @@ -219,19 +218,12 @@
    - - - - - -
    + {#if page.data.user}