feat(project): 添加vben5前端

This commit is contained in:
wcg
2026-01-04 13:45:07 +08:00
parent 2c0689fe02
commit 51ee3fb460
839 changed files with 74231 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
PORT=5320
ACCESS_TOKEN_SECRET=access_token_secret
REFRESH_TOKEN_SECRET=refresh_token_secret

View File

@@ -0,0 +1,15 @@
# @vben/backend-mock
## Description
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
## Running the app
```bash
# development
$ pnpm run start
# production mode
$ pnpm run build
```

View File

@@ -0,0 +1,14 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const codes =
MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
return useResponseSuccess(codes);
});

View File

@@ -0,0 +1,36 @@
import {
clearRefreshTokenCookie,
setRefreshTokenCookie,
} from '~/utils/cookie-utils';
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
import { forbiddenResponse } from '~/utils/response';
export default defineEventHandler(async (event) => {
const { password, username } = await readBody(event);
if (!password || !username) {
setResponseStatus(event, 400);
return useResponseError(
'BadRequestException',
'Username and password are required',
);
}
const findUser = MOCK_USERS.find(
(item) => item.username === username && item.password === password,
);
if (!findUser) {
clearRefreshTokenCookie(event);
return forbiddenResponse(event, 'Username or password is incorrect.');
}
const accessToken = generateAccessToken(findUser);
const refreshToken = generateRefreshToken(findUser);
setRefreshTokenCookie(event, refreshToken);
return useResponseSuccess({
...findUser,
accessToken,
});
});

View File

@@ -0,0 +1,15 @@
import {
clearRefreshTokenCookie,
getRefreshTokenFromCookie,
} from '~/utils/cookie-utils';
export default defineEventHandler(async (event) => {
const refreshToken = getRefreshTokenFromCookie(event);
if (!refreshToken) {
return useResponseSuccess('');
}
clearRefreshTokenCookie(event);
return useResponseSuccess('');
});

View File

@@ -0,0 +1,33 @@
import {
clearRefreshTokenCookie,
getRefreshTokenFromCookie,
setRefreshTokenCookie,
} from '~/utils/cookie-utils';
import { verifyRefreshToken } from '~/utils/jwt-utils';
import { forbiddenResponse } from '~/utils/response';
export default defineEventHandler(async (event) => {
const refreshToken = getRefreshTokenFromCookie(event);
if (!refreshToken) {
return forbiddenResponse(event);
}
clearRefreshTokenCookie(event);
const userinfo = verifyRefreshToken(refreshToken);
if (!userinfo) {
return forbiddenResponse(event);
}
const findUser = MOCK_USERS.find(
(item) => item.username === userinfo.username,
);
if (!findUser) {
return forbiddenResponse(event);
}
const accessToken = generateAccessToken(findUser);
setRefreshTokenCookie(event, refreshToken);
return accessToken;
});

View File

@@ -0,0 +1,28 @@
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const data = `
{
"code": 0,
"message": "success",
"data": [
{
"id": 123456789012345678901234567890123456789012345678901234567890,
"name": "John Doe",
"age": 30,
"email": "john-doe@demo.com"
},
{
"id": 987654321098765432109876543210987654321098765432109876543210,
"name": "Jane Smith",
"age": 25,
"email": "jane@demo.com"
}
]
}
`;
setHeader(event, 'Content-Type', 'application/json');
return data;
});

View File

@@ -0,0 +1,13 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const menus =
MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
return useResponseSuccess(menus);
});

View File

@@ -0,0 +1,5 @@
export default eventHandler((event) => {
const { status } = getQuery(event);
setResponseStatus(event, Number(status));
return useResponseError(`${status}`);
});

View File

@@ -0,0 +1,15 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(600);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,15 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(1000);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,15 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import {
sleep,
unAuthorizedResponse,
useResponseSuccess,
} from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(2000);
return useResponseSuccess(null);
});

View File

@@ -0,0 +1,61 @@
import { faker } from '@faker-js/faker';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
pid: 0,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
),
remark: faker.lorem.sentence(),
};
if (faker.datatype.boolean()) {
dataItem.children = Array.from(
{ length: faker.number.int({ min: 1, max: 5 }) },
() => ({
id: faker.string.uuid(),
pid: dataItem.id,
name: faker.commerce.department(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
),
remark: faker.lorem.sentence(),
}),
);
}
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(10);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const listData = structuredClone(mockData);
return useResponseSuccess(listData);
});

View File

@@ -0,0 +1,12 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(MOCK_MENU_LIST);
});

View File

@@ -0,0 +1,28 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse } from '~/utils/response';
const namesMap: Record<string, any> = {};
function getNames(menus: any[]) {
menus.forEach((menu) => {
namesMap[menu.name] = String(menu.id);
if (menu.children) {
getNames(menu.children);
}
});
}
getNames(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, name } = getQuery(event);
return (name as string) in namesMap &&
(!id || namesMap[name as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});

View File

@@ -0,0 +1,28 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse } from '~/utils/response';
const pathMap: Record<string, any> = { '/': 0 };
function getPaths(menus: any[]) {
menus.forEach((menu) => {
pathMap[menu.path] = String(menu.id);
if (menu.children) {
getPaths(menu.children);
}
});
}
getPaths(MOCK_MENU_LIST);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { id, path } = getQuery(event);
return (path as string) in pathMap &&
(!id || pathMap[path as string] !== String(id))
? useResponseSuccess(true)
: useResponseSuccess(false);
});

View File

@@ -0,0 +1,83 @@
import { faker } from '@faker-js/faker';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
const menuIds = getMenuIds(MOCK_MENU_LIST);
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem: Record<string, any> = {
id: faker.string.uuid(),
name: faker.commerce.product(),
status: faker.helpers.arrayElement([0, 1]),
createTime: formatterCN.format(
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
),
permissions: faker.helpers.arrayElements(menuIds),
remark: faker.lorem.sentence(),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const {
page = 1,
MaxResultCount = 20,
name,
id,
remark,
startTime,
endTime,
status,
} = getQuery(event);
let listData = structuredClone(mockData);
if (name) {
listData = listData.filter((item) =>
item.name.toLowerCase().includes(String(name).toLowerCase()),
);
}
if (id) {
listData = listData.filter((item) =>
item.id.toLowerCase().includes(String(id).toLowerCase()),
);
}
if (remark) {
listData = listData.filter((item) =>
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
);
}
if (startTime) {
listData = listData.filter((item) => item.createTime >= startTime);
}
if (endTime) {
listData = listData.filter((item) => item.createTime <= endTime);
}
if (['0', '1'].includes(status as string)) {
listData = listData.filter((item) => item.status === Number(status));
}
return usePageResponseSuccess(page as string, MaxResultCount as string, listData);
});

View File

@@ -0,0 +1,73 @@
import { faker } from '@faker-js/faker';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
function generateMockDataList(count: number) {
const dataList = [];
for (let i = 0; i < count; i++) {
const dataItem = {
id: faker.string.uuid(),
imageUrl: faker.image.avatar(),
imageUrl2: faker.image.avatar(),
open: faker.datatype.boolean(),
status: faker.helpers.arrayElement(['success', 'error', 'warning']),
productName: faker.commerce.productName(),
price: faker.commerce.price(),
currency: faker.finance.currencyCode(),
quantity: faker.number.int({ min: 1, max: 100 }),
available: faker.datatype.boolean(),
category: faker.commerce.department(),
releaseDate: faker.date.past(),
rating: faker.number.float({ min: 1, max: 5 }),
description: faker.commerce.productDescription(),
weight: faker.number.float({ min: 0.1, max: 10 }),
color: faker.color.human(),
inProduction: faker.datatype.boolean(),
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
};
dataList.push(dataItem);
}
return dataList;
}
const mockData = generateMockDataList(100);
export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
await sleep(600);
const { page, MaxResultCount, sortBy, sortOrder } = getQuery(event);
const listData = structuredClone(mockData);
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
listData.sort((a, b) => {
if (sortOrder === 'asc') {
if (sortBy === 'price') {
return (
Number.parseFloat(a[sortBy as string]) -
Number.parseFloat(b[sortBy as string])
);
} else {
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
}
} else {
if (sortBy === 'price') {
return (
Number.parseFloat(b[sortBy as string]) -
Number.parseFloat(a[sortBy as string])
);
} else {
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
}
}
});
}
return usePageResponseSuccess(page as string, MaxResultCount as string, listData);
});

View File

@@ -0,0 +1 @@
export default defineEventHandler(() => 'Test get handler');

View File

@@ -0,0 +1 @@
export default defineEventHandler(() => 'Test post handler');

View File

@@ -0,0 +1,13 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess({
url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
});
// return useResponseError("test")
});

View File

@@ -0,0 +1,10 @@
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse } from '~/utils/response';
export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(userinfo);
});

View File

@@ -0,0 +1,7 @@
import type { NitroErrorHandler } from 'nitropack';
const errorHandler: NitroErrorHandler = function (error, event) {
event.node.res.end(`[Error Handler] ${error.stack}`);
};
export default errorHandler;

View File

@@ -0,0 +1,19 @@
import { forbiddenResponse, sleep } from '~/utils/response';
export default defineEventHandler(async (event) => {
event.node.res.setHeader(
'Access-Control-Allow-Origin',
event.headers.get('Origin') ?? '*',
);
if (event.method === 'OPTIONS') {
event.node.res.statusCode = 204;
event.node.res.statusMessage = 'No Content.';
return 'OK';
} else if (
['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) &&
event.path.startsWith('/api/')
) {
await sleep(Math.floor(Math.random() * 2000));
return forbiddenResponse(event, '演示环境,禁止修改');
}
});

View File

@@ -0,0 +1,20 @@
import errorHandler from './error';
process.env.COMPATIBILITY_DATE = new Date().toISOString();
export default defineNitroConfig({
devErrorHandler: errorHandler,
errorHandler: '~/error',
routeRules: {
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers':
'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': '*',
},
},
},
});

View File

@@ -0,0 +1,21 @@
{
"name": "@vben/backend-mock",
"version": "0.0.1",
"description": "",
"private": true,
"license": "MIT",
"author": "",
"scripts": {
"build": "nitro build",
"start": "nitro dev"
},
"dependencies": {
"@faker-js/faker": "catalog:",
"jsonwebtoken": "catalog:",
"nitropack": "catalog:"
},
"devDependencies": {
"@types/jsonwebtoken": "catalog:",
"h3": "catalog:"
}
}

View File

@@ -0,0 +1,13 @@
export default defineEventHandler(() => {
return `
<h1>Hello Vben Admin</h1>
<h2>Mock service is starting</h2>
<ul>
<li><a href="/api/user">/api/user/info</a></li>
<li><a href="/api/menu">/api/menu/all</a></li>
<li><a href="/api/auth/codes">/api/auth/codes</a></li>
<li><a href="/api/auth/login">/api/auth/login</a></li>
<li><a href="/api/upload">/api/upload</a></li>
</ul>
`;
});

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@@ -0,0 +1,3 @@
{
"extends": "./.nitro/types/tsconfig.json"
}

View File

@@ -0,0 +1,26 @@
import type { EventHandlerRequest, H3Event } from 'h3';
export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
deleteCookie(event, 'jwt', {
httpOnly: true,
sameSite: 'none',
secure: true,
});
}
export function setRefreshTokenCookie(
event: H3Event<EventHandlerRequest>,
refreshToken: string,
) {
setCookie(event, 'jwt', refreshToken, {
httpOnly: true,
maxAge: 24 * 60 * 60, // unit: seconds
sameSite: 'none',
secure: true,
});
}
export function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) {
const refreshToken = getCookie(event, 'jwt');
return refreshToken;
}

View File

@@ -0,0 +1,59 @@
import type { EventHandlerRequest, H3Event } from 'h3';
import jwt from 'jsonwebtoken';
import { UserInfo } from './mock-data';
// TODO: Replace with your own secret key
const ACCESS_TOKEN_SECRET = 'access_token_secret';
const REFRESH_TOKEN_SECRET = 'refresh_token_secret';
export interface UserPayload extends UserInfo {
iat: number;
exp: number;
}
export function generateAccessToken(user: UserInfo) {
return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' });
}
export function generateRefreshToken(user: UserInfo) {
return jwt.sign(user, REFRESH_TOKEN_SECRET, {
expiresIn: '30d',
});
}
export function verifyAccessToken(
event: H3Event<EventHandlerRequest>,
): null | Omit<UserInfo, 'password'> {
const authHeader = getHeader(event, 'Authorization');
if (!authHeader?.startsWith('Bearer')) {
return null;
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload;
const username = decoded.username;
const user = MOCK_USERS.find((item) => item.username === username);
const { password: _pwd, ...userinfo } = user;
return userinfo;
} catch {
return null;
}
}
export function verifyRefreshToken(
token: string,
): null | Omit<UserInfo, 'password'> {
try {
const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
const username = decoded.username;
const user = MOCK_USERS.find((item) => item.username === username);
const { password: _pwd, ...userinfo } = user;
return userinfo;
} catch {
return null;
}
}

View File

