refactor(settings): 重构用户管理和设备管理页面
- 调整用户管理页面角色数据获取方法,使用 getRolesOptions 替代 getAllRoles - 更新用户表格组件接收的角色数据属性名及类型 - 修改设备管理页面路由路径,从 /device/list 调整为 /devices - 移除调试用 console.log 输出语句 - 添加选项类型 Options 接口定义 - 优化侧边栏导航结构与交互逻辑,支持父级菜单带链接可点击 - 引入日志模块用于 API 请求与响应记录 - 升级依赖包配置,移除 peer 标记 - 微调样式类名增强布局效果和用户体验
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -1473,7 +1473,6 @@
|
|||||||
"integrity": "sha512-/rnwfSWS3qwUSzvHynUTORF9xSJi7PCR9yXkxUOnRrNqyKmCmh3FPHH+E9BbgqxXfTevGXBqgnlh9kMb+9T5XA==",
|
"integrity": "sha512-/rnwfSWS3qwUSzvHynUTORF9xSJi7PCR9yXkxUOnRrNqyKmCmh3FPHH+E9BbgqxXfTevGXBqgnlh9kMb+9T5XA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.0.0",
|
||||||
"@sveltejs/acorn-typescript": "^1.0.5",
|
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||||
@@ -1513,7 +1512,6 @@
|
|||||||
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
|
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
|
||||||
"debug": "^4.4.1",
|
"debug": "^4.4.1",
|
||||||
@@ -1846,7 +1844,6 @@
|
|||||||
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
|
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
@@ -1897,7 +1894,6 @@
|
|||||||
"integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
|
"integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.47.0",
|
"@typescript-eslint/scope-manager": "8.47.0",
|
||||||
"@typescript-eslint/types": "8.47.0",
|
"@typescript-eslint/types": "8.47.0",
|
||||||
@@ -2116,7 +2112,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2493,7 +2488,6 @@
|
|||||||
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3614,7 +3608,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -3642,7 +3635,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.11",
|
"nanoid": "^3.3.11",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
@@ -3776,7 +3768,6 @@
|
|||||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -3793,7 +3784,6 @@
|
|||||||
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
|
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.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==",
|
"integrity": "sha512-d1R+3pFa39LXoHCsxHmV//D2pSFZlEMlnxCVQ54TlrQv+4o5pewJO0/Pc5MUp+j71PJrOrPJHTvREZJHn+ymDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/remapping": "^2.3.4",
|
"@jridgewell/remapping": "^2.3.4",
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -4718,7 +4707,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -4788,7 +4776,6 @@
|
|||||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import type { HttpMethod, JsonObject, JsonValue } from '$lib/types/http.ts';
|
import type { HttpMethod, JsonObject, JsonValue } from '$lib/types/http.ts';
|
||||||
import type { ApiResult } from '$lib/types/api.ts';
|
import type { ApiResult } from '$lib/types/api.ts';
|
||||||
|
import { log } from '$lib/log.ts';
|
||||||
|
|
||||||
|
|
||||||
interface RequestOptions extends Omit<RequestInit, 'method' | 'body'> {
|
interface RequestOptions extends Omit<RequestInit, 'method' | 'body'> {
|
||||||
@@ -13,8 +14,6 @@ const API_BASE_URL = import.meta.env.VITE_PUBLIC_API_URL || 'http://localhost:18
|
|||||||
|
|
||||||
const normalizeHeaders = (headers?: HeadersInit):Record<string, string> =>{
|
const normalizeHeaders = (headers?: HeadersInit):Record<string, string> =>{
|
||||||
const result:Record<string,string> = {};
|
const result:Record<string,string> = {};
|
||||||
|
|
||||||
console.log('normalizeHeaders', headers);
|
|
||||||
if (!headers){
|
if (!headers){
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -36,7 +35,7 @@ const normalizeHeaders = (headers?: HeadersInit):Record<string, string> =>{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('normalizeHeaders result:', result);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
export class HttpError extends Error {
|
export class HttpError extends Error {
|
||||||
@@ -48,7 +47,6 @@ export class HttpError extends Error {
|
|||||||
this.name = 'HttpError';
|
this.name = 'HttpError';
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.details = details;
|
this.details = details;
|
||||||
|
|
||||||
// 保持正确的原型链
|
// 保持正确的原型链
|
||||||
if (Error.captureStackTrace) {
|
if (Error.captureStackTrace) {
|
||||||
Error.captureStackTrace(this, HttpError);
|
Error.captureStackTrace(this, HttpError);
|
||||||
@@ -63,10 +61,10 @@ const httpRequest = async <T>(
|
|||||||
options: RequestOptions = {}
|
options: RequestOptions = {}
|
||||||
): Promise<ApiResult<T>> => {
|
): Promise<ApiResult<T>> => {
|
||||||
const fullUrl = `${API_BASE_URL}${url}`;
|
const fullUrl = `${API_BASE_URL}${url}`;
|
||||||
|
|
||||||
|
log.info('API Request:', method, fullUrl)
|
||||||
const { body , headers, ...rest } = options;
|
const { body , headers, ...rest } = options;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const requestHeaders: Record<string, string> = normalizeHeaders(headers);
|
const requestHeaders: Record<string, string> = normalizeHeaders(headers);
|
||||||
let requestBody: BodyInit | undefined;
|
let requestBody: BodyInit | undefined;
|
||||||
|
|
||||||
@@ -85,7 +83,7 @@ const httpRequest = async <T>(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
log.debug('API Request Body:', requestBody)
|
||||||
const response = await fetch(fullUrl, {
|
const response = await fetch(fullUrl, {
|
||||||
method,
|
method,
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
@@ -94,24 +92,27 @@ const httpRequest = async <T>(
|
|||||||
body: canHaveBody ? requestBody : undefined,
|
body: canHaveBody ? requestBody : undefined,
|
||||||
...rest
|
...rest
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('response', response);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
||||||
let errorDetail;
|
let errorDetail;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
errorDetail = await response.json();
|
errorDetail = await response.json();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error parsing JSON:', e);
|
console.error('Error parsing JSON:', e);
|
||||||
errorDetail = await response.text();
|
errorDetail = await response.text();
|
||||||
}
|
}
|
||||||
const message = `HTTP Error ${response.status} (${response.statusText})`;
|
const message = `HTTP Error ${response.status} (${response.statusText})`;
|
||||||
|
log.warn(message)
|
||||||
throw new HttpError(message, response.status, errorDetail);
|
throw new HttpError(message, response.status, errorDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = response.headers.get('Content-Type');
|
const contentType = response.headers.get('Content-Type');
|
||||||
if (contentType && contentType.includes('application/json')) {
|
if (contentType && contentType.includes('application/json')) {
|
||||||
return (await response.json()) as ApiResult<T>;
|
const res = await response.json();
|
||||||
|
log.info('API Response:', res)
|
||||||
|
return (res) as ApiResult<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { code: 200, msg: 'OK', data: null } ; // 这里的 as any 是为了兼容 T 可能是 null 的情况
|
return { code: 200, msg: 'OK', data: null } ; // 这里的 as any 是为了兼容 T 可能是 null 的情况
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { api } from '$lib/api/httpClient.ts';
|
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 = {
|
export const roleService = {
|
||||||
getAllRoles: async (token:string) => {
|
getRolesOptions: async (token:string) => {
|
||||||
const response = await api.get<RoleResponse[]>('/role/', {headers: {Authorization: `${token}`}});
|
const response = await api.get<Options[]>('/roles/options', {headers: {Authorization: `${token}`}});
|
||||||
if (response.code != 200 || !response.data){
|
if (response.code != 200 || !response.data){
|
||||||
throw new Error(response.msg);
|
throw new Error(response.msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { PageResult } from '$lib/types/dataTable.ts';
|
|||||||
|
|
||||||
export const userService = {
|
export const userService = {
|
||||||
getUserProfile: async (token:string) => {
|
getUserProfile: async (token:string) => {
|
||||||
const response = await api.get<UserProfile>('/user/profile', {headers: {Authorization: `${token}`}});
|
const response = await api.get<UserProfile>('/users/me', {headers: {Authorization: `${token}`}});
|
||||||
if (response.code != 200 || !response.data){
|
if (response.code != 200 || !response.data){
|
||||||
throw new Error(response.msg);
|
throw new Error(response.msg);
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ export const userService = {
|
|||||||
formData.append('pageNum', page.toString());
|
formData.append('pageNum', page.toString());
|
||||||
formData.append('pageSize', size.toString());
|
formData.append('pageSize', size.toString());
|
||||||
const response = await api.get<PageResult<UserProfile[]>>(
|
const response = await api.get<PageResult<UserProfile[]>>(
|
||||||
'/user/all',
|
'/users',
|
||||||
{
|
{
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: {Authorization: `${token}`}
|
headers: {Authorization: `${token}`}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header class="w-full h-18 flex justify-between items-center px-4 bg-base-300 flex-shrink-0">
|
<header class="w-full h-18 flex justify-between items-center px-4 bg-base-300 flex-shrink-0 z-10">
|
||||||
<div>
|
<div>
|
||||||
<!-- <button-->
|
<!-- <button-->
|
||||||
<!-- class="btn btn-square btn-ghost"-->
|
<!-- class="btn btn-square btn-ghost"-->
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
>
|
>
|
||||||
{#if page.data.user.avatar}
|
{#if page.data.user.avatar}
|
||||||
<img
|
<img
|
||||||
class="w-8 h-8 rounded-full z-10"
|
class="w-8 h-8 rounded-full "
|
||||||
src="{page.data.user.avatar}"
|
src="{page.data.user.avatar}"
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,12 +6,9 @@
|
|||||||
import Icon from '$lib/components/icon/Icon.svelte';
|
import Icon from '$lib/components/icon/Icon.svelte';
|
||||||
import type { NavItem, ProcessedNavItem } from '$lib/types/layout';
|
import type { NavItem, ProcessedNavItem } from '$lib/types/layout';
|
||||||
import { TOAST_KEY, type ToastState } from '$lib/stores/toast.svelte.ts';
|
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';
|
import { enhance } from '$app/forms';
|
||||||
// const sidebarState = getContext<SidebarState>(SIDEBAR_KEY);
|
|
||||||
|
|
||||||
// 1. 模拟数据:包含三层结构
|
|
||||||
|
|
||||||
|
// 1. 模拟数据:现在给父级 "device" 也加上了 href
|
||||||
const rawNavItems: NavItem[] = [
|
const rawNavItems: NavItem[] = [
|
||||||
{
|
{
|
||||||
id: 'dashboard',
|
id: 'dashboard',
|
||||||
@@ -29,7 +26,7 @@
|
|||||||
id: 'settings',
|
id: 'settings',
|
||||||
label: '系统设置',
|
label: '系统设置',
|
||||||
icon: 'settings',
|
icon: 'settings',
|
||||||
href: '/app/settings',
|
href: '/app/settings', // 父级带链接
|
||||||
subItems: [
|
subItems: [
|
||||||
{
|
{
|
||||||
id: 'auth',
|
id: 'auth',
|
||||||
@@ -55,20 +52,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "device",
|
id: 'device',
|
||||||
label: '设备管理',
|
label: '设备管理',
|
||||||
icon: 'laptop-settings',
|
icon: 'laptop-settings',
|
||||||
|
href: '/app/settings/devices', // 【修改点】父级现在有链接了,指向列表页
|
||||||
subItems: [
|
subItems: [
|
||||||
{
|
{
|
||||||
id: 'device',
|
id: 'device-type',
|
||||||
label: '设备管理',
|
|
||||||
href: '/app/settings/device/list'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'type',
|
|
||||||
label: '类型管理',
|
label: '类型管理',
|
||||||
href: '/app/settings/device/type'
|
href: '/app/settings/devices/type'
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -93,64 +86,50 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 递归计算高亮状态 (逻辑修复版)
|
||||||
* 递归计算高亮状态 (强类型版本)
|
* 修复了当 href 为 undefined 时的潜在报错,并增强了激活判断
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function processNavItems(items: NavItem[], currentPath: string): ProcessedNavItem[] {
|
function processNavItems(items: NavItem[], currentPath: string): ProcessedNavItem[] {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
const isSelfActive =
|
// 安全获取 href
|
||||||
item.href === '/' ? currentPath === '/' : currentPath.startsWith(item.href);
|
const href = item.href || '';
|
||||||
|
|
||||||
|
// 判断自身是否激活
|
||||||
|
// 如果 href 存在,且 (是根路径全等 OR 当前路径以 href 开头)
|
||||||
|
const isSelfActive = href
|
||||||
|
? (href === '/' ? currentPath === '/' : currentPath.startsWith(href))
|
||||||
|
: false;
|
||||||
|
|
||||||
let processedSubItems: ProcessedNavItem[] | undefined = undefined;
|
let processedSubItems: ProcessedNavItem[] | undefined = undefined;
|
||||||
|
|
||||||
let isChildActive = false;
|
let isChildActive = false;
|
||||||
|
|
||||||
if (item.subItems) {
|
if (item.subItems) {
|
||||||
// 递归调用
|
|
||||||
|
|
||||||
processedSubItems = processNavItems(item.subItems, currentPath);
|
processedSubItems = processNavItems(item.subItems, currentPath);
|
||||||
|
// 只要有一个子项激活,或者子项的子项激活,父级就算 ChildActive
|
||||||
// 检查子项激活状态
|
|
||||||
|
|
||||||
isChildActive = processedSubItems.some((sub) => sub.isActive || sub.isChildActive);
|
isChildActive = processedSubItems.some((sub) => sub.isActive || sub.isChildActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
subItems: processedSubItems,
|
||||||
subItems: processedSubItems, // 这里类型现在是 ProcessedNavItem[]
|
|
||||||
|
|
||||||
isActive: isSelfActive,
|
isActive: isSelfActive,
|
||||||
|
|
||||||
isChildActive: isChildActive
|
isChildActive: isChildActive
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 $derived 动态计算,类型自动推断为 ProcessedNavItem[]
|
// 使用 $derived 动态计算
|
||||||
|
|
||||||
let navItems = $derived(processNavItems(rawNavItems, page.url.pathname));
|
let navItems = $derived(processNavItems(rawNavItems, page.url.pathname));
|
||||||
|
|
||||||
// 获取 Toast 以便提示用户
|
// Toast & Logout 逻辑
|
||||||
|
|
||||||
const toast = getContext<ToastState>(TOAST_KEY);
|
const toast = getContext<ToastState>(TOAST_KEY);
|
||||||
|
|
||||||
// 处理提交结果的回调
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
toast.info('正在退出登录...');
|
toast.info('正在退出登录...');
|
||||||
|
|
||||||
return async ({ result, update }) => {
|
return async ({ result, update }) => {
|
||||||
// result.type 可能是 'redirect', 'success', 'failure'
|
|
||||||
|
|
||||||
if (result.type === 'redirect') {
|
if (result.type === 'redirect') {
|
||||||
toast.success('您已安全退出');
|
toast.success('您已安全退出');
|
||||||
}
|
}
|
||||||
|
|
||||||
// update() 会触发默认行为(也就是执行 redirect 跳转)
|
|
||||||
|
|
||||||
await update();
|
await update();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -158,44 +137,64 @@
|
|||||||
let logoutForm: HTMLFormElement;
|
let logoutForm: HTMLFormElement;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- 定义递归 Snippet,显式指定类型 -->
|
<!--
|
||||||
|
定义递归 Snippet
|
||||||
|
【修改点】:在 summary 内部增加了 conditional rendering,支持父级点击跳转
|
||||||
|
-->
|
||||||
{#snippet menuItem(item: ProcessedNavItem)}
|
{#snippet menuItem(item: ProcessedNavItem)}
|
||||||
<li>
|
<li>
|
||||||
{#if item.subItems && item.subItems.length > 0}
|
{#if item.subItems && item.subItems.length > 0}
|
||||||
<details open={item.isChildActive}>
|
<!--
|
||||||
<summary class="group {item.isActive ? 'font-medium text-primary' : ''}">
|
open 属性控制:
|
||||||
|
如果是自身激活(点了父级链接) 或者 子项激活(点了子级),都保持展开状态
|
||||||
|
-->
|
||||||
|
<details open={item.isActive || item.isChildActive}>
|
||||||
|
<summary class="group {item.isActive ? 'text-primary' : ''}">
|
||||||
{#if item.icon}
|
{#if item.icon}
|
||||||
<Icon id={item.icon} size="20" />
|
<Icon id={item.icon} size="20" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span class="truncate">{item.label}</span>
|
<!-- 【核心修改】:父级如果有 href,渲染为链接 -->
|
||||||
|
{#if item.href}
|
||||||
|
<!--
|
||||||
|
onclick stopPropagation 是关键:
|
||||||
|
阻止冒泡,防止点击链接时触发 <details> 的 toggle 行为。
|
||||||
|
这样点击文字是跳转,点击右侧箭头/空白是展开折叠。
|
||||||
|
-->
|
||||||
|
<a
|
||||||
|
href={resolve(item.href)}
|
||||||
|
class="flex-1 truncate hover:text-primary-focus transition-colors"
|
||||||
|
onclick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<!-- 没有 href,纯文本,点击整行触发展开折叠 -->
|
||||||
|
<span class="truncate">{item.label}</span>
|
||||||
|
{/if}
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each item.subItems as subItem (subItem.id)}
|
{#each item.subItems as subItem (subItem.id)}
|
||||||
<!-- 递归渲染子项 -->
|
|
||||||
|
|
||||||
{@render menuItem(subItem)}
|
{@render menuItem(subItem)}
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
{:else}
|
{:else}
|
||||||
<a href={resolve(item.href)} class="group {item.isActive ? 'active font-medium' : ''}">
|
<!-- 无子菜单的普通叶子节点 -->
|
||||||
|
<a href={resolve(item.href || '#')} class="group {item.isActive ? 'active font-medium' : ''}">
|
||||||
{#if item.icon}
|
{#if item.icon}
|
||||||
<Icon id={item.icon} size="20" />
|
<Icon id={item.icon} size="20" />
|
||||||
{:else}
|
{:else}
|
||||||
<!-- 无图标时的占位符,保持对齐 -->
|
|
||||||
|
|
||||||
<span class="w-5 text-center text-xs opacity-50">•</span>
|
<span class="w-5 text-center text-xs opacity-50">•</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<span class="truncate">{item.label}</span>
|
<span class="truncate">{item.label}</span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
<!-- Mobile Backdrop -->
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 z-20 cursor-pointer bg-black/50 backdrop-blur-sm md:hidden"
|
class="fixed inset-0 z-20 cursor-pointer bg-black/50 backdrop-blur-sm md:hidden"
|
||||||
role="button"
|
role="button"
|
||||||
@@ -203,6 +202,7 @@
|
|||||||
transition:fade={{ duration: 200 }}
|
transition:fade={{ duration: 200 }}
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<!-- Sidebar Container -->
|
||||||
<aside
|
<aside
|
||||||
class="fixed z-30 flex h-full w-64 flex-shrink-0 flex-col border-r border-base-100/70 bg-base-200 md:relative"
|
class="fixed z-30 flex h-full w-64 flex-shrink-0 flex-col border-r border-base-100/70 bg-base-200 md:relative"
|
||||||
in:fly={{ duration: 200, x: -100 }}
|
in:fly={{ duration: 200, x: -100 }}
|
||||||
@@ -211,7 +211,6 @@
|
|||||||
<div class="flex h-18 flex-shrink-0 items-center p-4">
|
<div class="flex h-18 flex-shrink-0 items-center p-4">
|
||||||
<a class="flex items-center gap-3" href={resolve('/app/dashboard')}>
|
<a class="flex items-center gap-3" href={resolve('/app/dashboard')}>
|
||||||
<Icon className="flex-shrink-0 rounded-box" id="logo" size="32" />
|
<Icon className="flex-shrink-0 rounded-box" id="logo" size="32" />
|
||||||
|
|
||||||
<p class="truncate font-serif text-lg font-bold">IT DTMS</p>
|
<p class="truncate font-serif text-lg font-bold">IT DTMS</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,19 +218,12 @@
|
|||||||
<div class="custom-scrollbar flex-1 overflow-y-auto">
|
<div class="custom-scrollbar flex-1 overflow-y-auto">
|
||||||
<ul class="menu menu-vertical w-full gap-1 px-2">
|
<ul class="menu menu-vertical w-full gap-1 px-2">
|
||||||
{#each navItems as item (item.id)}
|
{#each navItems as item (item.id)}
|
||||||
<!-- 初始渲染调用 -->
|
|
||||||
|
|
||||||
{@render menuItem(item)}
|
{@render menuItem(item)}
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<!-- 状态: {sidebarState.isSidebarExpanded ? '展开' : '收起'}-->
|
|
||||||
<!-- <button-->
|
|
||||||
<!-- onclick="{sidebarState.toggleSidebar}"-->
|
|
||||||
<!-- class="btn btn-square btn-ghost">-->
|
|
||||||
<!-- 123-->
|
|
||||||
<!-- </button>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- User Profile Section -->
|
||||||
{#if page.data.user}
|
{#if page.data.user}
|
||||||
<div class="flex-shrink-0 border-t border-base-content/10 bg-base-200/50 p-3">
|
<div class="flex-shrink-0 border-t border-base-content/10 bg-base-200/50 p-3">
|
||||||
<div class="dropdown dropdown-end dropdown-top w-full">
|
<div class="dropdown dropdown-end dropdown-top w-full">
|
||||||
@@ -243,14 +235,12 @@
|
|||||||
<div class="placeholder avatar">
|
<div class="placeholder avatar">
|
||||||
<div class="w-10 rounded-full bg-neutral text-neutral-content">
|
<div class="w-10 rounded-full bg-neutral text-neutral-content">
|
||||||
<img src={page.data.user.avatar} alt="avatar" />
|
<img src={page.data.user.avatar} alt="avatar" />
|
||||||
|
|
||||||
<span class="text-xs">User</span>
|
<span class="text-xs">User</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex min-w-0 flex-1 flex-col">
|
<div class="flex min-w-0 flex-1 flex-col">
|
||||||
<span class="truncate text-sm font-bold">{page.data.user.nickname}</span>
|
<span class="truncate text-sm font-bold">{page.data.user.nickname}</span>
|
||||||
|
|
||||||
<span class="truncate text-xs text-base-content/60">@{page.data.user.username}</span>
|
<span class="truncate text-xs text-base-content/60">@{page.data.user.username}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -262,23 +252,19 @@
|
|||||||
class="dropdown-content menu z-[1] mb-2 w-60 rounded-box border border-base-content/10 bg-base-100 p-2 shadow-lg"
|
class="dropdown-content menu z-[1] mb-2 w-60 rounded-box border border-base-content/10 bg-base-100 p-2 shadow-lg"
|
||||||
>
|
>
|
||||||
<li class="menu-title px-4 py-2">我的账户</li>
|
<li class="menu-title px-4 py-2">我的账户</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/app/user">
|
<a href="/app/user">
|
||||||
<Icon id="user-profile" size="16" />
|
<Icon id="user-profile" size="16" />
|
||||||
个人资料</a
|
个人资料</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/app/settings">
|
<a href="/app/settings">
|
||||||
<Icon id="settings" size="16" />
|
<Icon id="settings" size="16" />
|
||||||
设置</a
|
设置</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<div class="divider my-1"></div>
|
<div class="divider my-1"></div>
|
||||||
|
|
||||||
<li class="">
|
<li class="">
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center gap-2 text-left text-error"
|
class="flex w-full items-center gap-2 text-left text-error"
|
||||||
@@ -288,7 +274,6 @@
|
|||||||
退出登录
|
退出登录
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
action="/auth/logout"
|
action="/auth/logout"
|
||||||
method="POST"
|
method="POST"
|
||||||
@@ -303,19 +288,14 @@
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 保持原有样式 */
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
.custom-scrollbar::-webkit-scrollbar {
|
}
|
||||||
width: 5px;
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
}
|
background: transparent;
|
||||||
|
}
|
||||||
.custom-scrollbar::-webkit-scrollbar-track {
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
background: transparent;
|
background-color: rgba(156, 163, 175, 0.3);
|
||||||
}
|
border-radius: 20px;
|
||||||
|
}
|
||||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(156, 163, 175, 0.3);
|
|
||||||
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
|
|
||||||
import type { PageResult } from '$lib/types/dataTable.ts';
|
import type { PageResult } from '$lib/types/dataTable.ts';
|
||||||
import type { RoleResponse, UserProfile } from '$lib/types/user.ts';
|
import type { UserProfile } from '$lib/types/user.ts';
|
||||||
import { resolve } from '$app/paths';
|
|
||||||
import Icon from '$lib/components/icon/Icon.svelte';
|
import Icon from '$lib/components/icon/Icon.svelte';
|
||||||
|
import type { Options } from '$lib/types/api.ts';
|
||||||
|
|
||||||
let { users , roles } = $props<{
|
let { users , rolesOptions } = $props<{
|
||||||
users: PageResult<UserProfile[]>,
|
users: PageResult<UserProfile[]>,
|
||||||
roles: RoleResponse[]
|
rolesOptions: Options[]
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
||||||
@@ -32,9 +32,6 @@
|
|||||||
<div>
|
<div>
|
||||||
{#if users.total > 0}
|
{#if users.total > 0}
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="overflow-x-auto rounded-box shadow bg-base-100 mt-1 ">
|
<div class="overflow-x-auto rounded-box shadow bg-base-100 mt-1 ">
|
||||||
<div class="flex items-center justify-between px-4 pt-4 pb-2">
|
<div class="flex items-center justify-between px-4 pt-4 pb-2">
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
@@ -54,11 +51,11 @@
|
|||||||
<input type="search" required placeholder="Search" />
|
<input type="search" required placeholder="Search" />
|
||||||
<button class="btn btn-xs btn-primary">搜索</button>
|
<button class="btn btn-xs btn-primary">搜索</button>
|
||||||
</label>
|
</label>
|
||||||
{#if roles}
|
{#if rolesOptions}
|
||||||
<div class="filter w-64">
|
<div class="filter w-64">
|
||||||
<input class="btn filter-reset " type="radio" value='' name="metaframeworks" aria-label="All" onchange={handleRoleChange} />
|
<input class="btn filter-reset " type="radio" value='' name="metaframeworks" aria-label="All" onchange={handleRoleChange} />
|
||||||
{#each roles as role(role.id)}
|
{#each rolesOptions as role(role.value)}
|
||||||
<input class="btn " type="radio" name="metaframeworks" aria-label="{role.name}" value={role.id} onchange={handleRoleChange} />
|
<input class="btn" type="radio" name="metaframeworks" aria-label="{role.label}" value={role.value} onchange={handleRoleChange} />
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
43
src/lib/log.ts
Normal file
43
src/lib/log.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
|
||||||
|
type LogArgs = unknown[];
|
||||||
|
|
||||||
|
const getTime = () => new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||||
|
|
||||||
|
|
||||||
|
function print(level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR', message: string, args: LogArgs) {
|
||||||
|
if (browser) {
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
DEBUG: 'color: #999; font-weight: bold;',
|
||||||
|
INFO: 'color: #2196f3; font-weight: bold;',
|
||||||
|
WARN: 'color: #ff9800; font-weight: bold;',
|
||||||
|
ERROR: 'color: #f44336; font-weight: bold;',
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`%c[${level}] ${message}`, styles[level], ...args);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
DEBUG: '\x1b[90m',
|
||||||
|
INFO: '\x1b[34m',
|
||||||
|
WARN: '\x1b[33m',
|
||||||
|
ERROR: '\x1b[31m',
|
||||||
|
};
|
||||||
|
const reset = '\x1b[0m';
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${colors[level]}[${getTime()}] [${level}] ${message}${reset}`,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const log = {
|
||||||
|
debug: (message: string, ...args: LogArgs) => print('DEBUG', message, args),
|
||||||
|
info: (message: string, ...args: LogArgs) => print('INFO', message, args),
|
||||||
|
warn: (message: string, ...args: LogArgs) => print('WARN', message, args),
|
||||||
|
error: (message: string, ...args: LogArgs) => print('ERROR', message, args),
|
||||||
|
};
|
||||||
@@ -48,3 +48,8 @@ export interface CreateDeviceRequest {
|
|||||||
status: number;
|
status: number;
|
||||||
remark: string;
|
remark: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
|
|
||||||
<div class="flex-1 flex flex-col min-w-0 overflow-hidden">
|
<div class="flex-1 flex flex-col min-w-0 overflow-hidden relative">
|
||||||
|
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
123
|
||||||
|
</div>
|
||||||
@@ -13,7 +13,7 @@ export const load:PageServerLoad = async ({ cookies }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getRoles = async() => {
|
const getRoles = async() => {
|
||||||
return await roleService.getAllRoles(token);
|
return await roleService.getRolesOptions(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export const load:PageServerLoad = async ({ cookies }) => {
|
|||||||
|
|
||||||
streamed:{
|
streamed:{
|
||||||
userList: getUserList(),
|
userList: getUserList(),
|
||||||
roles: getRoles(),
|
rolesOptions: getRoles(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -5,24 +5,34 @@ import { resolve } from '$app/paths';
|
|||||||
const {data} = $props();
|
const {data} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-between items-center ">
|
<div class="flex justify-between items-center select-none">
|
||||||
<p class="font-bold">用户管理</p>
|
<p class="font-bold">用户管理</p>
|
||||||
<div class="breadcrumbs ">
|
<div class="breadcrumbs textarea-md text-base-content/70 ">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href={resolve('/app/dashboard')}>仪表盘</a></li>
|
<li><a href={resolve('/app/dashboard')}>仪表盘</a></li>
|
||||||
<li><a href={resolve('/app/settings')}>系统设置</a></li>
|
<li>系统设置</li>
|
||||||
<li><a href={resolve('/app/settings/auth')}>认证管理</a></li>
|
<li>认证管理</li>
|
||||||
<li><a href={resolve('/app/settings/auth/users')}>用户管理</a></li>
|
<li><a href={resolve('/app/settings/auth/users')}>用户管理</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#await data.streamed.userList}
|
{#await data.streamed.userList}
|
||||||
加载中
|
<div class=" flex flex-col items-center justify-center absolute top-0 left-0 bottom-0 right-0 backdrop-blur-2xl z-0">
|
||||||
|
<div class="loading w-28 h-28">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="text-base-content mt-4">加载中...</p>
|
||||||
|
</div>
|
||||||
{:then result}
|
{:then result}
|
||||||
{#await data.streamed.roles}
|
{#await data.streamed.rolesOptions}
|
||||||
加载中
|
<div class=" flex flex-col items-center justify-center absolute top-0 left-0 bottom-0 right-0 backdrop-blur-2xl z-0">
|
||||||
{:then roles}
|
<div class="loading w-28 h-28">
|
||||||
<UserTable users={result} roles={roles}/>
|
|
||||||
|
</div>
|
||||||
|
<p class="text-base-content mt-4">加载中...</p>
|
||||||
|
</div>
|
||||||
|
{:then rolesOptions}
|
||||||
|
<UserTable users={result} rolesOptions={rolesOptions}/>
|
||||||
{:catch err}
|
{:catch err}
|
||||||
<p>出错了: {err.message}</p>
|
<p>出错了: {err.message}</p>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export const load:PageServerLoad = async ({ cookies }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await deviceService.getAllDevices({ page: 1, size: 10 ,token:token});
|
const result = await deviceService.getAllDevices({ page: 1, size: 10 ,token:token});
|
||||||
console.log('result', result);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -3,7 +3,7 @@ import { resolve } from '$app/paths';
|
|||||||
|
|
||||||
const {data} = $props();
|
const {data} = $props();
|
||||||
|
|
||||||
console.log( data.streamed.deviceList);
|
console.log( data.streamed.list);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-between items-center ">
|
<div class="flex justify-between items-center ">
|
||||||
@@ -12,12 +12,12 @@ console.log( data.streamed.deviceList);
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href={resolve('/app/dashboard')}>仪表盘</a></li>
|
<li><a href={resolve('/app/dashboard')}>仪表盘</a></li>
|
||||||
<li><a href={resolve('/app/settings')}>系统设置</a></li>
|
<li><a href={resolve('/app/settings')}>系统设置</a></li>
|
||||||
<li><a href={resolve('/app/settings/device/list')}>设备管理</a></li>
|
<li><a href={resolve('/app/settings/devices')}>设备管理</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#await data.streamed.deviceList}
|
{#await data.streamed.list}
|
||||||
<p class="text-center">正在加载设备列表...</p>
|
<p class="text-center">正在加载设备列表...</p>
|
||||||
<p class="text-center">请稍后...</p>
|
<p class="text-center">请稍后...</p>
|
||||||
{:then result}
|
{:then result}
|
||||||
Reference in New Issue
Block a user