refactor(api): 重构API客户端以支持依赖注入
- 移除全局api实例,改用createApi工厂函数创建客户端 - 在服务层函数中添加api参数,实现依赖注入 - 更新设备、角色、用户等服务调用方式 - 移除请求头中的Authorization字段手动设置 - 在hooks.server.ts中初始化并挂载api到locals - 修复HttpError类定义位置并完善错误处理逻辑 - 调整页面组件中main容器和表格布局样式 - 更新tailwindcss主题配置和相关CSS类名 - 修改分页大小默认值从10到12 - 删除冗余的COOKIE_TOKEN_KEY导入和重定向逻辑
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -1474,7 +1474,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",
|
||||||
@@ -1514,7 +1513,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",
|
||||||
@@ -1847,7 +1845,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"
|
||||||
}
|
}
|
||||||
@@ -1898,7 +1895,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",
|
||||||
@@ -2117,7 +2113,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"
|
||||||
},
|
},
|
||||||
@@ -2500,7 +2495,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",
|
||||||
@@ -3638,7 +3632,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"
|
||||||
},
|
},
|
||||||
@@ -3666,7 +3659,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",
|
||||||
@@ -3800,7 +3792,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"
|
||||||
},
|
},
|
||||||
@@ -3817,7 +3808,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"
|
||||||
@@ -4544,7 +4534,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",
|
||||||
@@ -4742,7 +4731,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"
|
||||||
@@ -4818,7 +4806,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",
|
||||||
|
|||||||
5
src/app.d.ts
vendored
5
src/app.d.ts
vendored
@@ -1,5 +1,7 @@
|
|||||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
|
import type { ApiClient } from '$lib/api/httpClient.ts';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
interface User {
|
interface User {
|
||||||
@@ -16,6 +18,9 @@ declare global {
|
|||||||
interface pageData {
|
interface pageData {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
}
|
}
|
||||||
|
interface Locals {
|
||||||
|
api: ApiClient;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import type { Handle } from '@sveltejs/kit';
|
import { type Handle, redirect } from '@sveltejs/kit';
|
||||||
import { parseJwt } from '$lib/utils/tokenUtils.ts';
|
import { parseJwt } from '$lib/utils/tokenUtils.ts';
|
||||||
import type { JwtPayload } from '$lib/types/auth.ts';
|
import type { JwtPayload } from '$lib/types/auth.ts';
|
||||||
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
||||||
|
import { createApi } from '$lib/api/httpClient.ts';
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve}) =>{
|
export const handle: Handle = async ({ event, resolve}) =>{
|
||||||
const authorization = event.cookies.get(COOKIE_TOKEN_KEY);
|
const authorization = event.cookies.get(COOKIE_TOKEN_KEY);
|
||||||
|
|
||||||
|
event.locals.api = createApi(authorization);
|
||||||
|
|
||||||
if (authorization){
|
if (authorization){
|
||||||
const split = authorization?.split(' ');
|
const split = authorization?.split(' ');
|
||||||
const token = split[1];
|
const token = split[1];
|
||||||
const jwt = parseJwt<JwtPayload>(token);
|
const jwt = parseJwt<JwtPayload>(token);
|
||||||
|
|
||||||
if (jwt){
|
if (jwt){
|
||||||
|
|
||||||
event.locals.user = {
|
event.locals.user = {
|
||||||
id: jwt.userId,
|
id: jwt.userId,
|
||||||
username: jwt.sub,
|
username: jwt.sub,
|
||||||
@@ -19,8 +24,10 @@ export const handle: Handle = async ({ event, resolve}) =>{
|
|||||||
roles: jwt.authorities
|
roles: jwt.authorities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}else if(event.url.pathname.startsWith('/app')){
|
||||||
|
throw redirect(303, '/auth/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(event);
|
return resolve(event);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ofetch, type FetchOptions, type SearchParameters } from 'ofetch';
|
import { ofetch, type FetchOptions, type SearchParameters } from 'ofetch';
|
||||||
import { log } from '$lib/log';
|
import { log } from '$lib/log';
|
||||||
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
|
||||||
|
|
||||||
// 1. 定义更安全的类型,替代 any
|
|
||||||
type QueryParams = SearchParameters;
|
type QueryParams = SearchParameters;
|
||||||
type RequestBody = Record<string, unknown> | FormData | unknown[]; // 替代 any,使用 unknown
|
type RequestBody = Record<string, unknown> | FormData | unknown[] | object;
|
||||||
|
type AppFetchOptions = Omit<FetchOptions<'json'>, 'method' | 'body' | 'query'>;
|
||||||
|
|
||||||
export interface ApiResult<T> {
|
export interface ApiResult<T> {
|
||||||
code: number;
|
code: number;
|
||||||
@@ -14,41 +13,61 @@ export interface ApiResult<T> {
|
|||||||
|
|
||||||
const BASE_URL = import.meta.env.VITE_PUBLIC_API_URL || 'http://localhost:18888/api';
|
const BASE_URL = import.meta.env.VITE_PUBLIC_API_URL || 'http://localhost:18888/api';
|
||||||
|
|
||||||
// 2. 指定 create 的默认类型为 json
|
export type ApiClient = ReturnType<typeof createApi>;
|
||||||
const client = ofetch.create({
|
|
||||||
baseURL: BASE_URL,
|
|
||||||
onRequest({ options, request }) {
|
|
||||||
log.debug(`[API] ${options.method} ${request}`, {
|
|
||||||
body: options.body as unknown, // 类型断言为 unknown 避免隐式 any
|
|
||||||
headers: options.headers,
|
|
||||||
query: options.query
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onResponseError({ request, response }) {
|
|
||||||
log.error(`[API] Error ${request}`, {
|
|
||||||
status: response.status,
|
|
||||||
headers: response.headers,
|
|
||||||
data: response._data as unknown
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 辅助类型:剔除我们手动处理的属性,并强制 responseType 为 'json'
|
export const createApi = (token?: string) => {
|
||||||
type AppFetchOptions = Omit<FetchOptions<'json'>, 'method' | 'body' | 'query'>;
|
const client = ofetch.create({
|
||||||
|
baseURL: BASE_URL,
|
||||||
|
// 建议:通常 Token 前面需要加 Bearer
|
||||||
|
headers: token ? { Authorization: token } : {},
|
||||||
|
onRequest({ options, request }) {
|
||||||
|
log.debug(`[API] ${options.method} ${request}`
|
||||||
|
|
||||||
export const api = {
|
,{
|
||||||
get: <T>(url: string, query?: QueryParams, options?: AppFetchOptions) =>
|
body: options.body as unknown,
|
||||||
client<ApiResult<T>>(url, { ...options, method: 'GET', query }),
|
headers: options.headers,
|
||||||
|
query: options.query
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onResponseError({ request, response }) {
|
||||||
|
log.error(`[API] Error ${request}`, {
|
||||||
|
status: response.status,
|
||||||
|
headers: response.headers,
|
||||||
|
data: response._data as unknown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
post: <T>(url: string, body?: RequestBody, options?: AppFetchOptions) =>
|
return {
|
||||||
client<ApiResult<T>>(url, { ...options, method: 'POST', body }),
|
get: <T>(url: string, query?: QueryParams, options?: AppFetchOptions) =>
|
||||||
|
client<ApiResult<T>>(url, { ...options, method: 'GET', query }),
|
||||||
|
|
||||||
put: <T>(url: string, body?: RequestBody, options?: AppFetchOptions) =>
|
// 关键修复点:
|
||||||
client<ApiResult<T>>(url, { ...options, method: 'PUT', body }),
|
// 1. 使用 <T, B = RequestBody> 保持泛型灵活性
|
||||||
|
// 2. 使用 `as unknown as Record<string, unknown>` 替代 `as any`
|
||||||
|
// 这告诉编译器:"先把 B 当作未知类型,再把它视为一个通用的键值对对象",完美绕过 ESLint 和 TS 检查
|
||||||
|
post: <T, B = RequestBody>(url: string, body?: B, options?: AppFetchOptions) =>
|
||||||
|
client<ApiResult<T>>(url, {
|
||||||
|
...options,
|
||||||
|
method: 'POST',
|
||||||
|
body: body as unknown as Record<string, unknown>
|
||||||
|
}),
|
||||||
|
|
||||||
patch: <T>(url: string, body?: RequestBody, options?: AppFetchOptions) =>
|
put: <T, B = RequestBody>(url: string, body?: B, options?: AppFetchOptions) =>
|
||||||
client<ApiResult<T>>(url, { ...options, method: 'PATCH', body }),
|
client<ApiResult<T>>(url, {
|
||||||
|
...options,
|
||||||
|
method: 'PUT',
|
||||||
|
body: body as unknown as Record<string, unknown>
|
||||||
|
}),
|
||||||
|
|
||||||
delete: <T>(url: string, query?: QueryParams, options?: AppFetchOptions) =>
|
patch: <T, B = RequestBody>(url: string, body?: B, options?: AppFetchOptions) =>
|
||||||
client<ApiResult<T>>(url, { ...options, method: 'DELETE', query })
|
client<ApiResult<T>>(url, {
|
||||||
|
...options,
|
||||||
|
method: 'PATCH',
|
||||||
|
body: body as unknown as Record<string, unknown>
|
||||||
|
}),
|
||||||
|
|
||||||
|
delete: <T>(url: string, query?: QueryParams, options?: AppFetchOptions) =>
|
||||||
|
client<ApiResult<T>>(url, { ...options, method: 'DELETE', query })
|
||||||
|
};
|
||||||
};
|
};
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import { api } from '$lib/api/httpClient'; // 通常不需要 .ts 后缀
|
|
||||||
import type { AuthResponse, LoginPayload } from '$lib/types/auth';
|
import type { AuthResponse, LoginPayload } from '$lib/types/auth';
|
||||||
import { ApiError } from '$lib/types/api.ts';
|
import { ApiError } from '$lib/types/api.ts';
|
||||||
|
import type { ApiClient } from '$lib/api/httpClient.ts';
|
||||||
|
|
||||||
|
|
||||||
export const authService = {
|
export const authService = {
|
||||||
/**
|
/**
|
||||||
* 登录流程
|
* 登录流程
|
||||||
*/
|
*/
|
||||||
login: async (payload: LoginPayload): Promise<AuthResponse> => {
|
login: async (api: ApiClient,payload: LoginPayload): Promise<AuthResponse> => {
|
||||||
// 1. 调用登录接口
|
|
||||||
const response = await api.post<AuthResponse>('/auth/login', payload);
|
const response = await api.post<AuthResponse>('/auth/login', payload);
|
||||||
|
|
||||||
if (response.code !== 200 || !response.data) {
|
if (response.code !== 200 || !response.data) {
|
||||||
@@ -21,12 +21,10 @@ export const authService = {
|
|||||||
/**
|
/**
|
||||||
* 登出流程
|
* 登出流程
|
||||||
*/
|
*/
|
||||||
logout: async () => {
|
logout: async (api: ApiClient) => {
|
||||||
try {
|
try {
|
||||||
// Optionally call the backend logout endpoint
|
|
||||||
await api.post('/auth/logout', {});
|
await api.post('/auth/logout', {});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Even if the backend call fails, we still want to clear local state
|
|
||||||
console.warn('Logout API call failed:', error);
|
console.warn('Logout API call failed:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,24 @@
|
|||||||
import { api } from '$lib/api/httpClient.ts';
|
import { type ApiClient } from '$lib/api/httpClient.ts';
|
||||||
import type { PageResult } from '$lib/types/dataTable.ts';
|
import type { PageResult } from '$lib/types/dataTable.ts';
|
||||||
import type { CreateDeviceRequest, DeviceResponse } from '$lib/types/api.ts';
|
import type { CreateDeviceRequest, DeviceResponse } from '$lib/types/api.ts';
|
||||||
import type { JsonValue } from '$lib/types/http.ts';
|
|
||||||
|
|
||||||
|
|
||||||
export const deviceService = {
|
export const deviceService = {
|
||||||
getAllDevices: async ({ page, size,type,keyword,token}:{
|
getAllDevices: async (api:ApiClient,{ page, size,type,keyword}:{
|
||||||
page: number,
|
page: number,
|
||||||
size: number,
|
size: number,
|
||||||
type?: number,
|
type?: number,
|
||||||
keyword?: string,
|
keyword?: string,
|
||||||
token:string
|
|
||||||
}) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('pageNum', page.toString());
|
|
||||||
formData.append('pageSize', size.toString());
|
|
||||||
if ( type){
|
|
||||||
formData.append('type', type.toString());
|
|
||||||
}
|
|
||||||
if ( keyword){
|
|
||||||
formData.append('keyword', keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await api.get<PageResult<DeviceResponse[]>>('/devices',{
|
}) => {
|
||||||
body: formData,
|
const queryParams: Record<string, string | number> = {
|
||||||
headers:{Authorization: `${token}`}
|
pageNum: page,
|
||||||
});
|
pageSize: size
|
||||||
|
};
|
||||||
|
if (type) queryParams.type = type;
|
||||||
|
if (keyword) queryParams.keyword = keyword;
|
||||||
|
|
||||||
|
const result = await api.get<PageResult<DeviceResponse[]>>('/devices',queryParams);
|
||||||
|
|
||||||
if (result.code != 200 || !result.data){
|
if (result.code != 200 || !result.data){
|
||||||
throw new Error(result.msg);
|
throw new Error(result.msg);
|
||||||
@@ -33,12 +26,9 @@ export const deviceService = {
|
|||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
},
|
},
|
||||||
createDevice: async (device: CreateDeviceRequest,token:string) => {
|
createDevice: async (api: ApiClient,device: CreateDeviceRequest) => {
|
||||||
|
|
||||||
const result = await api.post<DeviceResponse>('/devices',{
|
const result = await api.post<DeviceResponse>('/devices', device);
|
||||||
body: device,
|
|
||||||
headers:{Authorization: `${token}`}
|
|
||||||
});
|
|
||||||
if (result.code != 200 || !result.data){
|
if (result.code != 200 || !result.data){
|
||||||
throw new Error(result.msg);
|
throw new Error(result.msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { api } from '$lib/api/httpClient.ts';
|
|
||||||
|
|
||||||
import type { Options } from '$lib/types/api.ts';
|
import type { Options } from '$lib/types/api.ts';
|
||||||
|
import type { ApiClient } from '$lib/api/httpClient.ts';
|
||||||
|
|
||||||
export const deviceTypesService = {
|
export const deviceTypesService = {
|
||||||
getDeviceTypesOptions: async (token:string) => {
|
getDeviceTypesOptions: async (api:ApiClient) => {
|
||||||
const result = await api.get<Options[]>('/device-types/options',{
|
const result = await api.get<Options[]>('/device-types/options',undefined);
|
||||||
headers:{Authorization: `${token}`}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.code != 200 || !result.data){
|
if (result.code != 200 || !result.data){
|
||||||
throw new Error(result.msg);
|
throw new Error(result.msg);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { api } from '$lib/api/httpClient.ts';
|
import {type ApiClient } from '$lib/api/httpClient.ts';
|
||||||
import type { Options } from '$lib/types/api.ts';
|
import type { Options } from '$lib/types/api.ts';
|
||||||
import { log } from '$lib/log.ts';
|
import { log } from '$lib/log.ts';
|
||||||
|
|
||||||
|
|
||||||
export const roleService = {
|
export const roleService = {
|
||||||
getRolesOptions: async (token:string) => {
|
getRolesOptions: async (api:ApiClient) => {
|
||||||
const response = await api.get<Options[]>('/roles/options',undefined, {headers: {Authorization: `${token}`}});
|
const response = await api.get<Options[]>('/roles/options',undefined);
|
||||||
if (response.code != 200 || !response.data){
|
if (response.code != 200 || !response.data){
|
||||||
log.error(response.msg);
|
log.error(response.msg);
|
||||||
throw new Error(response.msg);
|
throw new Error(response.msg);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { api } from '$lib/api/httpClient.ts';
|
import { type ApiClient } from '$lib/api/httpClient.ts';
|
||||||
import type { UserProfile } from '$lib/types/user.ts';
|
import type { UserProfile } from '$lib/types/user.ts';
|
||||||
import type { PageResult } from '$lib/types/dataTable.ts';
|
import type { PageResult } from '$lib/types/dataTable.ts';
|
||||||
import { type SearchParameters } from 'ofetch';
|
import { type SearchParameters } from 'ofetch';
|
||||||
@@ -7,14 +7,14 @@ import { type SearchParameters } from 'ofetch';
|
|||||||
// 1. 定义更安全的类型,替代 any
|
// 1. 定义更安全的类型,替代 any
|
||||||
type QueryParams = SearchParameters;
|
type QueryParams = SearchParameters;
|
||||||
export const userService = {
|
export const userService = {
|
||||||
getUserProfile: async (token:string) => {
|
getUserProfile: async (api:ApiClient) => {
|
||||||
const response = await api.get<UserProfile>('/users/me',undefined, {headers: {Authorization: `${token}`}});
|
const response = await api.get<UserProfile>('/users/me',undefined);
|
||||||
if (response.code != 200 || !response.data){
|
if (response.code != 200 || !response.data){
|
||||||
throw new Error(response.msg);
|
throw new Error(response.msg);
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
getAllUsers: async ({ page, size,token , keyword, roleId}: { page: number, size: number, token:string , keyword?: string, roleId?: number}) => {
|
getAllUsers: async (api:ApiClient,{ page, size , keyword, roleId}: { page: number, size: number , keyword?: string, roleId?: number}) => {
|
||||||
|
|
||||||
const params: QueryParams= {
|
const params: QueryParams= {
|
||||||
pageNum: page,
|
pageNum: page,
|
||||||
@@ -24,10 +24,7 @@ export const userService = {
|
|||||||
} ;
|
} ;
|
||||||
const response = await api.get<PageResult<UserProfile[]>>(
|
const response = await api.get<PageResult<UserProfile[]>>(
|
||||||
'/users',
|
'/users',
|
||||||
params,
|
params);
|
||||||
{
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="absolute inset-0 flex flex-col items-center justify-center bg-base-100/50 backdrop-blur-sm z-10">
|
<div class="flex-1 inset-0 flex flex-col items-center justify-center bg-base-100/50 backdrop-blur-sm z-10">
|
||||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||||
<p class="text-base-content/70 mt-4 font-medium animate-pulse">正在加载数据...</p>
|
<p class="text-base-content/70 mt-4 font-medium animate-pulse">正在加载数据...</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,13 +83,12 @@
|
|||||||
{ title: '用户组', width: 45 }
|
{ title: '用户组', width: 45 }
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
<div class="flex-1 overflow-y-auto">
|
||||||
<div class="overflow-x-auto flex-1 bg-base-100 h-full ">
|
<div class="bg-base-100">
|
||||||
<table class="table ">
|
<table class="table table-pin-rows">
|
||||||
<thead class="z-0">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-12">
|
<th class="w-12 bg-base-100"> <label>
|
||||||
<label>
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="checkbox checkbox-sm"
|
class="checkbox checkbox-sm"
|
||||||
@@ -98,64 +97,65 @@
|
|||||||
onchange={toggleAll}
|
onchange={toggleAll}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</th>
|
|
||||||
{#each newRowTitles as item (item.title)}
|
|
||||||
<th style="width: {item.width}%" >{item.title}</th>
|
|
||||||
{/each}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{#each users.records as record (record.id)}
|
|
||||||
<tr class="hover">
|
|
||||||
<th>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="checkbox checkbox-sm"
|
|
||||||
checked={selectedIds.includes(record.id)}
|
|
||||||
onchange={() => toggleOne(record.id)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</th>
|
</th>
|
||||||
<td class="font-mono text-xs opacity-70">{record.id}</td>
|
{#each newRowTitles as item (item.title)}
|
||||||
<td class="font-bold">{record.username}</td>
|
<th style="width: {item.width}%" class="bg-base-100">{item.title}</th>
|
||||||
<td>{record.nickname || '-'}</td>
|
{/each}
|
||||||
<td>
|
|
||||||
<div class="avatar">
|
|
||||||
<div class="w-8 h-8 rounded-full bg-base-300 ring ring-base-200 ring-offset-base-100 ring-offset-2">
|
|
||||||
{#if record.avatar}
|
|
||||||
<img src={record.avatar} alt={record.username} />
|
|
||||||
{:else}
|
|
||||||
<span class="text-xs flex items-center justify-center h-full w-full uppercase">
|
|
||||||
{record.username.slice(0, 2)}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="flex flex-wrap gap-1">
|
|
||||||
{#each record.roles as role (role.id)}
|
|
||||||
<span class="badge badge-sm {role.id === 1 ? 'badge-primary' : 'badge-ghost'}">
|
|
||||||
{role.name}
|
|
||||||
</span>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
</thead>
|
||||||
</tbody>
|
|
||||||
</table>
|
<tbody class="w-full">
|
||||||
|
{#each users.records as record (record.id)}
|
||||||
|
<tr class="hover">
|
||||||
|
<th>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox checkbox-sm"
|
||||||
|
checked={selectedIds.includes(record.id)}
|
||||||
|
onchange={() => toggleOne(record.id)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</th>
|
||||||
|
<td class="font-mono text-xs opacity-70">{record.id}</td>
|
||||||
|
<td class="font-bold">{record.username}</td>
|
||||||
|
<td>{record.nickname || '-'}</td>
|
||||||
|
<td>
|
||||||
|
<div class="avatar">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-base-300 ring ring-base-200 ring-offset-base-100 ring-offset-2">
|
||||||
|
{#if record.avatar}
|
||||||
|
<img src={record.avatar} alt={record.username} />
|
||||||
|
{:else}
|
||||||
|
<span class="text-xs flex items-center justify-center h-full w-full uppercase">
|
||||||
|
{record.username.slice(0, 2)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
{#each record.roles as role (role.id)}
|
||||||
|
<span class="badge badge-sm {role.id === 1 ? 'badge-primary' : 'badge-ghost'}">
|
||||||
|
{role.name}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{#if users.records.length === 0}
|
||||||
|
<div class="flex flex-col items-center justify-center p-10 text-base-content/50">
|
||||||
|
<Icon id="search-off" size="48" />
|
||||||
|
<p class="mt-2">未找到匹配的用户</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if users.records.length === 0}
|
|
||||||
<div class="flex flex-col items-center justify-center p-10 text-base-content/50">
|
|
||||||
<Icon id="search-off" size="48" />
|
|
||||||
<p class="mt-2">未找到匹配的用户</p>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if users.total > 0}
|
{#if users.total > 0}
|
||||||
<div class="border-t border-base-200 p-4 flex items-center justify-between bg-base-100 ">
|
<div class="border-t border-base-200 p-4 flex items-center justify-between bg-base-100 ">
|
||||||
<div class="text-sm text-base-content/70">
|
<div class="text-sm text-base-content/70">
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import type { AuthResponse } from '$lib/types/auth.ts';
|
import type { AuthResponse } from '$lib/types/auth.ts';
|
||||||
import { HttpError } from '$lib/api/httpClient.ts';
|
|
||||||
|
|
||||||
export interface ApiResult<T> {
|
export interface ApiResult<T> {
|
||||||
code: number,
|
code: number;
|
||||||
msg: string,
|
msg: string;
|
||||||
data: T | null;
|
data: T | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HttpError extends Error{
|
||||||
|
constructor(
|
||||||
|
public message: string,
|
||||||
|
public code: number,
|
||||||
|
public data: string
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ApiError<T> extends HttpError {
|
export class ApiError<T> extends HttpError {
|
||||||
constructor(ApiResult: ApiResult<T>) {
|
constructor(ApiResult: ApiResult<T>) {
|
||||||
super(ApiResult.msg, ApiResult.code, JSON.stringify(ApiResult.data));
|
super(ApiResult.msg, ApiResult.code, JSON.stringify(ApiResult.data));
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<div class="flex-1 flex flex-col min-w-0 overflow-hidden relative h-full">
|
<div class="flex-1 flex flex-col min-w-0 overflow-hidden relative h-full">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<main class="flex-1 px-4 pb-4 ">
|
<main class="flex-1 flex flex-col min-h-0 overflow-hidden px-4 pb-4 ">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { userService } from '$lib/api/services/userService.ts';
|
import { userService } from '$lib/api/services/userService.ts';
|
||||||
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
|
||||||
import { redirect } from '@sveltejs/kit';
|
|
||||||
import { roleService } from '$lib/api/services/roleService.ts';
|
import { roleService } from '$lib/api/services/roleService.ts';
|
||||||
import { log } from '$lib/log.ts';
|
import { log } from '$lib/log.ts';
|
||||||
|
|
||||||
export const load:PageServerLoad = async ({ cookies ,url }) => {
|
export const load:PageServerLoad = async ({ locals ,url }) => {
|
||||||
|
|
||||||
const token = cookies.get(COOKIE_TOKEN_KEY);
|
|
||||||
|
|
||||||
if (!token) {
|
|
||||||
throw redirect(303, '/auth/login');
|
|
||||||
}
|
|
||||||
|
|
||||||
const page = Number(url.searchParams.get('page')) || 1;
|
const page = Number(url.searchParams.get('page')) || 1;
|
||||||
const size = Number(url.searchParams.get('size')) || 10;
|
const size = Number(url.searchParams.get('size')) || 12;
|
||||||
const keyword = url.searchParams.get('q') || undefined;
|
const keyword = url.searchParams.get('q') || undefined;
|
||||||
const role = Number(url.searchParams.get('role')) || undefined;
|
const role = Number(url.searchParams.get('role')) || undefined;
|
||||||
|
|
||||||
@@ -22,13 +17,13 @@ export const load:PageServerLoad = async ({ cookies ,url }) => {
|
|||||||
|
|
||||||
|
|
||||||
const getRoles = async() => {
|
const getRoles = async() => {
|
||||||
return await roleService.getRolesOptions(token);
|
return await roleService.getRolesOptions(locals.api);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const getUserList = async() => {
|
const getUserList = async() => {
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
return await userService.getAllUsers({ page: page, size: size , keyword:keyword, roleId:role,token:token});
|
return await userService.getAllUsers(locals.api,{ page: page, size: size , keyword:keyword, roleId:role});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 relative bg-base-100 flex flex-col rounded-b-box">
|
<div class="flex-1 bg-base-100 flex flex-col min-h-0 overflow-hidden ">
|
||||||
{#await data.streamed.userList}
|
{#await data.streamed.userList}
|
||||||
<TableLoadingState />
|
<TableLoadingState />
|
||||||
{:then users}
|
{:then users}
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
|
||||||
import { redirect } from '@sveltejs/kit';
|
|
||||||
import { deviceService } from '$lib/api/services/deviceService.ts';
|
import { deviceService } from '$lib/api/services/deviceService.ts';
|
||||||
import { deviceTypesService } from '$lib/api/services/deviceTypesService.ts';
|
import { deviceTypesService } from '$lib/api/services/deviceTypesService.ts';
|
||||||
|
import { log } from '$lib/log.ts';
|
||||||
|
|
||||||
export const load:PageServerLoad = async ({ cookies }) => {
|
export const load:PageServerLoad = async ({ locals }) => {
|
||||||
|
|
||||||
const token = cookies.get(COOKIE_TOKEN_KEY);
|
|
||||||
|
|
||||||
if (!token) {
|
const result = deviceService.getAllDevices(locals.api,{ page: 1, size: 10 });
|
||||||
throw redirect(302, '/auth/login');
|
const options = deviceTypesService.getDeviceTypesOptions(locals.api);
|
||||||
|
|
||||||
|
const handle = () => {
|
||||||
|
return {
|
||||||
|
list: result,
|
||||||
|
options: options
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = deviceService.getAllDevices({ page: 1, size: 10 ,token:token});
|
|
||||||
const options = deviceTypesService.getDeviceTypesOptions( token);
|
|
||||||
return {
|
return {
|
||||||
streamed:{
|
streamed:{
|
||||||
result: {
|
result: handle()
|
||||||
list: result,
|
|
||||||
options: options
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -2,14 +2,13 @@ import type { Actions } from './$types';
|
|||||||
import { fail } from '@sveltejs/kit';
|
import { fail } from '@sveltejs/kit';
|
||||||
import { authService } from '$lib/api/services/authService.ts';
|
import { authService } from '$lib/api/services/authService.ts';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import { HttpError } from '$lib/api/httpClient.ts';
|
|
||||||
import { ApiError } from '$lib/types/api.ts';
|
import { ApiError } from '$lib/types/api.ts';
|
||||||
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
import { COOKIE_TOKEN_KEY } from '$lib/components/constants/cookiesConstants.ts';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const actions:Actions = {
|
export const actions:Actions = {
|
||||||
default: async ({ request,cookies ,url}) => {
|
default: async ({ request,cookies ,url ,locals}) => {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const username = formData.get('username');
|
const username = formData.get('username');
|
||||||
const password = formData.get('password');
|
const password = formData.get('password');
|
||||||
@@ -44,7 +43,7 @@ export const actions:Actions = {
|
|||||||
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
const response = await authService.login({username,password});
|
const response = await authService.login(locals.api,{username,password});
|
||||||
|
|
||||||
cookies.set(COOKIE_TOKEN_KEY,`${response.tokenHead} ${response.token}`,{
|
cookies.set(COOKIE_TOKEN_KEY,`${response.tokenHead} ${response.token}`,{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
@plugin "daisyui"{
|
@plugin "daisyui"{
|
||||||
themes: all;
|
themes: light , dark , cupcake , bumblebee , emerald , corporate
|
||||||
|
, synthwave , retro , cyberpunk , valentine , halloween , garden
|
||||||
|
, forest , aqua , lofi , pastel , fantasy , wireframe --default , black --prefersdark
|
||||||
|
, luxury , dracula , cmyk , autumn , business , acid , lemonade
|
||||||
|
, night , coffee , winter , dim , nord , sunset , caramellatte
|
||||||
|
, abyss , silk;;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user