v01
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
# 开发环境配置
|
||||
PORT=3001
|
||||
REACT_APP_ENV=development
|
||||
REACT_APP_API_URL=http://localhost:8080
|
||||
REACT_APP_API_URL=http://localhost:3001
|
||||
REACT_APP_API_TIMEOUT=10000
|
||||
REACT_APP_DEBUG=true
|
||||
GENERATE_SOURCEMAP=true
|
||||
|
||||
30
frontend/index.html
Normal file
30
frontend/index.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="UAdmin - 用户权限管理系统"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>UAdmin - 用户权限管理系统</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
15599
frontend/package-lock.json
generated
15599
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,12 +4,8 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.126",
|
||||
"@types/react": "^19.1.10",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
@ -18,24 +14,30 @@
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"env-cmd": "^10.1.0"
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.7.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/node": "^24.3.0",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^5.4.19"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start:dev": "env-cmd -f .env.development react-scripts start",
|
||||
"start:prod": "env-cmd -f .env.production react-scripts start",
|
||||
"start:test": "env-cmd -f .env.test react-scripts start",
|
||||
"build": "env-cmd -f .env.production react-scripts build",
|
||||
"build:dev": "env-cmd -f .env.development react-scripts build",
|
||||
"build:prod": "env-cmd -f .env.production react-scripts build",
|
||||
"build:test": "env-cmd -f .env.test react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"start": "vite",
|
||||
"dev": "env-cmd -f .env.development vite",
|
||||
"prod": "env-cmd -f .env.production vite",
|
||||
"start:test": "env-cmd -f .env.test vite",
|
||||
"build": "env-cmd -f .env.production vite build",
|
||||
"build:dev": "env-cmd -f .env.development vite build",
|
||||
"build:prod": "env-cmd -f .env.production vite build",
|
||||
"build:test": "env-cmd -f .env.test vite build",
|
||||
"test": "vitest",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { ConfigProvider, Spin, App as AntdApp } from 'antd';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import { AuthProvider, useAuth } from './hooks/useAuth';
|
||||
import Layout from './components/Layout';
|
||||
@ -13,12 +13,30 @@ import Permissions from './pages/Permissions';
|
||||
import Menus from './pages/Menus';
|
||||
import './App.css';
|
||||
|
||||
// 全局message配置
|
||||
import { message } from 'antd';
|
||||
message.config({
|
||||
top: 100,
|
||||
duration: 2,
|
||||
maxCount: 3,
|
||||
});
|
||||
|
||||
// 受保护的路由组件
|
||||
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '100vh',
|
||||
background: '#f0f2f5'
|
||||
}}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
@ -33,7 +51,17 @@ const PublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '100vh',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
|
||||
}}>
|
||||
<Spin size="large" style={{ color: 'white' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
@ -46,40 +74,42 @@ const PublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
function App() {
|
||||
return (
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* 公共路由 */}
|
||||
<Route path="/login" element={
|
||||
<PublicRoute>
|
||||
<Login />
|
||||
</PublicRoute>
|
||||
} />
|
||||
<Route path="/register" element={
|
||||
<PublicRoute>
|
||||
<Register />
|
||||
</PublicRoute>
|
||||
} />
|
||||
|
||||
{/* 受保护的路由 */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Layout />
|
||||
</ProtectedRoute>
|
||||
}>
|
||||
<Route index element={<Navigate to="/dashboard" replace />} />
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="users" element={<Users />} />
|
||||
<Route path="roles" element={<Roles />} />
|
||||
<Route path="permissions" element={<Permissions />} />
|
||||
<Route path="menus" element={<Menus />} />
|
||||
</Route>
|
||||
|
||||
{/* 404 重定向 */}
|
||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
<AntdApp>
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
{/* 公共路由 */}
|
||||
<Route path="/login" element={
|
||||
<PublicRoute>
|
||||
<Login />
|
||||
</PublicRoute>
|
||||
} />
|
||||
<Route path="/register" element={
|
||||
<PublicRoute>
|
||||
<Register />
|
||||
</PublicRoute>
|
||||
} />
|
||||
|
||||
{/* 受保护的路由 */}
|
||||
<Route path="/" element={
|
||||
<ProtectedRoute>
|
||||
<Layout />
|
||||
</ProtectedRoute>
|
||||
}>
|
||||
<Route index element={<Navigate to="/dashboard" replace />} />
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="users" element={<Users />} />
|
||||
<Route path="roles" element={<Roles />} />
|
||||
<Route path="permissions" element={<Permissions />} />
|
||||
<Route path="menus" element={<Menus />} />
|
||||
</Route>
|
||||
|
||||
{/* 404 重定向 */}
|
||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
</AntdApp>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,8 +49,9 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
|
||||
const login = async (data: LoginRequest): Promise<boolean> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log('useAuth: 开始登录请求', data);
|
||||
const response = await apiService.login(data);
|
||||
console.log('useAuth: 登录响应', response);
|
||||
|
||||
setToken(response.token);
|
||||
setUser(response.user);
|
||||
@ -60,19 +61,26 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
localStorage.setItem('user', JSON.stringify(response.user));
|
||||
|
||||
message.success('登录成功');
|
||||
console.log('useAuth: 登录成功');
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Login failed:', error);
|
||||
message.error(error.response?.data?.message || '登录失败');
|
||||
console.error('useAuth: 登录失败', error);
|
||||
// 检查是否是apiService抛出的业务错误
|
||||
if (error.response?.data?.code && error.response.data.code !== 200) {
|
||||
message.error(error.response.data.message || '登录失败');
|
||||
} else if (error.response?.status === 401) {
|
||||
message.error('用户名或密码错误');
|
||||
} else if (error.message) {
|
||||
message.error(error.message);
|
||||
} else {
|
||||
message.error('登录失败');
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const register = async (data: RegisterRequest): Promise<boolean> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await apiService.register(data);
|
||||
message.success('注册成功,请登录');
|
||||
return true;
|
||||
@ -80,8 +88,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
||||
console.error('Register failed:', error);
|
||||
message.error(error.response?.data?.message || '注册失败');
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import '@ant-design/v5-patch-for-react-19';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Form, Input, Button, Card, Typography, message } from 'antd';
|
||||
import { Form, Input, Button, Card, Typography } from 'antd';
|
||||
import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { useAuth } from '../hooks/useAuth';
|
||||
import { LoginRequest } from '../types';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import message from 'antd/es/message';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@ -15,12 +16,18 @@ const Login: React.FC = () => {
|
||||
const onFinish = async (values: LoginRequest) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
console.log('开始登录:', values);
|
||||
const success = await login(values);
|
||||
console.log('登录结果:', success);
|
||||
if (success) {
|
||||
console.log('登录成功,跳转到dashboard');
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
console.log('登录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
message.error('登录过程中发生错误');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ class ApiService {
|
||||
|
||||
constructor() {
|
||||
this.api = axios.create({
|
||||
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api',
|
||||
baseURL: '/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -53,10 +53,13 @@ class ApiService {
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Token过期或无效,清除本地存储并跳转到登录页
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
// 如果不是登录请求,才清除token并跳转
|
||||
if (!error.config?.url?.includes('/auth/login')) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
// 对于登录请求的401错误,直接返回错误让组件处理
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@ -65,7 +68,13 @@ class ApiService {
|
||||
|
||||
// 认证相关API
|
||||
async login(data: LoginRequest): Promise<LoginResponse> {
|
||||
const response = await this.api.post<ApiResponse<LoginResponse>>('/auth/login', data);
|
||||
const response = await this.api.post<ApiResponse<LoginResponse>>("/auth/login", data);
|
||||
// 检查业务错误码
|
||||
if (response.data.code !== 200) {
|
||||
const error = new Error(response.data.message);
|
||||
(error as any).response = { data: response.data };
|
||||
throw error;
|
||||
}
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
|
||||
232
frontend/src/services/api.ts.bak
Normal file
232
frontend/src/services/api.ts.bak
Normal file
@ -0,0 +1,232 @@
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import {
|
||||
User,
|
||||
Role,
|
||||
Permission,
|
||||
Menu,
|
||||
CreateUserRequest,
|
||||
UpdateUserRequest,
|
||||
CreateRoleRequest,
|
||||
UpdateRoleRequest,
|
||||
CreatePermissionRequest,
|
||||
UpdatePermissionRequest,
|
||||
CreateMenuRequest,
|
||||
UpdateMenuRequest,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
RegisterRequest,
|
||||
ApiResponse,
|
||||
PaginatedResponse,
|
||||
QueryParams,
|
||||
} from '../types';
|
||||
|
||||
class ApiService {
|
||||
private api: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.api = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// 请求拦截器 - 添加认证token
|
||||
this.api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器 - 处理错误
|
||||
this.api.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// 如果不是登录请求,才清除token并跳转
|
||||
if (!error.config?.url?.includes('/auth/login')) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
// 对于登录请求的401错误,直接返回错误让组件处理
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 认证相关API
|
||||
async login(data: LoginRequest): Promise<LoginResponse> {
|
||||
const response = await this.api.post<ApiResponse<LoginResponse>>('/auth/login', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async register(data: RegisterRequest): Promise<User> {
|
||||
const response = await this.api.post<ApiResponse<User>>('/auth/register', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
await this.api.post('/auth/logout');
|
||||
}
|
||||
|
||||
// 用户管理API
|
||||
async getUsers(params?: QueryParams): Promise<PaginatedResponse<User>> {
|
||||
const response = await this.api.get('/users', { params });
|
||||
const backendData = response.data.data;
|
||||
// 转换后端数据结构到前端期望的结构
|
||||
return {
|
||||
data: backendData.items.map((user: any) => ({
|
||||
...user,
|
||||
status: user.status === 1 ? 'active' : 'inactive'
|
||||
})),
|
||||
total: backendData.total,
|
||||
page: backendData.page,
|
||||
per_page: backendData.page_size,
|
||||
total_pages: Math.ceil(backendData.total / backendData.page_size)
|
||||
};
|
||||
}
|
||||
|
||||
async getUser(id: number): Promise<User> {
|
||||
const response = await this.api.get<ApiResponse<User>>(`/users/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createUser(data: CreateUserRequest): Promise<User> {
|
||||
const response = await this.api.post<ApiResponse<User>>('/users', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updateUser(id: number, data: UpdateUserRequest): Promise<User> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<User>>(`/users/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deleteUser(id: number): Promise<void> {
|
||||
await this.api.delete(`/users/${id}`);
|
||||
}
|
||||
|
||||
// 角色管理API
|
||||
async getRoles(params?: QueryParams): Promise<PaginatedResponse<Role>> {
|
||||
const response = await this.api.get<ApiResponse<PaginatedResponse<Role>>>('/roles', { params });
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getAllRoles(): Promise<Role[]> {
|
||||
const response = await this.api.get<ApiResponse<Role[]>>('/roles/all');
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getRole(id: number): Promise<Role> {
|
||||
const response = await this.api.get<ApiResponse<Role>>(`/roles/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createRole(data: CreateRoleRequest): Promise<Role> {
|
||||
const response = await this.api.post<ApiResponse<Role>>('/roles', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updateRole(id: number, data: UpdateRoleRequest): Promise<Role> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<Role>>(`/roles/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deleteRole(id: number): Promise<void> {
|
||||
await this.api.delete(`/roles/${id}`);
|
||||
}
|
||||
|
||||
// 权限管理API
|
||||
async getPermissions(params?: QueryParams): Promise<PaginatedResponse<Permission>> {
|
||||
const response = await this.api.get<ApiResponse<PaginatedResponse<Permission>>>('/permissions', { params });
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getAllPermissions(): Promise<Permission[]> {
|
||||
const response = await this.api.get<ApiResponse<Permission[]>>('/permissions/all');
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getPermission(id: number): Promise<Permission> {
|
||||
const response = await this.api.get<ApiResponse<Permission>>(`/permissions/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createPermission(data: CreatePermissionRequest): Promise<Permission> {
|
||||
const response = await this.api.post<ApiResponse<Permission>>('/permissions', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updatePermission(id: number, data: UpdatePermissionRequest): Promise<Permission> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<Permission>>(`/permissions/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deletePermission(id: number): Promise<void> {
|
||||
await this.api.delete(`/permissions/${id}`);
|
||||
}
|
||||
|
||||
// 菜单管理API
|
||||
async getMenus(params?: QueryParams): Promise<PaginatedResponse<Menu>> {
|
||||
const response = await this.api.get<ApiResponse<PaginatedResponse<Menu>>>('/menus', { params });
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getAllMenus(): Promise<Menu[]> {
|
||||
const response = await this.api.get<ApiResponse<Menu[]>>('/menus/all');
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async getMenu(id: number): Promise<Menu> {
|
||||
const response = await this.api.get<ApiResponse<Menu>>(`/menus/${id}`);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async createMenu(data: CreateMenuRequest): Promise<Menu> {
|
||||
const response = await this.api.post<ApiResponse<Menu>>('/menus', data);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async updateMenu(id: number, data: UpdateMenuRequest): Promise<Menu> {
|
||||
// 转换前端的字符串状态为后端期望的数字格式
|
||||
const backendData = {
|
||||
...data,
|
||||
status: data.status ? (data.status === 'active' ? 1 : 0) : undefined
|
||||
};
|
||||
const response = await this.api.put<ApiResponse<Menu>>(`/menus/${id}`, backendData);
|
||||
return response.data.data!;
|
||||
}
|
||||
|
||||
async deleteMenu(id: number): Promise<void> {
|
||||
await this.api.delete(`/menus/${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const apiService = new ApiService();
|
||||
export default apiService;
|
||||
30
frontend/vite.config.ts
Normal file
30
frontend/vite.config.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3001,
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8080',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
configure: (proxy, options) => {
|
||||
proxy.on('proxyReq', (proxyReq, req, res) => {
|
||||
console.log('Proxying request:', req.method, req.url);
|
||||
console.log('Headers:', req.headers);
|
||||
});
|
||||
proxy.on('proxyRes', (proxyRes, req, res) => {
|
||||
console.log('Proxy response:', proxyRes.statusCode, 'for', req.url);
|
||||
});
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.log('Proxy error:', err.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user