@@ -0,0 +1,390 @@
export interface UserInfo {
id: number;
password: string;
realName: string;
roles: string[];
username: string;
homePath?: string;
}
export const MOCK_USERS: UserInfo[] = [
{
id: 0,
password: '123456',
realName: 'Vben',
roles: ['super'],
username: 'vben',
},
{
id: 1,
password: '123456',
realName: 'Admin',
roles: ['admin'],
username: 'admin',
homePath: '/workspace',
},
{
id: 2,
password: '123456',
realName: 'Jack',
roles: ['user'],
username: 'jack',
homePath: '/analytics',
},
];
export const MOCK_CODES = [
// super
{
codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
username: 'vben',
},
{
// admin
codes: ['AC_100010', 'AC_100020', 'AC_100030'],
username: 'admin',
},
{
// user
codes: ['AC_1000001', 'AC_1000002'],
username: 'jack',
},
];
const dashboardMenus = [
{
meta: {
order: -1,
title: 'page.dashboard.title',
},
name: 'Dashboard',
path: '/dashboard',
redirect: '/analytics',
children: [
{
name: 'Analytics',
path: '/analytics',
component: '/dashboard/analytics/index',
meta: {
affixTab: true,
title: 'page.dashboard.analytics',
},
},
{
name: 'Workspace',
path: '/workspace',
component: '/dashboard/workspace/index',
meta: {
title: 'page.dashboard.workspace',
},
},
],
},
];
const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
const roleWithMenus = {
admin: {
component: '/demos/access/admin-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.adminVisible',
},
name: 'AccessAdminVisibleDemo',
path: '/demos/access/admin-visible',
},
super: {
component: '/demos/access/super-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.superVisible',
},
name: 'AccessSuperVisibleDemo',
path: '/demos/access/super-visible',
},
user: {
component: '/demos/access/user-visible',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.userVisible',
},
name: 'AccessUserVisibleDemo',
path: '/demos/access/user-visible',
},
};
return [
{
meta: {
icon: 'ic:baseline-view-in-ar',
keepAlive: true,
order: 1000,
title: 'demos.title',
},
name: 'Demos',
path: '/demos',
redirect: '/demos/access',
children: [
{
name: 'AccessDemos',
path: '/demosaccess',
meta: {
icon: 'mdi:cloud-key-outline',
title: 'demos.access.backendPermissions',
},
redirect: '/demos/access/page-control',
children: [
{
name: 'AccessPageControlDemo',
path: '/demos/access/page-control',
component: '/demos/access/index',
meta: {
icon: 'mdi:page-previous-outline',
title: 'demos.access.pageAccess',
},
},
{
name: 'AccessButtonControlDemo',
path: '/demos/access/button-control',
component: '/demos/access/button-control',
meta: {
icon: 'mdi:button-cursor',
title: 'demos.access.buttonControl',
},
},
{
name: 'AccessMenuVisible403Demo',
path: '/demos/access/menu-visible-403',
component: '/demos/access/menu-visible-403',
meta: {
authority: ['no-body'],
icon: 'mdi:button-cursor',
menuVisibleWithForbidden: true,
title: 'demos.access.menuVisible403',
},
},
roleWithMenus[role],
],
},
],
},
];
};
export const MOCK_MENUS = [
{
menus: [...dashboardMenus, ...createDemosMenus('super')],
username: 'vben',
},
{
menus: [...dashboardMenus, ...createDemosMenus('admin')],
username: 'admin',
},
{
menus: [...dashboardMenus, ...createDemosMenus('user')],
username: 'jack',
},
];
export const MOCK_MENU_LIST = [
{
id: 1,
name: 'Workspace',
status: 1,
type: 'menu',
icon: 'mdi:dashboard',
path: '/workspace',
component: '/dashboard/workspace/index',
meta: {
icon: 'carbon:workspace',
title: 'page.dashboard.workspace',
affixTab: true,
order: 0,
},
},
{
id: 2,
meta: {
icon: 'carbon:settings',
order: 9997,
title: 'system.title',
badge: 'new',
badgeType: 'normal',
badgeVariants: 'primary',
},
status: 1,
type: 'catalog',
name: 'System',
path: '/system',
children: [
{
id: 201,
pid: 2,
path: '/system/menu',
name: 'SystemMenu',
authCode: 'System:Menu:List',
status: 1,
type: 'menu',
meta: {
icon: 'carbon:menu',
title: 'system.menu.title',
},
component: '/system/menu/list',
children: [
{
id: 20_101,
pid: 201,
name: 'SystemMenuCreate',
status: 1,
type: 'button',
authCode: 'System:Menu:Create',
meta: { title: 'common.create' },
},
{
id: 20_102,
pid: 201,
name: 'SystemMenuEdit',
status: 1,
type: 'button',
authCode: 'System:Menu:Edit',
meta: { title: 'common.edit' },
},
{
id: 20_103,
pid: 201,
name: 'SystemMenuDelete',
status: 1,
type: 'button',
authCode: 'System:Menu:Delete',
meta: { title: 'common.delete' },
},
],
},
{
id: 202,
pid: 2,
path: '/system/dept',
name: 'SystemDept',
status: 1,
type: 'menu',
authCode: 'System:Dept:List',
meta: {
icon: 'carbon:container-services',
title: 'system.dept.title',
},
component: '/system/dept/list',
children: [
{
id: 20_401,
pid: 201,
name: 'SystemDeptCreate',
status: 1,
type: 'button',
authCode: 'System:Dept:Create',
meta: { title: 'common.create' },
},
{
id: 20_402,
pid: 201,
name: 'SystemDeptEdit',
status: 1,
type: 'button',
authCode: 'System:Dept:Edit',
meta: { title: 'common.edit' },
},
{
id: 20_403,
pid: 201,
name: 'SystemDeptDelete',
status: 1,
type: 'button',
authCode: 'System:Dept:Delete',
meta: { title: 'common.delete' },
},
],
},
],
},
{
id: 9,
meta: {
badgeType: 'dot',
order: 9998,
title: 'demos.vben.title',
icon: 'carbon:data-center',
},
name: 'Project',
path: '/vben-admin',
type: 'catalog',
status: 1,
children: [
{
id: 901,
pid: 9,
name: 'VbenDocument',
path: '/vben-admin/document',
component: 'IFrameView',
type: 'embedded',
status: 1,
meta: {
icon: 'carbon:book',
iframeSrc: 'https://doc.vben.pro',
title: 'demos.vben.document',
},
},
{
id: 902,
pid: 9,
name: 'VbenGithub',
path: '/vben-admin/github',
component: 'IFrameView',
type: 'link',
status: 1,
meta: {
icon: 'carbon:logo-github',
link: 'https://github.com/vbenjs/vue-vben-admin',
title: 'Github',
},
},
{
id: 903,
pid: 9,
name: 'VbenAntdv',
path: '/vben-admin/antdv',
component: 'IFrameView',
type: 'link',
status: 0,
meta: {
icon: 'carbon:hexagon-vertical-solid',
badgeType: 'dot',
link: 'https://ant.vben.pro',
title: 'demos.vben.antdv',
},
},
],
},
{
id: 10,
component: '_core/about/index',
type: 'menu',
status: 1,
meta: {
icon: 'lucide:copyright',
order: 9999,
title: 'demos.vben.about',
},
name: 'About',
path: '/about',
},
];
export function getMenuIds(menus: any[]) {
const ids: number[] = [];
menus.forEach((item) => {
ids.push(item.id);
if (item.children && item.children.length > 0) {
ids.push(...getMenuIds(item.children));
}
});
return ids;
}

View File

@@ -0,0 +1,68 @@
import type { EventHandlerRequest, H3Event } from 'h3';
export function useResponseSuccess<T = any>(data: T) {
return {
code: 0,
data,
error: null,
message: 'ok',
};
}
export function usePageResponseSuccess<T = any>(
page: number | string,
MaxResultCount: number | string,
list: T[],
{ message = 'ok' } = {},
) {
const pageData = pagination(
Number.parseInt(`${page}`),
Number.parseInt(`${MaxResultCount}`),
list,
);
return {
...useResponseSuccess({
items: pageData,
total: list.length,
}),
message,
};
}
export function useResponseError(message: string, error: any = null) {
return {
code: -1,
data: null,
error,
message,
};
}
export function forbiddenResponse(
event: H3Event<EventHandlerRequest>,
message = 'Forbidden Exception',
) {
setResponseStatus(event, 403);
return useResponseError(message, message);
}
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
setResponseStatus(event, 401);
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
}
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function pagination<T = any>(
pageNo: number,
MaxResultCount: number,
array: T[],
): T[] {
const offset = (pageNo - 1) * Number(MaxResultCount);
return offset + Number(MaxResultCount) >= array.length
? array.slice(offset)
: array.slice(offset, offset + Number(MaxResultCount));
}

View File

@@ -0,0 +1,8 @@
# 应用标题
VITE_APP_TITLE=Yi Admin
# 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=vben-web-antd
# 对store进行加密的密钥在将store持久化到localStorage时会使用该密钥进行加密
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key

View File

@@ -0,0 +1,7 @@
# public path
VITE_BASE=/
# Basic interface address SPA
VITE_GLOB_API_URL=/api/app
VITE_VISUALIZER=true

View File

@@ -0,0 +1,25 @@
# 端口号
VITE_PORT=5666
VITE_BASE=/
# 是否开启 Nitro Mock服务true 为开启false 为关闭
VITE_NITRO_MOCK=false
# 是否打开 devtoolstrue 为打开false 为关闭
VITE_DEVTOOLS=false
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 后台请求路径 具体在vite.config.mts配置代理
# VITE_GLOB_API_URL=http://101.37.70.137:19001/api/app
VITE_GLOB_API_URL=http://192.168.1.101:19001/api/app
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
VITE_GLOB_ENABLE_ENCRYPT=false
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
# 客户端id
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
VITE_GLOB_DEMO_MODE=false

View File

@@ -0,0 +1,35 @@
VITE_BASE=/
# 是否开启压缩,可以设置为 none, brotli, gzip
VITE_COMPRESS=gzip
# 是否开启 PWA
VITE_PWA=false
# vue-router 的模式
VITE_ROUTER_HISTORY=history
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
VITE_ARCHIVER=true
# 后端接口地址
# VITE_GLOB_API_URL=/prod-api
VITE_GLOB_API_URL=https://yiapi.wjys.top/api
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
VITE_GLOB_ENABLE_ENCRYPT=false
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
# 客户端id
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 开启SSE
VITE_GLOB_SSE_ENABLE=false
VITE_GLOB_DEMO_MODE=true

View File

@@ -0,0 +1,35 @@
# 该文件是为了给一个增加环境变量打包的例子
# 对应在根目录package.json -> build:antd:test 命令
VITE_BASE=/
# 是否开启压缩,可以设置为 none, brotli, gzip
VITE_COMPRESS=gzip
# 是否开启 PWA
VITE_PWA=false
# vue-router 的模式
VITE_ROUTER_HISTORY=history
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
VITE_ARCHIVER=true
# 后端接口地址
VITE_GLOB_API_URL=/test-api
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
VITE_GLOB_ENABLE_ENCRYPT=true
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
# 客户端id
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 开启SSE
VITE_GLOB_SSE_ENABLE=true

View File

@@ -0,0 +1,22 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="description" content="A Modern Back-end Management System" />
<meta name="keywords" content="Vben Admin Vue3 Vite" />
<meta name="author" content="Vben" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
<title><%= VITE_APP_TITLE %></title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,64 @@
{
"name": "@vben/web-antd",
"version": "1.4.1",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
"directory": "apps/web-antd"
},
"license": "MIT",
"author": {
"name": "vben",
"email": "ann.vben@gmail.com",
"url": "https://github.com/anncwb"
},
"type": "module",
"scripts": {
"build": "pnpm vite build",
"build:analyze": "pnpm vite build --mode analyze",
"dev": "pnpm vite --mode development",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit --skipLibCheck"
},
"imports": {
"#/*": "./src/*"
},
"dependencies": {
"@ant-design/icons-vue": "^7.0.1",
"@tinymce/tinymce-vue": "^6.0.1",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",
"@vben/constants": "workspace:*",
"@vben/hooks": "workspace:*",
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/plugins": "workspace:*",
"@vben/preferences": "workspace:*",
"@vben/request": "workspace:*",
"@vben/stores": "workspace:*",
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"dayjs": "catalog:",
"echarts": "^5.5.1",
"jsencrypt": "^3.3.2",
"lodash-es": "^4.17.21",
"pinia": "catalog:",
"tinymce": "^7.3.0",
"unplugin-vue-components": "^0.27.3",
"vue": "catalog:",
"vue-router": "catalog:",
"vue3-colorpicker": "^2.3.0"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",
"@types/lodash-es": "^4.17.12"
}
}

View File

@@ -0,0 +1 @@
export { default } from '@vben/tailwind-config/postcss';

View File

