瀏覽代碼

Merge branch 'fork' of 124-template-94/d8d-admin-mobile-starter-public into main

d8dfun 6 月之前
父節點
當前提交
f1563453c4

+ 3 - 2
HISTORY.md

@@ -3,5 +3,6 @@
 迁移管理页面,在正式环境中,需要验证env中配置的密码参数才能打开
 
 2025.05.13 0.1.0
-首页添加了迁移管理入口按钮, 无需登录即可访问
-打开迁移管理页面时,将迁移历史读取出来
+将admin api.ts 拆开
+打开迁移管理页面时,将迁移历史读取出来
+首页添加了迁移管理入口按钮, 无需登录即可访问

+ 0 - 782
client/admin/api.ts

@@ -1,782 +0,0 @@
-import axios from 'axios';
-import { getGlobalConfig } from './utils.ts';
-import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types';
-import 'dayjs/locale/zh-cn';
-import type { 
-  User, FileLibrary, FileCategory, ThemeSettings,
- SystemSetting, SystemSettingGroupData, 
- LoginLocation, LoginLocationDetail,
- Message, UserMessage, KnowInfo
-} from '../share/types.ts';
-
-
-
-// 定义API基础URL
-const API_BASE_URL = '/api';
-
-// 获取OSS完整URL
-export const getOssUrl = (path: string): string => {
-  // 获取全局配置中的OSS_HOST,如果不存在使用默认值
-  const ossHost = getGlobalConfig('OSS_BASE_URL') || '';
-  // 确保path不以/开头
-  const ossPath = path.startsWith('/') ? path.substring(1) : path;
-  return `${ossHost}/${ossPath}`;
-};
-
-// ===================
-// Auth API 定义部分
-// ===================
-
-// 定义API返回数据类型
-interface AuthLoginResponse {
-  message: string;
-  token: string;
-  refreshToken?: string;
-  user: User;
-}
-
-interface AuthResponse {
-  message: string;
-  [key: string]: any;
-}
-
-// 定义Auth API接口类型
-interface AuthAPIType {
-  login: (username: string, password: string, latitude?: number, longitude?: number) => Promise<AuthLoginResponse>;
-  register: (username: string, email: string, password: string) => Promise<AuthResponse>;
-  logout: () => Promise<AuthResponse>;
-  getCurrentUser: () => Promise<User>;
-  updateUser: (userId: number, userData: Partial<User>) => Promise<User>;
-  changePassword: (oldPassword: string, newPassword: string) => Promise<AuthResponse>;
-  requestPasswordReset: (email: string) => Promise<AuthResponse>;
-  resetPassword: (token: string, newPassword: string) => Promise<AuthResponse>;
-}
-
-
-// Auth相关API
-export const AuthAPI: AuthAPIType = {
-  // 登录API
-  login: async (username: string, password: string, latitude?: number, longitude?: number) => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/auth/login`, {
-        username,
-        password,
-        latitude,
-        longitude
-      });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 注册API
-  register: async (username: string, email: string, password: string) => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/auth/register`, { username, email, password });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 登出API
-  logout: async () => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/auth/logout`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 获取当前用户信息
-  getCurrentUser: async () => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/auth/me`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 更新用户信息
-  updateUser: async (userId: number, userData: Partial<User>) => {
-    try {
-      const response = await axios.put(`${API_BASE_URL}/auth/users/${userId}`, userData);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 修改密码
-  changePassword: async (oldPassword: string, newPassword: string) => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/auth/change-password`, { oldPassword, newPassword });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 请求重置密码
-  requestPasswordReset: async (email: string) => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/auth/request-password-reset`, { email });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 重置密码
-  resetPassword: async (token: string, newPassword: string) => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/auth/reset-password`, { token, newPassword });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-// 为UserAPI添加的接口响应类型
-interface UsersResponse {
-  data: User[];
-  pagination: {
-    total: number;
-    current: number;
-    pageSize: number;
-    totalPages: number;
-  };
-}
-
-interface UserResponse {
-  data: User;
-  message?: string;
-}
-
-interface UserCreateResponse {
-  message: string;
-  data: User;
-}
-
-interface UserUpdateResponse {
-  message: string;
-  data: User;
-}
-
-interface UserDeleteResponse {
-  message: string;
-  id: number;
-}
-
-// 用户管理API
-export const UserAPI = {
-  // 获取用户列表
-  getUsers: async (params?: { page?: number, limit?: number, search?: string }): Promise<UsersResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/users`, { params });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 获取单个用户详情
-  getUser: async (userId: number): Promise<UserResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/users/${userId}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 创建用户
-  createUser: async (userData: Partial<User>): Promise<UserCreateResponse> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/users`, userData);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 更新用户信息
-  updateUser: async (userId: number, userData: Partial<User>): Promise<UserUpdateResponse> => {
-    try {
-      const response = await axios.put(`${API_BASE_URL}/users/${userId}`, userData);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-  
-  // 删除用户
-  deleteUser: async (userId: number): Promise<UserDeleteResponse> => {
-    try {
-      const response = await axios.delete(`${API_BASE_URL}/users/${userId}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-
-// 定义文件相关接口类型
-interface FileUploadPolicyResponse {
-  message: string;
-  data: MinioUploadPolicy | OSSUploadPolicy;
-}
-
-interface FileListResponse {
-  message: string;
-  data: {
-    list: FileLibrary[];
-    pagination: {
-      current: number;
-      pageSize: number;
-      total: number;
-    };
-  };
-}
-
-interface FileSaveResponse {
-  message: string;
-  data: FileLibrary;
-}
-
-interface FileInfoResponse {
-  message: string;
-  data: FileLibrary;
-}
-
-interface FileDeleteResponse {
-  message: string;
-}
-
-
-interface FileCategoryListResponse {
-  data: FileCategory[];
-  total: number;
-  page: number;
-  pageSize: number;
-}
-
-interface FileCategoryCreateResponse {
-  message: string;
-  data: FileCategory;
-}
-
-interface FileCategoryUpdateResponse {
-  message: string;
-  data: FileCategory;
-}
-
-interface FileCategoryDeleteResponse {
-  message: string;
-}
-
-// 文件API接口定义
-export const FileAPI = {
-  // 获取文件上传策略
-  getUploadPolicy: async (filename: string, prefix: string = 'uploads/', maxSize: number = 10 * 1024 * 1024): Promise<FileUploadPolicyResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/upload/policy`, { 
-        params: { filename, prefix, maxSize } 
-      });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 保存文件信息
-  saveFileInfo: async (fileData: Partial<FileLibrary>): Promise<FileSaveResponse> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/upload/save`, fileData);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取文件列表
-  getFileList: async (params?: {
-    page?: number,
-    pageSize?: number,
-    category_id?: number,
-    fileType?: string,
-    keyword?: string
-  }): Promise<FileListResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/upload/list`, { params });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取单个文件信息
-  getFileInfo: async (id: number): Promise<FileInfoResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/upload/${id}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 更新文件下载计数
-  updateDownloadCount: async (id: number): Promise<FileDeleteResponse> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/upload/${id}/download`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 删除文件
-  deleteFile: async (id: number): Promise<FileDeleteResponse> => {
-    try {
-      const response = await axios.delete(`${API_BASE_URL}/upload/${id}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取文件分类列表
-  getCategories: async (params?: {
-    page?: number,
-    pageSize?: number,
-    search?: string
-  }): Promise<FileCategoryListResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/file-categories`, { params });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 创建文件分类
-  createCategory: async (data: Partial<FileCategory>): Promise<FileCategoryCreateResponse> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/file-categories`, data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 更新文件分类
-  updateCategory: async (id: number, data: Partial<FileCategory>): Promise<FileCategoryUpdateResponse> => {
-    try {
-      const response = await axios.put(`${API_BASE_URL}/file-categories/${id}`, data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 删除文件分类
-  deleteCategory: async (id: number): Promise<FileCategoryDeleteResponse> => {
-    try {
-      const response = await axios.delete(`${API_BASE_URL}/file-categories/${id}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-// Theme API 响应类型
-export interface ThemeSettingsResponse {
-  message: string;
-  data: ThemeSettings;
-}
-
-// Theme API 定义
-export const ThemeAPI = {
-  // 获取主题设置
-  getThemeSettings: async (): Promise<ThemeSettings> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/theme`);
-      return response.data.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 更新主题设置
-  updateThemeSettings: async (themeData: Partial<ThemeSettings>): Promise<ThemeSettings> => {
-    try {
-      const response = await axios.put(`${API_BASE_URL}/theme`, themeData);
-      return response.data.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 重置主题设置
-  resetThemeSettings: async (): Promise<ThemeSettings> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/theme/reset`);
-      return response.data.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-// 图表数据API接口类型
-interface ChartDataResponse<T> {
-  message: string;
-  data: T;
-}
-
-interface UserActivityData {
-  date: string;
-  count: number;
-}
-
-interface FileUploadsData {
-  month: string;
-  count: number;
-}
-
-interface FileTypesData {
-  type: string;
-  value: number;
-}
-
-interface DashboardOverviewData {
-  userCount: number;
-  fileCount: number;
-  articleCount: number;
-  todayLoginCount: number;
-}
-
-// 图表数据API
-export const ChartAPI = {
-  // 获取用户活跃度数据
-  getUserActivity: async (): Promise<ChartDataResponse<UserActivityData[]>> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/charts/user-activity`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取文件上传统计数据
-  getFileUploads: async (): Promise<ChartDataResponse<FileUploadsData[]>> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/charts/file-uploads`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取文件类型分布数据
-  getFileTypes: async (): Promise<ChartDataResponse<FileTypesData[]>> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/charts/file-types`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取仪表盘概览数据
-  getDashboardOverview: async (): Promise<ChartDataResponse<DashboardOverviewData>> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/charts/dashboard-overview`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-// 消息API接口类型
-interface MessagesResponse {
-  data: UserMessage[];
-  pagination: {
-    total: number;
-    current: number;
-    pageSize: number;
-    totalPages: number;
-  };
-}
-
-interface MessageResponse {
-  data: Message;
-  message?: string;
-}
-
-interface MessageCountResponse {
-  count: number;
-}
-
-// 消息API
-export const MessageAPI = {
-  // 获取消息列表
-  getMessages: async (params?: {
-    page?: number,
-    pageSize?: number,
-    type?: string,
-    status?: string,
-    search?: string
-  }): Promise<MessagesResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/messages`, { params });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 发送消息
-  sendMessage: async (data: {
-    title: string,
-    content: string,
-    type: string,
-    receiver_ids: number[]
-  }): Promise<MessageResponse> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/messages`, data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取未读消息数
-  getUnreadCount: async (): Promise<MessageCountResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/messages/count/unread`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 标记消息为已读
-  markAsRead: async (id: number): Promise<MessageResponse> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/messages/${id}/read`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 删除消息
-  deleteMessage: async (id: number): Promise<MessageResponse> => {
-    try {
-      const response = await axios.delete(`${API_BASE_URL}/messages/${id}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-// 地图相关API的接口类型定义
-export interface LoginLocationResponse {
-  message: string;
-  data: LoginLocation[];
-}
-
-export interface LoginLocationDetailResponse {
-  message: string;
-  data: LoginLocationDetail;
-}
-
-export interface LoginLocationUpdateResponse {
-  message: string;
-  data: LoginLocationDetail;
-}
-
-// 知识库相关接口类型定义
-export interface KnowInfoListResponse {
-  data: KnowInfo[];
-  pagination: {
-    total: number;
-    current: number;
-    pageSize: number;
-    totalPages: number;
-  };
-}
-
-interface KnowInfoResponse {
-  data: KnowInfo;
-  message?: string;
-}
-
-interface KnowInfoCreateResponse {
-  message: string;
-  data: KnowInfo;
-}
-
-interface KnowInfoUpdateResponse {
-  message: string;
-  data: KnowInfo;
-}
-
-interface KnowInfoDeleteResponse {
-  message: string;
-  id: number;
-}
-
-
-// 地图相关API
-export const MapAPI = {
-  // 获取地图标记点数据
-  getMarkers: async (params?: { 
-    startTime?: string; 
-    endTime?: string; 
-    userId?: number 
-  }): Promise<LoginLocationResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/map/markers`, { params });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取登录位置详情
-  getLocationDetail: async (locationId: number): Promise<LoginLocationDetailResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/map/location/${locationId}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 更新登录位置信息
-  updateLocation: async (locationId: number, data: { 
-    longitude: number; 
-    latitude: number; 
-    location_name?: string; 
-  }): Promise<LoginLocationUpdateResponse> => {
-    try {
-      const response = await axios.put(`${API_BASE_URL}/map/location/${locationId}`, data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-// 系统设置API
-// 知识库API
-export const KnowInfoAPI = {
-  // 获取知识库列表
-  getKnowInfos: async (params?: {
-    page?: number;
-    pageSize?: number;
-    title?: string;
-    category?: string;
-    tags?: string;
-  }): Promise<KnowInfoListResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/know-infos`, { params });
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取单个知识详情
-  getKnowInfo: async (id: number): Promise<KnowInfoResponse> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/know-infos/${id}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 创建知识
-  createKnowInfo: async (data: Partial<KnowInfo>): Promise<KnowInfoCreateResponse> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/know-infos`, data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 更新知识
-  updateKnowInfo: async (id: number, data: Partial<KnowInfo>): Promise<KnowInfoUpdateResponse> => {
-    try {
-      const response = await axios.put(`${API_BASE_URL}/know-infos/${id}`, data);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 删除知识
-  deleteKnowInfo: async (id: number): Promise<KnowInfoDeleteResponse> => {
-    try {
-      const response = await axios.delete(`${API_BASE_URL}/know-infos/${id}`);
-      return response.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-
-export const SystemAPI = {
-  // 获取所有系统设置
-  getSettings: async (): Promise<SystemSettingGroupData[]> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/settings`);
-      return response.data.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 获取指定分组的系统设置
-  getSettingsByGroup: async (group: string): Promise<SystemSetting[]> => {
-    try {
-      const response = await axios.get(`${API_BASE_URL}/settings/group/${group}`);
-      return response.data.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 更新系统设置
-  updateSettings: async (settings: Partial<SystemSetting>[]): Promise<SystemSetting[]> => {
-    try {
-      const response = await axios.put(`${API_BASE_URL}/settings`, settings);
-      return response.data.data;
-    } catch (error) {
-      throw error;
-    }
-  },
-
-  // 重置系统设置
-  resetSettings: async (): Promise<SystemSetting[]> => {
-    try {
-      const response = await axios.post(`${API_BASE_URL}/settings/reset`);
-      return response.data.data;
-    } catch (error) {
-      throw error;
-    }
-  }
-};
-

+ 104 - 0
client/admin/api/auth.ts

@@ -0,0 +1,104 @@
+import axios from 'axios';
+import type { User } from '../../share/types.ts';
+
+interface AuthLoginResponse {
+  message: string;
+  token: string;
+  refreshToken?: string;
+  user: User;
+}
+
+interface AuthResponse {
+  message: string;
+  [key: string]: any;
+}
+
+interface AuthAPIType {
+  login: (username: string, password: string, latitude?: number, longitude?: number) => Promise<AuthLoginResponse>;
+  register: (username: string, email: string, password: string) => Promise<AuthResponse>;
+  logout: () => Promise<AuthResponse>;
+  getCurrentUser: () => Promise<User>;
+  updateUser: (userId: number, userData: Partial<User>) => Promise<User>;
+  changePassword: (oldPassword: string, newPassword: string) => Promise<AuthResponse>;
+  requestPasswordReset: (email: string) => Promise<AuthResponse>;
+  resetPassword: (token: string, newPassword: string) => Promise<AuthResponse>;
+}
+
+export const AuthAPI: AuthAPIType = {
+  login: async (username: string, password: string, latitude?: number, longitude?: number) => {
+    try {
+      const response = await axios.post('/auth/login', {
+        username,
+        password,
+        latitude,
+        longitude
+      });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  register: async (username: string, email: string, password: string) => {
+    try {
+      const response = await axios.post('/auth/register', { username, email, password });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  logout: async () => {
+    try {
+      const response = await axios.post('/auth/logout');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  getCurrentUser: async () => {
+    try {
+      const response = await axios.get('/auth/me');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  updateUser: async (userId: number, userData: Partial<User>) => {
+    try {
+      const response = await axios.put(`/auth/users/${userId}`, userData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  changePassword: async (oldPassword: string, newPassword: string) => {
+    try {
+      const response = await axios.post('/auth/change-password', { oldPassword, newPassword });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  requestPasswordReset: async (email: string) => {
+    try {
+      const response = await axios.post('/auth/request-password-reset', { email });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  resetPassword: async (token: string, newPassword: string) => {
+    try {
+      const response = await axios.post('/auth/reset-password', { token, newPassword });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 66 - 0
client/admin/api/charts.ts

@@ -0,0 +1,66 @@
+import axios from 'axios';
+
+interface ChartDataResponse<T> {
+  message: string;
+  data: T;
+}
+
+interface UserActivityData {
+  date: string;
+  count: number;
+}
+
+interface FileUploadsData {
+  month: string;
+  count: number;
+}
+
+interface FileTypesData {
+  type: string;
+  value: number;
+}
+
+interface DashboardOverviewData {
+  userCount: number;
+  fileCount: number;
+  articleCount: number;
+  todayLoginCount: number;
+}
+
+export const ChartAPI = {
+  getUserActivity: async (): Promise<ChartDataResponse<UserActivityData[]>> => {
+    try {
+      const response = await axios.get('/charts/user-activity');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  getFileUploads: async (): Promise<ChartDataResponse<FileUploadsData[]>> => {
+    try {
+      const response = await axios.get('/charts/file-uploads');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  getFileTypes: async (): Promise<ChartDataResponse<FileTypesData[]>> => {
+    try {
+      const response = await axios.get('/charts/file-types');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  getDashboardOverview: async (): Promise<ChartDataResponse<DashboardOverviewData>> => {
+    try {
+      const response = await axios.get('/charts/dashboard-overview');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 159 - 0
client/admin/api/files.ts

@@ -0,0 +1,159 @@
+import axios from 'axios';
+import type { FileLibrary, FileCategory } from '../../share/types.ts';
+import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types';
+
+interface FileUploadPolicyResponse {
+  message: string;
+  data: MinioUploadPolicy | OSSUploadPolicy;
+}
+
+interface FileListResponse {
+  message: string;
+  data: {
+    list: FileLibrary[];
+    pagination: {
+      current: number;
+      pageSize: number;
+      total: number;
+    };
+  };
+}
+
+interface FileSaveResponse {
+  message: string;
+  data: FileLibrary;
+}
+
+interface FileInfoResponse {
+  message: string;
+  data: FileLibrary;
+}
+
+interface FileDeleteResponse {
+  message: string;
+}
+
+interface FileCategoryListResponse {
+  data: FileCategory[];
+  total: number;
+  page: number;
+  pageSize: number;
+}
+
+interface FileCategoryCreateResponse {
+  message: string;
+  data: FileCategory;
+}
+
+interface FileCategoryUpdateResponse {
+  message: string;
+  data: FileCategory;
+}
+
+interface FileCategoryDeleteResponse {
+  message: string;
+}
+
+export const FileAPI = {
+  getUploadPolicy: async (filename: string, prefix: string = 'uploads/', maxSize: number = 10 * 1024 * 1024): Promise<FileUploadPolicyResponse> => {
+    try {
+      const response = await axios.get('/upload/policy', {
+        params: { filename, prefix, maxSize } 
+      });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  saveFileInfo: async (fileData: Partial<FileLibrary>): Promise<FileSaveResponse> => {
+    try {
+      const response = await axios.post('/upload/save', fileData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  getFileList: async (params?: {
+    page?: number,
+    pageSize?: number,
+    category_id?: number,
+    fileType?: string,
+    keyword?: string
+  }): Promise<FileListResponse> => {
+    try {
+      const response = await axios.get('/upload/list', { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  getFileInfo: async (id: number): Promise<FileInfoResponse> => {
+    try {
+      const response = await axios.get(`/upload/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  updateDownloadCount: async (id: number): Promise<FileDeleteResponse> => {
+    try {
+      const response = await axios.post(`/upload/${id}/download`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  deleteFile: async (id: number): Promise<FileDeleteResponse> => {
+    try {
+      const response = await axios.delete(`/upload/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  getCategories: async (params?: {
+    page?: number,
+    pageSize?: number,
+    search?: string
+  }): Promise<FileCategoryListResponse> => {
+    try {
+      const response = await axios.get('/file-categories', { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  createCategory: async (data: Partial<FileCategory>): Promise<FileCategoryCreateResponse> => {
+    try {
+      const response = await axios.post('/file-categories', data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  updateCategory: async (id: number, data: Partial<FileCategory>): Promise<FileCategoryUpdateResponse> => {
+    try {
+      const response = await axios.put(`/file-categories/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  deleteCategory: async (id: number): Promise<FileCategoryDeleteResponse> => {
+    try {
+      const response = await axios.delete(`/file-categories/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 25 - 0
client/admin/api/index.ts

@@ -0,0 +1,25 @@
+import axios from 'axios';
+
+// 基础配置
+export const API_BASE_URL = '/api';
+// 全局axios配置
+axios.defaults.baseURL = API_BASE_URL;
+
+// 获取OSS完整URL
+export const getOssUrl = (path: string): string => {
+  // 获取全局配置中的OSS_HOST,如果不存在使用默认值
+  const ossHost = (window.CONFIG?.OSS_BASE_URL) || '';
+  // 确保path不以/开头
+  const ossPath = path.startsWith('/') ? path.substring(1) : path;
+  return `${ossHost}/${ossPath}`;
+};
+
+export * from './auth.ts';
+export * from './users.ts';
+export * from './files.ts';
+export * from './theme.ts';
+export * from './charts.ts';
+export * from './messages.ts';
+export * from './sys.ts';
+export * from './know_info.ts';
+export * from './maps.ts';

+ 92 - 0
client/admin/api/know_info.ts

@@ -0,0 +1,92 @@
+import axios from 'axios';
+import type { KnowInfo } from '../../share/types.ts';
+
+export interface KnowInfoListResponse {
+  data: KnowInfo[];
+  pagination: {
+    current: number;
+    pageSize: number;
+    total: number;
+    totalPages: number;
+  };
+}
+
+interface KnowInfoResponse {
+  data: KnowInfo;
+  message?: string;
+}
+
+interface KnowInfoCreateResponse {
+  message: string;
+  data: KnowInfo;
+}
+
+interface KnowInfoUpdateResponse {
+  message: string;
+  data: KnowInfo;
+}
+
+interface KnowInfoDeleteResponse {
+  message: string;
+  id: number;
+}
+
+
+// 知识库API
+export const KnowInfoAPI = {
+  // 获取知识库列表
+  getKnowInfos: async (params?: {
+    page?: number;
+    pageSize?: number;
+    title?: string;
+    category?: string;
+    tags?: string;
+  }): Promise<KnowInfoListResponse> => {
+    try {
+      const response = await axios.get('/know-infos', { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取单个知识详情
+  getKnowInfo: async (id: number): Promise<KnowInfoResponse> => {
+    try {
+      const response = await axios.get(`/know-infos/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 创建知识
+  createKnowInfo: async (data: Partial<KnowInfo>): Promise<KnowInfoCreateResponse> => {
+    try {
+      const response = await axios.post('/know-infos', data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新知识
+  updateKnowInfo: async (id: number, data: Partial<KnowInfo>): Promise<KnowInfoUpdateResponse> => {
+    try {
+      const response = await axios.put(`/know-infos/${id}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 删除知识
+  deleteKnowInfo: async (id: number): Promise<KnowInfoDeleteResponse> => {
+    try {
+      const response = await axios.delete(`/know-infos/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 63 - 0
client/admin/api/maps.ts

@@ -0,0 +1,63 @@
+import axios from 'axios';
+import { API_BASE_URL } from './index.ts';
+import type {
+ LoginLocation, LoginLocationDetail,
+} from '../../share/types.ts';
+
+
+// 地图相关API的接口类型定义
+export interface LoginLocationResponse {
+  message: string;
+  data: LoginLocation[];
+}
+
+export interface LoginLocationDetailResponse {
+  message: string;
+  data: LoginLocationDetail;
+}
+
+export interface LoginLocationUpdateResponse {
+  message: string;
+  data: LoginLocationDetail;
+}
+
+// 地图相关API
+export const MapAPI = {
+  // 获取地图标记点数据
+  getMarkers: async (params?: { 
+    startTime?: string; 
+    endTime?: string; 
+    userId?: number 
+  }): Promise<LoginLocationResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/map/markers`, { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取登录位置详情
+  getLocationDetail: async (locationId: number): Promise<LoginLocationDetailResponse> => {
+    try {
+      const response = await axios.get(`${API_BASE_URL}/map/location/${locationId}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新登录位置信息
+  updateLocation: async (locationId: number, data: { 
+    longitude: number; 
+    latitude: number; 
+    location_name?: string; 
+  }): Promise<LoginLocationUpdateResponse> => {
+    try {
+      const response = await axios.put(`${API_BASE_URL}/map/location/${locationId}`, data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 79 - 0
client/admin/api/messages.ts

@@ -0,0 +1,79 @@
+import axios from 'axios';
+import type { UserMessage, Message } from '../../share/types.ts';
+
+interface MessagesResponse {
+  data: UserMessage[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+    totalPages: number;
+  };
+}
+
+interface MessageResponse {
+  data: Message;
+  message?: string;
+}
+
+interface MessageCountResponse {
+  count: number;
+}
+
+export const MessageAPI = {
+  getMessages: async (params?: {
+    page?: number,
+    pageSize?: number,
+    type?: string,
+    status?: string,
+    search?: string
+  }): Promise<MessagesResponse> => {
+    try {
+      const response = await axios.get('/messages', { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  sendMessage: async (data: {
+    title: string,
+    content: string,
+    type: string,
+    receiver_ids: number[]
+  }): Promise<MessageResponse> => {
+    try {
+      const response = await axios.post('/messages', data);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  getUnreadCount: async (): Promise<MessageCountResponse> => {
+    try {
+      const response = await axios.get('/messages/count/unread');
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  markAsRead: async (id: number): Promise<MessageResponse> => {
+    try {
+      const response = await axios.post(`/messages/${id}/read`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  deleteMessage: async (id: number): Promise<MessageResponse> => {
+    try {
+      const response = await axios.delete(`/messages/${id}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 47 - 0
client/admin/api/sys.ts

@@ -0,0 +1,47 @@
+import axios from 'axios';
+
+import type {
+ SystemSetting, SystemSettingGroupData,
+} from '../../share/types.ts';
+
+export const SystemAPI = {
+  // 获取所有系统设置
+  getSettings: async (): Promise<SystemSettingGroupData[]> => {
+    try {
+      const response = await axios.get('/settings');
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 获取指定分组的系统设置
+  getSettingsByGroup: async (group: string): Promise<SystemSetting[]> => {
+    try {
+      const response = await axios.get(`/settings/group/${group}`);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 更新系统设置
+  updateSettings: async (settings: Partial<SystemSetting>[]): Promise<SystemSetting[]> => {
+    try {
+      const response = await axios.put('/settings', settings);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  // 重置系统设置
+  resetSettings: async (): Promise<SystemSetting[]> => {
+    try {
+      const response = await axios.post('/settings/reset');
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 36 - 0
client/admin/api/theme.ts

@@ -0,0 +1,36 @@
+import axios from 'axios';
+import type { ThemeSettings } from '../../share/types.ts';
+
+export interface ThemeSettingsResponse {
+  message: string;
+  data: ThemeSettings;
+}
+
+export const ThemeAPI = {
+  getThemeSettings: async (): Promise<ThemeSettings> => {
+    try {
+      const response = await axios.get('/theme');
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  updateThemeSettings: async (themeData: Partial<ThemeSettings>): Promise<ThemeSettings> => {
+    try {
+      const response = await axios.put('/theme', themeData);
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+
+  resetThemeSettings: async (): Promise<ThemeSettings> => {
+    try {
+      const response = await axios.post('/theme/reset');
+      return response.data.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 79 - 0
client/admin/api/users.ts

@@ -0,0 +1,79 @@
+import axios from 'axios';
+import type { User } from '../../share/types.ts';
+
+interface UsersResponse {
+  data: User[];
+  pagination: {
+    total: number;
+    current: number;
+    pageSize: number;
+    totalPages: number;
+  };
+}
+
+interface UserResponse {
+  data: User;
+  message?: string;
+}
+
+interface UserCreateResponse {
+  message: string;
+  data: User;
+}
+
+interface UserUpdateResponse {
+  message: string;
+  data: User;
+}
+
+interface UserDeleteResponse {
+  message: string;
+  id: number;
+}
+
+export const UserAPI = {
+  getUsers: async (params?: { page?: number, limit?: number, search?: string }): Promise<UsersResponse> => {
+    try {
+      const response = await axios.get('/users', { params });
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  getUser: async (userId: number): Promise<UserResponse> => {
+    try {
+      const response = await axios.get(`/users/${userId}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  createUser: async (userData: Partial<User>): Promise<UserCreateResponse> => {
+    try {
+      const response = await axios.post('/users', userData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  updateUser: async (userId: number, userData: Partial<User>): Promise<UserUpdateResponse> => {
+    try {
+      const response = await axios.put(`/users/${userId}`, userData);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  },
+  
+  deleteUser: async (userId: number): Promise<UserDeleteResponse> => {
+    try {
+      const response = await axios.delete(`/users/${userId}`);
+      return response.data;
+    } catch (error) {
+      throw error;
+    }
+  }
+};

+ 1 - 1
client/admin/components_uploader.tsx

@@ -16,7 +16,7 @@ import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types
 import 'dayjs/locale/zh-cn';
 import { OssType } from '../share/types.ts';
 
-import { FileAPI } from './api.ts';
+import { FileAPI } from './api/index.ts';
 
 // MinIO文件上传组件
 export const Uploader = ({ 

+ 1 - 1
client/admin/hooks_sys.tsx

@@ -24,7 +24,7 @@ import {
 import {
   AuthAPI,
   ThemeAPI
-} from './api.ts';
+} from './api/index.ts';
 
 
 // 配置 dayjs 插件

+ 10 - 18
client/admin/pages_chart.tsx

@@ -1,11 +1,6 @@
 import React from 'react';
 import { 
-  Layout, Menu, Button, Table, Space,
-  Form, Input, Select, message, Modal,
-  Card, Spin, Row, Col, Breadcrumb, Avatar,
-  Dropdown, ConfigProvider, theme, Typography,
-  Switch, Badge, Image, Upload, Divider, Descriptions,
-  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
+  Card, Spin, Row, Col, Statistic,
 } from 'antd';
 
 import { 
@@ -15,13 +10,10 @@ import { Line , Pie, Column} from "@ant-design/plots";
 import 'dayjs/locale/zh-cn';
 
 
-import { ChartAPI } from './api.ts';
+import { ChartAPI } from './api/index.ts';
 import { useTheme } from './hooks_sys.tsx';
 
-interface ChartTooltipInfo {
-  items: Array<Record<string, any>>;
-  title: string;
-}
+
 
 // 用户活跃度图表组件
 const UserActivityChart: React.FC = () => {
@@ -49,7 +41,7 @@ const UserActivityChart: React.FC = () => {
   };
 
   return (
-    <Card title="用户活跃度趋势" bordered={false}>
+    <Card title="用户活跃度趋势" variant="borderless">
       <Line {...config} />
     </Card>
   );
@@ -92,7 +84,7 @@ const FileUploadsChart: React.FC = () => {
   };
 
   return (
-    <Card title="文件上传统计" bordered={false}>
+    <Card title="文件上传统计" variant="borderless">
       <Column {...config} />
     </Card>
   );
@@ -130,7 +122,7 @@ const FileTypesChart: React.FC = () => {
   };
 
   return (
-    <Card title="文件类型分布" bordered={false}>
+    <Card title="文件类型分布" variant="borderless">
       <Pie {...config} />
     </Card>
   );
@@ -151,7 +143,7 @@ const DashboardOverview: React.FC = () => {
   return (
     <Row gutter={[16, 16]}>
       <Col xs={12} sm={12} md={6}>
-        <Card bordered={false}>
+        <Card variant="borderless">
           <Statistic
             title="用户总数"
             value={overviewData?.userCount || 0}
@@ -160,7 +152,7 @@ const DashboardOverview: React.FC = () => {
         </Card>
       </Col>
       <Col xs={12} sm={12} md={6}>
-        <Card bordered={false}>
+        <Card variant="borderless">
           <Statistic
             title="文件总数"
             value={overviewData?.fileCount || 0}
@@ -169,7 +161,7 @@ const DashboardOverview: React.FC = () => {
         </Card>
       </Col>
       <Col xs={12} sm={12} md={6}>
-        <Card bordered={false}>
+        <Card variant="borderless">
           <Statistic
             title="文章总数"
             value={overviewData?.articleCount || 0}
@@ -178,7 +170,7 @@ const DashboardOverview: React.FC = () => {
         </Card>
       </Col>
       <Col xs={12} sm={12} md={6}>
-        <Card bordered={false}>
+        <Card variant="borderless">
           <Statistic
             title="今日登录"
             value={overviewData?.todayLoginCount || 0}

+ 44 - 0
client/admin/pages_dashboard.tsx

@@ -0,0 +1,44 @@
+import React from 'react';
+import { 
+  Card, Row, Col, Typography, Statistic
+} from 'antd';
+
+const { Title } = Typography;
+
+// 仪表盘页面
+export const DashboardPage = () => {
+  return (
+    <div>
+      <Title level={2}>仪表盘</Title>
+      <Row gutter={16}>
+        <Col span={8}>
+          <Card>
+            <Statistic
+              title="活跃用户"
+              value={112893}
+              loading={false}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card>
+            <Statistic
+              title="系统消息"
+              value={93}
+              loading={false}
+            />
+          </Card>
+        </Col>
+        <Col span={8}>
+          <Card>
+            <Statistic
+              title="在线用户"
+              value={1128}
+              loading={false}
+            />
+          </Card>
+        </Col>
+      </Row>
+    </div>
+  );
+};

+ 9 - 336
client/admin/pages_sys.tsx → client/admin/pages_file_library.tsx

@@ -1,11 +1,8 @@
 import React, { useState, useEffect } from 'react';
 import { 
-  Layout, Menu, Button, Table, Space,
-  Form, Input, Select, message, Modal,
-  Card, Spin, Row, Col, Breadcrumb, Avatar,
-  Dropdown, ConfigProvider, theme, Typography,
-  Switch, Badge, Image, Upload, Divider, Descriptions,
-  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer
+  Button, Table, Space, Form, Input, Select, 
+  message, Modal, Card, Typography, Tag, Popconfirm,
+  Tabs, Image, Upload, Descriptions
 } from 'antd';
 import {
   UploadOutlined,
@@ -14,341 +11,17 @@ import {
   FileWordOutlined,
   FilePdfOutlined,
   FileOutlined,
-} from '@ant-design/icons';   
-import { 
-  useQuery,
-} from '@tanstack/react-query';
+} from '@ant-design/icons';
+import { useQuery } from '@tanstack/react-query';
 import dayjs from 'dayjs';
-import weekday from 'dayjs/plugin/weekday';
-import localeData from 'dayjs/plugin/localeData';
-import { uploadMinIOWithPolicy,uploadOSSWithPolicy } from '@d8d-appcontainer/api';
+import { uploadMinIOWithPolicy, uploadOSSWithPolicy } from '@d8d-appcontainer/api';
 import type { MinioUploadPolicy, OSSUploadPolicy } from '@d8d-appcontainer/types';
-import 'dayjs/locale/zh-cn';
-import type { 
-  FileLibrary, FileCategory, KnowInfo
-} from '../share/types.ts';
-
-import {
-   AuditStatus,AuditStatusNameMap,
-   OssType,
-} from '../share/types.ts';
-
-import { getEnumOptions } from './utils.ts';
-
-import {
-  FileAPI,
-  UserAPI,
-} from './api.ts';
-
-
-// 配置 dayjs 插件
-dayjs.extend(weekday);
-dayjs.extend(localeData);
-
-// 设置 dayjs 语言
-dayjs.locale('zh-cn');
+import { FileAPI } from './api/index.ts';
+import type { FileLibrary, FileCategory } from '../share/types.ts';
+import { OssType } from '../share/types.ts';
 
 const { Title } = Typography;
 
-
-// 仪表盘页面
-export const DashboardPage = () => {
-  return (
-    <div>
-      <Title level={2}>仪表盘</Title>
-      <Row gutter={16}>
-        <Col span={8}>
-          <Card>
-            <Statistic
-              title="活跃用户"
-              value={112893}
-              loading={false}
-            />
-          </Card>
-        </Col>
-        <Col span={8}>
-          <Card>
-            <Statistic
-              title="系统消息"
-              value={93}
-              loading={false}
-            />
-          </Card>
-        </Col>
-        <Col span={8}>
-          <Card>
-            <Statistic
-              title="在线用户"
-              value={1128}
-              loading={false}
-            />
-          </Card>
-        </Col>
-      </Row>
-    </div>
-  );
-};
-
-// 用户管理页面
-export const UsersPage = () => {
-  const [searchParams, setSearchParams] = useState({
-    page: 1,
-    limit: 10,
-    search: ''
-  });
-  const [modalVisible, setModalVisible] = useState(false);
-  const [modalTitle, setModalTitle] = useState('');
-  const [editingUser, setEditingUser] = useState<any>(null);
-  const [form] = Form.useForm();
-
-  const { data: usersData, isLoading, refetch } = useQuery({
-    queryKey: ['users', searchParams],
-    queryFn: async () => {
-      return await UserAPI.getUsers(searchParams);
-    }
-  });
-
-  const users = usersData?.data || [];
-  const pagination = {
-    current: searchParams.page,
-    pageSize: searchParams.limit,
-    total: usersData?.pagination?.total || 0
-  };
-
-  // 处理搜索
-  const handleSearch = (values: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      search: values.search || '',
-      page: 1
-    }));
-  };
-
-  // 处理分页变化
-  const handleTableChange = (newPagination: any) => {
-    setSearchParams(prev => ({
-      ...prev,
-      page: newPagination.current,
-      limit: newPagination.pageSize
-    }));
-  };
-
-  // 打开创建用户模态框
-  const showCreateModal = () => {
-    setModalTitle('创建用户');
-    setEditingUser(null);
-    form.resetFields();
-    setModalVisible(true);
-  };
-
-  // 打开编辑用户模态框
-  const showEditModal = (user: any) => {
-    setModalTitle('编辑用户');
-    setEditingUser(user);
-    form.setFieldsValue(user);
-    setModalVisible(true);
-  };
-
-  // 处理模态框确认
-  const handleModalOk = async () => {
-    try {
-      const values = await form.validateFields();
-      
-      if (editingUser) {
-        // 编辑用户
-        await UserAPI.updateUser(editingUser.id, values);
-        message.success('用户更新成功');
-      } else {
-        // 创建用户
-        await UserAPI.createUser(values);
-        message.success('用户创建成功');
-      }
-      
-      setModalVisible(false);
-      form.resetFields();
-      refetch(); // 刷新用户列表
-    } catch (error) {
-      console.error('表单提交失败:', error);
-      message.error('操作失败,请重试');
-    }
-  };
-
-  // 处理删除用户
-  const handleDelete = async (id: number) => {
-    try {
-      await UserAPI.deleteUser(id);
-      message.success('用户删除成功');
-      refetch(); // 刷新用户列表
-    } catch (error) {
-      console.error('删除用户失败:', error);
-      message.error('删除失败,请重试');
-    }
-  };
-  
-  const columns = [
-    {
-      title: '用户名',
-      dataIndex: 'username',
-      key: 'username',
-    },
-    {
-      title: '昵称',
-      dataIndex: 'nickname',
-      key: 'nickname',
-    },
-    {
-      title: '邮箱',
-      dataIndex: 'email',
-      key: 'email',
-    },
-    {
-      title: '角色',
-      dataIndex: 'role',
-      key: 'role',
-      render: (role: string) => (
-        <Tag color={role === 'admin' ? 'red' : 'blue'}>
-          {role === 'admin' ? '管理员' : '普通用户'}
-        </Tag>
-      ),
-    },
-    {
-      title: '创建时间',
-      dataIndex: 'created_at',
-      key: 'created_at',
-      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
-    },
-    {
-      title: '操作',
-      key: 'action',
-      render: (_: any, record: any) => (
-        <Space size="middle">
-          <Button type="link" onClick={() => showEditModal(record)}>
-            编辑
-          </Button>
-          <Popconfirm
-            title="确定要删除此用户吗?"
-            onConfirm={() => handleDelete(record.id)}
-            okText="确定"
-            cancelText="取消"
-          >
-            <Button type="link" danger>
-              删除
-            </Button>
-          </Popconfirm>
-        </Space>
-      ),
-    },
-  ];
-  
-  return (
-    <div>
-      <Title level={2}>用户管理</Title>
-      <Card>
-        <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16 }}>
-          <Form.Item name="search" label="搜索">
-            <Input placeholder="用户名/昵称/邮箱" allowClear />
-          </Form.Item>
-          <Form.Item>
-            <Space>
-              <Button type="primary" htmlType="submit">
-                搜索
-              </Button>
-              <Button type="primary" onClick={showCreateModal}>
-                创建用户
-              </Button>
-            </Space>
-          </Form.Item>
-        </Form>
-
-        <Table
-          columns={columns}
-          dataSource={users}
-          loading={isLoading}
-          rowKey="id"
-          pagination={{
-            ...pagination,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            showTotal: (total) => `共 ${total} 条记录`
-          }}
-          onChange={handleTableChange}
-        />
-      </Card>
-
-      {/* 创建/编辑用户模态框 */}
-      <Modal
-        title={modalTitle}
-        open={modalVisible}
-        onOk={handleModalOk}
-        onCancel={() => {
-          setModalVisible(false);
-          form.resetFields();
-        }}
-        width={600}
-      >
-        <Form
-          form={form}
-          layout="vertical"
-        >
-          <Form.Item
-            name="username"
-            label="用户名"
-            rules={[
-              { required: true, message: '请输入用户名' },
-              { min: 3, message: '用户名至少3个字符' }
-            ]}
-          >
-            <Input placeholder="请输入用户名" />
-          </Form.Item>
-
-          <Form.Item
-            name="nickname"
-            label="昵称"
-            rules={[{ required: true, message: '请输入昵称' }]}
-          >
-            <Input placeholder="请输入昵称" />
-          </Form.Item>
-
-          <Form.Item
-            name="email"
-            label="邮箱"
-            rules={[
-              { required: true, message: '请输入邮箱' },
-              { type: 'email', message: '请输入有效的邮箱地址' }
-            ]}
-          >
-            <Input placeholder="请输入邮箱" />
-          </Form.Item>
-
-          {!editingUser && (
-            <Form.Item
-              name="password"
-              label="密码"
-              rules={[
-                { required: true, message: '请输入密码' },
-                { min: 6, message: '密码至少6个字符' }
-              ]}
-            >
-              <Input.Password placeholder="请输入密码" />
-            </Form.Item>
-          )}
-
-          <Form.Item
-            name="role"
-            label="角色"
-            rules={[{ required: true, message: '请选择角色' }]}
-          >
-            <Select placeholder="请选择角色">
-              <Select.Option value="user">普通用户</Select.Option>
-              <Select.Option value="admin">管理员</Select.Option>
-            </Select>
-          </Form.Item>
-        </Form>
-      </Modal>
-    </div>
-  );
-};
-
 // 文件库管理页面
 export const FileLibraryPage = () => {
   const [loading, setLoading] = useState(false);

+ 0 - 543
client/admin/pages_know_info.test.tsx

@@ -1,543 +0,0 @@
-import { JSDOM } from 'jsdom'
-import React from 'react'
-import {render, waitFor, within, fireEvent} from '@testing-library/react'
-import {userEvent} from '@testing-library/user-event'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { createBrowserRouter, RouterProvider } from 'react-router'
-import {
-  assertEquals,
-  assertExists,
-  assertNotEquals,
-  assertRejects,
-  assert,
-} from "https://deno.land/std@0.217.0/assert/mod.ts";
-import axios from 'axios';
-import { KnowInfoPage } from "./pages_know_info.tsx"
-import { AuthProvider } from './hooks_sys.tsx'
-import { ProtectedRoute } from './components_protected_route.tsx'
-
-// 拦截React DOM中的attachEvent和detachEvent错误
-const originalError = console.error;
-console.error = (...args) => {
-  // 过滤掉attachEvent和detachEvent相关的错误
-  if (args[0] instanceof Error) {
-    if (args[0].message?.includes('attachEvent is not a function') || 
-        args[0].message?.includes('detachEvent is not a function')) {
-      return; // 不输出这些错误
-    }
-  } else if (typeof args[0] === 'string') {
-    if (args[0].includes('attachEvent is not a function') || 
-        args[0].includes('detachEvent is not a function')) {
-      return; // 不输出这些错误
-    }
-  }
-  originalError(...args);
-};
-
-// 应用入口组件
-const App = () => {
-  // 路由配置
-  const router = createBrowserRouter([
-    {
-      path: '/',
-      element: (
-        <ProtectedRoute>
-          <KnowInfoPage />
-        </ProtectedRoute>
-      )
-    },
-  ]);
-  return <RouterProvider router={router} />
-};
-// setup function
-function setup() {
-
-  const dom = new JSDOM(`<body></body>`, { 
-    runScripts: "dangerously",
-    pretendToBeVisual: true,
-    url: "http://localhost",
-  });
-  
-  // 模拟浏览器环境
-  globalThis.window = dom.window;
-  globalThis.document = dom.window.document;
-  
-  // 添加必要的 DOM 配置
-  globalThis.Node = dom.window.Node;
-  globalThis.Document = dom.window.Document;
-  globalThis.HTMLInputElement = dom.window.HTMLInputElement;
-  globalThis.HTMLButtonElement = dom.window.HTMLButtonElement;
-  
-  // 定义浏览器环境所需的类
-  globalThis.Element = dom.window.Element;
-  globalThis.HTMLElement = dom.window.HTMLElement;
-  globalThis.ShadowRoot = dom.window.ShadowRoot;
-  globalThis.SVGElement = dom.window.SVGElement;
-  
-  
-  
-  // 模拟 getComputedStyle
-  globalThis.getComputedStyle = (elt) => {
-    const style = new dom.window.CSSStyleDeclaration();
-    style.getPropertyValue = () => '';
-    return style;
-  };
-  
-  // 模拟matchMedia函数
-  globalThis.matchMedia = (query) => ({
-    matches: query.includes('max-width'),
-    media: query,
-    onchange: null,
-    addListener: () => {},
-    removeListener: () => {},
-    addEventListener: () => {},
-    removeEventListener: () => {},
-    dispatchEvent: () => false,
-  });
-  
-  // 模拟动画相关API
-  globalThis.AnimationEvent = globalThis.AnimationEvent || dom.window.Event;
-  globalThis.TransitionEvent = globalThis.TransitionEvent || dom.window.Event;
-  
-  // 模拟requestAnimationFrame
-  globalThis.requestAnimationFrame = globalThis.requestAnimationFrame || ((cb) => setTimeout(cb, 0));
-  globalThis.cancelAnimationFrame = globalThis.cancelAnimationFrame || clearTimeout;
-  
-  // 设置浏览器尺寸相关方法
-  window.resizeTo = (width, height) => {
-    window.innerWidth = width || window.innerWidth;
-    window.innerHeight = height || window.innerHeight;
-    window.dispatchEvent(new Event('resize'));
-  };
-  window.scrollTo = () => {};
-  
-  
-  const customScreen = within(document.body);
-
-  const user = userEvent.setup({
-    document: dom.window.document,
-    delay: 10,
-    skipAutoClose: true,
-  });
-  
-  localStorage.setItem('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwidXNlcm5hbWUiOiJhZG1pbiIsInNlc3Npb25JZCI6Ijk4T2lzTW5SMm0zQ0dtNmo4SVZrNyIsInJvbGVJbmZvIjpudWxsLCJpYXQiOjE3NDQzNjIzNTUsImV4cCI6MTc0NDQ0ODc1NX0.k1Ld7qWAZmdzsbjmrl_0ec1FqF_GimaOuQIic4znRtc');
-
-  axios.defaults.baseURL = 'https://23957.dev.d8dcloud.com'
-
-  const queryClient = new QueryClient()
-
-  return {
-    user,
-    // Import `render` from the framework library of your choice.
-    // See https://testing-library.com/docs/dom-testing-library/install#wrappers
-    ...render(
-      <QueryClientProvider client={queryClient}>
-        <AuthProvider>
-          <App />
-        </AuthProvider>
-      </QueryClientProvider>
-    ),
-  }
-}
-
-// // 使用异步测试处理组件渲染
-// Deno.test({
-//   name: '知识库管理页面基础测试',
-//   fn: async (t) => {
-//     // 存储所有需要清理的定时器
-//     const timers: number[] = [];
-//     const originalSetTimeout = globalThis.setTimeout;
-//     const originalSetInterval = globalThis.setInterval;
-
-//     // 重写定时器方法以跟踪所有创建的定时器
-//     globalThis.setTimeout = ((callback, delay, ...args) => {
-//       const id = originalSetTimeout(callback, delay, ...args);
-//       timers.push(id);
-//       return id;
-//     }) as typeof setTimeout;
-
-//     globalThis.setInterval = ((callback, delay, ...args) => {
-//       const id = originalSetInterval(callback, delay, ...args);
-//       timers.push(id);
-//       return id;
-//     }) as typeof setInterval;
-
-//     // 清理函数
-//     const cleanup = () => {
-//       for (const id of timers) {
-//         clearTimeout(id);
-//         clearInterval(id);
-//       }
-//       // 恢复原始定时器方法
-//       globalThis.setTimeout = originalSetTimeout;
-//       globalThis.setInterval = originalSetInterval;
-//     };
-
-
-
-//     try {
-
-
-//       // // 渲染组件
-//       // const {
-//       //   findByText, findByPlaceholderText, queryByText, 
-//       //   findByRole, findAllByRole, findByLabelText, findAllByText, debug,
-//       //   queryByRole
-
-//       // } = render(
-//       //   <QueryClientProvider client={queryClient}>
-//       //     <AuthProvider>
-//       //       <App />
-//       //     </AuthProvider>
-//       //   </QueryClientProvider>
-//       // );
-
-//       // 测试1: 基本渲染
-//       await t.step('应正确渲染页面元素', async () => {
-//         const { findByText } = setup()
-//         await waitFor(async () => {
-//           const title = await findByText(/知识库管理/i);
-//           assertExists(title, '未找到知识库管理标题');
-//         }, {
-//           timeout: 1000 * 5,
-//         });
-//       });
-
-//       // 初始加载表格数据
-//       await t.step('初始加载表格数据', async () => {
-//         const { findByRole } = setup()
-//         await waitFor(async () => {
-//           const table = await findByRole('table');
-//           const rows = await within(table).findAllByRole('row');
-
-//           // 应该大于2行
-//           assert(rows.length > 2, '表格没有数据'); // 1是表头行 2是数据行
-          
-//         }, {
-//           timeout: 1000 * 5,
-//         });
-//       });
-
-//       // 测试2: 搜索表单功能
-//       await t.step('搜索表单应正常工作', async () => {
-//         const {findByPlaceholderText, findByText, findByRole, user} = setup()
-
-//         // 等待知识库管理标题出现
-//         await waitFor(async () => {
-//           const title = await findByText(/知识库管理/i);
-//           assertExists(title, '未找到知识库管理标题');
-//         }, {
-//           timeout: 1000 * 5,
-//         });
-
-//         // 直接查找标题搜索输入框和搜索按钮
-//         const searchInput = await findByPlaceholderText(/请输入文章标题/i) as HTMLInputElement;
-//         const searchButton = await findByText(/搜 索/i);
-
-//         assertExists(searchInput, '未找到搜索输入框');
-//         assertExists(searchButton, '未找到搜索按钮');
-        
-//         // 输入搜索内容
-//         await user.type(searchInput, '数据分析')
-//         assertEquals(searchInput.value, '数据分析', '搜索输入框值未更新');
-        
-//         // 提交搜索
-//         await user.click(searchButton);
-        
-
-//         let rows: HTMLElement[] = [];
-
-        
-//         const table = await findByRole('table');
-//         assertExists(table, '未找到数据表格');
-
-//         // 等待表格刷新并验证
-//         await waitFor(async () => {
-//           rows = await within(table).findAllByRole('row');
-//           assert(rows.length === 2, '表格未刷新');
-//         }, {
-//           timeout: 1000 * 5,
-//           onTimeout: () => new Error('等待表格刷新超时')
-//         });
-
-//         // 等待搜索结果并验证
-//         await waitFor(async () => {
-//           rows = await within(table).findAllByRole('row');
-//           assert(rows.length > 2, '表格没有数据');
-//         }, {
-//           timeout: 1000 * 5,
-//           onTimeout: () => new Error('等待搜索结果超时')
-//         });
-
-
-        
-//         // 检查至少有一行包含"数据分析"
-//         const matchResults = await Promise.all(rows.map(async row => {
-//           try{
-//             const cells = await within(row).findAllByRole('cell');
-//             return cells.some(cell => {
-//               return cell.textContent?.includes('数据分析')
-//             });
-//           } catch (error: unknown) {
-//             // console.error('搜索结果获取失败', error)
-//             return false
-//           }
-//         }))
-//         // console.log('matchResults', matchResults)
-//         const hasMatch = matchResults.some(result => result);
-
-//         assert(hasMatch, '搜索结果中没有找到包含"数据分析"的文章');
-//       });
-
-//       // 测试3: 表格数据加载
-//       await t.step('表格应加载并显示数据', async () => {
-//         const {findByRole, queryByText} = setup()
-//         // 等待数据加载完成或表格出现,最多等待5秒
-//         await waitFor(async () => {
-//           // 检查加载状态是否消失
-//           const loading = queryByText(/正在加载数据/i);
-//           if (loading) {
-//             throw new Error('数据仍在加载中');
-//           }
-          
-//           // 检查表格是否出现
-//           const table = await findByRole('table');
-//           assertExists(table, '未找到数据表格');
-          
-//           // 检查表格是否有数据行
-//           const rows = await within(table).findAllByRole('row');
-//           assertNotEquals(rows.length, 1, '表格没有数据行'); // 1是表头行
-//         }, {
-//           timeout: 5000, // 5秒超时
-//           onTimeout: (error) => {
-//             return new Error(`数据加载超时: ${error.message}`);
-//           }
-//         });
-//       });
-
-//       // 测试4: 添加文章功能
-//       await t.step('应能打开添加文章模态框', async () => {
-//         const {findByText, findByRole, user} = setup()
-//         // 等待知识库管理标题出现
-//         await waitFor(async () => {
-//           const title = await findByText(/知识库管理/i);
-//           assertExists(title, '未找到知识库管理标题');
-//         }, {
-//           timeout: 1000 * 5,
-//         });
-
-//         const addButton = await findByText(/添加文章/i);
-//         assertExists(addButton, '未找到添加文章按钮');
-
-//         await user.click(addButton);
-
-//         // 找到模态框
-//         const modal = await findByRole('dialog');
-//         assertExists(modal, '未找到模态框');
-        
-//         const modalTitle = await within(modal).findByText(/添加知识库文章/i);
-//         assertExists(modalTitle, '未找到添加文章模态框');
-        
-//         // 验证表单字段
-//         const titleInput = await within(modal).findByPlaceholderText(/请输入文章标题/i);
-//         assertExists(titleInput, '未找到标题输入框');
-
-//         const contentInput = await within(modal).findByPlaceholderText(/请输入文章内容/i);
-//         assertExists(contentInput, '未找到文章内容输入框');
-
-//         // 取消
-//         const cancelButton = await within(modal).findByText(/取 消/i);
-//         assertExists(cancelButton, '未找到取消按钮');
-//         await user.click(cancelButton);
-
-//         // 验证模态框是否关闭
-//         await waitFor(async () => {
-          
-//           const modalTitle = await within(modal).findByText(/添加知识库文章/i);
-//           assertExists(!modalTitle, '模态框未关闭');
-//         }, {
-//           timeout: 1000 * 5,
-//           onTimeout: () => new Error('等待模态框关闭超时')
-//         });
-//       });
-      
-//   } finally {
-//       // 确保清理所有定时器
-//       cleanup();
-//     }
-//   },
-//   sanitizeOps: false, // 禁用操作清理检查
-//   sanitizeResources: false, // 禁用资源清理检查
-// });
-
-Deno.test({
-  name: '知识库管理页面新增测试',
-  fn: async (t) => {
-    // 存储所有需要清理的定时器
-    const timers: number[] = [];
-    const originalSetTimeout = globalThis.setTimeout;
-    const originalSetInterval = globalThis.setInterval;
-
-    // 重写定时器方法以跟踪所有创建的定时器
-    globalThis.setTimeout = ((callback, delay, ...args) => {
-      const id = originalSetTimeout(callback, delay, ...args);
-      timers.push(id);
-      return id;
-    }) as typeof setTimeout;
-
-    globalThis.setInterval = ((callback, delay, ...args) => {
-      const id = originalSetInterval(callback, delay, ...args);
-      timers.push(id);
-      return id;
-    }) as typeof setInterval;
-
-    // 清理函数
-    const cleanup = () => {
-      for (const id of timers) {
-        clearTimeout(id);
-        clearInterval(id);
-      }
-      // 恢复原始定时器方法
-      globalThis.setTimeout = originalSetTimeout;
-      globalThis.setInterval = originalSetInterval;
-    };
-
-
-
-    try {
-
-
-      // // 渲染组件
-      // const {
-      //   findByText, findByPlaceholderText, queryByText, 
-      //   findByRole, findAllByRole, findByLabelText, findAllByText, debug,
-      //   queryByRole
-
-      // } = render(
-      //   <QueryClientProvider client={queryClient}>
-      //     <AuthProvider>
-      //       <App />
-      //     </AuthProvider>
-      //   </QueryClientProvider>
-      // );
-
-
-      // 测试5: 完整添加文章流程
-      await t.step('应能完整添加一篇文章', async () => {
-        const {findByText, findByRole, debug, user} = setup()
-        // 等待知识库管理标题出现
-        await waitFor(async () => {
-          const title = await findByText(/知识库管理/i);
-          assertExists(title, '未找到知识库管理标题');
-        }, {
-          timeout: 1000 * 5,
-        });
-        // 打开添加模态框
-        const addButton = await findByText(/添加文章/i);
-        assertExists(addButton, '未找到添加文章按钮');
-
-        await user.click(addButton);
-
-        // 找到模态框
-        const modal = await findByRole('dialog');
-        assertExists(modal, '未找到模态框');
-
-        const modalTitle = await within(modal).findByText(/添加知识库文章/i);
-        assertExists(modalTitle, '未找到添加文章模态框');
-
-        // 确保上一个测试的输入框值不会影响这个测试
-        // 在模态框内查找标题和内容输入框
-        const titleInput = await within(modal).findByPlaceholderText(/请输入文章标题/i) as HTMLInputElement;
-        const contentInput = await within(modal).findByPlaceholderText(/请输入文章内容/i) as HTMLTextAreaElement;
-        const submitButton = await within(modal).findByText(/确 定/i);
-
-        assertExists(titleInput, '未找到模态框中的标题输入框');
-        assertExists(contentInput, '未找到文章内容输入框');
-        assertExists(submitButton, '未找到提交按钮');
-
-        // 先清空输入框,以防止之前的测试影响
-        // await user.clear(titleInput);
-        // await user.clear(contentInput);
-        
-        // 输入新值
-        await user.type(titleInput, '测试文章标题');
-        await user.type(contentInput, '这是测试文章内容');
-
-        debug(titleInput);
-        debug(contentInput);
-        debug(submitButton);
-
-        // 提交表单
-        await user.click(submitButton);
-
-        // 验证表单字段
-        assertEquals(titleInput.value, '测试文章标题', '模态框中标题输入框值未更新');
-        assertEquals(contentInput.value, '这是测试文章内容', '内容输入框值未更新');
-        
-        let rows: HTMLElement[] = [];
-
-        
-        const table = await findByRole('table');
-        assertExists(table, '未找到数据表格');
-
-        // 等待表格刷新并验证
-        await waitFor(async () => {
-          rows = await within(table).findAllByRole('row');
-          assert(rows.length === 2, '表格未刷新');
-        }, {
-          timeout: 1000 * 5,
-          onTimeout: () => new Error('等待表格刷新超时')
-        });
-
-        // 等待搜索结果并验证
-        await waitFor(async () => {
-          rows = await within(table).findAllByRole('row');
-          assert(rows.length > 2, '表格没有数据');
-        }, {
-          timeout: 1000 * 5,
-          onTimeout: () => new Error('等待搜索结果超时')
-        });
-        
-        // 检查至少有一行包含"测试文章标题"
-        const matchResults = await Promise.all(rows.map(async row => {
-          try{
-            const cells = await within(row).findAllByRole('cell');
-            return cells.some(cell => {
-              return cell.textContent?.includes('测试文章标题')
-            });
-          } catch (error: unknown) {
-            // console.error('搜索结果获取失败', error)
-            return false
-          }
-        }))
-        // console.log('matchResults', matchResults)
-        const hasMatch = matchResults.some(result => result);
-
-        assert(hasMatch, '搜索结果中没有找到包含"测试文章标题"的文章');
-      });
-
-  // // 测试5: 分页功能
-  // await t.step('应显示分页控件', async () => {
-  //   const pagination = await findByRole('navigation');
-  //   assertExists(pagination, '未找到分页控件');
-    
-  //   const pageItems = await findAllByRole('button', { name: /1|2|3|下一页|上一页/i });
-  //   assertNotEquals(pageItems.length, 0, '未找到分页按钮');
-  // });
-
-  // // 测试6: 操作按钮
-  // await t.step('应显示操作按钮', async () => {
-  //   const editButtons = await findAllByText(/编辑/i);
-  //   assertNotEquals(editButtons.length, 0, '未找到编辑按钮');
-    
-  //   const deleteButtons = await findAllByText(/删除/i);
-  //   assertNotEquals(deleteButtons.length, 0, '未找到删除按钮');
-  // });
-    } finally {
-      // 确保清理所有定时器
-      cleanup();
-    }
-  },
-  sanitizeOps: false, // 禁用操作清理检查
-  sanitizeResources: false, // 禁用资源清理检查
-});

+ 2 - 6
client/admin/pages_know_info.tsx

@@ -24,22 +24,19 @@ import weekday from 'dayjs/plugin/weekday';
 import localeData from 'dayjs/plugin/localeData';
 import 'dayjs/locale/zh-cn';
 import type { 
-  FileLibrary, FileCategory, KnowInfo
+  KnowInfo
 } from '../share/types.ts';
 
 import {
    AuditStatus,AuditStatusNameMap,
-   OssType,
 } from '../share/types.ts';
 
 import { getEnumOptions } from './utils.ts';
 
 import {
-  FileAPI,
-  UserAPI,
   KnowInfoAPI,
   type KnowInfoListResponse
-} from './api.ts';
+} from './api/index.ts';
 
 
 // 配置 dayjs 插件
@@ -49,7 +46,6 @@ dayjs.extend(localeData);
 // 设置 dayjs 语言
 dayjs.locale('zh-cn');
 
-const { Title } = Typography;
 
 
 // 知识库管理页面组件

+ 6 - 13
client/admin/pages_map.tsx

@@ -1,19 +1,11 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
 import { 
-  Layout, Menu, Button, Table, Space,
-  Form, Input, Select, message, Modal,
-  Card, Spin, Row, Col, Breadcrumb, Avatar,
-  Dropdown, ConfigProvider, theme, Typography,
-  Switch, Badge, Image, Upload, Divider, Descriptions,
-  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer,
-  Tree
+  Button, Space, Drawer,
+   Select, message, 
+  Card, Spin, Typography,Descriptions,DatePicker, 
 } from 'antd';
 import {
-  MenuFoldOutlined,
-  MenuUnfoldOutlined,
-  AppstoreOutlined,
   EnvironmentOutlined,
-  SearchOutlined,
   ClockCircleOutlined,
   UserOutlined,
   GlobalOutlined
@@ -28,7 +20,8 @@ import type {
    MarkerData, LoginLocation, LoginLocationDetail, User
 } from '../share/types.ts';
 
-import { MapAPI,UserAPI } from './api.ts';
+import { UserAPI } from './api/index.ts';
+import { MapAPI } from './api/index.ts';
 import dayjs from 'dayjs';
 
 const { RangePicker } = DatePicker;

+ 2 - 4
client/admin/pages_messages.tsx

@@ -1,19 +1,17 @@
 import React, { useState } from 'react';
-import { useQuery, useMutation, useQueryClient, UseMutationResult } from '@tanstack/react-query';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
 import { Button, Table, Space, Modal, Form, Input, Select, message } from 'antd';
 import type { TableProps } from 'antd';
 import dayjs from 'dayjs';
 import 'dayjs/locale/zh-cn';
 
-import { MessageAPI } from './api.ts';
-import { UserAPI } from './api.ts';
+import { MessageAPI , UserAPI } from './api/index.ts';
 import type { UserMessage } from '../share/types.ts';
 import { MessageStatusNameMap , MessageStatus} from '../share/types.ts';
 
 export  const MessagesPage = () => {
   const queryClient = useQueryClient();
   const [form] = Form.useForm();
-  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
   const [isModalVisible, setIsModalVisible] = useState(false);
   const [searchParams, setSearchParams] = useState({
     page: 1,

+ 34 - 45
client/admin/pages_settings.tsx

@@ -1,20 +1,14 @@
-import React, { useState, useEffect } from 'react';
+import React, { useEffect } from 'react';
 import { 
-  Layout, Menu, Button, Table, Space,
+  Button,Space,
   Form, Input, Select, message, Modal,
-  Card, Spin, Row, Col, Breadcrumb, Avatar,
-  Dropdown, ConfigProvider, theme, Typography,
-  Switch, Badge, Image, Upload, Divider, Descriptions,
-  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer, InputNumber,ColorPicker,
-  Popover
+  Card, Spin, Typography,
+  Switch, Tabs, Alert, InputNumber
 } from 'antd';
 import {
-  UploadOutlined,
   ReloadOutlined,
   SaveOutlined,
-  BgColorsOutlined
 } from '@ant-design/icons';
-import { debounce } from 'lodash';
 import { 
   useQuery,
   useMutation,
@@ -25,25 +19,20 @@ import weekday from 'dayjs/plugin/weekday';
 import localeData from 'dayjs/plugin/localeData';
 import 'dayjs/locale/zh-cn';
 import type { 
-  FileLibrary, FileCategory, KnowInfo, SystemSetting, SystemSettingValue,
-  ColorScheme
+  SystemSetting, SystemSettingValue
 } from '../share/types.ts';
-import { ThemeMode } from '../share/types.ts';
 
 import {
   SystemSettingGroup,
   SystemSettingKey,
-  FontSize,
-  CompactMode, 
   AllowedFileType
 } from '../share/types.ts';
 
 
-import { getEnumOptions } from './utils.ts';
 
 import {
-  SystemAPI,
-} from './api.ts';
+  SystemAPI
+} from './api/index.ts';
 
 import { useTheme } from './hooks_sys.tsx';
 
@@ -241,44 +230,44 @@ export const SettingsPage = () => {
             items={Object.values(SystemSettingGroup).map(group => ({
               key: group,
               label: String(GROUP_TITLES[group]),
-              children: (
-                <div>
-                  <Alert
+                children: (
+                  <div>
+                    <Alert
                     message={GROUP_DESCRIPTIONS[group]}
-                    type="info"
-                    showIcon
-                    style={{ marginBottom: 24 }}
-                  />
-                  <Form
-                    form={form}
-                    layout="vertical"
-                    onFinish={handleSubmit}
-                  >
+                      type="info"
+                      showIcon
+                      style={{ marginBottom: 24 }}
+                    />
+                    <Form
+                      form={form}
+                      layout="vertical"
+                      onFinish={handleSubmit}
+                    >
                     {settingsData
                       ?.find(g => g.name === group)
                       ?.settings.map(setting => (
-                        <Form.Item
+                      <Form.Item
                           key={setting.key}
                           label={setting.description || setting.key}
                           name={setting.key}
                           rules={[{ required: true, message: `请输入${setting.description || setting.key}` }]}
                         >
                           {renderSettingInput(setting)}
-                        </Form.Item>
+                      </Form.Item>
                       ))}
-                    <Form.Item>
-                      <Button 
-                        type="primary" 
-                        htmlType="submit"
-                        icon={<SaveOutlined />}
-                        loading={updateSettingsMutation.isPending}
-                      >
-                        保存设置
-                      </Button>
-                    </Form.Item>
-                  </Form>
-                </div>
-              )
+                      <Form.Item>
+                        <Button
+                          type="primary"
+                          htmlType="submit"
+                          icon={<SaveOutlined />}
+                          loading={updateSettingsMutation.isPending}
+                        >
+                          保存设置
+                        </Button>
+                      </Form.Item>
+                    </Form>
+                  </div>
+                )
             }))}
           />
         </Spin>

+ 0 - 115
client/admin/pages_theme_setting.test.tsx

@@ -1,115 +0,0 @@
-import { JSDOM } from 'jsdom'
-import React from 'react'
-import {render, fireEvent, within, screen} from '@testing-library/react'
-import { ThemeSettingsPage } from "./pages_theme_settings.tsx"
-import { ThemeProvider } from "./hooks_sys.tsx"
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import {
-  assertEquals,
-  assertExists,
-  assertNotEquals,
-  assertRejects,
-} from "https://deno.land/std@0.217.0/assert/mod.ts";
-
-const queryClient = new QueryClient()
-
-
-const dom = new JSDOM(`<body></body>`, { 
-  runScripts: "dangerously",
-  pretendToBeVisual: true,
-  url: "http://localhost"
-});
-
-// 模拟浏览器环境
-globalThis.window = dom.window;
-globalThis.document = dom.window.document;
-
-// 定义浏览器环境所需的类
-globalThis.Element = dom.window.Element;
-globalThis.HTMLElement = dom.window.HTMLElement;
-globalThis.ShadowRoot = dom.window.ShadowRoot;
-globalThis.SVGElement = dom.window.SVGElement;
-
-// 模拟 getComputedStyle
-globalThis.getComputedStyle = (elt) => {
-  const style = new dom.window.CSSStyleDeclaration();
-  style.getPropertyValue = () => '';
-  return style;
-};
-
-// 模拟matchMedia函数
-globalThis.matchMedia = (query) => ({
-  matches: query.includes('max-width'),
-  media: query,
-  onchange: null,
-  addListener: () => {},
-  removeListener: () => {},
-  addEventListener: () => {},
-  removeEventListener: () => {},
-  dispatchEvent: () => false,
-});
-
-// 模拟动画相关API
-globalThis.AnimationEvent = globalThis.AnimationEvent || dom.window.Event;
-globalThis.TransitionEvent = globalThis.TransitionEvent || dom.window.Event;
-
-// 模拟requestAnimationFrame
-globalThis.requestAnimationFrame = globalThis.requestAnimationFrame || ((cb) => setTimeout(cb, 0));
-globalThis.cancelAnimationFrame = globalThis.cancelAnimationFrame || clearTimeout;
-
-// 设置浏览器尺寸相关方法
-window.resizeTo = (width, height) => {
-  window.innerWidth = width || window.innerWidth;
-  window.innerHeight = height || window.innerHeight;
-  window.dispatchEvent(new Event('resize'));
-};
-window.scrollTo = () => {};
-
-const customScreen = within(document.body);
-
-// 使用异步测试处理真实API调用
-Deno.test('主题设置页面测试', async (t) => {
-  // 渲染组件
-  const {findByRole, debug} = render(
-    <QueryClientProvider client={queryClient}>
-      <ThemeProvider>
-        <ThemeSettingsPage />
-      </ThemeProvider>
-    </QueryClientProvider>
-  )
-
-  // debug(await findByRole('radio', { name: /浅色模式/i }))
-
-  // 测试1: 渲染基本元素
-  await t.step('应渲染主题设置标题', async () => {
-    const title = await customScreen.findByText(/主题设置/i)
-    assertExists(title, '未找到主题设置标题')
-  })
-
-  // 测试2: 表单初始化状态
-  await t.step('表单应正确初始化', async () => {
-    // 检查主题模式选择
-    const lightRadio = await customScreen.findByRole('radio', { name: /浅色模式/i })
-    assertExists(lightRadio, '未找到浅色模式单选按钮')
-    
-    // 检查主题模式标签
-    const themeModeLabel = await customScreen.findByText(/主题模式/i)
-    assertExists(themeModeLabel, '未找到主题模式标签')
-    
-    // // 检查主题模式选择器 - Ant Design 使用 div 包裹 radio 而不是 radiogroup
-    // const themeModeField = await customScreen.findByTestId('theme-mode-selector')
-    // assertExists(themeModeField, '未找到主题模式选择器')
-  })
-
-  // 测试3: 配色方案选择
-  await t.step('应显示配色方案选项', async () => {
-    // 查找预设配色方案标签
-    const colorSchemeLabel = await customScreen.findByText('预设配色方案')
-    assertExists(colorSchemeLabel, '未找到预设配色方案标签')
-    
-    // 查找配色方案按钮
-    const colorSchemeButtons = await customScreen.findAllByRole('button')
-    assertNotEquals(colorSchemeButtons.length, 0, '未找到配色方案按钮')
-  })
-})
-

+ 5 - 28
client/admin/pages_theme_settings.tsx

@@ -1,53 +1,30 @@
 import React, { useState, useEffect } from 'react';
 import { 
-  Layout, Menu, Button, Table, Space,
-  Form, Input, Select, message, Modal,
-  Card, Spin, Row, Col, Breadcrumb, Avatar,
-  Dropdown, ConfigProvider, theme, Typography,
-  Switch, Badge, Image, Upload, Divider, Descriptions,
-  Popconfirm, Tag, Statistic, DatePicker, Radio, Progress, Tabs, List, Alert, Collapse, Empty, Drawer, InputNumber,ColorPicker,
-  Popover
+  Button, Space,
+  Form, message, 
+  Card, Spin, Typography,
+  Switch, 
+  Popconfirm, Radio, InputNumber,ColorPicker,
 } from 'antd';
-import {
-  UploadOutlined,
-  ReloadOutlined,
-  SaveOutlined,
-  BgColorsOutlined
-} from '@ant-design/icons';
-import { debounce } from 'lodash';
-import { 
-  useQuery,
-  useMutation,
-  useQueryClient,
-} from '@tanstack/react-query';
 import dayjs from 'dayjs';
 import weekday from 'dayjs/plugin/weekday';
 import localeData from 'dayjs/plugin/localeData';
 import 'dayjs/locale/zh-cn';
 import type { 
-  FileLibrary, FileCategory, KnowInfo, SystemSetting, SystemSettingValue,
   ColorScheme
 } from '../share/types.ts';
 import { ThemeMode } from '../share/types.ts';
 
 import {
-  SystemSettingGroup,
-  SystemSettingKey,
   FontSize,
   CompactMode, 
-  AllowedFileType
 } from '../share/types.ts';
 
 
-import { getEnumOptions } from './utils.ts';
 
-import {
-  SystemAPI,
-} from './api.ts';
 
 import { useTheme } from './hooks_sys.tsx';
 
-import { Uploader } from './components_uploader.tsx';
 
 // 配置 dayjs 插件
 dayjs.extend(weekday);

+ 270 - 0
client/admin/pages_users.tsx

@@ -0,0 +1,270 @@
+import React, { useState } from 'react';
+import { 
+  Button, Table, Space, Form, Input, Select, 
+  message, Modal, Card, Typography, Tag, Popconfirm 
+} from 'antd';
+import { useQuery } from '@tanstack/react-query';
+import dayjs from 'dayjs';
+import { UserAPI } from './api/index.ts';
+
+const { Title } = Typography;
+
+// 用户管理页面
+export const UsersPage = () => {
+  const [searchParams, setSearchParams] = useState({
+    page: 1,
+    limit: 10,
+    search: ''
+  });
+  const [modalVisible, setModalVisible] = useState(false);
+  const [modalTitle, setModalTitle] = useState('');
+  const [editingUser, setEditingUser] = useState<any>(null);
+  const [form] = Form.useForm();
+
+  const { data: usersData, isLoading, refetch } = useQuery({
+    queryKey: ['users', searchParams],
+    queryFn: async () => {
+      return await UserAPI.getUsers(searchParams);
+    }
+  });
+
+  const users = usersData?.data || [];
+  const pagination = {
+    current: searchParams.page,
+    pageSize: searchParams.limit,
+    total: usersData?.pagination?.total || 0
+  };
+
+  // 处理搜索
+  const handleSearch = (values: any) => {
+    setSearchParams(prev => ({
+      ...prev,
+      search: values.search || '',
+      page: 1
+    }));
+  };
+
+  // 处理分页变化
+  const handleTableChange = (newPagination: any) => {
+    setSearchParams(prev => ({
+      ...prev,
+      page: newPagination.current,
+      limit: newPagination.pageSize
+    }));
+  };
+
+  // 打开创建用户模态框
+  const showCreateModal = () => {
+    setModalTitle('创建用户');
+    setEditingUser(null);
+    form.resetFields();
+    setModalVisible(true);
+  };
+
+  // 打开编辑用户模态框
+  const showEditModal = (user: any) => {
+    setModalTitle('编辑用户');
+    setEditingUser(user);
+    form.setFieldsValue(user);
+    setModalVisible(true);
+  };
+
+  // 处理模态框确认
+  const handleModalOk = async () => {
+    try {
+      const values = await form.validateFields();
+      
+      if (editingUser) {
+        // 编辑用户
+        await UserAPI.updateUser(editingUser.id, values);
+        message.success('用户更新成功');
+      } else {
+        // 创建用户
+        await UserAPI.createUser(values);
+        message.success('用户创建成功');
+      }
+      
+      setModalVisible(false);
+      form.resetFields();
+      refetch(); // 刷新用户列表
+    } catch (error) {
+      console.error('表单提交失败:', error);
+      message.error('操作失败,请重试');
+    }
+  };
+
+  // 处理删除用户
+  const handleDelete = async (id: number) => {
+    try {
+      await UserAPI.deleteUser(id);
+      message.success('用户删除成功');
+      refetch(); // 刷新用户列表
+    } catch (error) {
+      console.error('删除用户失败:', error);
+      message.error('删除失败,请重试');
+    }
+  };
+  
+  const columns = [
+    {
+      title: '用户名',
+      dataIndex: 'username',
+      key: 'username',
+    },
+    {
+      title: '昵称',
+      dataIndex: 'nickname',
+      key: 'nickname',
+    },
+    {
+      title: '邮箱',
+      dataIndex: 'email',
+      key: 'email',
+    },
+    {
+      title: '角色',
+      dataIndex: 'role',
+      key: 'role',
+      render: (role: string) => (
+        <Tag color={role === 'admin' ? 'red' : 'blue'}>
+          {role === 'admin' ? '管理员' : '普通用户'}
+        </Tag>
+      ),
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'created_at',
+      key: 'created_at',
+      render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render: (_: any, record: any) => (
+        <Space size="middle">
+          <Button type="link" onClick={() => showEditModal(record)}>
+            编辑
+          </Button>
+          <Popconfirm
+            title="确定要删除此用户吗?"
+            onConfirm={() => handleDelete(record.id)}
+            okText="确定"
+            cancelText="取消"
+          >
+            <Button type="link" danger>
+              删除
+            </Button>
+          </Popconfirm>
+        </Space>
+      ),
+    },
+  ];
+  
+  return (
+    <div>
+      <Title level={2}>用户管理</Title>
+      <Card>
+        <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16 }}>
+          <Form.Item name="search" label="搜索">
+            <Input placeholder="用户名/昵称/邮箱" allowClear />
+          </Form.Item>
+          <Form.Item>
+            <Space>
+              <Button type="primary" htmlType="submit">
+                搜索
+              </Button>
+              <Button type="primary" onClick={showCreateModal}>
+                创建用户
+              </Button>
+            </Space>
+          </Form.Item>
+        </Form>
+
+        <Table
+          columns={columns}
+          dataSource={users}
+          loading={isLoading}
+          rowKey="id"
+          pagination={{
+            ...pagination,
+            showSizeChanger: true,
+            showQuickJumper: true,
+            showTotal: (total) => `共 ${total} 条记录`
+          }}
+          onChange={handleTableChange}
+        />
+      </Card>
+
+      {/* 创建/编辑用户模态框 */}
+      <Modal
+        title={modalTitle}
+        open={modalVisible}
+        onOk={handleModalOk}
+        onCancel={() => {
+          setModalVisible(false);
+          form.resetFields();
+        }}
+        width={600}
+      >
+        <Form
+          form={form}
+          layout="vertical"
+        >
+          <Form.Item
+            name="username"
+            label="用户名"
+            rules={[
+              { required: true, message: '请输入用户名' },
+              { min: 3, message: '用户名至少3个字符' }
+            ]}
+          >
+            <Input placeholder="请输入用户名" />
+          </Form.Item>
+
+          <Form.Item
+            name="nickname"
+            label="昵称"
+            rules={[{ required: true, message: '请输入昵称' }]}
+          >
+            <Input placeholder="请输入昵称" />
+          </Form.Item>
+
+          <Form.Item
+            name="email"
+            label="邮箱"
+            rules={[
+              { required: true, message: '请输入邮箱' },
+              { type: 'email', message: '请输入有效的邮箱地址' }
+            ]}
+          >
+            <Input placeholder="请输入邮箱" />
+          </Form.Item>
+
+          {!editingUser && (
+            <Form.Item
+              name="password"
+              label="密码"
+              rules={[
+                { required: true, message: '请输入密码' },
+                { min: 6, message: '密码至少6个字符' }
+              ]}
+            >
+              <Input.Password placeholder="请输入密码" />
+            </Form.Item>
+          )}
+
+          <Form.Item
+            name="role"
+            label="角色"
+            rules={[{ required: true, message: '请选择角色' }]}
+          >
+            <Select placeholder="请选择角色">
+              <Select.Option value="user">普通用户</Select.Option>
+              <Select.Option value="admin">管理员</Select.Option>
+            </Select>
+          </Form.Item>
+        </Form>
+      </Modal>
+    </div>
+  );
+};

+ 7 - 3
client/admin/web_app.tsx

@@ -55,10 +55,14 @@ import {
 } from './hooks_sys.tsx';
 
 import {
-  DashboardPage,
-  UsersPage,
+  DashboardPage
+} from './pages_dashboard.tsx';
+import {
+  UsersPage
+} from './pages_users.tsx';
+import {
   FileLibraryPage
-} from './pages_sys.tsx';
+} from './pages_file_library.tsx';
 import { KnowInfoPage } from './pages_know_info.tsx';
 import { MessagesPage } from './pages_messages.tsx';
 import {SettingsPage } from './pages_settings.tsx';