diff --git a/frontend/src/api/user.ts b/frontend/src/api/user.ts deleted file mode 100644 index 040e4212..00000000 --- a/frontend/src/api/user.ts +++ /dev/null @@ -1,47 +0,0 @@ -import request from '@/utils/request'; -import type { MenuDTO } from '@/pages/System/Menu/types'; -import type { UserDTO } from '@/pages/System/User/types'; - -export interface LoginParams { - username: string; - password: string; -} - -export interface LoginResult { - token: string; - userId: number; - username: string; - nickname: string; -} - -export const login = async (data: LoginParams): Promise => { - return request.post('/api/v1/users/login', data); -}; - -export const logout = async (): Promise => { - return request.post('/api/auth/logout'); -}; - -export const getUserMenus = async (): Promise => { - return request.get('/api/v1/menu/user'); -}; - -export const getUsers = async (params: any) => { - return request.get('/api/system/users', { params }); -}; - -export const createUser = async (data: any) => { - return request.post('/api/system/users', data); -}; - -export const updateUser = async (id: number, data: any) => { - return request.put(`/api/system/users/${id}`, data); -}; - -export const deleteUser = async (id: number) => { - return request.delete(`/api/system/users/${id}`); -}; - -export const resetPassword = async (id: number) => { - return request.post(`/api/system/users/${id}/reset-password`); -}; \ No newline at end of file diff --git a/frontend/src/layouts/BasicLayout.tsx b/frontend/src/layouts/BasicLayout.tsx index dbcb7b17..903cbf10 100644 --- a/frontend/src/layouts/BasicLayout.tsx +++ b/frontend/src/layouts/BasicLayout.tsx @@ -11,9 +11,9 @@ import { CloudOutlined } from '@ant-design/icons'; import * as AntdIcons from '@ant-design/icons'; -import { logout, setMenus } from '../store/userSlice'; +import { logout, setMenus, setUserInfo } from '../store/userSlice'; import type { MenuProps } from 'antd'; -import { getUserMenus } from '../api/user'; +import { getCurrentUser, getUserMenus } from '../pages/Login/service'; import { getWeather } from '../services/weather'; import type { MenuDTO } from '../pages/System/Menu/types'; import type { RootState } from '../store'; @@ -35,6 +35,7 @@ const BasicLayout: React.FC = () => { const [loading, setLoading] = useState(true); const [currentTime, setCurrentTime] = useState(dayjs()); const [weather, setWeather] = useState({ temp: '--', weather: '未知', city: '未知' }); + const [isInitialized, setIsInitialized] = useState(false); useEffect(() => { // 每秒更新时间 @@ -44,11 +45,39 @@ const BasicLayout: React.FC = () => { // 获取天气信息 const fetchWeather = async () => { - const data = await getWeather(); - setWeather(data); + try { + const data = await getWeather(); + setWeather(data); + } catch (error) { + console.error('获取天气信息失败:', error); + } + }; + + // 初始化用户数据 + const initializeUserData = async () => { + if (isInitialized) return; + try { + setLoading(true); + + // 获取并更新用户信息 + const userData = await getCurrentUser(); + dispatch(setUserInfo(userData)); + + // 获取并更新用户菜单 + const menuData = await getUserMenus(); + dispatch(setMenus(menuData)); + + setIsInitialized(true); + } catch (error) { + message.error('初始化用户数据失败'); + } finally { + setLoading(false); + } }; fetchWeather(); + initializeUserData(); + // 每半小时更新一次天气 const weatherTimer = setInterval(fetchWeather, 30 * 60 * 1000); @@ -56,22 +85,7 @@ const BasicLayout: React.FC = () => { clearInterval(timer); clearInterval(weatherTimer); }; - }, []); - - const fetchUserMenus = async () => { - try { - const data = await getUserMenus(); - dispatch(setMenus(data)); - } catch (error) { - message.error('获取菜单失败'); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchUserMenus(); - }, []); + }, [isInitialized]); const handleLogout = () => { confirm({ diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 6a4f31a9..99479725 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,18 +2,11 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { RouterProvider } from 'react-router-dom'; import { Provider } from 'react-redux'; -import { ConfigProvider } from 'antd'; -import zhCN from 'antd/locale/zh_CN'; import router from './router'; import store from './store'; -import './index.css'; ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - + + + ); \ No newline at end of file diff --git a/frontend/src/pages/Login/index.module.css b/frontend/src/pages/Login/index.module.css index 2791778b..9b0c0974 100644 --- a/frontend/src/pages/Login/index.module.css +++ b/frontend/src/pages/Login/index.module.css @@ -1,17 +1,62 @@ -.container { +.loginContainer { height: 100vh; + width: 100vw; display: flex; justify-content: center; align-items: center; - background-color: #f0f2f5; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } -.loginCard { +.loginBox { width: 400px; + padding: 40px; + background: rgba(255, 255, 255, 0.9); + border-radius: 16px; + box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37); + backdrop-filter: blur(4px); } -.loginCard :global(.ant-card-head-title) { +.logo { text-align: center; + margin-bottom: 40px; +} + +.logo img { + width: 64px; + height: 64px; + margin-bottom: 16px; +} + +.logo h1 { font-size: 24px; - font-weight: bold; + color: #333; + margin: 0; +} + +.input { + height: 50px; + border-radius: 8px; + border: 1px solid #e8e8e8; + transition: all 0.3s; +} + +.input:hover, +.input:focus { + border-color: #764ba2; + box-shadow: 0 0 0 2px rgba(118, 75, 162, 0.2); +} + +.loginButton { + height: 50px; + border-radius: 8px; + font-size: 16px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + margin-top: 16px; + transition: all 0.3s; +} + +.loginButton:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(118, 75, 162, 0.4); } \ No newline at end of file diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx index c7411895..14ceed47 100644 --- a/frontend/src/pages/Login/index.tsx +++ b/frontend/src/pages/Login/index.tsx @@ -2,52 +2,36 @@ import React, {useEffect, useState} from 'react'; import {Form, Input, Button, message, Select} from 'antd'; import {useNavigate} from 'react-router-dom'; import {useDispatch} from 'react-redux'; -import {login} from '../../api/user'; -import {getEnabledTenants} from '../System/Tenant/service'; +import {login, getTenantList} from './service'; import {setToken, setUserInfo} from '../../store/userSlice'; -import type {LoginParams} from '../../types/user'; -import type {TenantDTO} from '../System/Tenant/types'; +import styles from './index.module.css'; const Login: React.FC = () => { const navigate = useNavigate(); const dispatch = useDispatch(); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); - const [tenants, setTenants] = useState([]); + const [tenants, setTenants] = useState([]); useEffect(() => { + const fetchTenants = async () => { + try { + const data = await getTenantList(); + setTenants(data); + } catch (error) { + console.error('获取租户列表失败:', error); + } + }; + fetchTenants(); }, []); - const fetchTenants = async () => { - try { - const data = await getEnabledTenants(); - setTenants(data); - } catch (error) { - console.error('获取租户列表失败:', error); - } - }; - - const onFinish = async (values: LoginParams & { tenantId?: string }) => { + const onFinish = async (values) => { try { setLoading(true); - const {tenantId, ...loginParams} = values; - const result = await login(loginParams); - - // 保存租户ID到localStorage - if (tenantId) { - localStorage.setItem('tenantId', tenantId); - } - + const result = await login(values); dispatch(setToken(result.token)); - dispatch(setUserInfo({ - id: result.id, - username: result.username, - nickname: result.nickname, - email: result.email, - phone: result.phone - })); - + dispatch(setUserInfo(result)); message.success('登录成功'); navigate('/dashboard', {replace: true}); } catch (error) { @@ -58,26 +42,18 @@ const Login: React.FC = () => { }; return ( -
-
-

系统登录

+
+
+
+ QC-NAS +

QC-NAS

+
{ label: tenant.name, value: tenant.code }))} + className={styles.input} /> @@ -96,18 +73,30 @@ const Login: React.FC = () => { name="username" rules={[{required: true, message: '请输入用户名!'}]} > - + - + - diff --git a/frontend/src/pages/Login/service.ts b/frontend/src/pages/Login/service.ts new file mode 100644 index 00000000..9aa0cd14 --- /dev/null +++ b/frontend/src/pages/Login/service.ts @@ -0,0 +1,34 @@ +import request from '@/utils/request'; +import type { LoginRequest, LoginResponse } from './types'; +import type { TenantResponse } from '../System/Tenant/types'; +import type { MenuResponse } from '../System/Menu/types'; + +export const login = async (data: LoginRequest): Promise => { + return request.post('/api/v1/user/login', data, { + errorMessage: '登录失败,请检查用户名和密码' + }); +}; + +export const logout = async (): Promise => { + return request.post('/api/v1/user/logout', null, { + errorMessage: '退出登录失败,请稍后重试' + }); +}; + +export const getCurrentUser = async (): Promise => { + return request.get('/api/v1/user/current', { + errorMessage: '获取用户信息失败,请重新登录' + }); +}; + +export const getUserMenus = async (): Promise => { + return request.get('/api/v1/user/menus', { + errorMessage: '获取菜单失败,请刷新页面重试' + }); +}; + +export const getTenantList = async (): Promise => { + return request.get('/api/v1/tenant/list', { + errorMessage: '获取租户列表失败,请刷新重试' + }); +}; \ No newline at end of file diff --git a/frontend/src/pages/Login/types.ts b/frontend/src/pages/Login/types.ts new file mode 100644 index 00000000..74d2ccb0 --- /dev/null +++ b/frontend/src/pages/Login/types.ts @@ -0,0 +1,14 @@ +export interface LoginRequest { + username: string; + password: string; + tenantId?: string; +} + +export interface LoginResponse { + id: number; + username: string; + nickname?: string; + email?: string; + phone?: string; + token: string; +} \ No newline at end of file diff --git a/frontend/src/pages/Login/types/index.ts b/frontend/src/pages/Login/types/index.ts new file mode 100644 index 00000000..8611b878 --- /dev/null +++ b/frontend/src/pages/Login/types/index.ts @@ -0,0 +1,2 @@ +export * from './request'; +export * from './response'; \ No newline at end of file diff --git a/frontend/src/pages/Login/types/request.ts b/frontend/src/pages/Login/types/request.ts new file mode 100644 index 00000000..782414cd --- /dev/null +++ b/frontend/src/pages/Login/types/request.ts @@ -0,0 +1,5 @@ +export interface LoginRequest { + username: string; + password: string; + tenantId?: string; +} \ No newline at end of file diff --git a/frontend/src/pages/Login/types/response.ts b/frontend/src/pages/Login/types/response.ts new file mode 100644 index 00000000..ab9d38cd --- /dev/null +++ b/frontend/src/pages/Login/types/response.ts @@ -0,0 +1,8 @@ +export interface LoginResponse { + id: number; + username: string; + nickname?: string; + email?: string; + phone?: string; + token: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Department/service.ts b/frontend/src/pages/System/Department/service.ts index d23d97ec..1f1ac5af 100644 --- a/frontend/src/pages/System/Department/service.ts +++ b/frontend/src/pages/System/Department/service.ts @@ -1,24 +1,33 @@ import request from '@/utils/request'; -import type { DepartmentDTO } from './types'; +import type { DepartmentResponse, DepartmentRequest, DepartmentQuery } from './types'; -export const getDepartmentTree = async (): Promise => { - return request.get('/api/system/departments/tree'); +export const getDepartments = async (params?: DepartmentQuery) => { + return request.get('/api/v1/department', { + params, + errorMessage: '获取部门列表失败,请刷新重试' + }); }; -export const createDepartment = async (data: Partial): Promise => { - return request.post('/api/system/departments', data); +export const createDepartment = async (data: DepartmentRequest) => { + return request.post('/api/v1/department', data, { + errorMessage: '创建部门失败,请稍后重试' + }); }; -export const updateDepartment = async (id: number, data: Partial): Promise => { - return request.put(`/api/system/departments/${id}`, data); +export const updateDepartment = async (id: number, data: DepartmentRequest) => { + return request.put(`/api/v1/department/${id}`, data, { + errorMessage: '更新部门失败,请稍后重试' + }); }; -export const deleteDepartment = async (id: number): Promise => { - return request.delete(`/api/system/departments/${id}`); +export const deleteDepartment = async (id: number) => { + return request.delete(`/api/v1/department/${id}`, { + errorMessage: '删除部门失败,请稍后重试' + }); }; -export const getNextSort = async (parentId?: number): Promise => { - return request.get('/api/system/departments/next-sort', { - params: { parentId } +export const getDepartmentTree = async () => { + return request.get('/api/v1/department/tree', { + errorMessage: '获取部门树失败,请刷新重试' }); }; \ No newline at end of file diff --git a/frontend/src/pages/System/Department/types/query.ts b/frontend/src/pages/System/Department/types/query.ts new file mode 100644 index 00000000..77d4d2eb --- /dev/null +++ b/frontend/src/pages/System/Department/types/query.ts @@ -0,0 +1,7 @@ +import { BaseQuery } from '@/types/base/query'; + +export interface DepartmentQuery extends BaseQuery { + name?: string; + code?: string; + enabled?: boolean; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Department/types/request.ts b/frontend/src/pages/System/Department/types/request.ts new file mode 100644 index 00000000..6503804f --- /dev/null +++ b/frontend/src/pages/System/Department/types/request.ts @@ -0,0 +1,9 @@ +import { BaseRequest } from '@/types/base/request'; + +export interface DepartmentRequest extends BaseRequest { + name: string; + code: string; + parentId?: number; + sort?: number; + description?: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Department/types/response.ts b/frontend/src/pages/System/Department/types/response.ts new file mode 100644 index 00000000..981c836a --- /dev/null +++ b/frontend/src/pages/System/Department/types/response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from '@/types/base/response'; + +export interface DepartmentResponse extends BaseResponse { + name: string; + code: string; + parentId?: number; + sort: number; + description?: string; + children?: DepartmentResponse[]; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/service.ts b/frontend/src/pages/System/Menu/service.ts index e14424f3..5da5af05 100644 --- a/frontend/src/pages/System/Menu/service.ts +++ b/frontend/src/pages/System/Menu/service.ts @@ -1,22 +1,33 @@ import request from '@/utils/request'; -import type { MenuDTO, MenuQuery } from './types'; +import type { MenuResponse, MenuRequest, MenuQuery } from './types'; -export const getMenuTree = async (): Promise => { - return request.get('/api/system/menus/tree'); +export const getMenus = async (params?: MenuQuery) => { + return request.get('/api/v1/menu', { + params, + errorMessage: '获取菜单列表失败,请刷新重试' + }); }; -export const getMenuTreeWithoutButtons = async (): Promise => { - return request.get('/api/system/menus/tree/menu'); +export const createMenu = async (data: MenuRequest) => { + return request.post('/api/v1/menu', data, { + errorMessage: '创建菜单失败,请稍后重试' + }); }; -export const createMenu = async (data: Partial): Promise => { - return request.post('/api/system/menus', data); +export const updateMenu = async (id: number, data: MenuRequest) => { + return request.put(`/api/v1/menu/${id}`, data, { + errorMessage: '更新菜单失败,请稍后重试' + }); }; -export const updateMenu = async (id: number, data: Partial): Promise => { - return request.put(`/api/system/menus/${id}`, data); +export const deleteMenu = async (id: number) => { + return request.delete(`/api/v1/menu/${id}`, { + errorMessage: '删除菜单失败,请稍后重试' + }); }; -export const deleteMenu = async (id: number): Promise => { - return request.delete(`/api/system/menus/${id}`); +export const getMenuTree = async () => { + return request.get('/api/v1/menu/tree', { + errorMessage: '获取菜单树失败,请刷新重试' + }); }; \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/types.ts b/frontend/src/pages/System/Menu/types.ts index 2b8c7e20..06f013af 100644 --- a/frontend/src/pages/System/Menu/types.ts +++ b/frontend/src/pages/System/Menu/types.ts @@ -1,40 +1,31 @@ -export interface MenuDTO { - id: number; - name: string; - permission?: string; - path?: string; - component?: string; - type: MenuTypeEnum; - icon?: string; - parentId?: number; - sort: number; - hidden: boolean; - enabled: boolean; - children?: MenuDTO[]; -} - export interface MenuQuery { name?: string; - permission?: string; - type?: MenuTypeEnum; + path?: string; + enabled?: boolean; + pageNum?: number; + pageSize?: number; + sortField?: string; + sortOrder?: string; +} + +export interface MenuRequest { + name: string; + path?: string; + icon?: string; parentId?: number; + sort?: number; enabled?: boolean; } -export enum MenuTypeEnum { - DIRECTORY = 0, - MENU = 1, - BUTTON = 2 -} - -export const MenuTypeNames: Record = { - [MenuTypeEnum.DIRECTORY]: '目录', - [MenuTypeEnum.MENU]: '菜单', - [MenuTypeEnum.BUTTON]: '按钮' -}; - -export const MenuTypeOptions = [ - { label: '目录', value: MenuTypeEnum.DIRECTORY }, - { label: '菜单', value: MenuTypeEnum.MENU }, - { label: '按钮', value: MenuTypeEnum.BUTTON } -]; \ No newline at end of file +export interface MenuResponse { + id: number; + name: string; + path?: string; + icon?: string; + parentId?: number; + sort: number; + enabled: boolean; + children?: MenuResponse[]; + createTime: string; + updateTime: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/types/index.ts b/frontend/src/pages/System/Menu/types/index.ts new file mode 100644 index 00000000..4741b3e5 --- /dev/null +++ b/frontend/src/pages/System/Menu/types/index.ts @@ -0,0 +1,3 @@ +export * from './query'; +export * from './request'; +export * from './response'; \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/types/query.ts b/frontend/src/pages/System/Menu/types/query.ts new file mode 100644 index 00000000..6759cf66 --- /dev/null +++ b/frontend/src/pages/System/Menu/types/query.ts @@ -0,0 +1,7 @@ +import { BaseQuery } from '@/types/base/query'; + +export interface MenuQuery extends BaseQuery { + name?: string; + path?: string; + enabled?: boolean; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/types/request.ts b/frontend/src/pages/System/Menu/types/request.ts new file mode 100644 index 00000000..9ffe14e3 --- /dev/null +++ b/frontend/src/pages/System/Menu/types/request.ts @@ -0,0 +1,9 @@ +import { BaseRequest } from '@/types/base/request'; + +export interface MenuRequest extends BaseRequest { + name: string; + path?: string; + icon?: string; + parentId?: number; + sort?: number; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Menu/types/response.ts b/frontend/src/pages/System/Menu/types/response.ts new file mode 100644 index 00000000..7e95b044 --- /dev/null +++ b/frontend/src/pages/System/Menu/types/response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from '@/types/base/response'; + +export interface MenuResponse extends BaseResponse { + name: string; + path?: string; + icon?: string; + parentId?: number; + sort: number; + children?: MenuResponse[]; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Role/service.ts b/frontend/src/pages/System/Role/service.ts index c075e60f..1b436fbd 100644 --- a/frontend/src/pages/System/Role/service.ts +++ b/frontend/src/pages/System/Role/service.ts @@ -1,26 +1,39 @@ import request from '@/utils/request'; -import type { RoleDTO } from './types'; +import type { RoleResponse, RoleRequest, RoleQuery } from './types'; -export const getRoles = async (): Promise => { - return request.get('/api/system/roles'); +export const getRoles = async (params?: RoleQuery) => { + return request.get('/api/v1/role', { + params, + errorMessage: '获取角色列表失败,请刷新重试' + }); }; -export const createRole = async (data: Partial): Promise => { - return request.post('/api/system/roles', data); +export const createRole = async (data: RoleRequest) => { + return request.post('/api/v1/role', data, { + errorMessage: '创建角色失败,请稍后重试' + }); }; -export const updateRole = async (id: number, data: Partial): Promise => { - return request.put(`/api/system/roles/${id}`, data); +export const updateRole = async (id: number, data: RoleRequest) => { + return request.put(`/api/v1/role/${id}`, data, { + errorMessage: '更新角色失败,请稍后重试' + }); }; -export const deleteRole = async (id: number): Promise => { - return request.delete(`/api/system/roles/${id}`); +export const deleteRole = async (id: number) => { + return request.delete(`/api/v1/role/${id}`, { + errorMessage: '删除角色失败,请稍后重试' + }); }; -export const getRoleMenuIds = async (roleId: number): Promise => { - return request.get(`/api/system/roles/${roleId}/menus`); +export const assignMenus = async (roleId: number, menuIds: number[]) => { + return request.post(`/api/v1/role/${roleId}/menus`, menuIds, { + errorMessage: '分配菜单失败,请稍后重试' + }); }; -export const updateRoleMenus = async (roleId: number, menuIds: number[]): Promise => { - return request.put(`/api/system/roles/${roleId}/menus`, menuIds); +export const getRoleMenus = async (roleId: number) => { + return request.get(`/api/v1/role/${roleId}/menus`, { + errorMessage: '获取角色菜单失败,请刷新重试' + }); }; \ No newline at end of file diff --git a/frontend/src/pages/System/Role/types/index.ts b/frontend/src/pages/System/Role/types/index.ts new file mode 100644 index 00000000..4741b3e5 --- /dev/null +++ b/frontend/src/pages/System/Role/types/index.ts @@ -0,0 +1,3 @@ +export * from './query'; +export * from './request'; +export * from './response'; \ No newline at end of file diff --git a/frontend/src/pages/System/Role/types/query.ts b/frontend/src/pages/System/Role/types/query.ts new file mode 100644 index 00000000..58550e63 --- /dev/null +++ b/frontend/src/pages/System/Role/types/query.ts @@ -0,0 +1,7 @@ +import { BaseQuery } from '@/types/base/query'; + +export interface RoleQuery extends BaseQuery { + name?: string; + code?: string; + enabled?: boolean; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Role/types/request.ts b/frontend/src/pages/System/Role/types/request.ts new file mode 100644 index 00000000..d7f5034c --- /dev/null +++ b/frontend/src/pages/System/Role/types/request.ts @@ -0,0 +1,7 @@ +import { BaseRequest } from '@/types/base/request'; + +export interface RoleRequest extends BaseRequest { + name: string; + code: string; + description?: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Role/types/response.ts b/frontend/src/pages/System/Role/types/response.ts new file mode 100644 index 00000000..1189e34b --- /dev/null +++ b/frontend/src/pages/System/Role/types/response.ts @@ -0,0 +1,7 @@ +import { BaseResponse } from '@/types/base/response'; + +export interface RoleResponse extends BaseResponse { + name: string; + code: string; + description?: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Tenant/service.ts b/frontend/src/pages/System/Tenant/service.ts index d78139e1..10e83bd2 100644 --- a/frontend/src/pages/System/Tenant/service.ts +++ b/frontend/src/pages/System/Tenant/service.ts @@ -1,37 +1,33 @@ import request from '@/utils/request'; -import type {TenantDTO, TenantQuery} from './types'; +import type { TenantResponse, TenantRequest, TenantQuery } from './types'; -// 获取租户列表(分页) export const getTenants = async (params?: TenantQuery) => { - return request.get('/api/system/tenants', {params}); + return request.get('/api/v1/tenant', { + params, + errorMessage: '获取租户列表失败,请刷新重试' + }); }; -// 获取所有启用的租户(用于登录页面) -export const getEnabledTenants = async () => { - return request.get('/api/v1/tenant/list'); +export const createTenant = async (data: TenantRequest) => { + return request.post('/api/v1/tenant', data, { + errorMessage: '创建租户失败,请稍后重试' + }); }; -// 创建租户 -export const createTenant = async (data: Partial) => { - return request.post('/api/system/tenants', data); +export const updateTenant = async (id: number, data: TenantRequest) => { + return request.put(`/api/v1/tenant/${id}`, data, { + errorMessage: '更新租户失败,请稍后重试' + }); }; -// 更新租户 -export const updateTenant = async (id: number, data: Partial) => { - return request.put(`/api/system/tenants/${id}`, data); -}; - -// 删除租户 export const deleteTenant = async (id: number) => { - return request.delete(`/api/system/tenants/${id}`); + return request.delete(`/api/v1/tenant/${id}`, { + errorMessage: '删除租户失败,请稍后重试' + }); }; -// 检查租户编码是否存在 -export const checkTenantCode = async (code: string) => { - return request.get(`/api/system/tenants/check-code`, {params: {code}}); -}; - -// 检查租户名称是否存在 -export const checkTenantName = async (name: string) => { - return request.get(`/api/system/tenants/check-name`, {params: {name}}); +export const getEnabledTenants = async () => { + return request.get('/api/v1/tenant/enabled', { + errorMessage: '获取可用租户列表失败,请刷新重试' + }); }; \ No newline at end of file diff --git a/frontend/src/pages/System/Tenant/types.ts b/frontend/src/pages/System/Tenant/types.ts index f38ea0b0..dfec31bb 100644 --- a/frontend/src/pages/System/Tenant/types.ts +++ b/frontend/src/pages/System/Tenant/types.ts @@ -1,19 +1,32 @@ -export interface TenantDTO { - id: number; - name: string; - code: string; - contactName?: string; - contactPhone?: string; - email?: string; - address?: string; - enabled: boolean; - createTime?: string; - updateTime?: string; - version?: number; -} - export interface TenantQuery { name?: string; code?: string; enabled?: boolean; + pageNum?: number; + pageSize?: number; + sortField?: string; + sortOrder?: string; +} + +export interface TenantRequest { + name: string; + code: string; + address?: string; + contactName?: string; + contactPhone?: string; + email?: string; + enabled?: boolean; +} + +export interface TenantResponse { + id: number; + name: string; + code: string; + address?: string; + contactName?: string; + contactPhone?: string; + email?: string; + enabled: boolean; + createTime: string; + updateTime: string; } \ No newline at end of file diff --git a/frontend/src/pages/System/Tenant/types/index.ts b/frontend/src/pages/System/Tenant/types/index.ts new file mode 100644 index 00000000..4741b3e5 --- /dev/null +++ b/frontend/src/pages/System/Tenant/types/index.ts @@ -0,0 +1,3 @@ +export * from './query'; +export * from './request'; +export * from './response'; \ No newline at end of file diff --git a/frontend/src/pages/System/Tenant/types/query.ts b/frontend/src/pages/System/Tenant/types/query.ts new file mode 100644 index 00000000..771e8117 --- /dev/null +++ b/frontend/src/pages/System/Tenant/types/query.ts @@ -0,0 +1,7 @@ +import { BaseQuery } from '@/types/base/query'; + +export interface TenantQuery extends BaseQuery { + name?: string; + code?: string; + enabled?: boolean; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Tenant/types/request.ts b/frontend/src/pages/System/Tenant/types/request.ts new file mode 100644 index 00000000..eeef7624 --- /dev/null +++ b/frontend/src/pages/System/Tenant/types/request.ts @@ -0,0 +1,10 @@ +import { BaseRequest } from '@/types/base/request'; + +export interface TenantRequest extends BaseRequest { + name: string; + code: string; + address?: string; + contactName?: string; + contactPhone?: string; + email?: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/Tenant/types/response.ts b/frontend/src/pages/System/Tenant/types/response.ts new file mode 100644 index 00000000..29b29419 --- /dev/null +++ b/frontend/src/pages/System/Tenant/types/response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from '@/types/base/response'; + +export interface TenantResponse extends BaseResponse { + name: string; + code: string; + address?: string; + contactName?: string; + contactPhone?: string; + email?: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/User/service.ts b/frontend/src/pages/System/User/service.ts index 283cfb84..05f179ae 100644 --- a/frontend/src/pages/System/User/service.ts +++ b/frontend/src/pages/System/User/service.ts @@ -1,30 +1,57 @@ import request from '@/utils/request'; -import type { UserDTO, UserQuery } from './types'; +import type { UserResponse, UserRequest, UserQuery } from './types'; -export const getUsers = async (params: UserQuery): Promise => { - return request.get('/api/system/users', { params }); +export const getUsers = async (params?: UserQuery) => { + return request.get('/api/v1/user', { + params, + errorMessage: '获取用户列表失败,请刷新重试' + }); }; -export const createUser = async (data: Partial): Promise => { - return request.post('/api/system/users', data); +export const createUser = async (data: UserRequest) => { + return request.post('/api/v1/user', data, { + errorMessage: '创建用户失败,请稍后重试' + }); }; -export const updateUser = async (id: number, data: Partial): Promise => { - return request.put(`/api/system/users/${id}`, data); +export const updateUser = async (id: number, data: UserRequest) => { + return request.put(`/api/v1/user/${id}`, data, { + errorMessage: '更新用户信息失败,请稍后重试' + }); }; -export const deleteUser = async (id: number): Promise => { - return request.delete(`/api/system/users/${id}`); +export const deleteUser = async (id: number) => { + return request.delete(`/api/v1/user/${id}`, { + errorMessage: '删除用户失败,请稍后重试' + }); }; -export const resetPassword = async (id: number, password: string): Promise => { - return request.post(`/api/system/users/${id}/reset-password`, { password }); +export const assignRoles = async (userId: number, roleIds: number[]) => { + return request.post(`/api/v1/user/${userId}/roles`, roleIds, { + errorMessage: '分配角色失败,请稍后重试' + }); }; -export const getUserRoleIds = async (userId: number): Promise => { - return request.get(`/api/system/users/${userId}/roles`); +export const resetPassword = async (id: number) => { + return request.post(`/api/v1/user/${id}/reset-password`, null, { + errorMessage: '重置密码失败,请稍后重试' + }); }; -export const updateUserRoles = async (userId: number, roleIds: number[]): Promise => { - return request.put(`/api/system/users/${userId}/roles`, roleIds); +export const updatePassword = async (id: number, data: { oldPassword: string; newPassword: string }) => { + return request.post(`/api/v1/user/${id}/update-password`, data, { + errorMessage: '修改密码失败,请稍后重试' + }); +}; + +export const enableUser = async (id: number) => { + return request.post(`/api/v1/user/${id}/enable`, null, { + errorMessage: '启用用户失败,请稍后重试' + }); +}; + +export const disableUser = async (id: number) => { + return request.post(`/api/v1/user/${id}/disable`, null, { + errorMessage: '禁用用户失败,请稍后重试' + }); }; \ No newline at end of file diff --git a/frontend/src/pages/System/User/types.ts b/frontend/src/pages/System/User/types.ts index 8907bdc6..5b38553c 100644 --- a/frontend/src/pages/System/User/types.ts +++ b/frontend/src/pages/System/User/types.ts @@ -1,21 +1,30 @@ -export interface UserDTO { +export interface UserQuery { + username?: string; + nickname?: string; + email?: string; + enabled?: boolean; + pageNum?: number; + pageSize?: number; + sortField?: string; + sortOrder?: string; +} + +export interface UserRequest { + username: string; + nickname?: string; + email?: string; + phone?: string; + password?: string; + enabled?: boolean; +} + +export interface UserResponse { id: number; username: string; nickname?: string; email?: string; phone?: string; enabled: boolean; - deptId?: number; - deptName?: string; - version?: number; - createTime?: string; - updateTime?: string; -} - -export interface UserQuery { - page: number; - size: number; - keyword?: string; - deptId?: number; - enabled?: boolean; + createTime: string; + updateTime: string; } \ No newline at end of file diff --git a/frontend/src/pages/System/User/types/index.ts b/frontend/src/pages/System/User/types/index.ts new file mode 100644 index 00000000..4741b3e5 --- /dev/null +++ b/frontend/src/pages/System/User/types/index.ts @@ -0,0 +1,3 @@ +export * from './query'; +export * from './request'; +export * from './response'; \ No newline at end of file diff --git a/frontend/src/pages/System/User/types/query.ts b/frontend/src/pages/System/User/types/query.ts new file mode 100644 index 00000000..0a185d42 --- /dev/null +++ b/frontend/src/pages/System/User/types/query.ts @@ -0,0 +1,8 @@ +import { BaseQuery } from '@/types/base/query'; + +export interface UserQuery extends BaseQuery { + username?: string; + nickname?: string; + email?: string; + enabled?: boolean; +} \ No newline at end of file diff --git a/frontend/src/pages/System/User/types/request.ts b/frontend/src/pages/System/User/types/request.ts new file mode 100644 index 00000000..3e7d3d0f --- /dev/null +++ b/frontend/src/pages/System/User/types/request.ts @@ -0,0 +1,9 @@ +import { BaseRequest } from '@/types/base/request'; + +export interface UserRequest extends BaseRequest { + username: string; + nickname?: string; + email?: string; + phone?: string; + password?: string; +} \ No newline at end of file diff --git a/frontend/src/pages/System/User/types/response.ts b/frontend/src/pages/System/User/types/response.ts new file mode 100644 index 00000000..022fb844 --- /dev/null +++ b/frontend/src/pages/System/User/types/response.ts @@ -0,0 +1,8 @@ +import { BaseResponse } from '@/types/base/response'; + +export interface UserResponse extends BaseResponse { + username: string; + nickname?: string; + email?: string; + phone?: string; +} \ No newline at end of file diff --git a/frontend/src/store/userSlice.ts b/frontend/src/store/userSlice.ts index 75b0bdb3..733a7b70 100644 --- a/frontend/src/store/userSlice.ts +++ b/frontend/src/store/userSlice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import type { MenuDTO } from '@/pages/System/Menu/types'; +import type { MenuResponse } from '@/pages/System/Menu/types'; interface UserInfo { id: number; @@ -12,12 +12,12 @@ interface UserInfo { interface UserState { token: string | null; userInfo: UserInfo | null; - menus: MenuDTO[]; + menus: MenuResponse[]; } const initialState: UserState = { token: localStorage.getItem('token'), - userInfo: localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')!) : null, + userInfo: null, menus: [] }; @@ -33,7 +33,7 @@ const userSlice = createSlice({ state.userInfo = action.payload; localStorage.setItem('userInfo', JSON.stringify(action.payload)); }, - setMenus: (state, action: PayloadAction) => { + setMenus: (state, action: PayloadAction) => { state.menus = action.payload; }, logout: (state) => { @@ -41,6 +41,7 @@ const userSlice = createSlice({ state.userInfo = null; state.menus = []; localStorage.removeItem('token'); + localStorage.removeItem('tenantId'); localStorage.removeItem('userInfo'); } } diff --git a/frontend/src/types/base/query.ts b/frontend/src/types/base/query.ts new file mode 100644 index 00000000..f60af2f3 --- /dev/null +++ b/frontend/src/types/base/query.ts @@ -0,0 +1,6 @@ +export interface BaseQuery { + pageNum?: number; + pageSize?: number; + sortField?: string; + sortOrder?: string; +} \ No newline at end of file diff --git a/frontend/src/types/base/request.ts b/frontend/src/types/base/request.ts new file mode 100644 index 00000000..9e10c898 --- /dev/null +++ b/frontend/src/types/base/request.ts @@ -0,0 +1,3 @@ +export interface BaseRequest { + enabled?: boolean; +} \ No newline at end of file diff --git a/frontend/src/types/base/response.ts b/frontend/src/types/base/response.ts new file mode 100644 index 00000000..74e6112c --- /dev/null +++ b/frontend/src/types/base/response.ts @@ -0,0 +1,9 @@ +export interface BaseResponse { + id: number; + createTime: string; + updateTime: string; + createBy?: string; + updateBy?: string; + enabled: boolean; + version: number; +} \ No newline at end of file diff --git a/frontend/src/types/user.ts b/frontend/src/types/user.ts index 569a5e17..c2c2aa73 100644 --- a/frontend/src/types/user.ts +++ b/frontend/src/types/user.ts @@ -13,7 +13,11 @@ export interface UserInfo { export interface LoginResult { token: string; - userInfo: UserInfo; + id: number; + username: string; + nickname: string; + email: string; + phone: string; } export interface UserState { diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts index 82c7fe43..7e303c71 100644 --- a/frontend/src/utils/request.ts +++ b/frontend/src/utils/request.ts @@ -1,13 +1,19 @@ -import axios, {AxiosResponse} from 'axios'; -import {message} from 'antd'; +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; +import { message } from 'antd'; -export interface ApiResponse { +export interface ApiResponse { code: number; message: string; data: T; success: boolean; } +export interface RequestOptions extends AxiosRequestConfig { + skipErrorHandler?: boolean; + hideErrorMessage?: boolean; + errorMessage?: string; +} + const request = axios.create({ baseURL: '', timeout: 300000, @@ -21,11 +27,13 @@ request.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); const tenantId = localStorage.getItem('tenantId'); + if (token) { config.headers.Authorization = `Bearer ${token}`; } + if (tenantId) { - config.headers['X-Devops-Tenant-Id'] = tenantId; + config.headers['X-Tenant-Id'] = tenantId; } return config; @@ -38,16 +46,48 @@ request.interceptors.request.use( request.interceptors.response.use( (response: AxiosResponse>) => { const res = response.data; - if (res.code !== 200) { - message.error(res.message || '请求失败'); - return Promise.reject(new Error(res.message || '请求失败')); + const config = response.config as RequestOptions; + + if (res.success) { + return res.data; } - return Promise.resolve(res.data); + + if (!config.hideErrorMessage) { + message.error(res.message || '操作失败'); + } + + return Promise.reject(new Error(res.message)); }, (error) => { - message.error(error.response?.data?.message || '请求失败'); + const config = error.config as RequestOptions; + const response = error.response; + + if (!config.hideErrorMessage) { + if (response?.data?.message && response?.status !== 500) { + message.error(response.data.message); + } else { + message.error(config.errorMessage || '系统错误,请稍后重试'); + } + } + return Promise.reject(error); } ); -export default request; \ No newline at end of file +const extendedRequest = { + get: (url: string, config?: RequestOptions) => + request.get(url, config), + + post: (url: string, data?: any, config?: RequestOptions) => + request.post(url, data, config), + + put: (url: string, data?: any, config?: RequestOptions) => + request.put(url, data, config), + + delete: (url: string, config?: RequestOptions) => + request.delete(url, config), + + request: request +}; + +export default extendedRequest; \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..51583d70 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index af6fea18..e26dba62 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -10,12 +10,11 @@ export default defineConfig({ } }, server: { - port: 5173, + port: 3000, proxy: { '/api': { target: 'http://localhost:8080', - changeOrigin: true, - secure: false + changeOrigin: true } } }