@@ -0,0 +1,258 @@
/**
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
*/
import type { Component } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import {
computed,
defineAsyncComponent,
defineComponent,
getCurrentInstance,
h,
ref,
} from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { notification } from 'ant-design-vue';
import { FileUploadOld, ImageUploadOld } from '#/components/upload-old';
const RichTextarea = defineAsyncComponent(() =>
import('#/components/tinymce/index').then((res) => res.Tinymce),
);
const FileUpload = defineAsyncComponent(() =>
import('#/components/upload').then((res) => res.FileUpload),
);
const ImageUpload = defineAsyncComponent(() =>
import('#/components/upload').then((res) => res.ImageUpload),
);
const AutoComplete = defineAsyncComponent(
() => import('ant-design-vue/es/auto-complete'),
);
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
const Cascader = defineAsyncComponent(
() => import('ant-design-vue/es/cascader'),
);
const Checkbox = defineAsyncComponent(
() => import('ant-design-vue/es/checkbox'),
);
const CheckboxGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
);
const DatePicker = defineAsyncComponent(
() => import('ant-design-vue/es/date-picker'),
);
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
const InputNumber = defineAsyncComponent(
() => import('ant-design-vue/es/input-number'),
);
const InputPassword = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('ant-design-vue/es/mentions'),
);
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
const RadioGroup = defineAsyncComponent(() =>
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
);
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
const Textarea = defineAsyncComponent(() =>
import('ant-design-vue/es/input').then((res) => res.Textarea),
);
const TimePicker = defineAsyncComponent(
() => import('ant-design-vue/es/time-picker'),
);
const TreeSelect = defineAsyncComponent(
() => import('ant-design-vue/es/tree-select'),
);
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
componentProps: Recordable<any> = {},
) => {
return defineComponent({
name: component.name,
inheritAttrs: false,
setup: (props: any, { attrs, expose, slots }) => {
// 改为placeholder 解决在keepalive & 语言切换 & tab切换 显示不变的问题
const computedPlaceholder = computed(
() =>
props?.placeholder ||
attrs?.placeholder ||
$t(`ui.placeholder.${type}`),
);
// 透传组件暴露的方法
const innerRef = ref();
const publicApi: Recordable<any> = {};
expose(publicApi);
const instance = getCurrentInstance();
instance?.proxy?.$nextTick(() => {
for (const key in innerRef.value) {
if (typeof innerRef.value[key] === 'function') {
publicApi[key] = innerRef.value[key];
}
}
});
return () =>
h(
component,
{
...componentProps,
placeholder: computedPlaceholder.value,
...props,
...attrs,
ref: innerRef,
},
slots,
);
},
});
};
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
| 'ApiSelect'
| 'ApiTreeSelect'
| 'AutoComplete'
| 'Cascader'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
| 'FileUpload'
| 'FileUploadOld'
| 'IconPicker'
| 'ImageUpload'
| 'ImageUploadOld'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'PrimaryButton'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'RichTextarea'
| 'Select'
| 'Space'
| 'Switch'
| 'Textarea'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
async function initComponentAdapter() {
const components: Partial<Record<ComponentType, Component>> = {
// 如果你的组件体积比较大,可以使用异步加载
// Button: () =>
// import('xxx').then((res) => res.Button),
ApiSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiSelect',
},
'select',
{
component: Select,
loadingSlot: 'suffixIcon',
visibleEvent: 'onDropdownVisibleChange',
modelPropName: 'value',
},
),
ApiTreeSelect: withDefaultPlaceholder(
{
...ApiComponent,
name: 'ApiTreeSelect',
},
'select',
{
component: TreeSelect,
fieldNames: { label: 'label', value: 'value', children: 'children' },
loadingSlot: 'suffixIcon',
modelPropName: 'value',
optionsPropName: 'treeData',
visibleEvent: 'onVisibleChange',
},
),
AutoComplete,
Cascader: withDefaultPlaceholder(Cascader, 'select'),
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认按钮
DefaultButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
iconSlot: 'addonAfter',
inputComponent: Input,
modelValueProp: 'value',
}),
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
// 自定义主要按钮
PrimaryButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Radio,
RadioGroup,
RangePicker,
Rate,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
ImageUpload,
FileUpload,
RichTextarea,
ImageUploadOld,
FileUploadOld,
};
// 将组件注册到全局共享状态中
globalShareState.setComponents(components);
// 定义全局共享状态中的消息提示
globalShareState.defineMessage({
// 复制成功消息提示
copyPreferencesSuccess: (title, content) => {
notification.success({
description: content,
message: title,
placement: 'bottomRight',
});
},
});
}
export { initComponentAdapter };

View File

@@ -0,0 +1,56 @@
import type {
VbenFormSchema as FormSchema,
VbenFormProps,
} from '@vben/common-ui';
import type { ComponentType } from './component';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { isArray } from 'lodash-es';
async function initSetupVbenForm() {
setupVbenForm<ComponentType>({
config: {
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
RichTextarea: 'modelValue',
Switch: 'checked',
Upload: 'fileList',
},
},
defineRules: {
// 输入项目必填国际化适配
required: (value, _params, ctx) => {
if (value === undefined || value === null || value.length === 0) {
return $t('ui.formRules.required', [ctx.label]);
}
return true;
},
// 选择项目必填国际化适配
selectRequired: (value, _params, ctx) => {
if (
[false, null, undefined].includes(value) ||
(isArray(value) && value.length === 0)
) {
return $t('ui.formRules.selectRequired', [ctx.label]);
}
return true;
},
},
});
}
const useVbenForm = useForm<ComponentType>;
export { initSetupVbenForm, useVbenForm, z };
export type VbenFormSchema = FormSchema<ComponentType>;
export type { VbenFormProps };
export type FormSchemaGetter = () => VbenFormSchema[];

View File

@@ -0,0 +1,137 @@
import type { VxeGridPropTypes } from '@vben/plugins/vxe-table';
import { h } from 'vue';
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
import { Button, Image } from 'ant-design-vue';
import { useVbenForm } from './form';
setupVbenVxeTable({
configVxeTable: (vxeUI) => {
vxeUI.setConfig({
grid: {
align: 'center',
border: false,
minHeight: 180,
formConfig: {
// 全局禁用vxe-table的表单配置使用formOptions
enabled: false,
},
proxyConfig: {
autoLoad: true,
response: {
result: 'items',
total: 'totalCount',
list: 'items',
},
showActiveMsg: true,
showResponseMsg: false,
},
// 溢出展示形式
showOverflow: true,
pagerConfig: {
// 默认条数
pageSize: 10,
// 分页可选条数
pageSizes: [10, 20, 30, 40, 50],
},
rowConfig: {
// 鼠标移入行显示 hover 样式
isHover: true,
// 点击行高亮
isCurrent: false,
},
columnConfig: {
// 可拖拽列宽
resizable: true,
},
// 右上角工具栏
toolbarConfig: {
// 自定义列
custom: true,
customOptions: {
icon: 'vxe-icon-setting',
},
// 最大化
zoom: true,
// 刷新
refresh: true,
refreshOptions: {
// 默认为reload 修改为在当前页刷新
code: 'query',
},
},
// 圆角按钮
round: true,
// 表格尺寸
size: 'medium',
customConfig: {
// 表格右上角自定义列配置 是否保存到localStorage
// 必须存在id参数才能使用
storage: false,
},
},
});
// 表格配置项可以用 cellRender: { name: 'CellImage' },
vxeUI.renderer.add('CellImage', {
renderTableDefault(_renderOpts, params) {
const { column, row } = params;
return h(Image, { src: row[column.field] });
},
});
// 表格配置项可以用 cellRender: { name: 'CellLink' },
vxeUI.renderer.add('CellLink', {
renderTableDefault(renderOpts) {
const { props } = renderOpts;
return h(
Button,
{ size: 'small', type: 'link' },
{ default: () => props?.text },
);
},
});
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
// vxeUI.formats.add
},
useVbenForm,
});
export { useVbenVxeGrid };
export type * from '@vben/plugins/vxe-table';
/**
* 判断vxe-table的复选框是否选中
* @param tableApi api
* @returns boolean
*/
export function vxeCheckboxChecked(
tableApi: ReturnType<typeof useVbenVxeGrid>[1],
) {
return tableApi?.grid?.getCheckboxRecords?.()?.length > 0;
}
/**
* 通用的 排序参数添加到请求参数中
* @param params 请求参数
* @param sortList vxe-table的排序参数
*/
export function addSortParams(
params: Record<string, any>,
sortList: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[],
) {
// 这里是排序取消 length为0 就不添加参数了
if (sortList.length === 0) {
return;
}
// 支持单/多字段排序
const orderByColumn = sortList.map((item) => item.field).join(',');
const isAsc = sortList.map((item) => item.order).join(',');
params.orderByColumn = orderByColumn;
params.isAsc = isAsc;
}

View File

@@ -0,0 +1,42 @@
export type ID = number | string;
export type IDS = (number | string)[];
export interface BaseEntity {
createBy?: string;
createDept?: string;
createTime?: string;
updateBy?: string;
updateTime?: string;
}
/**
* 分页信息
* @param rows 结果集
* @param total 总数
*/
export interface PageResult<T = any> {
items: T[];
totalCount: number;
}
/**
* 分页查询参数
*
* 排序支持的用法如下:
* {isAsc:"asc",orderByColumn:"id"} order by id asc
* {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
* {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
* {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
*
* @param SkipCount 当前页
* @param MaxResultCount 每页大小
* @param orderByColumn 排序字段
* @param isAsc 是否升序
*/
export interface PageQuery {
isAsc?: string;
orderByColumn?: string;
SkipCount?: number;
MaxResultCount?: number;
[key: string]: any;
}

View File

@@ -0,0 +1,204 @@
import type { GrantType } from '@vben/common-ui';
import type { HttpResponse } from '@vben/request';
import { h } from 'vue';
import { useAppConfig } from '@vben/hooks';
import { Modal } from 'ant-design-vue';
import { requestClient } from '#/api/request';
const { clientId, sseEnable } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
export namespace AuthApi {
/**
* @description: 所有登录类型都需要用到的
* @param clientId 客户端ID 这里为必填项 但是在loginApi内部处理了 所以为可选
* @param grantType 授权/登录类型
* @param tenantId 租户id
*/
export interface BaseLoginParams {
clientId?: string;
grantType: GrantType;
tenantId: string;
}
/**
* @description: oauth登录需要用到的参数
* @param socialCode 第三方参数
* @param socialState 第三方参数
* @param source 与后端的 justauth.type.xxx的回调地址的source对应
*/
export interface OAuthLoginParams extends BaseLoginParams {
socialCode: string;
socialState: string;
source: string;
}
/**
* @description: 验证码登录需要用到的参数
* @param code 验证码 可选(未开启验证码情况)
* @param uuid 验证码ID 可选(未开启验证码情况)
* @param username 用户名
* @param password 密码
*/
export interface SimpleLoginParams extends BaseLoginParams {
code?: string;
uuid?: string;
username: string;
password: string;
}
export type LoginParams = OAuthLoginParams | SimpleLoginParams;
// /** 登录接口参数 */
// export interface LoginParams {
// code?: string;
// grantType: string;
// password: string;
// tenantId: string;
// username: string;
// uuid?: string;
// }
/** 登录接口返回值 */
export interface LoginResult {
access_token: string;
client_id: string;
expire_in: number;
}
export interface RefreshTokenResult {
data: string;
status: number;
}
}
/**
* 登录
*/
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>(
'/account/login',
{ ...data, clientId },
{
encrypt: true,
},
);
}
/**
* 用户登出
* @returns void
*/
export async function doLogout() {
const resp = await requestClient.post<HttpResponse<void>>(
'account/logout',
null,
{
isTransformResponse: false,
},
);
// 无奈之举 对错误用法的提示
if (resp.code === 401 && import.meta.env.DEV) {
Modal.destroyAll();
Modal.warn({
title: '后端配置出现错误',
centered: true,
content: h('div', { class: 'flex flex-col gap-2' }, [
`检测到你的logout接口返回了401, 导致前端一直进入循环逻辑???`,
...Array.from({ length: 3 }, () =>
h(
'span',
{ class: 'font-bold text-red-500 text-[18px]' },
'去检查你的后端配置!别盯着前端找问题了!这不是前端问题!',
),
),
]),
});
}
}
/**
* 关闭sse连接
* @returns void
*/
export function seeConnectionClose() {
/**
* 未开启sse 不需要处理
*/
if (!sseEnable) {
return;
}
return requestClient.get<void>('/resource/sse/close');
}
/**
* @param companyName 租户/公司名称
* @param domain 绑定域名(不带http(s)://) 可选
* @param tenantId 租户id
*/
export interface TenantOption {
companyName: string;
domain?: string;
tenantId: string;
}
/**
* @param tenantEnabled 是否启用租户
* @param voList 租户列表
*/
export interface TenantResp {
tenantEnabled: boolean;
voList: TenantOption[];
}
/**
* 获取租户列表 下拉框使用
*/
export function tenantList() {
return requestClient.get<TenantResp>('/tenant/select');
}
/**
* vben的 先不删除
* @returns string[]
*/
export async function getAccessCodesApi() {
return requestClient.get<string[]>('/auth/codes');
}
/**
* 绑定第三方账号
* @param source 绑定的来源
* @returns 跳转url
*/
export function authBinding(source: string, tenantId: string) {
return requestClient.get<string>(`/auth/binding/${source}`, {
params: {
domain: window.location.host,
tenantId,
},
});
}
/**
* 取消绑定
* @param id id
*/
export function authUnbinding(id: string) {
return requestClient.deleteWithMsg<void>(`/auth/unlock/${id}`);
}
/**
* oauth授权回调
* @param data oauth授权
* @returns void
*/
export function authCallback(data: AuthApi.OAuthLoginParams) {
return requestClient.post<void>('/auth/social/callback', data);
}

View File

@@ -0,0 +1,42 @@
import { requestClient } from '#/api/request';
/**
* 发送短信验证码
* @param phonenumber 手机号
* @returns void
*/
export function sendSmsCode(phonenumber: string) {
return requestClient.get<void>('/resource/sms/code', {
params: { phonenumber },
});
}
/**
* 发送邮件验证码
* @param email 邮箱
* @returns void
*/
export function sendEmailCode(email: string) {
return requestClient.get<void>('/resource/email/code', {
params: { email },
});
}
/**
* @param img 图片验证码 需要和base64拼接
* @param isEnableCaptcha 是否开启
* @param uuid 验证码ID
*/
export interface CaptchaResponse {
isEnableCaptcha: boolean;
img: string;
uuid: string;
}
/**
* 图片验证码
* @returns resp
*/
export function captchaImage() {
return requestClient.get<CaptchaResponse>('/account/captcha-image');
}

View File

@@ -0,0 +1,4 @@
export * from './auth';
export * from './menu';
export * from './upload';
export * from './user';

