diff --git a/HISTORY.md b/HISTORY.md index d16fc32..692f97c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,5 +3,6 @@ 迁移管理页面,在正式环境中,需要验证env中配置的密码参数才能打开 2025.05.13 0.1.0 -首页添加了迁移管理入口按钮, 无需登录即可访问 -打开迁移管理页面时,将迁移历史读取出来 \ No newline at end of file +将admin api.ts 拆开 +打开迁移管理页面时,将迁移历史读取出来 +首页添加了迁移管理入口按钮, 无需登录即可访问 \ No newline at end of file diff --git a/client/admin/api.ts b/client/admin/api.ts deleted file mode 100644 index 852a365..0000000 --- a/client/admin/api.ts +++ /dev/null @@ -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; - register: (username: string, email: string, password: string) => Promise; - logout: () => Promise; - getCurrentUser: () => Promise; - updateUser: (userId: number, userData: Partial) => Promise; - changePassword: (oldPassword: string, newPassword: string) => Promise; - requestPasswordReset: (email: string) => Promise; - resetPassword: (token: string, newPassword: string) => Promise; -} - - -// 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) => { - 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 => { - try { - const response = await axios.get(`${API_BASE_URL}/users`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个用户详情 - getUser: async (userId: number): Promise => { - try { - const response = await axios.get(`${API_BASE_URL}/users/${userId}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建用户 - createUser: async (userData: Partial): Promise => { - try { - const response = await axios.post(`${API_BASE_URL}/users`, userData); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新用户信息 - updateUser: async (userId: number, userData: Partial): Promise => { - try { - const response = await axios.put(`${API_BASE_URL}/users/${userId}`, userData); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除用户 - deleteUser: async (userId: number): Promise => { - 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 => { - 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): Promise => { - 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 => { - try { - const response = await axios.get(`${API_BASE_URL}/upload/list`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个文件信息 - getFileInfo: async (id: number): Promise => { - try { - const response = await axios.get(`${API_BASE_URL}/upload/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 更新文件下载计数 - updateDownloadCount: async (id: number): Promise => { - try { - const response = await axios.post(`${API_BASE_URL}/upload/${id}/download`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除文件 - deleteFile: async (id: number): Promise => { - 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 => { - try { - const response = await axios.get(`${API_BASE_URL}/file-categories`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建文件分类 - createCategory: async (data: Partial): Promise => { - 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): Promise => { - 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 => { - 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 => { - try { - const response = await axios.get(`${API_BASE_URL}/theme`); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 更新主题设置 - updateThemeSettings: async (themeData: Partial): Promise => { - try { - const response = await axios.put(`${API_BASE_URL}/theme`, themeData); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 重置主题设置 - resetThemeSettings: async (): Promise => { - try { - const response = await axios.post(`${API_BASE_URL}/theme/reset`); - return response.data.data; - } catch (error) { - throw error; - } - } -}; - -// 图表数据API接口类型 -interface ChartDataResponse { - 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> => { - try { - const response = await axios.get(`${API_BASE_URL}/charts/user-activity`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取文件上传统计数据 - getFileUploads: async (): Promise> => { - try { - const response = await axios.get(`${API_BASE_URL}/charts/file-uploads`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取文件类型分布数据 - getFileTypes: async (): Promise> => { - try { - const response = await axios.get(`${API_BASE_URL}/charts/file-types`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取仪表盘概览数据 - getDashboardOverview: async (): Promise> => { - 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 => { - 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 => { - try { - const response = await axios.post(`${API_BASE_URL}/messages`, data); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取未读消息数 - getUnreadCount: async (): Promise => { - try { - const response = await axios.get(`${API_BASE_URL}/messages/count/unread`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 标记消息为已读 - markAsRead: async (id: number): Promise => { - try { - const response = await axios.post(`${API_BASE_URL}/messages/${id}/read`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 删除消息 - deleteMessage: async (id: number): Promise => { - 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 => { - try { - const response = await axios.get(`${API_BASE_URL}/map/markers`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取登录位置详情 - getLocationDetail: async (locationId: number): Promise => { - 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 => { - 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 => { - try { - const response = await axios.get(`${API_BASE_URL}/know-infos`, { params }); - return response.data; - } catch (error) { - throw error; - } - }, - - // 获取单个知识详情 - getKnowInfo: async (id: number): Promise => { - try { - const response = await axios.get(`${API_BASE_URL}/know-infos/${id}`); - return response.data; - } catch (error) { - throw error; - } - }, - - // 创建知识 - createKnowInfo: async (data: Partial): Promise => { - 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): Promise => { - 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 => { - 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 => { - try { - const response = await axios.get(`${API_BASE_URL}/settings`); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 获取指定分组的系统设置 - getSettingsByGroup: async (group: string): Promise => { - try { - const response = await axios.get(`${API_BASE_URL}/settings/group/${group}`); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 更新系统设置 - updateSettings: async (settings: Partial[]): Promise => { - try { - const response = await axios.put(`${API_BASE_URL}/settings`, settings); - return response.data.data; - } catch (error) { - throw error; - } - }, - - // 重置系统设置 - resetSettings: async (): Promise => { - try { - const response = await axios.post(`${API_BASE_URL}/settings/reset`); - return response.data.data; - } catch (error) { - throw error; - } - } -}; - diff --git a/client/admin/api/auth.ts b/client/admin/api/auth.ts new file mode 100644 index 0000000..cc46b82 --- /dev/null +++ b/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; + register: (username: string, email: string, password: string) => Promise; + logout: () => Promise; + getCurrentUser: () => Promise; + updateUser: (userId: number, userData: Partial) => Promise; + changePassword: (oldPassword: string, newPassword: string) => Promise; + requestPasswordReset: (email: string) => Promise; + resetPassword: (token: string, newPassword: string) => Promise; +} + +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) => { + 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; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/charts.ts b/client/admin/api/charts.ts new file mode 100644 index 0000000..64c185e --- /dev/null +++ b/client/admin/api/charts.ts @@ -0,0 +1,66 @@ +import axios from 'axios'; + +interface ChartDataResponse { + 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> => { + try { + const response = await axios.get('/charts/user-activity'); + return response.data; + } catch (error) { + throw error; + } + }, + + getFileUploads: async (): Promise> => { + try { + const response = await axios.get('/charts/file-uploads'); + return response.data; + } catch (error) { + throw error; + } + }, + + getFileTypes: async (): Promise> => { + try { + const response = await axios.get('/charts/file-types'); + return response.data; + } catch (error) { + throw error; + } + }, + + getDashboardOverview: async (): Promise> => { + try { + const response = await axios.get('/charts/dashboard-overview'); + return response.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/files.ts b/client/admin/api/files.ts new file mode 100644 index 0000000..a0ccb2a --- /dev/null +++ b/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 => { + try { + const response = await axios.get('/upload/policy', { + params: { filename, prefix, maxSize } + }); + return response.data; + } catch (error) { + throw error; + } + }, + + saveFileInfo: async (fileData: Partial): Promise => { + 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 => { + try { + const response = await axios.get('/upload/list', { params }); + return response.data; + } catch (error) { + throw error; + } + }, + + getFileInfo: async (id: number): Promise => { + try { + const response = await axios.get(`/upload/${id}`); + return response.data; + } catch (error) { + throw error; + } + }, + + updateDownloadCount: async (id: number): Promise => { + try { + const response = await axios.post(`/upload/${id}/download`); + return response.data; + } catch (error) { + throw error; + } + }, + + deleteFile: async (id: number): Promise => { + 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 => { + try { + const response = await axios.get('/file-categories', { params }); + return response.data; + } catch (error) { + throw error; + } + }, + + createCategory: async (data: Partial): Promise => { + try { + const response = await axios.post('/file-categories', data); + return response.data; + } catch (error) { + throw error; + } + }, + + updateCategory: async (id: number, data: Partial): Promise => { + try { + const response = await axios.put(`/file-categories/${id}`, data); + return response.data; + } catch (error) { + throw error; + } + }, + + deleteCategory: async (id: number): Promise => { + try { + const response = await axios.delete(`/file-categories/${id}`); + return response.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/index.ts b/client/admin/api/index.ts new file mode 100644 index 0000000..2ac6ce7 --- /dev/null +++ b/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'; \ No newline at end of file diff --git a/client/admin/api/know_info.ts b/client/admin/api/know_info.ts new file mode 100644 index 0000000..50ae3fd --- /dev/null +++ b/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 => { + try { + const response = await axios.get('/know-infos', { params }); + return response.data; + } catch (error) { + throw error; + } + }, + + // 获取单个知识详情 + getKnowInfo: async (id: number): Promise => { + try { + const response = await axios.get(`/know-infos/${id}`); + return response.data; + } catch (error) { + throw error; + } + }, + + // 创建知识 + createKnowInfo: async (data: Partial): Promise => { + try { + const response = await axios.post('/know-infos', data); + return response.data; + } catch (error) { + throw error; + } + }, + + // 更新知识 + updateKnowInfo: async (id: number, data: Partial): Promise => { + try { + const response = await axios.put(`/know-infos/${id}`, data); + return response.data; + } catch (error) { + throw error; + } + }, + + // 删除知识 + deleteKnowInfo: async (id: number): Promise => { + try { + const response = await axios.delete(`/know-infos/${id}`); + return response.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/maps.ts b/client/admin/api/maps.ts new file mode 100644 index 0000000..36e6e3a --- /dev/null +++ b/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 => { + try { + const response = await axios.get(`${API_BASE_URL}/map/markers`, { params }); + return response.data; + } catch (error) { + throw error; + } + }, + + // 获取登录位置详情 + getLocationDetail: async (locationId: number): Promise => { + 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 => { + try { + const response = await axios.put(`${API_BASE_URL}/map/location/${locationId}`, data); + return response.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/messages.ts b/client/admin/api/messages.ts new file mode 100644 index 0000000..bc633cb --- /dev/null +++ b/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 => { + 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 => { + try { + const response = await axios.post('/messages', data); + return response.data; + } catch (error) { + throw error; + } + }, + + getUnreadCount: async (): Promise => { + try { + const response = await axios.get('/messages/count/unread'); + return response.data; + } catch (error) { + throw error; + } + }, + + markAsRead: async (id: number): Promise => { + try { + const response = await axios.post(`/messages/${id}/read`); + return response.data; + } catch (error) { + throw error; + } + }, + + deleteMessage: async (id: number): Promise => { + try { + const response = await axios.delete(`/messages/${id}`); + return response.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/sys.ts b/client/admin/api/sys.ts new file mode 100644 index 0000000..6130a7e --- /dev/null +++ b/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 => { + try { + const response = await axios.get('/settings'); + return response.data.data; + } catch (error) { + throw error; + } + }, + + // 获取指定分组的系统设置 + getSettingsByGroup: async (group: string): Promise => { + try { + const response = await axios.get(`/settings/group/${group}`); + return response.data.data; + } catch (error) { + throw error; + } + }, + + // 更新系统设置 + updateSettings: async (settings: Partial[]): Promise => { + try { + const response = await axios.put('/settings', settings); + return response.data.data; + } catch (error) { + throw error; + } + }, + + // 重置系统设置 + resetSettings: async (): Promise => { + try { + const response = await axios.post('/settings/reset'); + return response.data.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/theme.ts b/client/admin/api/theme.ts new file mode 100644 index 0000000..ad9c189 --- /dev/null +++ b/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 => { + try { + const response = await axios.get('/theme'); + return response.data.data; + } catch (error) { + throw error; + } + }, + + updateThemeSettings: async (themeData: Partial): Promise => { + try { + const response = await axios.put('/theme', themeData); + return response.data.data; + } catch (error) { + throw error; + } + }, + + resetThemeSettings: async (): Promise => { + try { + const response = await axios.post('/theme/reset'); + return response.data.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/api/users.ts b/client/admin/api/users.ts new file mode 100644 index 0000000..052f796 --- /dev/null +++ b/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 => { + try { + const response = await axios.get('/users', { params }); + return response.data; + } catch (error) { + throw error; + } + }, + + getUser: async (userId: number): Promise => { + try { + const response = await axios.get(`/users/${userId}`); + return response.data; + } catch (error) { + throw error; + } + }, + + createUser: async (userData: Partial): Promise => { + try { + const response = await axios.post('/users', userData); + return response.data; + } catch (error) { + throw error; + } + }, + + updateUser: async (userId: number, userData: Partial): Promise => { + try { + const response = await axios.put(`/users/${userId}`, userData); + return response.data; + } catch (error) { + throw error; + } + }, + + deleteUser: async (userId: number): Promise => { + try { + const response = await axios.delete(`/users/${userId}`); + return response.data; + } catch (error) { + throw error; + } + } +}; \ No newline at end of file diff --git a/client/admin/components_uploader.tsx b/client/admin/components_uploader.tsx index bf9785e..0671b05 100644 --- a/client/admin/components_uploader.tsx +++ b/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 = ({ diff --git a/client/admin/hooks_sys.tsx b/client/admin/hooks_sys.tsx index c0245f9..a222c66 100644 --- a/client/admin/hooks_sys.tsx +++ b/client/admin/hooks_sys.tsx @@ -24,7 +24,7 @@ import { import { AuthAPI, ThemeAPI -} from './api.ts'; +} from './api/index.ts'; // 配置 dayjs 插件 diff --git a/client/admin/pages_chart.tsx b/client/admin/pages_chart.tsx index 5de33e0..ac084e2 100644 --- a/client/admin/pages_chart.tsx +++ b/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>; - title: string; -} + // 用户活跃度图表组件 const UserActivityChart: React.FC = () => { @@ -49,7 +41,7 @@ const UserActivityChart: React.FC = () => { }; return ( - + ); @@ -92,7 +84,7 @@ const FileUploadsChart: React.FC = () => { }; return ( - + ); @@ -130,7 +122,7 @@ const FileTypesChart: React.FC = () => { }; return ( - + ); @@ -151,7 +143,7 @@ const DashboardOverview: React.FC = () => { return ( - + { - + { - + { - + { + return ( +
+ 仪表盘 + + + + + + + + + + + + + + + + + +
+ ); +}; \ No newline at end of file diff --git a/client/admin/pages_sys.tsx b/client/admin/pages_file_library.tsx similarity index 70% rename from client/admin/pages_sys.tsx rename to client/admin/pages_file_library.tsx index 64cad76..81fe93f 100644 --- a/client/admin/pages_sys.tsx +++ b/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 ( -
- 仪表盘 - - - - - - - - - - - - - - - - - -
- ); -}; - -// 用户管理页面 -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(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) => ( - - {role === 'admin' ? '管理员' : '普通用户'} - - ), - }, - { - 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) => ( - - - handleDelete(record.id)} - okText="确定" - cancelText="取消" - > - - - - ), - }, - ]; - - return ( -
- 用户管理 - -
- - - - - - - - - -
- - `共 ${total} 条记录` - }} - onChange={handleTableChange} - /> - - - {/* 创建/编辑用户模态框 */} - { - setModalVisible(false); - form.resetFields(); - }} - width={600} - > -
- - - - - - - - - - - - - {!editingUser && ( - - - - )} - - - - - -
- - ); -}; - // 文件库管理页面 export const FileLibraryPage = () => { const [loading, setLoading] = useState(false); diff --git a/client/admin/pages_know_info.test.tsx b/client/admin/pages_know_info.test.tsx deleted file mode 100644 index 9f76896..0000000 --- a/client/admin/pages_know_info.test.tsx +++ /dev/null @@ -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: ( - - - - ) - }, - ]); - return -}; -// setup function -function setup() { - - const dom = new JSDOM(``, { - 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( - - - - - - ), - } -} - -// // 使用异步测试处理组件渲染 -// 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( -// // -// // -// // -// // -// // -// // ); - -// // 测试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( - // - // - // - // - // - // ); - - - // 测试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, // 禁用资源清理检查 -}); \ No newline at end of file diff --git a/client/admin/pages_know_info.tsx b/client/admin/pages_know_info.tsx index ed27972..124936e 100644 --- a/client/admin/pages_know_info.tsx +++ b/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; // 知识库管理页面组件 diff --git a/client/admin/pages_map.tsx b/client/admin/pages_map.tsx index 6d13eee..e65f217 100644 --- a/client/admin/pages_map.tsx +++ b/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; diff --git a/client/admin/pages_messages.tsx b/client/admin/pages_messages.tsx index 3dc7c56..0e8cdf3 100644 --- a/client/admin/pages_messages.tsx +++ b/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([]); const [isModalVisible, setIsModalVisible] = useState(false); const [searchParams, setSearchParams] = useState({ page: 1, diff --git a/client/admin/pages_settings.tsx b/client/admin/pages_settings.tsx index dfa3061..d86a4b4 100644 --- a/client/admin/pages_settings.tsx +++ b/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: ( -
- + -
+ type="info" + showIcon + style={{ marginBottom: 24 }} + /> + {settingsData ?.find(g => g.name === group) ?.settings.map(setting => ( - {renderSettingInput(setting)} - + ))} - - - - -
- ) + + + + + + ) }))} /> diff --git a/client/admin/pages_theme_setting.test.tsx b/client/admin/pages_theme_setting.test.tsx deleted file mode 100644 index 3ed0d48..0000000 --- a/client/admin/pages_theme_setting.test.tsx +++ /dev/null @@ -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(``, { - 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( - - - - - - ) - - // 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, '未找到配色方案按钮') - }) -}) - diff --git a/client/admin/pages_theme_settings.tsx b/client/admin/pages_theme_settings.tsx index 613d10d..a6f6e12 100644 --- a/client/admin/pages_theme_settings.tsx +++ b/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); diff --git a/client/admin/pages_users.tsx b/client/admin/pages_users.tsx new file mode 100644 index 0000000..4040f81 --- /dev/null +++ b/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(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) => ( + + {role === 'admin' ? '管理员' : '普通用户'} + + ), + }, + { + 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) => ( + + + handleDelete(record.id)} + okText="确定" + cancelText="取消" + > + + + + ), + }, + ]; + + return ( +
+ 用户管理 + +
+ + + + + + + + + + + +
`共 ${total} 条记录` + }} + onChange={handleTableChange} + /> + + + {/* 创建/编辑用户模态框 */} + { + setModalVisible(false); + form.resetFields(); + }} + width={600} + > +
+ + + + + + + + + + + + + {!editingUser && ( + + + + )} + + + + + +
+ + ); +}; \ No newline at end of file diff --git a/client/admin/web_app.tsx b/client/admin/web_app.tsx index 1726e7b..f89e5d5 100644 --- a/client/admin/web_app.tsx +++ b/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';