增加了request、reponse、service规范

This commit is contained in:
戚辰先生 2024-11-30 08:33:01 +08:00
parent 39b26ac2f8
commit 9fce8ff134
45 changed files with 634 additions and 286 deletions

View File

@ -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<LoginResult> => {
return request.post('/api/v1/users/login', data);
};
export const logout = async (): Promise<void> => {
return request.post('/api/auth/logout');
};
export const getUserMenus = async (): Promise<MenuDTO[]> => {
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`);
};

View File

@ -11,9 +11,9 @@ import {
CloudOutlined CloudOutlined
} from '@ant-design/icons'; } from '@ant-design/icons';
import * as AntdIcons 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 type { MenuProps } from 'antd';
import { getUserMenus } from '../api/user'; import { getCurrentUser, getUserMenus } from '../pages/Login/service';
import { getWeather } from '../services/weather'; import { getWeather } from '../services/weather';
import type { MenuDTO } from '../pages/System/Menu/types'; import type { MenuDTO } from '../pages/System/Menu/types';
import type { RootState } from '../store'; import type { RootState } from '../store';
@ -35,6 +35,7 @@ const BasicLayout: React.FC = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [currentTime, setCurrentTime] = useState(dayjs()); const [currentTime, setCurrentTime] = useState(dayjs());
const [weather, setWeather] = useState({ temp: '--', weather: '未知', city: '未知' }); const [weather, setWeather] = useState({ temp: '--', weather: '未知', city: '未知' });
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => { useEffect(() => {
// 每秒更新时间 // 每秒更新时间
@ -44,11 +45,39 @@ const BasicLayout: React.FC = () => {
// 获取天气信息 // 获取天气信息
const fetchWeather = async () => { const fetchWeather = async () => {
try {
const data = await getWeather(); const data = await getWeather();
setWeather(data); 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(); fetchWeather();
initializeUserData();
// 每半小时更新一次天气 // 每半小时更新一次天气
const weatherTimer = setInterval(fetchWeather, 30 * 60 * 1000); const weatherTimer = setInterval(fetchWeather, 30 * 60 * 1000);
@ -56,22 +85,7 @@ const BasicLayout: React.FC = () => {
clearInterval(timer); clearInterval(timer);
clearInterval(weatherTimer); clearInterval(weatherTimer);
}; };
}, []); }, [isInitialized]);
const fetchUserMenus = async () => {
try {
const data = await getUserMenus();
dispatch(setMenus(data));
} catch (error) {
message.error('获取菜单失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchUserMenus();
}, []);
const handleLogout = () => { const handleLogout = () => {
confirm({ confirm({

View File

@ -2,18 +2,11 @@ import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom'; import { RouterProvider } from 'react-router-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import router from './router'; import router from './router';
import store from './store'; import store from './store';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}> <Provider store={store}>
<ConfigProvider locale={zhCN}>
<RouterProvider router={router} /> <RouterProvider router={router} />
</ConfigProvider>
</Provider> </Provider>
</React.StrictMode>
); );

View File

@ -1,17 +1,62 @@
.container { .loginContainer {
height: 100vh; height: 100vh;
width: 100vw;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: #f0f2f5; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
} }
.loginCard { .loginBox {
width: 400px; 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; text-align: center;
font-size: 24px; margin-bottom: 40px;
font-weight: bold; }
.logo img {
width: 64px;
height: 64px;
margin-bottom: 16px;
}
.logo h1 {
font-size: 24px;
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);
} }

View File

@ -2,52 +2,36 @@ import React, {useEffect, useState} from 'react';
import {Form, Input, Button, message, Select} from 'antd'; import {Form, Input, Button, message, Select} from 'antd';
import {useNavigate} from 'react-router-dom'; import {useNavigate} from 'react-router-dom';
import {useDispatch} from 'react-redux'; import {useDispatch} from 'react-redux';
import {login} from '../../api/user'; import {login, getTenantList} from './service';
import {getEnabledTenants} from '../System/Tenant/service';
import {setToken, setUserInfo} from '../../store/userSlice'; import {setToken, setUserInfo} from '../../store/userSlice';
import type {LoginParams} from '../../types/user'; import styles from './index.module.css';
import type {TenantDTO} from '../System/Tenant/types';
const Login: React.FC = () => { const Login: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [tenants, setTenants] = useState<TenantDTO[]>([]); const [tenants, setTenants] = useState([]);
useEffect(() => { useEffect(() => {
fetchTenants();
}, []);
const fetchTenants = async () => { const fetchTenants = async () => {
try { try {
const data = await getEnabledTenants(); const data = await getTenantList();
setTenants(data); setTenants(data);
} catch (error) { } catch (error) {
console.error('获取租户列表失败:', error); console.error('获取租户列表失败:', error);
} }
}; };
const onFinish = async (values: LoginParams & { tenantId?: string }) => { fetchTenants();
}, []);
const onFinish = async (values) => {
try { try {
setLoading(true); setLoading(true);
const {tenantId, ...loginParams} = values; const result = await login(values);
const result = await login(loginParams);
// 保存租户ID到localStorage
if (tenantId) {
localStorage.setItem('tenantId', tenantId);
}
dispatch(setToken(result.token)); dispatch(setToken(result.token));
dispatch(setUserInfo({ dispatch(setUserInfo(result));
id: result.id,
username: result.username,
nickname: result.nickname,
email: result.email,
phone: result.phone
}));
message.success('登录成功'); message.success('登录成功');
navigate('/dashboard', {replace: true}); navigate('/dashboard', {replace: true});
} catch (error) { } catch (error) {
@ -58,26 +42,18 @@ const Login: React.FC = () => {
}; };
return ( return (
<div style={{ <div className={styles.loginContainer}>
height: '100vh', <div className={styles.loginBox}>
display: 'flex', <div className={styles.logo}>
justifyContent: 'center', <img src="/logo.png" alt="QC-NAS" />
alignItems: 'center', <h1>QC-NAS</h1>
background: '#f0f2f5' </div>
}}>
<div style={{
width: '400px',
padding: '40px',
background: '#fff',
borderRadius: '8px',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
}}>
<h2 style={{textAlign: 'center', marginBottom: '30px'}}></h2>
<Form <Form
form={form} form={form}
name="login" name="login"
onFinish={onFinish} onFinish={onFinish}
autoComplete="off" autoComplete="off"
size="large"
> >
<Form.Item <Form.Item
name="tenantId" name="tenantId"
@ -89,6 +65,7 @@ const Login: React.FC = () => {
label: tenant.name, label: tenant.name,
value: tenant.code value: tenant.code
}))} }))}
className={styles.input}
/> />
</Form.Item> </Form.Item>
@ -96,18 +73,30 @@ const Login: React.FC = () => {
name="username" name="username"
rules={[{required: true, message: '请输入用户名!'}]} rules={[{required: true, message: '请输入用户名!'}]}
> >
<Input placeholder="用户名" size="large"/> <Input
placeholder="用户名"
className={styles.input}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="password" name="password"
rules={[{required: true, message: '请输入密码!'}]} rules={[{required: true, message: '请输入密码!'}]}
> >
<Input.Password placeholder="密码" size="large"/> <Input.Password
placeholder="密码"
className={styles.input}
/>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit" block size="large" loading={loading}> <Button
type="primary"
htmlType="submit"
block
loading={loading}
className={styles.loginButton}
>
</Button> </Button>
</Form.Item> </Form.Item>

View File

@ -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<LoginResponse> => {
return request.post('/api/v1/user/login', data, {
errorMessage: '登录失败,请检查用户名和密码'
});
};
export const logout = async (): Promise<void> => {
return request.post('/api/v1/user/logout', null, {
errorMessage: '退出登录失败,请稍后重试'
});
};
export const getCurrentUser = async (): Promise<LoginResponse> => {
return request.get('/api/v1/user/current', {
errorMessage: '获取用户信息失败,请重新登录'
});
};
export const getUserMenus = async (): Promise<MenuResponse[]> => {
return request.get('/api/v1/user/menus', {
errorMessage: '获取菜单失败,请刷新页面重试'
});
};
export const getTenantList = async (): Promise<TenantResponse[]> => {
return request.get('/api/v1/tenant/list', {
errorMessage: '获取租户列表失败,请刷新重试'
});
};

View File

@ -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;
}

View File

@ -0,0 +1,2 @@
export * from './request';
export * from './response';

View File

@ -0,0 +1,5 @@
export interface LoginRequest {
username: string;
password: string;
tenantId?: string;
}

View File

@ -0,0 +1,8 @@
export interface LoginResponse {
id: number;
username: string;
nickname?: string;
email?: string;
phone?: string;
token: string;
}

View File

@ -1,24 +1,33 @@
import request from '@/utils/request'; import request from '@/utils/request';
import type { DepartmentDTO } from './types'; import type { DepartmentResponse, DepartmentRequest, DepartmentQuery } from './types';
export const getDepartmentTree = async (): Promise<DepartmentDTO[]> => { export const getDepartments = async (params?: DepartmentQuery) => {
return request.get('/api/system/departments/tree'); return request.get('/api/v1/department', {
}; params,
errorMessage: '获取部门列表失败,请刷新重试'
export const createDepartment = async (data: Partial<DepartmentDTO>): Promise<DepartmentDTO> => { });
return request.post('/api/system/departments', data); };
};
export const createDepartment = async (data: DepartmentRequest) => {
export const updateDepartment = async (id: number, data: Partial<DepartmentDTO>): Promise<DepartmentDTO> => { return request.post('/api/v1/department', data, {
return request.put(`/api/system/departments/${id}`, data); errorMessage: '创建部门失败,请稍后重试'
}; });
};
export const deleteDepartment = async (id: number): Promise<void> => {
return request.delete(`/api/system/departments/${id}`); export const updateDepartment = async (id: number, data: DepartmentRequest) => {
}; return request.put(`/api/v1/department/${id}`, data, {
errorMessage: '更新部门失败,请稍后重试'
export const getNextSort = async (parentId?: number): Promise<number> => { });
return request.get('/api/system/departments/next-sort', { };
params: { parentId }
export const deleteDepartment = async (id: number) => {
return request.delete(`/api/v1/department/${id}`, {
errorMessage: '删除部门失败,请稍后重试'
});
};
export const getDepartmentTree = async () => {
return request.get('/api/v1/department/tree', {
errorMessage: '获取部门树失败,请刷新重试'
}); });
}; };

View File

@ -0,0 +1,7 @@
import { BaseQuery } from '@/types/base/query';
export interface DepartmentQuery extends BaseQuery {
name?: string;
code?: string;
enabled?: boolean;
}

View File

@ -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;
}

View File

@ -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[];
}

View File

@ -1,22 +1,33 @@
import request from '@/utils/request'; import request from '@/utils/request';
import type { MenuDTO, MenuQuery } from './types'; import type { MenuResponse, MenuRequest, MenuQuery } from './types';
export const getMenuTree = async (): Promise<MenuDTO[]> => { export const getMenus = async (params?: MenuQuery) => {
return request.get('/api/system/menus/tree'); return request.get('/api/v1/menu', {
params,
errorMessage: '获取菜单列表失败,请刷新重试'
});
}; };
export const getMenuTreeWithoutButtons = async (): Promise<MenuDTO[]> => { export const createMenu = async (data: MenuRequest) => {
return request.get('/api/system/menus/tree/menu'); return request.post('/api/v1/menu', data, {
errorMessage: '创建菜单失败,请稍后重试'
});
}; };
export const createMenu = async (data: Partial<MenuDTO>): Promise<MenuDTO> => { export const updateMenu = async (id: number, data: MenuRequest) => {
return request.post('/api/system/menus', data); return request.put(`/api/v1/menu/${id}`, data, {
errorMessage: '更新菜单失败,请稍后重试'
});
}; };
export const updateMenu = async (id: number, data: Partial<MenuDTO>): Promise<MenuDTO> => { export const deleteMenu = async (id: number) => {
return request.put(`/api/system/menus/${id}`, data); return request.delete(`/api/v1/menu/${id}`, {
errorMessage: '删除菜单失败,请稍后重试'
});
}; };
export const deleteMenu = async (id: number): Promise<void> => { export const getMenuTree = async () => {
return request.delete(`/api/system/menus/${id}`); return request.get('/api/v1/menu/tree', {
errorMessage: '获取菜单树失败,请刷新重试'
});
}; };

View File

@ -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 { export interface MenuQuery {
name?: string; name?: string;
permission?: string; path?: string;
type?: MenuTypeEnum; enabled?: boolean;
pageNum?: number;
pageSize?: number;
sortField?: string;
sortOrder?: string;
}
export interface MenuRequest {
name: string;
path?: string;
icon?: string;
parentId?: number; parentId?: number;
sort?: number;
enabled?: boolean; enabled?: boolean;
} }
export enum MenuTypeEnum { export interface MenuResponse {
DIRECTORY = 0, id: number;
MENU = 1, name: string;
BUTTON = 2 path?: string;
icon?: string;
parentId?: number;
sort: number;
enabled: boolean;
children?: MenuResponse[];
createTime: string;
updateTime: string;
} }
export const MenuTypeNames: Record<MenuTypeEnum, string> = {
[MenuTypeEnum.DIRECTORY]: '目录',
[MenuTypeEnum.MENU]: '菜单',
[MenuTypeEnum.BUTTON]: '按钮'
};
export const MenuTypeOptions = [
{ label: '目录', value: MenuTypeEnum.DIRECTORY },
{ label: '菜单', value: MenuTypeEnum.MENU },
{ label: '按钮', value: MenuTypeEnum.BUTTON }
];

View File

@ -0,0 +1,3 @@
export * from './query';
export * from './request';
export * from './response';

View File

@ -0,0 +1,7 @@
import { BaseQuery } from '@/types/base/query';
export interface MenuQuery extends BaseQuery {
name?: string;
path?: string;
enabled?: boolean;
}

View File

@ -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;
}

View File

@ -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[];
}

View File

@ -1,26 +1,39 @@
import request from '@/utils/request'; import request from '@/utils/request';
import type { RoleDTO } from './types'; import type { RoleResponse, RoleRequest, RoleQuery } from './types';
export const getRoles = async (): Promise<RoleDTO[]> => { export const getRoles = async (params?: RoleQuery) => {
return request.get('/api/system/roles'); return request.get('/api/v1/role', {
params,
errorMessage: '获取角色列表失败,请刷新重试'
});
}; };
export const createRole = async (data: Partial<RoleDTO>): Promise<RoleDTO> => { export const createRole = async (data: RoleRequest) => {
return request.post('/api/system/roles', data); return request.post('/api/v1/role', data, {
errorMessage: '创建角色失败,请稍后重试'
});
}; };
export const updateRole = async (id: number, data: Partial<RoleDTO>): Promise<RoleDTO> => { export const updateRole = async (id: number, data: RoleRequest) => {
return request.put(`/api/system/roles/${id}`, data); return request.put(`/api/v1/role/${id}`, data, {
errorMessage: '更新角色失败,请稍后重试'
});
}; };
export const deleteRole = async (id: number): Promise<void> => { export const deleteRole = async (id: number) => {
return request.delete(`/api/system/roles/${id}`); return request.delete(`/api/v1/role/${id}`, {
errorMessage: '删除角色失败,请稍后重试'
});
}; };
export const getRoleMenuIds = async (roleId: number): Promise<number[]> => { export const assignMenus = async (roleId: number, menuIds: number[]) => {
return request.get(`/api/system/roles/${roleId}/menus`); return request.post(`/api/v1/role/${roleId}/menus`, menuIds, {
errorMessage: '分配菜单失败,请稍后重试'
});
}; };
export const updateRoleMenus = async (roleId: number, menuIds: number[]): Promise<void> => { export const getRoleMenus = async (roleId: number) => {
return request.put(`/api/system/roles/${roleId}/menus`, menuIds); return request.get(`/api/v1/role/${roleId}/menus`, {
errorMessage: '获取角色菜单失败,请刷新重试'
});
}; };

View File

@ -0,0 +1,3 @@
export * from './query';
export * from './request';
export * from './response';

View File

@ -0,0 +1,7 @@
import { BaseQuery } from '@/types/base/query';
export interface RoleQuery extends BaseQuery {
name?: string;
code?: string;
enabled?: boolean;
}

View File

@ -0,0 +1,7 @@
import { BaseRequest } from '@/types/base/request';
export interface RoleRequest extends BaseRequest {
name: string;
code: string;
description?: string;
}

View File

@ -0,0 +1,7 @@
import { BaseResponse } from '@/types/base/response';
export interface RoleResponse extends BaseResponse {
name: string;
code: string;
description?: string;
}

View File

@ -1,37 +1,33 @@
import request from '@/utils/request'; import request from '@/utils/request';
import type {TenantDTO, TenantQuery} from './types'; import type { TenantResponse, TenantRequest, TenantQuery } from './types';
// 获取租户列表(分页)
export const getTenants = async (params?: TenantQuery) => { export const getTenants = async (params?: TenantQuery) => {
return request.get<TenantDTO[]>('/api/system/tenants', {params}); return request.get('/api/v1/tenant', {
params,
errorMessage: '获取租户列表失败,请刷新重试'
});
}; };
// 获取所有启用的租户(用于登录页面) export const createTenant = async (data: TenantRequest) => {
export const getEnabledTenants = async () => { return request.post('/api/v1/tenant', data, {
return request.get<TenantDTO[]>('/api/v1/tenant/list'); errorMessage: '创建租户失败,请稍后重试'
});
}; };
// 创建租户 export const updateTenant = async (id: number, data: TenantRequest) => {
export const createTenant = async (data: Partial<TenantDTO>) => { return request.put(`/api/v1/tenant/${id}`, data, {
return request.post<TenantDTO>('/api/system/tenants', data); errorMessage: '更新租户失败,请稍后重试'
});
}; };
// 更新租户
export const updateTenant = async (id: number, data: Partial<TenantDTO>) => {
return request.put<TenantDTO>(`/api/system/tenants/${id}`, data);
};
// 删除租户
export const deleteTenant = async (id: number) => { export const deleteTenant = async (id: number) => {
return request.delete(`/api/system/tenants/${id}`); return request.delete(`/api/v1/tenant/${id}`, {
errorMessage: '删除租户失败,请稍后重试'
});
}; };
// 检查租户编码是否存在 export const getEnabledTenants = async () => {
export const checkTenantCode = async (code: string) => { return request.get('/api/v1/tenant/enabled', {
return request.get<boolean>(`/api/system/tenants/check-code`, {params: {code}}); errorMessage: '获取可用租户列表失败,请刷新重试'
}; });
// 检查租户名称是否存在
export const checkTenantName = async (name: string) => {
return request.get<boolean>(`/api/system/tenants/check-name`, {params: {name}});
}; };

View File

@ -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 { export interface TenantQuery {
name?: string; name?: string;
code?: string; code?: string;
enabled?: boolean; 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;
} }

View File

@ -0,0 +1,3 @@
export * from './query';
export * from './request';
export * from './response';

View File

@ -0,0 +1,7 @@
import { BaseQuery } from '@/types/base/query';
export interface TenantQuery extends BaseQuery {
name?: string;
code?: string;
enabled?: boolean;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,30 +1,57 @@
import request from '@/utils/request'; 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<UserDTO[]> => { export const getUsers = async (params?: UserQuery) => {
return request.get('/api/system/users', { params }); return request.get('/api/v1/user', {
params,
errorMessage: '获取用户列表失败,请刷新重试'
});
}; };
export const createUser = async (data: Partial<UserDTO>): Promise<UserDTO> => { export const createUser = async (data: UserRequest) => {
return request.post('/api/system/users', data); return request.post('/api/v1/user', data, {
errorMessage: '创建用户失败,请稍后重试'
});
}; };
export const updateUser = async (id: number, data: Partial<UserDTO>): Promise<UserDTO> => { export const updateUser = async (id: number, data: UserRequest) => {
return request.put(`/api/system/users/${id}`, data); return request.put(`/api/v1/user/${id}`, data, {
errorMessage: '更新用户信息失败,请稍后重试'
});
}; };
export const deleteUser = async (id: number): Promise<void> => { export const deleteUser = async (id: number) => {
return request.delete(`/api/system/users/${id}`); return request.delete(`/api/v1/user/${id}`, {
errorMessage: '删除用户失败,请稍后重试'
});
}; };
export const resetPassword = async (id: number, password: string): Promise<void> => { export const assignRoles = async (userId: number, roleIds: number[]) => {
return request.post(`/api/system/users/${id}/reset-password`, { password }); return request.post(`/api/v1/user/${userId}/roles`, roleIds, {
errorMessage: '分配角色失败,请稍后重试'
});
}; };
export const getUserRoleIds = async (userId: number): Promise<number[]> => { export const resetPassword = async (id: number) => {
return request.get(`/api/system/users/${userId}/roles`); return request.post(`/api/v1/user/${id}/reset-password`, null, {
errorMessage: '重置密码失败,请稍后重试'
});
}; };
export const updateUserRoles = async (userId: number, roleIds: number[]): Promise<void> => { export const updatePassword = async (id: number, data: { oldPassword: string; newPassword: string }) => {
return request.put(`/api/system/users/${userId}/roles`, roleIds); 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: '禁用用户失败,请稍后重试'
});
}; };

View File

@ -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; id: number;
username: string; username: string;
nickname?: string; nickname?: string;
email?: string; email?: string;
phone?: string; phone?: string;
enabled: boolean; enabled: boolean;
deptId?: number; createTime: string;
deptName?: string; updateTime: string;
version?: number;
createTime?: string;
updateTime?: string;
}
export interface UserQuery {
page: number;
size: number;
keyword?: string;
deptId?: number;
enabled?: boolean;
} }

View File

@ -0,0 +1,3 @@
export * from './query';
export * from './request';
export * from './response';

View File

@ -0,0 +1,8 @@
import { BaseQuery } from '@/types/base/query';
export interface UserQuery extends BaseQuery {
username?: string;
nickname?: string;
email?: string;
enabled?: boolean;
}

View File

@ -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;
}

View File

@ -0,0 +1,8 @@
import { BaseResponse } from '@/types/base/response';
export interface UserResponse extends BaseResponse {
username: string;
nickname?: string;
email?: string;
phone?: string;
}

View File

@ -1,5 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import type { MenuDTO } from '@/pages/System/Menu/types'; import type { MenuResponse } from '@/pages/System/Menu/types';
interface UserInfo { interface UserInfo {
id: number; id: number;
@ -12,12 +12,12 @@ interface UserInfo {
interface UserState { interface UserState {
token: string | null; token: string | null;
userInfo: UserInfo | null; userInfo: UserInfo | null;
menus: MenuDTO[]; menus: MenuResponse[];
} }
const initialState: UserState = { const initialState: UserState = {
token: localStorage.getItem('token'), token: localStorage.getItem('token'),
userInfo: localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')!) : null, userInfo: null,
menus: [] menus: []
}; };
@ -33,7 +33,7 @@ const userSlice = createSlice({
state.userInfo = action.payload; state.userInfo = action.payload;
localStorage.setItem('userInfo', JSON.stringify(action.payload)); localStorage.setItem('userInfo', JSON.stringify(action.payload));
}, },
setMenus: (state, action: PayloadAction<MenuDTO[]>) => { setMenus: (state, action: PayloadAction<MenuResponse[]>) => {
state.menus = action.payload; state.menus = action.payload;
}, },
logout: (state) => { logout: (state) => {
@ -41,6 +41,7 @@ const userSlice = createSlice({
state.userInfo = null; state.userInfo = null;
state.menus = []; state.menus = [];
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('tenantId');
localStorage.removeItem('userInfo'); localStorage.removeItem('userInfo');
} }
} }

View File

@ -0,0 +1,6 @@
export interface BaseQuery {
pageNum?: number;
pageSize?: number;
sortField?: string;
sortOrder?: string;
}

View File

@ -0,0 +1,3 @@
export interface BaseRequest {
enabled?: boolean;
}

View File

@ -0,0 +1,9 @@
export interface BaseResponse {
id: number;
createTime: string;
updateTime: string;
createBy?: string;
updateBy?: string;
enabled: boolean;
version: number;
}

View File

@ -13,7 +13,11 @@ export interface UserInfo {
export interface LoginResult { export interface LoginResult {
token: string; token: string;
userInfo: UserInfo; id: number;
username: string;
nickname: string;
email: string;
phone: string;
} }
export interface UserState { export interface UserState {

View File

@ -1,13 +1,19 @@
import axios, {AxiosResponse} from 'axios'; import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import {message} from 'antd'; import { message } from 'antd';
export interface ApiResponse<T> { export interface ApiResponse<T = any> {
code: number; code: number;
message: string; message: string;
data: T; data: T;
success: boolean; success: boolean;
} }
export interface RequestOptions extends AxiosRequestConfig {
skipErrorHandler?: boolean;
hideErrorMessage?: boolean;
errorMessage?: string;
}
const request = axios.create({ const request = axios.create({
baseURL: '', baseURL: '',
timeout: 300000, timeout: 300000,
@ -21,11 +27,13 @@ request.interceptors.request.use(
(config) => { (config) => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const tenantId = localStorage.getItem('tenantId'); const tenantId = localStorage.getItem('tenantId');
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
} }
if (tenantId) { if (tenantId) {
config.headers['X-Devops-Tenant-Id'] = tenantId; config.headers['X-Tenant-Id'] = tenantId;
} }
return config; return config;
@ -38,16 +46,48 @@ request.interceptors.request.use(
request.interceptors.response.use( request.interceptors.response.use(
(response: AxiosResponse<ApiResponse<any>>) => { (response: AxiosResponse<ApiResponse<any>>) => {
const res = response.data; const res = response.data;
if (res.code !== 200) { const config = response.config as RequestOptions;
message.error(res.message || '请求失败');
return Promise.reject(new Error(res.message || '请求失败')); 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) => { (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); return Promise.reject(error);
} }
); );
export default request; const extendedRequest = {
get: <T = any>(url: string, config?: RequestOptions) =>
request.get<any, T>(url, config),
post: <T = any>(url: string, data?: any, config?: RequestOptions) =>
request.post<any, T>(url, data, config),
put: <T = any>(url: string, data?: any, config?: RequestOptions) =>
request.put<any, T>(url, data, config),
delete: <T = any>(url: string, config?: RequestOptions) =>
request.delete<any, T>(url, config),
request: request
};
export default extendedRequest;

23
frontend/tsconfig.json Normal file
View File

@ -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" }]
}

View File

@ -10,12 +10,11 @@ export default defineConfig({
} }
}, },
server: { server: {
port: 5173, port: 3000,
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8080', target: 'http://localhost:8080',
changeOrigin: true, changeOrigin: true
secure: false
} }
} }
} }