View File

@@ -0,0 +1,45 @@
import { requestClient } from '#/api/request';
/**
* @description: 菜单meta
* @param title 菜单名
* @param icon 菜单图标
* @param noCache 是否不缓存
* @param link 外链链接
*/
export interface MenuMeta {
icon: string;
link?: string;
noCache: boolean;
title: string;
}
/**
* @description: 菜单
* @param name 菜单名
* @param path 菜单路径
* @param hidden 是否隐藏
* @param component 组件名称 Layout
* @param alwaysShow 总是显示
* @param query 路由参数(json形式)
* @param meta 路由信息
* @param children 子路由信息
*/
export interface Menu {
alwaysShow?: boolean;
children: Menu[];
component: string;
hidden: boolean;
meta: MenuMeta;
name: string;
path: string;
query?: string;
redirect?: string;
}
/**
* 获取用户所有菜单
*/
export async function getAllMenusApi() {
return requestClient.get<Menu[]>('/account/Vue3Router/vben5');
}

View File

@@ -0,0 +1,47 @@
import type { AxiosRequestConfig } from '@vben/request';
import { requestClient } from '#/api/request';
/**
* Axios上传进度事件
*/
export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress'];
/**
* 默认上传结果
*/
export interface UploadResult {
url: string;
fileName: string;
ossId: string;
}
/**
* 通过单文件上传接口
* @param file 上传的文件
* @param options 一些配置项
* @param options.onUploadProgress 上传进度事件
* @param options.signal 上传取消信号
* @param options.otherData 其他请求参数 后端拓展可能会用到
* @returns 上传结果
*/
export function uploadApi(
file: Blob | File,
options?: {
onUploadProgress?: AxiosProgressEvent;
otherData?: Record<string, any>;
signal?: AbortSignal;
},
) {
const { onUploadProgress, signal, otherData = {} } = options ?? {};
return requestClient.upload<UploadResult>(
'/resource/oss/upload',
{ file, ...otherData },
{ onUploadProgress, signal, timeout: 60_000 },
);
}
/**
* 上传api type
*/
export type UploadApi = typeof uploadApi;

View File

@@ -0,0 +1,47 @@
import { requestClient } from '#/api/request';
export interface Role {
dataScope: string;
flag: boolean;
roleId: number;
roleKey: string;
roleName: string;
roleSort: number;
status: string;
superAdmin: boolean;
}
export interface User {
avatar: string;
createTime: string;
deptId: number;
deptName: string;
email: string;
loginDate: string;
loginIp: string;
nickName: string;
phonenumber: string;
remark: string;
roles: Role[];
sex: string;
status: string;
tenantId: string;
userId: number;
userName: string;
userType: string;
}
export interface UserInfoResp {
permissionCodes: string[];
roles: string[];
roleCodes: string[];
user: User;
}
/**
* 获取用户信息
* 存在返回null的情况(401) 不会抛出异常 需要手动抛异常
*/
export async function getUserInfoApi() {
return requestClient.get<null | UserInfoResp>('account');
}

View File

@@ -0,0 +1,28 @@
import { requestClient } from './request';
/**
* @description: contentType
*/
export const ContentTypeEnum = {
// form-data upload
FORM_DATA: 'multipart/form-data;charset=UTF-8',
// form-data qs
FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8',
// json
JSON: 'application/json;charset=UTF-8',
} as const;
/**
* 通用下载接口 封装一层
* @param url 请求地址
* @param data 请求参数
* @returns blob二进制
*/
export function commonExport(url: string, data: Record<string, any>) {
return requestClient.post<Blob>(url, data, {
data,
headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
isTransformResponse: false,
responseType: 'blob',
});
}

View File

@@ -0,0 +1 @@
export * from './core';

View File

@@ -0,0 +1,90 @@
import { requestClient } from '#/api/request';
export interface CommandStats {
name: string;
value: string;
}
export interface RedisInfo {
[key: string]: string;
}
export interface CacheInfo {
commandStats: CommandStats[];
dbSize: number;
info: RedisInfo;
}
export interface CacheName {
cacheName: string;
remark: string;
}
export interface CacheValue {
cacheName: string;
cacheKey: string;
cacheValue: string;
}
/**
*
* @returns redis信息
*/
export function redisCacheInfo() {
return requestClient.get<CacheInfo>('/monitor/cache');
}
/**
* 查询缓存名称列表
* @returns 缓存名称列表
*/
export function listCacheName() {
return requestClient.get<CacheName[]>('/monitor-cache/name');
}
/**
* 查询缓存键名列表
* @param cacheName 缓存名称
* @returns 缓存键名列表
*/
export function listCacheKey(cacheName: string) {
return requestClient.get<string[]>(`/monitor-cache/key/${cacheName}`);
}
/**
* 查询缓存内容
* @param cacheName 缓存名称
* @param cacheKey 缓存键名
* @returns 缓存内容
*/
export function getCacheValue(cacheName: string, cacheKey: string) {
return requestClient.get<CacheValue>(
`/monitor-cache/value/${cacheName}/${cacheKey}`,
);
}
/**
* 清理指定名称缓存
* @param cacheName 缓存名称
*/
export function clearCacheName(cacheName: string) {
return requestClient.deleteWithMsg<void>(`/monitor-cache/key/${cacheName}`);
}
/**
* 清理指定键名缓存
* @param cacheName 缓存名称
* @param cacheKey 缓存键名
*/
export function clearCacheKey(cacheName: string, cacheKey: string) {
return requestClient.deleteWithMsg<void>(
`/monitor-cache/value/${cacheName}/${cacheKey}`,
);
}
/**
* 清理全部缓存
*/
export function clearCacheAll() {
return requestClient.deleteWithMsg<void>('/monitor-cache/clear');
}

View File

@@ -0,0 +1,61 @@
import type { LoginLog } from './model';
import type { IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
loginInfoClean = '/login-log/clean',
loginInfoExport = '/login-log/export',
root = '/login-log',
userUnlock = '/login-log/unlock',
}
/**
* 登录日志列表
* @param params 查询参数
* @returns list[]
*/
export function loginInfoList(params?: PageQuery) {
return requestClient.get<PageResult<LoginLog>>(Api.root, { params });
}
/**
* 导出登录日志
* @param data 表单参数
* @returns excel
*/
export function loginInfoExport(data: any) {
return commonExport(Api.loginInfoExport, data);
}
/**
* 移除登录日志
* @param infoIds 登录日志id数组
* @returns void
*/
export function loginInfoRemove(infoIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: infoIds.join(',') },
});
}
/**
* 账号解锁
* @param username 用户名(账号)
* @returns void
*/
export function userUnlock(username: string) {
return requestClient.get<void>(`${Api.userUnlock}/${username}`, {
successMessageMode: 'message',
});
}
/**
* 清空全部登录日志
* @returns void
*/
export function loginInfoClean() {
return requestClient.deleteWithMsg<void>(Api.loginInfoClean);
}

View File

@@ -0,0 +1,11 @@
export interface LoginLog {
id: string;
loginUser: string;
loginLocation: string;
loginIp: string;
browser: string;
os: string;
logMsg: string;
creationTime: string;
creatorId: string | null;
}

View File

@@ -0,0 +1,46 @@
import type { OnlineUser } from './model';
import type { IDS, PageQuery, PageResult } from '#/api/common';
import { requestClient } from '#/api/request';
enum Api {
root = '/online',
}
/**
* 当前账号的在线设备 个人中心使用
* @returns OnlineUser[]
*/
export function onlineDeviceList() {
return requestClient.get<PageResult<OnlineUser>>(Api.root);
}
/**
* 这里的分页参数无效 返回的是全部的分页
* @param params 请求参数
* @returns 结果
*/
export function onlineList(params?: PageQuery) {
return requestClient.get<PageResult<OnlineUser>>(Api.root, { params });
}
/**
* 强制下线
* @param tokenId 连接Id
* @returns void
*/
export function forceLogout(tokenId: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: tokenId.join(',') },
});
}
/**
* 个人中心用的 跟上面的不同是用的Post
* @param tokenId 连接Id
* @returns void
*/
export function forceLogout2(tokenId: string) {
return requestClient.deleteWithMsg<void>(`${Api.root}/myself/${tokenId}`);
}

View File

@@ -0,0 +1,10 @@
export interface OnlineUser {
connnectionId?: string;
userId?: string;
userName?: string;
loginTime: number;
ipaddr?: string;
loginLocation?: string;
os?: string;
browser?: string;
}

View File

@@ -0,0 +1,48 @@
import type { OperationLog } from './model';
import type { IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
operLogClean = '/operation-log/clean',
operLogExport = '/operation-log/export',
root = '/operation-log',
}
/**
* 操作日志分页
* @param params 查询参数
* @returns 分页结果
*/
export function operLogList(params?: PageQuery) {
return requestClient.get<PageResult<OperationLog>>(Api.root, {
params,
});
}
/**
* 删除操作日志
* @param operIds id/ids
*/
export function operLogRemove(operIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: operIds.join(',') },
});
}
/**
* 清空全部分页日志
*/
export function operLogClean() {
return requestClient.deleteWithMsg<void>(Api.operLogClean);
}
/**
* 导出操作日志
* @param data 查询参数
*/
export function operLogExport(data: Partial<OperationLog>) {
return commonExport(Api.operLogExport, data);
}

View File

@@ -0,0 +1,14 @@
export interface OperationLog {
id: string;
title: string;
operType: string;
requestMethod: string;
operUser: string;
operIp: string;
operLocation: string;
method: string;
requestParam: string;
requestResult: string;
creationTime: string;
creatorId: string | null;
}

View File

