feat: 初始化财务系统基础架构
新增财务系统基础架构,包括: - 核心API接口(用户、菜单、权限) - 状态管理模块(系统字典) - 路由配置(仪表盘、系统管理) - 基础页面布局(登录、个人中心、错误页) - 国际化配置 - 环境变量配置 - 组件库集成(表格、表单、图表) - 构建工具配置(vite、tailwind) - 权限控制模块
This commit is contained in:
69
apps/finance/src/api/core/auth.ts
Normal file
69
apps/finance/src/api/core/auth.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { BasicUserInfo } from '@vben-core/typings';
|
||||
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface UserInfo extends BasicUserInfo {
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
lastTime: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult<T, U> {
|
||||
userToken: T;
|
||||
userEntity: U;
|
||||
}
|
||||
|
||||
export interface UserToken {
|
||||
token: string;
|
||||
userId: string;
|
||||
expireTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult<AuthApi.UserToken, AuthApi.UserInfo>>('/auth/login', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
*/
|
||||
export async function getAccessCodesApi() {
|
||||
// return requestClient.get<string[]>('/auth/codes');
|
||||
return [];
|
||||
}
|
||||
3
apps/finance/src/api/core/index.ts
Normal file
3
apps/finance/src/api/core/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './menu';
|
||||
export * from './user';
|
||||
10
apps/finance/src/api/core/menu.ts
Normal file
10
apps/finance/src/api/core/menu.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { RouteRecordStringComponent } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
export async function getAllMenusApi() {
|
||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
||||
}
|
||||
10
apps/finance/src/api/core/user.ts
Normal file
10
apps/finance/src/api/core/user.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { UserInfo } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
}
|
||||
1
apps/finance/src/api/index.ts
Normal file
1
apps/finance/src/api/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './core';
|
||||
24
apps/finance/src/api/permission/user.ts
Normal file
24
apps/finance/src/api/permission/user.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export const userApi = {
|
||||
/**
|
||||
* 获取文件列表
|
||||
*/
|
||||
getUserList: (data: any) => {
|
||||
return requestClient.post('common/sysUser/getSysUserList', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
*/
|
||||
createUser: (data: any) => {
|
||||
return requestClient.post('common/sysUser/addSysUser', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改用户
|
||||
*/
|
||||
updateUser: (data: any) => {
|
||||
return requestClient.post('common/user/addUser', data);
|
||||
},
|
||||
};
|
||||
223
apps/finance/src/api/request.ts
Normal file
223
apps/finance/src/api/request.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* 该文件可自行根据业务逻辑进行调整
|
||||
*/
|
||||
import type { RequestClientOptions } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
defaultResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
RequestClient,
|
||||
} from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||
const client = new RequestClient({
|
||||
...options,
|
||||
baseURL,
|
||||
});
|
||||
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
async function doReAuthenticate() {
|
||||
console.warn('Access token or refresh token is invalid or expired. ');
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
|
||||
if (preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked) {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
// 显示登录过期提示
|
||||
message.error({
|
||||
content: '您的登录状态已过期,请重新登录',
|
||||
duration: 3,
|
||||
});
|
||||
// 短暂延迟后跳转,让用户看到提示
|
||||
setTimeout(() => {
|
||||
authStore.logout();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token逻辑
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
const accessStore = useAccessStore();
|
||||
const resp = await refreshTokenApi();
|
||||
const newToken = resp.data;
|
||||
accessStore.setAccessToken(newToken);
|
||||
return newToken;
|
||||
}
|
||||
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `${token}` : null;
|
||||
}
|
||||
|
||||
// 处理请求参数中的undefined值,将其替换为空字符串
|
||||
function handleUndefinedParams(params: any): any {
|
||||
if (!params || typeof params !== 'object') {
|
||||
return params;
|
||||
}
|
||||
|
||||
const handleUndefined = (obj: any): any => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => handleUndefined(item));
|
||||
}
|
||||
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
const result: any = {};
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const value = obj[key];
|
||||
if (value === undefined) {
|
||||
result[key] = '';
|
||||
} else if (value !== null && typeof value === 'object') {
|
||||
result[key] = handleUndefined(value);
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
return handleUndefined(params);
|
||||
}
|
||||
|
||||
// 请求头处理
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: async (config) => {
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
config.headers.token = formatToken(accessStore.accessToken);
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
|
||||
// 处理请求参数中的undefined值
|
||||
if (config.params) {
|
||||
config.params = handleUndefinedParams(config.params);
|
||||
}
|
||||
|
||||
// 处理请求体中的undefined值
|
||||
if (config.data && !(config.data instanceof FormData)) {
|
||||
// 如果是FormData,不进行处理,避免丢失参数
|
||||
// FormData不需要处理undefined值,因为浏览器会自动处理
|
||||
config.data = handleUndefinedParams(config.data);
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
});
|
||||
|
||||
// 自定义拦截器,处理后端返回的status为200但code不为0的情况
|
||||
client.addResponseInterceptor({
|
||||
fulfilled: (response) => {
|
||||
const { data: responseData, status } = response;
|
||||
|
||||
// 检查HTTP状态码为200但响应数据中code不为0的情况
|
||||
if (status === 200 && responseData?.code !== 0) {
|
||||
// 创建一个错误对象,模拟HTTP错误响应
|
||||
const error = new Error(`Request failed with code ${responseData.code}`);
|
||||
Object.assign(error, {
|
||||
response: {
|
||||
...response,
|
||||
status: responseData.code, // 将后端返回的code作为HTTP状态码
|
||||
data: responseData,
|
||||
},
|
||||
});
|
||||
|
||||
// 对于特定的错误码,执行特殊处理
|
||||
if (responseData.code === 401) {
|
||||
// 登录失效的情况,触发重新认证逻辑
|
||||
// 不要等待doReAuthenticate完成,因为它会跳转页面,不需要等待
|
||||
doReAuthenticate().catch((error_) => {
|
||||
console.error('doReAuthenticate error:', error_);
|
||||
});
|
||||
// 对于401错误,不抛出错误,而是返回一个特殊标记的响应
|
||||
// 这样可以避免控制台显示未捕获的错误
|
||||
return {
|
||||
...response,
|
||||
__isLoginExpired: true,
|
||||
} as any;
|
||||
}
|
||||
|
||||
// 抛出错误,让后续的错误处理拦截器处理
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 对于其他情况,正常返回响应
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
// 处理返回的响应数据格式
|
||||
client.addResponseInterceptor(
|
||||
defaultResponseInterceptor({
|
||||
codeField: 'code',
|
||||
dataField: 'data',
|
||||
successCode: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.message ?? responseData?.msg ?? '';
|
||||
const responseCode = error?.response?.status ?? responseData?.code;
|
||||
|
||||
// 如果是401错误,已经在自定义拦截器中处理了,这里不需要额外提示
|
||||
if (responseCode === 401) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// 如果有后端返回的错误信息,优先显示
|
||||
if (errorMessage) {
|
||||
message.error(errorMessage);
|
||||
} else if (msg) {
|
||||
// 否则显示默认的错误信息
|
||||
message.error(msg);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL, {
|
||||
responseReturn: 'data',
|
||||
timeout: 0, // 设置为0表示没有超时时间限制
|
||||
});
|
||||
|
||||
export const baseRequestClient = new RequestClient({
|
||||
baseURL: apiURL,
|
||||
timeout: 0, // 设置为0表示没有超时时间限制
|
||||
});
|
||||
Reference in New Issue
Block a user