first commit

This commit is contained in:
liu
2026-07-13 15:23:29 +08:00
commit 50885a98c8
473 changed files with 33772 additions and 0 deletions
+270
View File
@@ -0,0 +1,270 @@
import * as AntdIcons from '@ant-design/icons';
const antdIcons = Object.keys(AntdIcons).filter((icon) => {
return !['default', 'getTwoToneColor', 'setTwoToneColor', 'createFromIconfontCN', 'IconProvider'].includes(icon) && !icon.includes('TwoTone');
})
const useIcons = [
'icon-guanzhu',
'icon-hulve',
'icon-fuwuqi',
'icon-daishenhe',
'icon-zhongduan',
'icon-hexinzichan',
'icon-yishenhe',
'icon-wangluoshebei',
'icon-quanbuzichan',
'icon-gongjizhe',
'icon-shouhaizhe',
'icon-zhongwei',
'icon-gaowei',
'icon-diwei',
'icon-daichuzhishijianzongshu',
'icon-wakuang',
'icon-shijianzongshu',
'icon-shixianzhujigeshu',
'icon-APTshijian',
'icon-yichangliuliang',
'icon-jiangshizhuji',
'icon-weixieqingbao',
'icon-eyichengxu',
'icon-siyoudizhi',
'icon-WEBweihu',
'icon-zhenchagenzong',
'icon-wuqigoujian',
'icon-zaihetoudi',
'icon-loudongliyong',
'icon-anzhuangzhiru',
'icon-minglingyukongzhi',
'icon-mubiaodacheng',
'icon-henjiqingli',
'icon-wenjianjia',
]
const direction = [
'StepBackward',
'StepForward',
'FastBackward',
'FastForward',
'Shrink',
'ArrowsAlt',
'Down',
'Up',
'Left',
'Right',
'CaretUp',
'CaretDown',
'CaretLeft',
'CaretRight',
'UpCircle',
'DownCircle',
'LeftCircle',
'RightCircle',
'DoubleRight',
'DoubleLeft',
'VerticalLeft',
'VerticalRight',
'VerticalAlignTop',
'VerticalAlignMiddle',
'VerticalAlignBottom',
'Forward',
'Backward',
'Rollback',
'Enter',
'Retweet',
'Swap',
'SwapLeft',
'SwapRight',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'PlayCircle',
'UpSquare',
'DownSquare',
'LeftSquare',
'RightSquare',
'Login',
'Logout',
'MenuFold',
'MenuUnfold',
'BorderBottom',
'BorderHorizontal',
'BorderInner',
'BorderOuter',
'BorderLeft',
'BorderRight',
'BorderTop',
'BorderVerticle',
'PicCenter',
'PicLeft',
'PicRight',
'RadiusBottomleft',
'RadiusBottomright',
'RadiusUpleft',
'RadiusUpright',
'Fullscreen',
'FullscreenExit',
];
const suggestion = [
'Question',
'QuestionCircle',
'Plus',
'PlusCircle',
'Pause',
'PauseCircle',
'Minus',
'MinusCircle',
'PlusSquare',
'MinusSquare',
'Article',
'InfoCircle',
'Exclamation',
'ExclamationCircle',
'Close',
'CloseCircle',
'CloseSquare',
'Check',
'CheckCircle',
'CheckSquare',
'ClockCircle',
'Warning',
'IssuesClose',
'Stop',
];
const editor = [
'Edit',
'Form',
'Copy',
'Scissor',
'Delete',
'Snippets',
'Diff',
'Highlight',
'AlignCenter',
'AlignLeft',
'AlignRight',
'BgColors',
'Bold',
'Italic',
'Underline',
'Strikethrough',
'Redo',
'Undo',
'ZoomIn',
'ZoomOut',
'FontColors',
'FontSize',
'LineHeight',
'Dash',
'SmallDash',
'SortAscending',
'SortDescending',
'Drag',
'OrderedList',
'UnorderedList',
'RadiusSetting',
'ColumnWidth',
'ColumnHeight',
];
const data = [
'AreaChart',
'PieChart',
'BarChart',
'DotChart',
'LineChart',
'RadarChart',
'HeatMap',
'Fall',
'Rise',
'Stock',
'BoxPlot',
'Fund',
'Sliders',
];
const logo = [
'Android',
'Apple',
'Windows',
'Ie',
'Chrome',
'Github',
'Aliwangwang',
'Dingding',
'WeiboSquare',
'WeiboCircle',
'TaobaoCircle',
'Html5',
'Weibo',
'Twitter',
'Wechat',
'WhatsApp',
'Youtube',
'AlipayCircle',
'Taobao',
'Dingtalk',
'Skype',
'Qq',
'MediumWorkmark',
'Gitlab',
'Medium',
'Linkedin',
'GooglePlus',
'Dropbox',
'Facebook',
'Codepen',
'CodeSandbox',
'CodeSandboxCircle',
'Amazon',
'Google',
'CodepenCircle',
'Alipay',
'AntDesign',
'AntCloud',
'Aliyun',
'Zhihu',
'Slack',
'SlackSquare',
'Behance',
'BehanceSquare',
'Dribbble',
'DribbbleSquare',
'Instagram',
'Yuque',
'Alibaba',
'Yahoo',
'Reddit',
'Sketch',
];
const other = [...direction, ...suggestion, ...editor,...data, ...logo, ...useIcons]
const allIcons = [...antdIcons, ...useIcons];
const directionIcons = allIcons.filter(icon => direction.includes(icon.replace(/(Outlined|Filled)$/, '')));
const suggestionIcons = allIcons.filter(icon => suggestion.includes(icon.replace(/(Outlined|Filled)$/, '')));
const editorIcons = allIcons.filter(icon => editor.includes(icon.replace(/(Outlined|Filled)$/, '')));
const dataIcons = allIcons.filter(icon => data.includes(icon.replace(/(Outlined|Filled)$/, '')));
const logoIcons = allIcons.filter(icon => logo.includes(icon.replace(/(Outlined|Filled)$/, '')));
const otherIcons = allIcons.filter(icon => !other.includes(icon.replace(/(Outlined|Filled)$/, '')));
export const categories = {
allIcons,
useIcons,
directionIcons,
suggestionIcons,
editorIcons,
dataIcons,
logoIcons,
otherIcons,
};
// 阿里iconfont链接,也可以按需要指定
export const oauthScriptUrl = [
'//at.alicdn.com/t/c/font_4413039_6ow46w95lhw.js'
];
export type Categories = typeof categories;
export type CategoriesKeys = keyof Categories;
+267
View File
@@ -0,0 +1,267 @@
import axios, {type AxiosRequestConfig, type AxiosResponse} from "axios";
import type {AxiosInstance} from "axios";
import type {NotificationArgsProps} from "antd";
// 全局变量声明
window.requests = [];
window.tokenRefreshing = false;
// 请求去重映射表
const pendingMap = new Map<string, AbortController>();
// 配置常量
const REQUEST_TIMEOUT = 10000; // 请求超时时间
const LOGIN_PATH = '/login'; // 登录页面路径
/**
* 添加请求到待处理映射表,实现请求去重
* @param config - Axios 请求配置
*/
function addPending(config: AxiosRequestConfig): void {
const pendingKey = getPendingKey(config);
if (!config.signal) {
const controller = new AbortController();
config.signal = controller.signal;
// 只在不存在时添加,避免重复请求
if (!pendingMap.has(pendingKey)) {
pendingMap.set(pendingKey, controller);
}
}
}
/**
* 移除待处理的请求,取消重复请求
* @param config - Axios 请求配置
*/
function removePending(config: AxiosRequestConfig): void {
const pendingKey = getPendingKey(config);
if (pendingMap.has(pendingKey)) {
const controller = pendingMap.get(pendingKey);
controller?.abort();
pendingMap.delete(pendingKey);
}
}
/**
* 生成每个请求的唯一标识键
* @param config - Axios 请求配置
* @returns 请求的唯一标识字符串
*/
function getPendingKey(config: AxiosRequestConfig): string {
let { data } = config;
const { url, method, params, headers } = config;
// 处理响应中返回的字符串格式 data
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) {
// 如果解析失败,保持原值
console.warn('Failed to parse request data:', e);
}
}
// 安全获取 header 值
const headersObj = headers as Record<string, any> || {};
const language = headersObj['User-Language'] || headersObj['i18nextLng'] || '';
const auth = headersObj['Authorization'] || '';
return [
url || '',
method || '',
language,
auth,
JSON.stringify(params || {}),
JSON.stringify(data || {}),
].join('&');
}
/**
* 处理网络错误(HTTP 状态码错误)
* @param errStatus - HTTP 状态码
*/
const handleNetworkError = async (errStatus?: number): Promise<void> => {
if (!errStatus) {
window.$message?.error("无法连接到服务器!");
return;
}
// 401 未授权 - 需要特殊处理,清除认证信息并跳转登录
if (errStatus === 401) {
window.$message?.error('您未登录,或者登录已经超时,请先登录!');
// 清除本地存储的认证信息
localStorage.removeItem('token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('auth-storage');
// 避免在登录页重复跳转
if (window.location.pathname !== LOGIN_PATH) {
window.location.href = LOGIN_PATH;
}
return;
}
// HTTP 状态码错误消息映射表
const errorMessages: Record<number, string> = {
302: '接口重定向了!',
400: '参数不正确!',
403: '您没有权限操作!',
404: '请求错误,未找到该资源!',
408: '请求超时!',
409: '系统已存在相同数据!',
500: '服务器内部错误!',
501: '服务未实现!',
502: '网关错误!',
503: '服务不可用!',
504: '服务暂时无法访问,请稍后再试!',
505: 'HTTP版本不受支持!',
};
const messageString = errorMessages[errStatus] || `连接错误 (状态码: ${errStatus})`;
window.$message?.error(messageString);
};
/**
* 处理业务错误(接口返回的业务逻辑错误)
* @param data - 接口返回的数据结构
*/
const handleBusinessError = async (data: API.ResponseStructure<any>): Promise<void> => {
const { msg = '', showType = 0, description = '', placement = 'topRight' } = data;
// 通知配置
const notificationProps: NotificationArgsProps = {
message: msg,
description: description,
placement: placement
};
// 根据 showType 展示不同类型的提示
// 0-2: Message 提示; 3-5: Notification 通知; 99: 静默处理
switch (showType) {
case 99: // 静默处理,不显示任何提示
break;
case 0: // Message 成功提示
if (msg) window.$message?.success(msg);
break;
case 1: // Message 警告提示
if (msg) window.$message?.warning(msg);
break;
case 2: // Message 错误提示
if (msg) window.$message?.error(msg);
break;
case 3: // Notification 成功通知
if (msg) window.$notification?.success(notificationProps);
break;
case 4: // Notification 警告通知
if (msg) window.$notification?.warning(notificationProps);
break;
case 5: // Notification 错误通知
if (msg) window.$notification?.error(notificationProps);
break;
default: // 默认使用 Message 错误提示
if (msg) window.$message?.error(msg);
}
};
/**
* 创建并配置 Axios 实例
* @param axiosConfig - Axios 请求配置
* @returns Promise<AxiosResponse<T>>
*/
function createAxios<Data, T = API.ResponseStructure<Data>>(
axiosConfig: AxiosRequestConfig
): Promise<AxiosResponse<T>> {
// 创建 axios 实例
const instance: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_BASE_URL || '',
timeout: REQUEST_TIMEOUT,
responseType: 'json',
// 允许携带凭证
withCredentials: false,
});
// ==================== 请求拦截器 ====================
instance.interceptors.request.use(
(config) => {
// 请求去重:移除重复的待处理请求
removePending(config);
// 将当前请求添加到待处理映射表
addPending(config);
// 确保 headers 存在
config.headers = config.headers || {};
// 自动附加国际化语言到请求头
const currentLanguage = localStorage.getItem('i18nextLng');
if (currentLanguage) {
config.headers['User-Language'] = currentLanguage;
}
// 自动附加 Token 到请求头(排除登录接口)
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
(error) => {
// 请求错误处理
console.error('Request Error:', error);
return Promise.reject(error);
}
);
// ==================== 响应拦截器 ====================
instance.interceptors.response.use(
async (response) => {
// 请求完成后,从待处理映射表中移除
removePending(response.config);
const { data } = response;
// 业务成功,直接返回
if (data?.success) {
return Promise.resolve(response);
}
// 业务失败,统一处理业务错误
await handleBusinessError(data);
return Promise.reject(response);
},
async (err) => {
// 请求被取消(去重或手动取消)
if (axios.isCancel(err)) {
console.warn('请求已取消:', err.message);
return Promise.reject(err);
}
// 有响应但状态码错误(4xx, 5xx)
if (err.response) {
removePending(err.response.config);
await handleNetworkError(err.response.status);
return Promise.reject(err.response);
}
// 网络错误或超时(无响应)
if (err.code === 'ECONNABORTED') {
window.$message?.error('请求超时,请稍后重试!');
} else if (err.message?.includes('Network Error')) {
window.$message?.error('网络连接失败,请检查网络!');
} else {
window.$message?.error(`网络错误: ${err.message || '未知错误'}`);
}
return Promise.reject(err);
}
);
// 执行请求并返回
return instance(axiosConfig) as Promise<AxiosResponse<T>>;
}
export default createAxios;