@@ -0,0 +1,322 @@
/**
* 该文件可自行根据业务逻辑进行调整
*/
import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import { $t } from '@vben/locales';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
errorMessageResponseInterceptor,
RequestClient,
stringify,
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { message, Modal } from 'ant-design-vue';
import { useAuthStore } from '#/store';
import {
decryptBase64,
decryptWithAes,
encryptBase64,
encryptWithAes,
generateAesKey,
} from '#/utils/encryption/crypto';
import * as encryptUtil from '#/utils/encryption/jsencrypt';
const { apiURL, clientId, enableEncrypt, demoMode } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
/**
* 是否已经处在登出过程中了 一个标志位
* 主要是防止一个页面会请求多个api 都401 会导致登出执行多次
*/
let isLogoutProcessing = false;
/**
* 定义一个401专用异常 用于可能会用到的区分场景?
*/
export class UnauthorizedException extends Error {}
/**
* 演示模式错误,用于标识演示环境禁止修改的错误
*/
export class DemoModeException extends Error {
constructor(message: string) {
super(message);
this.name = 'DemoModeException';
// 添加标记,用于错误拦截器识别
(this as any).__isDemoModeError = true;
}
}
function createRequestClient(baseURL: string) {
const client = new RequestClient({
// 后端地址
baseURL,
// 消息提示类型
errorMessageMode: 'message',
// 是否返回原生响应 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
});
/**
* 重新认证逻辑
*/
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 {
await authStore.logout();
}
}
/**
* 刷新token逻辑
*/
async function doRefreshToken() {
// 不需要
// 保留此方法只是为了合并方便
return '';
}
function formatToken(token: null | string) {
return token ? `Bearer ${token}` : null;
}
client.addRequestInterceptor({
fulfilled: (config) => {
// 演示模式:拦截所有修改操作
if (demoMode) {
const method = config.method?.toUpperCase() || '';
const isModifyMethod = ['DELETE', 'PATCH', 'POST', 'PUT'].includes(
method,
);
// 排除登录等认证接口,允许通过
const isAuthPath =
config.url?.includes('/auth/') ||
config.url?.includes('/login') ||
config.url?.includes('/logout');
if (isModifyMethod && !isAuthPath) {
// 显示错误提示
message.error('演示环境,禁止修改');
// 抛出演示模式错误,错误拦截器会识别并跳过处理
throw new DemoModeException('演示环境,禁止修改');
}
}
const accessStore = useAccessStore();
// 添加token
config.headers.Authorization = formatToken(accessStore.accessToken);
/**
* locale跟后台不一致 需要转换
*/
const language = preferences.app.locale.replace('-', '_');
config.headers['Accept-Language'] = language;
config.headers['Content-Language'] = language;
/**
* 添加全局clientId
* 关于header的clientId被错误绑定到实体类
* https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS
*/
config.headers.ClientID = clientId;
/**
* 格式化get/delete参数
* 如果包含自定义的paramsSerializer则不走此逻辑
*/
if (
['DELETE', 'GET'].includes(config.method?.toUpperCase() || '') &&
config.params &&
!config.paramsSerializer
) {
/**
* 1. 格式化参数 微服务在传递区间时间选择(后端的params Map类型参数)需要格式化key 否则接收不到
* 2. 数组参数需要格式化 后端才能正常接收 会变成arr=1&arr=2&arr=3的格式来接收
*/
config.paramsSerializer = (params) =>
stringify(params, { arrayFormat: 'repeat' });
}
const { encrypt } = config;
// 全局开启请求加密功能 && 该请求开启 && 是post/put请求
if (
enableEncrypt &&
encrypt &&
['POST', 'PUT'].includes(config.method?.toUpperCase() || '')
) {
const aesKey = generateAesKey();
config.headers['encrypt-key'] = encryptUtil.encrypt(
encryptBase64(aesKey),
);
config.data =
typeof config.data === 'object'
? encryptWithAes(JSON.stringify(config.data), aesKey)
: encryptWithAes(config.data, aesKey);
}
return config;
},
});
// 通用的错误处理, 如果没有进入上面的错误处理逻辑,就会进入这里
// 主要处理http状态码不为200(如网络异常/离线)的情况 必须放在在下面的响应拦截器之前
const errorInterceptor = errorMessageResponseInterceptor(
(msg: string, error: any) => {
// 如果是演示模式错误,已经在请求拦截器中提示过了,这里不再提示
if (error?.__isDemoModeError || error?.name === 'DemoModeException') {
return;
}
message.error(msg);
},
);
client.addResponseInterceptor(errorInterceptor);
client.addResponseInterceptor<HttpResponse>({
fulfilled: async (response) => {
const encryptKey = (response.headers ?? {})['encrypt-key'];
if (encryptKey) {
/** RSA私钥解密 拿到解密秘钥的base64 */
const base64Str = encryptUtil.decrypt(encryptKey);
/** base64 解码 得到请求头的 AES 秘钥 */
const aesSecret = decryptBase64(base64Str.toString());
/** 使用aesKey解密 responseData */
const decryptData = decryptWithAes(
response.data as unknown as string,
aesSecret,
);
/** 赋值 需要转为对象 */
response.data = JSON.parse(decryptData);
}
const { isReturnNativeResponse, isTransformResponse } = response.config;
// 是否返回原生响应 比如:需要获取响应时使用该属性
if (isReturnNativeResponse) {
return response;
}
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取codedatamessage这些信息时开启
if (!isTransformResponse) {
/**
* 需要判断下载二进制的情况 正常是返回二进制 报错会返回json
* 当type为blob且content-type为application/json时 则判断已经下载出错
*/
if (
response.config.responseType === 'blob' &&
response.headers['content-type']?.includes?.('application/json')
) {
// 这时候的data为blob类型
const blob = response.data as unknown as Blob;
// 拿到字符串转json对象
response.data = JSON.parse(await blob.text());
// 然后按正常逻辑执行下面的代码(判断业务状态码)
} else {
// 其他情况 直接返回
return response.data;
}
}
const axiosResponseData = response.data;
if (!axiosResponseData) {
throw new Error($t('http.apiRequestFailed'));
}
console.log('axiosResponseData', axiosResponseData);
// 适配新的后端数据结构: { statusCode, data, succeeded, errors, extras, timestamp }
const { statusCode, data, succeeded, errors, extras, timestamp } =
axiosResponseData;
// 业务状态码为200且succeeded为true则请求成功
const hasSuccess = statusCode === 200 && succeeded === true;
if (hasSuccess) {
const successMsg = $t(`http.operationSuccess`);
if (response.config.successMessageMode === 'modal') {
Modal.success({
content: successMsg,
title: $t('http.successTip'),
});
} else if (response.config.successMessageMode === 'message') {
message.success(successMsg);
}
// 直接返回data字段
return data;
}
// 在此处根据自己项目的实际情况对不同的statusCode执行不同的操作
// 如果不希望中断当前请求请return数据否则直接抛出异常即可
let timeoutMsg = '';
switch (statusCode) {
case 401: {
// 已经在登出过程中 不再执行
if (isLogoutProcessing) {
throw new UnauthorizedException(timeoutMsg);
}
isLogoutProcessing = true;
const _msg = $t('http.loginTimeout');
const userStore = useAuthStore();
userStore.logout().finally(() => {
message.error(_msg);
isLogoutProcessing = false;
});
// 不再执行下面逻辑
throw new UnauthorizedException(_msg);
}
default: {
// 优先使用errors字段作为错误信息
if (errors && Array.isArray(errors) && errors.length > 0) {
timeoutMsg = errors.join(', ');
} else if (typeof errors === 'string') {
timeoutMsg = errors;
} else {
timeoutMsg = $t('http.apiRequestFailed');
}
}
}
// errorMessageMode='modal'的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
if (response.config.errorMessageMode === 'modal') {
Modal.error({
content: timeoutMsg,
title: $t('http.errorTip'),
});
} else if (response.config.errorMessageMode === 'message') {
message.error(timeoutMsg);
}
throw new Error(timeoutMsg || $t('http.apiRequestFailed'));
},
});
// token过期的处理
client.addResponseInterceptor(
authenticateResponseInterceptor({
client,
doReAuthenticate,
doRefreshToken,
enableRefreshToken: preferences.app.enableRefreshToken,
formatToken,
}),
);
return client;
}
export const requestClient = createRequestClient(apiURL);
export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@@ -0,0 +1,12 @@
import type { ServerInfo } from './model';
import { requestClient } from '#/api/request';
/**
* 获取服务器信息
* @returns 服务器信息
*/
export function getServerInfo() {
return requestClient.get<ServerInfo>('/monitor-server/info');
}

View File

@@ -0,0 +1,46 @@
export interface CpuInfo {
coreTotal: number;
logicalProcessors: number;
cpuRate: number;
}
export interface MemoryInfo {
totalRAM: string;
usedRam: string;
freeRam: string;
ramRate: number;
}
export interface SystemInfo {
computerName: string;
osName: string;
serverIP: string;
osArch: string;
}
export interface AppInfo {
name: string;
version: string;
startTime: string;
runTime: string;
rootPath: string;
webRootPath: string;
}
export interface DiskInfo {
diskName: string;
typeName: string;
totalSize: string;
availableFreeSpace: string;
used: string;
availablePercent: number;
}
export interface ServerInfo {
cpu: CpuInfo;
memory: MemoryInfo;
sys: SystemInfo;
app: AppInfo;
disk: DiskInfo[];
}

View File

@@ -0,0 +1,77 @@
import type { Client } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
clientChangeStatus = '/client/changeStatus',
clientExport = '/client/export',
clientList = '/client/list',
root = '/client',
}
/**
* 查询客户端分页列表
* @param params 请求参数
* @returns 列表
*/
export function clientList(params?: PageQuery) {
return requestClient.get<PageResult<Client>>(Api.clientList, { params });
}
/**
* 导出客户端excel
* @param data 请求参数
*/
export function clientExport(data: Partial<Client>) {
return commonExport(Api.clientExport, data);
}
/**
* 客户端详情
* @param id id
* @returns 详情
*/
export function clientInfo(id: ID) {
return requestClient.get<Client>(`${Api.root}/${id}`);
}
/**
* 客户端新增
* @param data 参数
*/
export function clientAdd(data: Partial<Client>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 客户端修改
* @param data 参数
*/
export function clientUpdate(data: Partial<Client>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 客户端状态修改
* @param data 状态
*/
export function clientChangeStatus(data: any) {
const requestData = {
clientId: data.clientId,
status: data.status,
};
return requestClient.putWithMsg<void>(Api.clientChangeStatus, requestData);
}
/**
* 客户端删除
* @param ids id集合
*/
export function clientRemove(ids: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: ids.join(',') },
});
}

View File

@@ -0,0 +1,12 @@
export interface Client {
id: number;
clientId: string;
clientKey: string;
clientSecret: string;
grantTypeList: string[];
grantType: string;
deviceType: string;
activeTimeout: number;
timeout: number;
status: string;
}

View File

@@ -0,0 +1,78 @@
import type { SysConfig } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
configExport = '/config/export',
configInfoByKey = '/config/config-key',
configList = '/config/list',
configRefreshCache = '/config/refreshCache',
root = '/config',
}
/**
* 系统参数分页列表
* @param params 请求参数
* @returns 列表
*/
export function configList(params?: PageQuery) {
return requestClient.get<PageResult<SysConfig>>(Api.root, { params });
}
export function configInfo(configId: ID) {
return requestClient.get<SysConfig>(`${Api.root}/${configId}`);
}
/**
* 导出
* @param data 参数
*/
export function configExport(data: Partial<SysConfig>) {
return commonExport(Api.configExport, data);
}
/**
* 刷新缓存
* @returns void
*/
export function configRefreshCache() {
return requestClient.deleteWithMsg<void>(Api.configRefreshCache);
}
/**
* 更新系统配置
* @param data 参数
*/
export function configUpdate(data: Partial<SysConfig>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 新增系统配置
* @param data 参数
*/
export function configAdd(data: Partial<SysConfig>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 删除配置
* @param configIds ids
*/
export function configRemove(configIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: configIds.join(',') },
});
}
/**
* 获取配置信息
* @param configKey configKey
* @returns value
*/
export function configInfoByKey(configKey: string) {
return requestClient.get<string>(`${Api.configInfoByKey}/${configKey}`);
}

View File

@@ -0,0 +1,14 @@
export interface SysConfig {
id: string;
configName: string;
configKey: string;
configValue: string;
configType: string | null;
orderNum: number;
remark: string | null;
isDeleted: boolean;
creationTime: string;
creatorId: string | null;
lastModifierId: string | null;
lastModificationTime: string | null;
}

View File

@@ -0,0 +1,64 @@
import type { Dept } from './model';
import type { ID } from '#/api/common';
import { requestClient } from '#/api/request';
enum Api {
deptList = '/dept/list',
deptNodeInfo = '/dept/list/exclude',
root = '/dept',
}
/**
* 部门列表
* @returns list
*/
export function deptList(params?: { deptName?: string; status?: string }) {
return requestClient.get<Dept[]>(Api.deptList, { params });
}
/**
* 查询部门列表(排除节点)
* @param deptId 部门ID
* @returns void
*/
export function deptNodeList(deptId: ID) {
return requestClient.get<Dept[]>(`${Api.deptNodeInfo}/${deptId}`);
}
/**
* 部门详情
* @param deptId 部门id
* @returns 部门信息
*/
export function deptInfo(deptId: ID) {
return requestClient.get<Dept>(`${Api.root}/${deptId}`);
}
/**
* 部门新增
* @param data 参数
*/
export function deptAdd(data: Partial<Dept>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 部门更新
* @param data 参数
*/
export function deptUpdate(data: Partial<Dept>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 注意这里只允许单删除
* @param deptId ID
* @returns void
*/
export function deptRemove(deptId: ID) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: deptId },
});
}

View File

@@ -0,0 +1,14 @@
export interface Dept {
creationTime: string;
creatorId?: string | null;
state: boolean;
deptName: string;
deptCode?: string;
leader?: string;
leaderName?: string;
parentId: string | null;
remark?: string;
orderNum: number;
id: string;
children?: Dept[];
}

View File

@@ -0,0 +1,17 @@
export interface DictData {
id: string;
isDeleted: boolean;
orderNum: number;
state: boolean;
remark: string | null;
listClass: string | null;
cssClass: string | null;
dictType: string;
dictLabel: string | null;
dictValue: string;
isDefault: boolean;
creationTime: string;
creatorId: string | null;
lastModifierId: string | null;
lastModificationTime: string | null;
}

View File

