init
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,4 +7,5 @@
|
||||
/src/.umi-test
|
||||
/dist
|
||||
.swc
|
||||
yarn.local
|
||||
yarn.local
|
||||
yarn.lock
|
||||
@@ -1,9 +1,9 @@
|
||||
import { defineConfig } from "umi";
|
||||
import { defineConfig } from "@umijs/max";
|
||||
|
||||
export default defineConfig({
|
||||
routes: [
|
||||
{ path: "/", component: "index" },
|
||||
{ path: "/docs", component: "docs" },
|
||||
],
|
||||
npmClient: 'yarn',
|
||||
// 最小化有效配置
|
||||
history: { type: 'hash' },
|
||||
plugins: [],
|
||||
// 禁用MFSU功能
|
||||
mfsu: false,
|
||||
});
|
||||
|
||||
12
config/proxy.js
Normal file
12
config/proxy.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
const proxy = {
|
||||
'/api': {
|
||||
'target': 'http://jsonplaceholder.typicode.com/',
|
||||
'changeOrigin': true,
|
||||
'pathRewrite': { '^/api' : '' },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export default proxy;
|
||||
12
config/routes.js
Normal file
12
config/routes.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
|
||||
// 简化的路由配置
|
||||
const routes = [
|
||||
{ path: '/', component: './index' },
|
||||
{ path: '/login', component: './login' },
|
||||
{ path: '/docs', component: './docs' },
|
||||
// 404 页面
|
||||
{ path: '/*', component: './404' },
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@@ -1,3 +1,21 @@
|
||||
{
|
||||
"extends": "./src/.umi/tsconfig.json"
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react-jsx",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@@/*": ["./src/.umi/*"],
|
||||
"@@@/*": ["./public/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
||||
26
package.json
26
package.json
@@ -2,18 +2,36 @@
|
||||
"private": true,
|
||||
"author": "liuhanhua <1>",
|
||||
"scripts": {
|
||||
"dev": "umi dev",
|
||||
"dev": "cross-env UMI_ENV=dev max dev",
|
||||
"dev:pre": "cross-env UMI_ENV=pre umi dev",
|
||||
"build": "umi build",
|
||||
"postinstall": "umi setup",
|
||||
"setup": "umi setup",
|
||||
"start": "npm run dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"umi": "^4.5.3"
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@ant-design/pro-components": "^2.8.7",
|
||||
"@ebay/nice-modal-react": "^1.2.13",
|
||||
"@umijs/plugin-initial-state": "^2.4.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-react": "^1.0.6",
|
||||
"accounting": "^0.4.1",
|
||||
"antd-style": "^3.7.1",
|
||||
"axios": "^1.11.0",
|
||||
"less": "^4.4.0",
|
||||
"less-loader": "^12.3.0",
|
||||
"rc-tween-one": "^3.0.6",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"antd": "^5.27.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^5.0.3"
|
||||
"@umijs/plugins": "^4.5.3",
|
||||
"typescript": "^5.0.3",
|
||||
"@umijs/max": "^4.5.3",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/app.jsx
10
src/app.jsx
@@ -0,0 +1,10 @@
|
||||
// 简化的 Umi 运行时配置文件
|
||||
|
||||
// 简单的路由切换处理
|
||||
export function onRouteChange({ matchedRoutes }) {
|
||||
if (matchedRoutes && matchedRoutes.length > 0) {
|
||||
document.title = matchedRoutes[matchedRoutes.length - 1].route.title || 'LeasePal 商户管理系统';
|
||||
}
|
||||
}
|
||||
|
||||
// 移除默认对象导出,避免渲染错误
|
||||
106
src/global.less
106
src/global.less
@@ -0,0 +1,106 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
.colorWeak {
|
||||
filter: invert(80%);
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
|
||||
left: unset;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ant-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
&-thead > tr,
|
||||
&-tbody > tr {
|
||||
> th,
|
||||
> td {
|
||||
white-space: pre;
|
||||
> span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-layout-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 首页样式
|
||||
.home-container {
|
||||
padding: 24px;
|
||||
h1 {
|
||||
margin-bottom: 24px;
|
||||
color: #262626;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.cards-container {
|
||||
.home-card {
|
||||
margin-bottom: 16px;
|
||||
transition: all 0.3s ease;
|
||||
&:hover {
|
||||
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 文档页面样式
|
||||
.docs-container {
|
||||
padding: 24px;
|
||||
.intro-card {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.docs-list {
|
||||
.ant-list-item {
|
||||
padding: 16px;
|
||||
transition: all 0.3s;
|
||||
border-radius: 8px;
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
.contact-info {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,19 @@
|
||||
import { Link, Outlet } from 'umi';
|
||||
// 简化的布局组件
|
||||
import React from 'react';
|
||||
import { Layout as AntdLayout } from 'antd';
|
||||
import { Outlet } from 'umi';
|
||||
import styles from './index.less';
|
||||
|
||||
export default function Layout() {
|
||||
const { Content } = AntdLayout;
|
||||
|
||||
const AdminLayout = () => {
|
||||
return (
|
||||
<div className={styles.navs}>
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/">Home</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/docs">Docs</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/umijs/umi">Github</a>
|
||||
</li>
|
||||
</ul>
|
||||
<Outlet />
|
||||
</div>
|
||||
<AntdLayout className={styles.layout}>
|
||||
<Content className={styles.content}>
|
||||
<Outlet />
|
||||
</Content>
|
||||
</AntdLayout>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default AdminLayout;
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
.navs {
|
||||
ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
}
|
||||
li {
|
||||
margin-right: 1em;
|
||||
// 简化的布局样式
|
||||
.layout {
|
||||
min-height: 100vh;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
// 基本响应式设计
|
||||
@media (max-width: 576px) {
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
17
src/pages/404.jsx
Normal file
17
src/pages/404.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
// 简单的404页面
|
||||
import React from 'react';
|
||||
import { Link } from 'umi';
|
||||
|
||||
const NotFoundPage = () => {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '50px 0' }}>
|
||||
<h1>404</h1>
|
||||
<h2>抱歉,您访问的页面不存在</h2>
|
||||
<p>
|
||||
<Link to="/">返回首页</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFoundPage;
|
||||
7
src/pages/404.less
Normal file
7
src/pages/404.less
Normal file
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 60vh;
|
||||
padding: 24px;
|
||||
}
|
||||
@@ -1,7 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
const DocsPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>This is umi docs.</p>
|
||||
<div style={{ padding: '24px' }}>
|
||||
<h1>系统文档中心</h1>
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<h2>欢迎使用LeasePal商户管理系统</h2>
|
||||
<p>本系统旨在帮助管理员高效管理商户信息、租赁合同和相关业务流程。</p>
|
||||
</div>
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<h3>快速导航</h3>
|
||||
<ul>
|
||||
<li><strong>系统概述</strong> - LeasePal商户管理系统的整体架构和功能介绍</li>
|
||||
<li><strong>商户管理</strong> - 如何添加、编辑和管理商户信息</li>
|
||||
<li><strong>租赁业务</strong> - 租赁合同的创建、管理和状态跟踪</li>
|
||||
<li><strong>API接口文档</strong> - 系统提供的所有API接口详细说明</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p style={{ color: '#666' }}>如有问题或需要帮助,请联系系统管理员</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import yayJpg from '../assets/yay.jpg';
|
||||
import React from 'react';
|
||||
|
||||
export default function HomePage() {
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Yay! Welcome to umi!</h2>
|
||||
<p>
|
||||
<img src={yayJpg} width="388" />
|
||||
</p>
|
||||
<p>
|
||||
To get started, edit <code>pages/index.tsx</code> and save to reload.
|
||||
</p>
|
||||
<div style={{ padding: '24px' }}>
|
||||
<h1>LeasePal 商户管理系统</h1>
|
||||
<p>欢迎使用商户管理系统</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
|
||||
81
src/pages/login.jsx
Normal file
81
src/pages/login.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { useState } from 'react';
|
||||
import './login.less';
|
||||
|
||||
const LoginPage = () => {
|
||||
const [username, setUsername] = useState('admin');
|
||||
const [password, setPassword] = useState('admin123');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// 模拟登录请求
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// 模拟登录成功,存储token
|
||||
localStorage.setItem('token', 'mock-token-123456');
|
||||
|
||||
// 显示成功消息
|
||||
alert('登录成功');
|
||||
|
||||
// 跳转到首页
|
||||
window.location.href = '/';
|
||||
} catch (error) {
|
||||
alert('登录失败,请检查用户名和密码');
|
||||
console.error('Login error:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="loginContainer">
|
||||
<div className="loginCard">
|
||||
<h2 className="loginTitle">LeasePal 商户管理系统</h2>
|
||||
<h5 className="loginSubtitle">登录</h5>
|
||||
|
||||
<form onSubmit={handleSubmit} className="loginForm">
|
||||
<div className="formItem">
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="用户名"
|
||||
required
|
||||
className="formInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="formItem">
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="密码"
|
||||
required
|
||||
className="formInput"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="formItem">
|
||||
<button
|
||||
type="submit"
|
||||
className="loginButton"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? '登录中...' : '登录'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="loginTips">
|
||||
<p><strong>提示:</strong>用户名: admin, 密码: admin123</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
59
src/pages/login.less
Normal file
59
src/pages/login.less
Normal file
@@ -0,0 +1,59 @@
|
||||
.loginContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.loginCard {
|
||||
width: 400px;
|
||||
max-width: 90%;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.loginTitle {
|
||||
text-align: center;
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.loginSubtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
.loginForm {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loginButton {
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loginTips {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
background-color: #f5f5f5;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 768px) {
|
||||
.loginCard {
|
||||
width: 90%;
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.site-form-item-icon {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
30
src/utils/downloadCSV.js
Normal file
30
src/utils/downloadCSV.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 将文本内容下载为CSV文件
|
||||
* @param {string} text - CSV文本内容
|
||||
* @param {string} filename - 文件名,默认为export.csv
|
||||
*/
|
||||
export const downloadCSVFromText = (text, filename = 'export.csv') => {
|
||||
try {
|
||||
// 创建Blob对象
|
||||
const blob = new Blob([text], { type: 'text/csv;charset=utf-8;' });
|
||||
|
||||
// 创建下载链接
|
||||
let link = document.createElement('a');
|
||||
|
||||
// 处理不同浏览器的URL创建方式
|
||||
if (link.download !== undefined) {
|
||||
// 支持HTML5的浏览器
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', filename);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url); // 释放URL对象以避免内存泄漏
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('下载CSV文件失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
198
src/utils/request.js
Normal file
198
src/utils/request.js
Normal file
@@ -0,0 +1,198 @@
|
||||
import axios from 'axios';
|
||||
import { message } from "antd";
|
||||
import { history } from "@umijs/max";
|
||||
import { getLocale } from "@@/exports";
|
||||
import { downloadCSVFromText } from "@/utils/downloadCSV";
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: process.env.API_BASE_URL || '/api', // 使用UMI风格的环境变量或默认值
|
||||
timeout: 30000, // 请求超时时间
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
}
|
||||
});
|
||||
|
||||
// 存储请求的取消令牌
|
||||
const cancelTokenSourceMap = new Map();
|
||||
|
||||
/**
|
||||
* 生成请求唯一标识
|
||||
* @param {Object} config - 请求配置
|
||||
* @returns {string} 唯一标识
|
||||
*/
|
||||
function generateRequestKey(config) {
|
||||
const { method, url, params, data } = config;
|
||||
return [
|
||||
method.toLowerCase(),
|
||||
url,
|
||||
JSON.stringify(params),
|
||||
JSON.stringify(data)
|
||||
].join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消重复请求
|
||||
* @param {Object} config - 请求配置
|
||||
*/
|
||||
function cancelDuplicateRequest(config) {
|
||||
const requestKey = generateRequestKey(config);
|
||||
|
||||
// 如果存在重复请求,取消之前的
|
||||
if (cancelTokenSourceMap.has(requestKey)) {
|
||||
const source = cancelTokenSourceMap.get(requestKey);
|
||||
source.cancel('取消重复请求');
|
||||
cancelTokenSourceMap.delete(requestKey);
|
||||
}
|
||||
|
||||
// 创建新的取消令牌
|
||||
const source = axios.CancelToken.source();
|
||||
config.cancelToken = source.token;
|
||||
cancelTokenSourceMap.set(requestKey, source);
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
// 取消重复请求
|
||||
cancelDuplicateRequest(config);
|
||||
|
||||
const { method, headers } = config;
|
||||
|
||||
if ('POST' === method.toUpperCase() || 'PUT' === method.toUpperCase()) {
|
||||
headers["Content-Type"] = 'application/json';
|
||||
}
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
headers["token"] = token;
|
||||
}
|
||||
headers["Accept-Language"] = getLocale();
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
if (axios.isCancel(error)) {
|
||||
console.log('请求已取消:', error.message);
|
||||
return Promise.resolve({ data: { success: false, message: error.message } });
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
const errorHandler = (error) => {
|
||||
const { response } = error || {};
|
||||
const { status, data } = response || {};
|
||||
|
||||
// 根据不同错误类型显示不同提示
|
||||
const errorMessage = data?.message || "请求失败,请稍后重试";
|
||||
|
||||
switch (status) {
|
||||
case 401:
|
||||
message.error("登录已过期,请重新登录");
|
||||
localStorage.removeItem("token");
|
||||
if (history.location.pathname !== '/login') {
|
||||
setTimeout(() => {
|
||||
history.push("/login");
|
||||
}, 1000);
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
message.error("没有权限执行此操作");
|
||||
break;
|
||||
case 404:
|
||||
message.error("请求的资源不存在");
|
||||
break;
|
||||
case 500:
|
||||
message.error("服务器内部错误");
|
||||
break;
|
||||
default:
|
||||
message.error(errorMessage);
|
||||
}
|
||||
|
||||
return Promise.resolve({ success: false, message: errorMessage, error });
|
||||
}
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
// 移除取消令牌,避免内存泄漏
|
||||
const requestKey = generateRequestKey(response.config);
|
||||
cancelTokenSourceMap.delete(requestKey);
|
||||
|
||||
const { headers } = response || {};
|
||||
|
||||
// 假设后端统一返回格式为 { code, data, message }
|
||||
if (response.status === 200) {
|
||||
if (headers["content-type"] && headers["content-type"].includes('text/csv')) {
|
||||
downloadCSVFromText(response.data);
|
||||
return {
|
||||
data: null,
|
||||
success: true
|
||||
}
|
||||
}
|
||||
|
||||
// 处理分页数据结构
|
||||
const responseData = response.data;
|
||||
if (responseData.data && responseData.data.paging && responseData.data.data) {
|
||||
const data = responseData.data;
|
||||
data['list'] = data.data;
|
||||
data['total'] = data.paging.total;
|
||||
delete data['data'];
|
||||
}
|
||||
return responseData; // 直接返回业务数据
|
||||
}
|
||||
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
// 确保在错误情况下也移除取消令牌
|
||||
if (error.config) {
|
||||
const requestKey = generateRequestKey(error.config);
|
||||
cancelTokenSourceMap.delete(requestKey);
|
||||
}
|
||||
|
||||
// 处理取消请求的情况
|
||||
if (axios.isCancel(error)) {
|
||||
console.log('请求已取消:', error.message);
|
||||
return Promise.resolve({ success: false, message: error.message });
|
||||
}
|
||||
|
||||
return errorHandler(error);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 封装请求方法
|
||||
* @param {string} method - 请求方法
|
||||
* @param {string} url - 请求URL
|
||||
* @param {Object} data - 请求数据
|
||||
* @param {Object} config - 额外配置
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function request(method, url, data = {}, config = {}) {
|
||||
return service({
|
||||
method,
|
||||
url,
|
||||
[method.toLowerCase() === 'get' ? 'params' : 'data']: data,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
// 导出常用请求方法
|
||||
export const http = {
|
||||
get: (url, params, config) => request('get', url, params, config),
|
||||
post: (url, data, config) => request('post', url, data, config),
|
||||
put: (url, data, config) => request('put', url, data, config),
|
||||
delete: (url, data, config) => request('delete', url, data, config),
|
||||
patch: (url, data, config) => request('patch', url, data, config),
|
||||
|
||||
// 取消所有请求
|
||||
cancelAllRequests() {
|
||||
cancelTokenSourceMap.forEach((source) => {
|
||||
source.cancel('取消所有请求');
|
||||
});
|
||||
cancelTokenSourceMap.clear();
|
||||
}
|
||||
};
|
||||
|
||||
export default service;
|
||||
1
typings.d.ts
vendored
1
typings.d.ts
vendored
@@ -1 +0,0 @@
|
||||
import 'umi/typings';
|
||||
Reference in New Issue
Block a user