@@ -0,0 +1,78 @@
import type { DictData } from './dict-data-model';
import type { ID, IDS, PageQuery } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
dictDataExport = '/dict/data/export',
dictDataInfo = '/dictionary/dic-type',
dictDataList = '/dict/data/list',
root = '/dictionary',
}
/**
* 主要是DictTag组件使用
* @param dictType 字典类型
* @returns 字典数据
*/
export function dictDataInfo(dictType: string) {
return requestClient.get<DictData[]>(`${Api.dictDataInfo}/${dictType}`);
}
/**
* 字典数据
* @param params 查询参数
* @returns 字典数据列表
*/
export function dictDataList(params?: PageQuery) {
return requestClient.get<DictData[]>(Api.root, { params });
}
/**
* 导出字典数据
* @param data 表单参数
* @returns blob
*/
export function dictDataExport(data: Partial<DictData>) {
return commonExport(Api.dictDataExport, data);
}
/**
* 删除
* @param dictIds 字典ID Array
* @returns void
*/
export function dictDataRemove(dictIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: dictIds.join(',') },
});
}
/**
* 新增
* @param data 表单参数
* @returns void
*/
export function dictDataAdd(data: Partial<DictData>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 修改
* @param data 表单参数
* @returns void
*/
export function dictDataUpdate(data: Partial<DictData>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 查询字典数据详细
* @param id 字典ID
* @returns 字典数据
*/
export function dictDetailInfo(id: ID) {
return requestClient.get<DictData>(`${Api.root}/${id}`);
}

View File

@@ -0,0 +1,13 @@
export interface DictType {
id: string;
isDeleted: boolean;
orderNum: number;
state: boolean | null;
dictName: string;
dictType: string;
remark: string | null;
creationTime: string;
creatorId: string | null;
lastModifierId: string | null;
lastModificationTime: string | null;
}

View File

@@ -0,0 +1,87 @@
import type { DictType } from './dict-type-model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
dictOptionSelectList = '/dictionary-type/select-data-list',
dictTypeExport = '/dictionary-type/export',
dictTypeList = '/dictionary-type/list',
dictTypeRefreshCache = '/dictionary-type/refreshCache',
root = '/dictionary-type',
}
/**
* 获取字典类型列表
* @param params 请求参数
* @returns list
*/
export function dictTypeList(params?: PageQuery) {
return requestClient.get<PageResult<DictType>>(Api.root, { params });
}
/**
* 导出字典类型列表
* @param data 表单参数
* @returns blob
*/
export function dictTypeExport(data: Partial<DictType>) {
return commonExport(Api.dictTypeExport, data);
}
/**
* 删除字典类型
* @param dictIds 字典类型id数组
* @returns void
*/
export function dictTypeRemove(dictIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: dictIds.join(',') },
});
}
/**
* 刷新字典缓存
* @returns void
*/
export function refreshDictTypeCache() {
return requestClient.deleteWithMsg<void>(Api.dictTypeRefreshCache);
}
/**
* 新增
* @param data 表单参数
* @returns void
*/
export function dictTypeAdd(data: Partial<DictType>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 修改
* @param data 表单参数
* @returns void
*/
export function dictTypeUpdate(data: Partial<DictType>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 查询详情
* @param dictId 字典类型id
* @returns 信息
*/
export function dictTypeInfo(dictId: ID) {
return requestClient.get<DictType>(`${Api.root}/${dictId}`);
}
/**
* 这个在ele用到 v5用不上
* 下拉框 返回值和list一样
* @returns options
*/
export function dictOptionSelectList() {
return requestClient.get<DictType[]>(Api.dictOptionSelectList);
}

View File

@@ -0,0 +1,84 @@
import type { Menu, MenuOption, MenuQuery, MenuResp } from './model';
import type { ID, IDS } from '#/api/common';
import { requestClient } from '#/api/request';
enum Api {
menuList = '/menu/list',
menuTreeSelect = '/menu/tree',
root = '/menu',
tenantPackageMenuTreeselect = '/menu/tenantPackageMenuTreeselect',
}
/**
* 菜单列表
* @param params 参数
* @returns 列表
*/
export function menuList(params?: MenuQuery) {
return requestClient.get<Menu[]>(Api.menuList, { params });
}
/**
* 菜单详情
* @param menuId 菜单id
* @returns 菜单详情
*/
export function menuInfo(menuId: ID) {
return requestClient.get<Menu>(`${Api.root}/${menuId}`);
}
/**
* 菜单新增
* @param data 参数
*/
export function menuAdd(data: Partial<Menu>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 菜单更新
* @param data 参数
*/
export function menuUpdate(data: Partial<Menu>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 菜单删除
* @param menuIds ids
*/
export function menuRemove(menuIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: menuIds.join(',') },
});
}
/**
* 下拉框使用 返回所有的菜单
* @returns []
*/
export function menuTreeSelect() {
return requestClient.get<MenuOption[]>(Api.menuTreeSelect);
}
/**
* 租户套餐使用
* @param packageId packageId
* @returns resp
*/
export function tenantPackageMenuTreeSelect(packageId: ID) {
return requestClient.get<MenuResp>(
`${Api.tenantPackageMenuTreeselect}/${packageId}`,
);
}
/**
* 批量删除菜单
* @param menuIds 菜单ids
* @returns void
*/
export function menuCascadeRemove(menuIds: IDS) {
return requestClient.deleteWithMsg<void>(`${Api.root}/cascade/${menuIds}`);
}

View File

@@ -0,0 +1,57 @@
export interface Menu {
id: string;
isDeleted: boolean;
creationTime: string;
creatorId: string | null;
lastModifierId: string | null;
lastModificationTime: string | null;
orderNum: number;
state: boolean;
menuName: string;
routerName?: string | null;
menuType: string;
permissionCode?: string | null;
parentId: string;
menuIcon?: string | null;
router?: string | null;
isLink: boolean;
isCache: boolean;
isShow: boolean;
remark?: string | null;
component?: string | null;
query?: string | null;
children?: Menu[];
}
/**
* @description 菜单信息
* @param menuName 菜单名称
*/
export interface MenuOption {
id: string;
parentId: string;
orderNum: number;
menuName: string;
menuType: string;
menuIcon?: string | null;
children?: MenuOption[] | null;
}
/**
* @description 菜单返回
* @param checkedKeys 选中的菜单id
* @param menus 菜单信息
*/
export interface MenuResp {
checkedKeys: string[];
menus: MenuOption[];
}
/**
* 菜单表单查询
*/
export interface MenuQuery {
menuName?: string;
isShow?: boolean;
state?: boolean;
}

View File

@@ -0,0 +1,53 @@
import type { Notice } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { requestClient } from '#/api/request';
enum Api {
root = '/notice',
}
/**
* 通知公告分页
* @param params 分页参数
* @returns 分页结果
*/
export function noticeList(params?: PageQuery) {
return requestClient.get<PageResult<Notice>>(Api.root, { params });
}
/**
* 通知公告详情
* @param id id
* @returns 详情
*/
export function noticeInfo(id: ID) {
return requestClient.get<Notice>(`${Api.root}/${id}`);
}
/**
* 通知公告新增
* @param data 参数
*/
export function noticeAdd(data: Partial<Notice>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 通知公告更新
* @param data 参数
*/
export function noticeUpdate(data: any) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 通知公告删除
* @param ids ids
*/
export function noticeRemove(ids: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: ids.join(',') },
});
}

View File

@@ -0,0 +1,13 @@
export interface Notice {
id: string;
title: string;
type: string;
content: string;
state: boolean;
isDeleted: boolean;
creationTime: string;
creatorId?: string | null;
lastModifierId?: string | null;
lastModificationTime?: string | null;
orderNum: number;
}

View File

@@ -0,0 +1,48 @@
import type { OssConfig } from './model';
import type { ID, IDS, PageQuery } from '#/api/common';
import { requestClient } from '#/api/request';
enum Api {
ossConfigChangeStatus = '/resource/oss/config/changeStatus',
ossConfigList = '/resource/oss/config/list',
root = '/resource/oss/config',
}
// 获取OSS配置列表
export function ossConfigList(params?: PageQuery) {
return requestClient.get<OssConfig[]>(Api.ossConfigList, { params });
}
// 获取OSS配置的信息
export function ossConfigInfo(ossConfigId: ID) {
return requestClient.get<OssConfig>(`${Api.root}/${ossConfigId}`);
}
// 添加新的OSS配置
export function ossConfigAdd(data: Partial<OssConfig>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
// 更新现有的OSS配置
export function ossConfigUpdate(data: Partial<OssConfig>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
// 删除OSS配置
export function ossConfigRemove(ossConfigIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: ossConfigIds.join(',') },
});
}
// 更改OSS配置的状态
export function ossConfigChangeStatus(data: any) {
const requestData: Partial<OssConfig> = {
ossConfigId: data.ossConfigId,
status: data.status,
configKey: data.configKey,
};
return requestClient.putWithMsg(Api.ossConfigChangeStatus, requestData);
}

View File

@@ -0,0 +1,16 @@
export interface OssConfig {
ossConfigId: number;
configKey: string;
accessKey: string;
secretKey: string;
bucketName: string;
prefix: string;
endpoint: string;
domain: string;
isHttps: string;
region: string;
status: string;
ext1: string;
remark: string;
accessPolicy: string;
}

View File

@@ -0,0 +1,77 @@
import type { AxiosRequestConfig } from '@vben/request';
import type { OssFile } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { ContentTypeEnum } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
ossDownload = '/resource/oss/download',
ossInfo = '/resource/oss/listByIds',
ossList = '/resource/oss/list',
ossUpload = '/resource/oss/upload',
root = '/resource/oss',
}
/**
* 文件list
* @param params 参数
* @returns 分页
*/
export function ossList(params?: PageQuery) {
return requestClient.get<PageResult<OssFile>>(Api.ossList, { params });
}
/**
* 查询文件信息 返回为数组
* @param ossIds id数组
* @returns 信息数组
*/
export function ossInfo(ossIds: ID | IDS) {
return requestClient.get<OssFile[]>(`${Api.ossInfo}/${ossIds}`);
}
/**
* @deprecated 使用apps/web-antd/src/api/core/upload.ts uploadApi方法
* @param file 文件
* @returns void
*/
export function ossUpload(file: Blob | File) {
const formData = new FormData();
formData.append('file', file);
return requestClient.postWithMsg(Api.ossUpload, formData, {
headers: { 'Content-Type': ContentTypeEnum.FORM_DATA },
timeout: 30 * 1000,
});
}
/**
* 下载文件 返回为二进制
* @param ossId ossId
* @param onDownloadProgress 下载进度(可选)
* @returns blob
*/
export function ossDownload(
ossId: ID,
onDownloadProgress?: AxiosRequestConfig['onDownloadProgress'],
) {
return requestClient.get<Blob>(`${Api.ossDownload}/${ossId}`, {
responseType: 'blob',
timeout: 30 * 1000,
isTransformResponse: false,
onDownloadProgress,
});
}
/**
* 删除文件
* @param ossIds id数组
* @returns void
*/
export function ossRemove(ossIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: ossIds.join(',') },
});
}

View File

@@ -0,0 +1,28 @@
export interface OssFile {
ossId: string;
fileName: string;
originalName: string;
fileSuffix: string;
url: string;
createTime: string;
createBy: number;
createByName: string;
service: string;
}
export interface OssConfig {
ossConfigId: number;
configKey: string;
accessKey: string;
secretKey: string;
bucketName: string;
prefix: string;
endpoint: string;
domain: string;
isHttps: string;
region: string;
status: string;
ext1: string;
remark: string;
accessPolicy: string;
}

View File

@@ -0,0 +1,77 @@
import type { Post } from './model';
import type { ID, IDS, PageQuery } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
postExport = '/post/export',
postList = '/post/list',
postSelect = '/post/select-data-list',
root = '/post',
}
/**
* 获取岗位列表
* @param params 参数
* @returns Post[]
*/
export function postList(params?: PageQuery) {
return requestClient.get<Post[]>(Api.root, { params });
}
/**
* 导出岗位信息
* @param data 请求参数
* @returns blob
*/
export function postExport(data: Partial<Post>) {
return commonExport(Api.postExport, data);
}
/**
* 查询岗位信息
* @param id 岗位id
* @returns 岗位信息
*/
export function postInfo(id: ID) {
return requestClient.get<Post>(`${Api.root}/${id}`);
}
/**
* 岗位新增
* @param data 参数
* @returns void
*/
export function postAdd(data: Partial<Post>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 岗位更新
* @param data 参数
* @returns void
*/
export function postUpdate(data: Partial<Post>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 岗位删除
* @param postIds ids
* @returns void
*/
export function postRemove(postIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: postIds.join(',') },
});
}
/**
* 获取岗位下拉列表
* @returns 岗位
*/
export function postOptionSelect(deptId: string) {
return requestClient.get<Post[]>(`${Api.postSelect}?keywords=${deptId}`);
}

View File

@@ -0,0 +1,17 @@
/**
* @description: Post interface
*/
export interface Post {
id: string;
isDeleted: boolean;
creationTime: string;
creatorId: string | null;
lastModifierId: string | null;
lastModificationTime: string | null;
orderNum: number;
state: boolean;
postCode: string;
postName: string;
deptId: string;
remark: string | null;
}

View File

@@ -0,0 +1,65 @@
import type { FileCallBack, UpdatePasswordParam, UserProfile } from './model';
import { buildUUID } from '@vben/utils';
import { requestClient } from '#/api/request';
enum Api {
root = '/user/profile',
updateAvatar = '/user/profile/avatar',
updatePassword = '/user/profile/updatePwd',
}
/**
* 用户个人主页信息
* @returns userInformation
*/
export function userProfile() {
return requestClient.get<UserProfile>(Api.root);
}
/**
* 更新用户个人主页信息
* @param data
* @returns void
*/
export function userProfileUpdate(data: any) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 用户修改密码 (需要加密)
* @param data
* @returns void
*/
export function userUpdatePassword(data: UpdatePasswordParam) {
return requestClient.putWithMsg<void>(Api.updatePassword, data, {
encrypt: true,
});
}
/**
* 用户更新个人头像
* @param fileCallback data
* @returns void
*/
export function userUpdateAvatar(fileCallback: FileCallBack) {
/** 直接点击头像上传 filename为空 由于后台通过拓展名判断(默认文件名blob) 会上传失败 */
let { file } = fileCallback;
const { filename } = fileCallback;
/**
* Blob转File类型
* 1. 在直接点击确认 filename为空 取uuid作为文件名
* 2. 选择上传必须转为File类型 Blob类型上传后台获取文件名为空
*/
file = filename
? new File([file], filename)
: new File([file], `${buildUUID()}.png`);
return requestClient.post(
Api.updateAvatar,
{
avatarfile: file,
},
{ headers: { 'Content-Type': 'multipart/form-data' } },
);
}

View File

@@ -0,0 +1,75 @@
export interface Dept {
deptId: number;
parentId: number;
parentName?: any;
ancestors: string;
deptName: string;
orderNum: number;
leader: string;
phone?: any;
email: string;
status: string;
createTime?: any;
}
export interface Role {
roleId: number;
roleName: string;
roleKey: string;
roleSort: number;
dataScope: string;
menuCheckStrictly?: any;
deptCheckStrictly?: any;
status: string;
remark: string;
createTime?: any;
flag: boolean;
superAdmin: boolean;
}
export interface User {
userId: number;
tenantId: string;
deptId: number;
userName: string;
nickName: string;
userType: string;
email: string;
phonenumber: string;
sex: string;
avatar: string;
status: string;
loginIp: string;
loginDate: string;
remark: string;
createTime: string;
dept: Dept;
roles: Role[];
roleIds?: string[];
postIds?: string[];
roleId: number;
deptName: string;
}
/**
* @description 用户个人主页信息
* @param user 用户信息
* @param roleGroup 角色名称
* @param postGroup 岗位名称
*/
export interface UserProfile {
user: User;
roleGroup: string;
postGroup: string;
}
export interface UpdatePasswordParam {
oldPassword: string;
newPassword: string;
}
interface FileCallBack {
name: string;
file: Blob;
filename: string;
}

View File

@@ -0,0 +1,161 @@
import type { MenuResp } from '../menu/model';
import type { User } from '../user/model';
import type { DeptResp, Role } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
roleAuthUser = '/role/auth-user',
roleDataScope = '/role/data-scope',
roleDeptTree = '/role/dept-tree',
roleExport = '/role/export',
roleList = '/role/list',
roleMenuTree = '/role/menu-tree',
roleOptionSelect = '/role/select-data-list',
root = '/role',
}
/**
* 查询角色分页列表
* @param params 搜索条件
* @returns 分页列表
*/
export function roleList(params?: PageQuery) {
return requestClient.get<PageResult<Role>>(Api.root, { params });
}
/**
* 导出角色信息
* @param data 查询参数
* @returns blob
*/
export function roleExport(data: Partial<Role>) {
return commonExport(Api.roleExport, data);
}
/**
* 查询角色信息
* @param roleId 角色id
* @returns 角色信息
*/
export function roleInfo(roleId: ID) {
return requestClient.get<Role>(`${Api.root}/${roleId}`);
}
/**
* 角色新增
* @param data 参数
* @returns void
*/
export function roleAdd(data: Partial<Role>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 角色更新
* @param data 参数
* @returns void
*/
export function roleUpdate(data: Partial<Role>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 角色删除
* @param roleIds ids
* @returns void
*/
export function roleRemove(roleIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: roleIds.join(',') },
});
}
/**
* 更新数据权限
* @param data
* @returns void
*/
export function roleDataScope(data: any) {
return requestClient.putWithMsg<void>(Api.roleDataScope, data);
}
/**
* @deprecated 全局并没有用到这个方法
*/
export function roleOptionSelect(params?: any) {
return requestClient.get(Api.roleOptionSelect, { params });
}
/**
* 已分配角色的用户分页
* @param params 请求参数
* @returns 分页
*/
export function roleAllocatedList(roleId: ID, params?: PageQuery) {
return requestClient.get<PageResult<User>>(`${Api.roleAuthUser}/${roleId}/true`, { params });
}
/**
* 未授权的用户
* @param params
* @returns void
*/
export function roleUnallocatedList(roleId: ID, params?: PageQuery) {
return requestClient.get<PageResult<User>>(`${Api.roleAuthUser}/${roleId}/false`, { params });
}
/**
* 批量取消授权
* @param roleId 角色ID
* @param userIds 用户ID集合
* @returns void
*/
export function roleAuthCancelAll(roleId: ID, userIds: IDS) {
return requestClient.deleteWithMsg<void>(
`${Api.roleAuthUser}`,
{
data: {
roleId,
userIds,
}
},
);
}
/**
* 批量授权用户
* @param roleId 角色ID
* @param userIds 用户ID集合
* @returns void
*/
export function roleSelectAll(roleId: ID, userIds: IDS) {
return requestClient.postWithMsg<void>(
`${Api.roleAuthUser}`,
{
roleId,
userIds,
},
);
}
/**
* 根据角色id获取部门树
* @param roleId 角色id
* @returns DeptResp
*/
export function roleDeptTree(roleId: ID) {
return requestClient.get<DeptResp>(`${Api.roleDeptTree}/${roleId}`);
}
/**
* 返回对应角色的菜单
* @param roleId id
* @returns resp
*/
export function roleMenuTreeSelect(roleId: ID) {
return requestClient.get<MenuResp>(`${Api.roleMenuTree}/${roleId}`);
}

View File

@@ -0,0 +1,30 @@
export interface Role {
id: string;
creationTime: string;
creatorId?: string | null;
lastModifierId?: string | null;
lastModificationTime?: string | null;
isDeleted?: boolean;
orderNum: number;
state: boolean;
roleName: string;
roleCode: string;
remark?: string | null;
dataScope: string;
menuIds?: string[];
deptIds?: string[];
}
export interface DeptOption {
id: string;
parentId: string | null;
orderNum: number;
deptName: string;
state: boolean;
children?: DeptOption[] | null;
}
export interface DeptResp {
checkedKeys: string[];
depts: DeptOption[];
}

View File

@@ -0,0 +1,25 @@
import type { SocialInfo } from './model';
import type { ID } from '#/api/common';
import { requestClient } from '#/api/request';
enum Api {
root = '/social',
socialList = '/social/list',
}
/**
* 获取绑定的社交信息列表
* @returns info
*/
export function socialList() {
return requestClient.get<SocialInfo[]>(Api.socialList);
}
/**
* @deprecated 并没有用到这个方法
*/
export function socialInfo(id: ID) {
return requestClient.get(`${Api.root}/${id}`);
}

View File

@@ -0,0 +1,26 @@
export interface SocialInfo {
id: string;
userId: number;
tenantId: string;
authId: string;
source: string;
accessToken: string;
expireIn: number;
refreshToken: string;
openId: string;
userName: string;
nickName: string;
email: string;
avatar: string;
accessCode?: any;
unionId?: any;
scope: string;
tokenType: string;
idToken?: any;
macAlgorithm?: any;
macKey?: any;
code?: any;
oauthToken?: any;
oauthTokenSecret?: any;
createTime: string;
}

View File

@@ -0,0 +1,93 @@
import type { TenantPackage } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
packageChangeStatus = '/tenant/package/changeStatus',
packageExport = '/tenant/package/export',
packageList = '/tenant/package/list',
packageSelectList = '/tenant/package/selectList',
root = '/tenant/package',
}
/**
* 租户套餐分页列表
* @param params 请求参数
* @returns 分页列表
*/
export function packageList(params?: PageQuery) {
return requestClient.get<PageResult<TenantPackage>>(Api.packageList, {
params,
});
}
/**
* 租户套餐下拉框
* @returns 下拉框
*/
export function packageSelectList() {
return requestClient.get<TenantPackage[]>(Api.packageSelectList);
}
/**
* 租户套餐导出
* @param data 参数
* @returns blob
*/
export function packageExport(data: Partial<TenantPackage>) {
return commonExport(Api.packageExport, data);
}
/**
* 租户套餐信息
* @param id id
* @returns 信息
*/
export function packageInfo(id: ID) {
return requestClient.get<TenantPackage>(`${Api.root}/${id}`);
}
/**
* 租户套餐新增
* @param data data
* @returns void
*/
export function packageAdd(data: Partial<TenantPackage>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 租户套餐更新
* @param data data
* @returns void
*/
export function packageUpdate(data: Partial<TenantPackage>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 租户套餐状态变更
* @param data data
* @returns void
*/
export function packageChangeStatus(data: Partial<TenantPackage>) {
const packageId = {
packageId: data.packageId,
status: data.status,
};
return requestClient.putWithMsg<void>(Api.packageChangeStatus, packageId);
}
/**
* 租户套餐移除
* @param ids ids
* @returns void
*/
export function packageRemove(ids: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: ids.join(',') },
});
}

View File

@@ -0,0 +1,17 @@
/**
* @description 租户套餐
* @param packageId id
* @param packageName 名称
* @param menuIds 菜单id 格式为[1,2,3] 返回为string 提交为数组
* @param remark 备注
* @param menuCheckStrictly 是否关联父节点
* @param status 状态
*/
export interface TenantPackage {
packageId: string;
packageName: string;
menuIds: number[] | string;
remark: string;
menuCheckStrictly: boolean;
status: string;
}

View File

@@ -0,0 +1,113 @@
import type { Tenant } from './model';
import type { ID, IDS, PageQuery } from '#/api/common';
import { commonExport } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
dictSync = '/tenant/syncTenantDict',
root = '/tenant',
tenantDynamic = '/tenant/dynamic',
tenantDynamicClear = '/tenant/dynamic/clear',
tenantExport = '/tenant/export',
tenantSyncPackage = '/tenant/syncTenantPackage',
}
/**
* 查询租户分页列表
* @param params 参数
* @returns 分页
*/
export function tenantList(params?: PageQuery) {
return requestClient.get<Tenant[]>(Api.root, { params });
}
/**
* 租户导出
* @param data data
* @returns void
*/
export function tenantExport(data: Partial<Tenant>) {
return commonExport(Api.tenantExport, data);
}
/**
* 查询租户信息
* @param id id
* @returns 租户信息
*/
export function tenantInfo(id: ID) {
return requestClient.get<Tenant>(`${Api.root}/${id}`);
}
/**
* 新增租户 必须开启加密
* @param data data
* @returns void
*/
export function tenantAdd(data: Partial<Tenant>) {
return requestClient.postWithMsg<void>(Api.root, data, { encrypt: true });
}
/**
* 租户更新
* @param data data
* @returns void
*/
export function tenantUpdate(data: Partial<Tenant>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 租户删除
* @param ids ids
* @returns void
*/
export function tenantRemove(ids: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: ids.join(',') },
});
}
/**
* 动态切换租户
* @param tenantId 租户ID
* @returns void
*/
export function tenantDynamicToggle(tenantId: string) {
return requestClient.get<void>(`${Api.tenantDynamic}/${tenantId}`);
}
/**
* 清除 动态切换租户
* @returns void
*/
export function tenantDynamicClear() {
return requestClient.get<void>(Api.tenantDynamicClear);
}
/**
* 租户套餐同步
* @param tenantId 租户id
* @param packageId 套餐id
* @returns void
*/
export function tenantSyncPackage(tenantId: string, packageId: string) {
return requestClient.get<void>(Api.tenantSyncPackage, {
params: { packageId, tenantId },
successMessageMode: 'message',
});
}
/**
* 同步租户字典
* @param tenantId 租户ID
* @returns void
*/
export function dictSyncTenant(tenantId?: string) {
return requestClient.get<void>(Api.dictSync, {
params: { tenantId },
successMessageMode: 'message',
});
}

View File

@@ -0,0 +1,26 @@
export interface Tenant {
id: string;
name: string;
entityVersion: number;
tenantConnectionString: string;
dbType: number;
isDeleted: boolean;
creationTime: string;
creatorId: string | null;
lastModifierId: string | null;
lastModificationTime: string | null;
// 以下字段可能来自 ExtraProperties 或后端扩展
accountCount?: number;
address?: string;
companyName?: string;
contactPhone?: string;
contactUserName?: string;
domain?: string;
expireTime?: string;
intro?: string;
licenseNumber?: any;
packageId?: string;
remark?: string;
status?: string | boolean;
tenantId?: string;
}

View File

@@ -0,0 +1,156 @@
import type {
DeptTreeData,
ResetPwdParam,
User,
UserImportParam,
} from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { commonExport, ContentTypeEnum } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
deptTree = '/dept/tree',
listDeptUsers = '/user/dept',
root = '/user',
userAuthRole = '/user/authRole',
userExport = '/user/export',
userImport = '/user/importData',
userImportTemplate = '/user/importTemplate',
userResetPassword = '/account/rest-password',
}
/**
* 获取用户列表
* @param params
* @returns User
*/
export function userList(params?: PageQuery) {
return requestClient.get<PageResult<User>>(Api.root, { params });
}
/**
* 导出excel
* @param data data
* @returns blob
*/
export function userExport(data: Partial<User>) {
return commonExport(Api.userExport, data);
}
/**
* 从excel导入用户
* @param data
* @returns void
*/
export function userImportData(data: UserImportParam) {
return requestClient.post<{ code: number; msg: string }>(
Api.userImport,
data,
{
headers: {
'Content-Type': ContentTypeEnum.FORM_DATA,
},
isTransformResponse: false,
},
);
}
/**
* 下载用户导入模板
* @returns blob
*/
export function downloadImportTemplate() {
return requestClient.post<Blob>(
Api.userImportTemplate,
{},
{
isTransformResponse: false,
responseType: 'blob',
},
);
}
/**
* 可以不传ID 返回部门和角色options 需要获得原始数据
* 不传ID时一定要带最后的/
* @param userId 用户ID
* @returns 用户信息
*/
export function findUserInfo(userId: ID) {
return requestClient.get<User>(`${Api.root}/${userId}`);
}
/**
* 新增用户
* @param data data
* @returns void
*/
export function userAdd(data: Partial<User>) {
return requestClient.postWithMsg<void>(Api.root, data);
}
/**
* 更新用户
* @param data data
* @returns void
*/
export function userUpdate(data: Partial<User>) {
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
}
/**
* 删除用户
* @param userIds 用户ID数组
* @returns void
*/
export function userRemove(userIds: IDS) {
return requestClient.deleteWithMsg<void>(Api.root, {
params: { ids: userIds.join(',') },
});
}
/**
* 重置用户密码 需要加密
* @param data
* @returns void
*/
export function userResetPassword(data: ResetPwdParam) {
return requestClient.putWithMsg<void>(`${Api.userResetPassword}/${data.id}`, data, {
encrypt: true,
});
}
/**
* 这个方法未调用过
* @param userId
* @returns void
*/
export function getUserAuthRole(userId: ID) {
return requestClient.get(`${Api.userAuthRole}/${userId}`);
}
/**
* 这个方法未调用过
* @param userId
* @returns void
*/
export function userAuthRoleUpdate(userId: ID, roleIds: number[]) {
return requestClient.putWithMsg(Api.userAuthRole, { roleIds, userId });
}
/**
* 获取部门树
* @returns 部门树数组
*/
export function getDeptTree() {
return requestClient.get<DeptTreeData[]>(Api.deptTree);
}
/**
* 获取部门下的所有用户信息
*/
export function listUserByDeptId(deptId: ID) {
return requestClient.get<User[]>(`${Api.listDeptUsers}/${deptId}`);
}

View File

@@ -0,0 +1,100 @@
import type { Dept } from '../dept/model';
/**
* @description: 用户导入
* @param updateSupport 是否覆盖数据
* @param file excel文件
*/
export interface UserImportParam {
updateSupport: boolean;
file: Blob | File;
}
/**
* @description: 重置密码
*/
export interface ResetPwdParam {
id: string;
password: string;
}
export interface Role {
roleId: string;
roleName: string;
roleKey: string;
roleSort: number;
dataScope: string;
menuCheckStrictly?: boolean;
deptCheckStrictly?: boolean;
status: string;
remark: string;
createTime?: string;
flag: boolean;
superAdmin: boolean;
}
export interface User {
id: string;
isDeleted: boolean;
name?: string | null;
age?: number | null;
userName: string;
icon?: string | null;
nick?: string | null;
email?: string | null;
ip?: string | null;
address?: string | null;
phone?: number | null;
introduction?: string | null;
remark?: string | null;
sex: string; // SexEnum
deptId?: string | null;
creationTime: string;
creatorId?: string | null;
lastModifierId?: string | null;
lastModificationTime?: string | null;
orderNum: number;
state: boolean;
deptName?: string | null;
posts?: Post[];
roles?: Role[];
dept?: Dept | null;
}
export interface Post {
postId: number;
postCode: string;
postName: string;
postSort: number;
status: string;
remark: string;
createTime: string;
}
/**
* @description 用户信息
* @param user 用户个人信息
* @param roleIds 角色IDS 不传id为空
* @param roles 所有的角色
* @param postIds 岗位IDS 不传id为空
* @param posts 所有的岗位
*/
export interface UserInfoResponse {
user?: User;
roleIds?: string[];
roles: Role[];
postIds?: number[];
posts?: Post[];
}
/**
* @description: 部门树
*/
export interface DeptTreeData {
id: string;
parentId: string;
orderNum: number;
deptName: string;
state: boolean;
children?: DeptTreeData[] | null;
}

View File

@@ -0,0 +1,103 @@
import type { GenInfo } from './model';
import type { ID, IDS, PageQuery } from '#/api/common';
import { ContentTypeEnum } from '#/api/helper';
import { requestClient } from '#/api/request';
enum Api {
batchGenCode = '/tool/gen/batchGenCode',
columnList = '/tool/gen/column',
dataSourceNames = '/tool/gen/getDataNames',
download = '/tool/gen/download',
genCode = '/tool/gen/genCode',
generatedList = '/tool/gen/list',
importTable = '/tool/gen/importTable',
preview = '/tool/gen/preview',
readyToGenList = '/tool/gen/db/list',
root = '/tool/gen',
syncDb = '/tool/gen/synchDb',
}
// 查询代码生成列表
export function generatedList(params?: PageQuery) {
return requestClient.get(Api.generatedList, { params });
}
// 修改代码生成业务
export function genInfo(tableId: ID) {
return requestClient.get<GenInfo>(`${Api.root}/${tableId}`);
}
// 查询数据库列表
export function readyToGenList(params?: PageQuery) {
return requestClient.get(Api.readyToGenList, { params });
}
// 查询数据表字段列表
export function columnList(tableId: ID) {
return requestClient.get(`${Api.columnList}/${tableId}`);
}
/**
* 导入表结构(保存)
* @param tables table名称数组 如sys_a, sys_b
* @param dataName 数据源名称
* @returns ret
*/
export function importTable(tables: string | string[], dataName: string) {
return requestClient.postWithMsg(
Api.importTable,
{ dataName, tables },
{
headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
},
);
}
// 修改保存代码生成业务
export function editSave(data: any) {
return requestClient.putWithMsg(Api.root, data);
}
// 删除代码生成
export function genRemove(tableIds: IDS) {
return requestClient.deleteWithMsg(`${Api.root}/${tableIds}`);
}
// 预览代码
export function previewCode(tableId: ID) {
return requestClient.get<{ [key: string]: string }>(
`${Api.preview}/${tableId}`,
);
}
// 生成代码(下载方式)
export function genDownload(tableId: ID) {
return requestClient.get<Blob>(`${Api.download}/${tableId}`);
}
// 生成代码(自定义路径)
export function genWithPath(tableId: ID) {
return requestClient.get<void>(`${Api.genCode}/${tableId}`);
}
// 同步数据库
export function syncDb(tableId: ID) {
return requestClient.get(`${Api.syncDb}/${tableId}`, {
successMessageMode: 'message',
});
}
// 批量生成代码
export function batchGenCode(tableIdStr: ID | IDS) {
return requestClient.get<Blob>(Api.batchGenCode, {
isTransformResponse: false,
params: { tableIdStr },
responseType: 'blob',
});
}
// 查询数据源名称列表
export function getDataSourceNames() {
return requestClient.get<string[]>(Api.dataSourceNames);
}

View File

@@ -0,0 +1,187 @@
export interface Column {
createDept?: any;
createBy?: any;
createTime?: any;
updateBy?: any;
updateTime?: any;
columnId: string;
tableId: string;
columnName: string;
columnComment: string;
columnType: string;
javaType: string;
javaField: string;
isPk: string;
isIncrement: string;
isRequired: string;
isInsert?: any;
isEdit: string;
isList: string;
isQuery?: any;
queryType: string;
htmlType: string;
dictType: string;
sort: number;
list: boolean;
required: boolean;
pk: boolean;
insert: boolean;
edit: boolean;
usableColumn: boolean;
superColumn: boolean;
increment: boolean;
query: boolean;
capJavaField: string;
}
export interface Table {
createDept?: any;
createBy?: any;
createTime?: any;
updateBy?: any;
updateTime?: any;
tableId: string;
dataName: string;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className: string;
tplCategory: string;
packageName: string;
moduleName: string;
businessName: string;
functionName: string;
functionAuthor: string;
genType?: any;
genPath?: any;
pkColumn?: any;
columns: Column[];
options?: any;
remark?: any;
treeCode?: any;
treeParentCode?: any;
treeName?: any;
menuIds?: any;
parentMenuId?: any;
parentMenuName?: any;
tree: boolean;
crud: boolean;
}
export interface Row {
createDept: number;
createBy: number;
createTime: string;
updateBy: number;
updateTime: string;
columnId: string;
tableId: string;
columnName: string;
columnComment: string;
columnType: string;
javaType: string;
javaField: string;
isPk: string;
isIncrement: string;
isRequired: string;
isInsert?: any;
isEdit: string;
isList: string;
isQuery?: any;
queryType: string;
htmlType: string;
dictType: string;
sort: number;
list: boolean;
required: boolean;
pk: boolean;
insert: boolean;
edit: boolean;
usableColumn: boolean;
superColumn: boolean;
increment: boolean;
query: boolean;
capJavaField: string;
}
export interface Column {
createDept?: any;
createBy?: any;
createTime?: any;
updateBy?: any;
updateTime?: any;
columnId: string;
tableId: string;
columnName: string;
columnComment: string;
columnType: string;
javaType: string;
javaField: string;
isPk: string;
isIncrement: string;
isRequired: string;
isInsert?: any;
isEdit: string;
isList: string;
isQuery?: any;
queryType: string;
htmlType: string;
dictType: string;
sort: number;
list: boolean;
required: boolean;
pk: boolean;
insert: boolean;
edit: boolean;
usableColumn: boolean;
superColumn: boolean;
increment: boolean;
query: boolean;
capJavaField: string;
}
export interface Info {
createDept?: any;
createBy?: any;
createTime?: any;
updateBy?: any;
updateTime?: any;
tableId: string;
dataName: string;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className: string;
tplCategory: string;
packageName: string;
moduleName: string;
businessName: string;
functionName: string;
functionAuthor: string;
genType: string;
genPath: string;
pkColumn?: any;
columns: Column[];
options?: any;
remark?: any;
treeCode?: any;
treeParentCode?: any;
treeName?: any;
menuIds?: any;
parentMenuId?: any;
parentMenuName?: any;
tree: boolean;
crud: boolean;
// 树表需要添加此属性
params?: any;
popupComponent?: string;
formComponent?: string;
}
export interface GenInfo {
tables: Table[];
rows: Row[];
info: Info;
}

View File

@@ -0,0 +1,63 @@
import type {
CategoryForm,
CategoryQuery,
CategoryTree,
CategoryVO,
} from './model';
import type { ID, IDS } from '#/api/common';
import { requestClient } from '#/api/request';
/**
* 获取流程分类树列表
* @returns tree
*/
export function categoryTree() {
return requestClient.get<CategoryTree[]>('/workflow/category/categoryTree');
}
/**
* 查询流程分类列表
* @param params
* @returns 流程分类列表
*/
export function categoryList(params?: CategoryQuery) {
return requestClient.get<CategoryVO[]>(`/workflow/category/list`, { params });
}
/**
* 查询流程分类详情
* @param id id
* @returns 流程分类详情
*/
export function categoryInfo(id: ID) {
return requestClient.get<CategoryVO>(`/workflow/category/${id}`);
}
/**
* 新增流程分类
* @param data
* @returns void
*/
export function categoryAdd(data: CategoryForm) {
return requestClient.postWithMsg<void>('/workflow/category', data);
}
/**
* 更新流程分类
* @param data
* @returns void
*/
export function categoryUpdate(data: CategoryForm) {
return requestClient.putWithMsg<void>('/workflow/category', data);
}
/**
* 删除流程分类
* @param id id
* @returns void
*/
export function categoryRemove(id: ID | IDS) {
return requestClient.deleteWithMsg<void>(`/workflow/category/${id}`);
}

View File

@@ -0,0 +1,97 @@
import type { BaseEntity } from '#/api/common';
export interface CategoryVO {
/**
* 主键
*/
id: number | string;
/**
* 分类名称
*/
categoryName: string;
/**
* 分类编码
*/
categoryCode: string;
/**
* 父级id
*/
parentId: number | string;
/**
* 排序
*/
sortNum: number;
/**
* 子对象
*/
children: CategoryVO[];
key: string;
}
export interface CategoryForm extends BaseEntity {
/**
* 主键
*/
id?: number | string;
/**
* 分类名称
*/
categoryName?: string;
/**
* 分类编码
*/
categoryCode?: string;
/**
* 父级id
*/
parentId?: number | string;
/**
* 排序
*/
sortNum?: number;
}
export interface CategoryQuery {
/**
* 分类名称
*/
categoryName?: string;
/**
* 分类编码
*/
categoryCode?: string;
/**
* 父级id
*/
parentId?: number | string;
/**
* 排序
*/
sortNum?: number;
/**
* 日期范围参数
*/
params?: any;
}
export interface CategoryTree {
id: number;
parentId: number;
label: string;
weight: number;
children: CategoryTree[];
key: string;
}

View File

@@ -0,0 +1,155 @@
import type { ProcessDefinition } from './model';
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
import { requestClient } from '#/api/request';
/**
* 全部的流程定义
* @param params 查询参数
* @returns 分页
*/
export function workflowDefinitionList(params?: PageQuery) {
return requestClient.get<PageResult<ProcessDefinition>>(
'/workflow/definition/list',
{ params },
);
}
/**
* 未发布的流程定义
* @param params 查询参数
* @returns 分页
*/
export function unPublishList(params?: PageQuery) {
return requestClient.get<PageResult<ProcessDefinition>>(
'/workflow/definition/unPublishList',
{ params },
);
}
/**
* 获取历史流程定义列表
* @param flowCode
* @returns ProcessDefinition[]
*/
export function getHisListByKey(flowCode: string) {
return requestClient.get<ProcessDefinition[]>(
`/workflow/definition/getHisListByKey/${flowCode}`,
);
}
/**
* 获取流程定义详细信息
* @param id id
* @returns ProcessDefinition
*/
export function workflowDefinitionInfo(id: ID) {
return requestClient.get<ProcessDefinition>(`/workflow/definition/${id}`);
}
/**
* 新增流程定义
* @param data
*/
export function workflowDefinitionAdd(data: any) {
return requestClient.postWithMsg<void>('/workflow/definition', data);
}
/**
* 更新流程定义
* @param data
*/
export function workflowDefinitionUpdate(data: any) {
return requestClient.putWithMsg<void>('/workflow/definition', data);
}
/**
* 发布流程定义
* @param id id
* @returns boolean
*/
export function workflowDefinitionPublish(id: ID) {
return requestClient.putWithMsg<boolean>(
`/workflow/definition/publish/${id}`,
);
}
/**
* 取消发布流程定义
* @param id id
* @returns boolean
*/
export function workflowDefinitionUnPublish(id: ID) {
return requestClient.putWithMsg<boolean>(
`/workflow/definition/unPublish/${id}`,
);
}
/**
* 删除流程定义
* @param ids idList
*/
export function workflowDefinitionDelete(ids: IDS) {
return requestClient.deleteWithMsg<void>(`/workflow/definition/${ids}`);
}
/**
* 复制流程定义
* @param id id
*/
export function workflowDefinitionCopy(id: ID) {
return requestClient.postWithMsg<void>(`/workflow/definition/copy/${id}`);
}
/**
* 导入流程定义
* @returns boolean
*/
export function workflowDefinitionImport(data: {
category: ID;
file: Blob | File;
}) {
return requestClient.postWithMsg<boolean>(
'/workflow/definition/importDef',
data,
{ headers: { 'Content-Type': 'multipart/form-data' } },
);
}
/**
* 导出流程定义
* @param id id
* @returns blob
*/
export function workflowDefinitionExport(id: ID) {
return requestClient.postWithMsg<Blob>(
`/workflow/definition/exportDef/${id}`,
{},
{
responseType: 'blob',
isTransformResponse: false,
},
);
}
/**
* 获取流程定义xml字符串
* @param id id
* @returns xml
*/
export function workflowDefinitionXml(id: ID) {
return requestClient.get<string>(`/workflow/definition/xmlString/${id}`);
}
/**
* 激活/挂起流程定义
* @param id 流程定义id
* @param active 激活/挂起
* @returns boolean
*/
export function workflowDefinitionActive(id: ID, active: boolean) {
return requestClient.putWithMsg<boolean>(
`/workflow/definition/active/${id}?active=${active}`,
);
}

Some files were not shown because too many files have changed in this diff Show More