chore: 脚手架
This commit is contained in:
parent
4bd4f7490b
commit
c31259598b
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -23,7 +23,7 @@
|
|||||||
"editor.suggestSelection": "recentlyUsedByPrefix",
|
"editor.suggestSelection": "recentlyUsedByPrefix",
|
||||||
"editor.acceptSuggestionOnEnter": "smart",
|
"editor.acceptSuggestionOnEnter": "smart",
|
||||||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
||||||
"editor.stickyScroll.enabled": true,
|
"editor.stickyScroll.enabled": false,
|
||||||
"editor.hover.sticky": true,
|
"editor.hover.sticky": true,
|
||||||
"editor.suggest.insertMode": "replace",
|
"editor.suggest.insertMode": "replace",
|
||||||
"editor.bracketPairColorization.enabled": true,
|
"editor.bracketPairColorization.enabled": true,
|
||||||
@ -46,7 +46,7 @@
|
|||||||
"terminal.integrated.persistentSessionReviveProcess": "never",
|
"terminal.integrated.persistentSessionReviveProcess": "never",
|
||||||
"terminal.integrated.tabs.enabled": true,
|
"terminal.integrated.tabs.enabled": true,
|
||||||
"terminal.integrated.scrollback": 10000,
|
"terminal.integrated.scrollback": 10000,
|
||||||
"terminal.integrated.stickyScroll.enabled": true,
|
"terminal.integrated.stickyScroll.enabled": false,
|
||||||
|
|
||||||
// files
|
// files
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
# 端口号
|
# 端口号
|
||||||
VITE_PORT=5555
|
VITE_PORT=5555
|
||||||
|
# base路径
|
||||||
VITE_BASE=/
|
VITE_BASE=/
|
||||||
|
|
||||||
# 接口地址
|
|
||||||
VITE_GLOB_API_URL=/api
|
|
||||||
|
|
||||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||||
VITE_NITRO_MOCK=true
|
VITE_NITRO_MOCK=true
|
||||||
|
|
||||||
# 是否打开 devtools,true 为打开,false 为关闭
|
# 是否打开 devtools,true 为打开,false 为关闭
|
||||||
VITE_DEVTOOLS=false
|
VITE_DEVTOOLS=false
|
||||||
|
|
||||||
# 是否注入全局loading
|
# 是否注入全局loading
|
||||||
VITE_INJECT_APP_LOADING=true
|
VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
|
# 后台请求路径 具体在vite.config.mts配置代理
|
||||||
|
VITE_GLOB_API_URL=/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
|
||||||
|
|
||||||
|
# 开启WEBSOCKET
|
||||||
|
VITE_GLOB_WEBSOCKET_ENABLE=false
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
VITE_BASE=/
|
VITE_BASE=/
|
||||||
|
|
||||||
# 接口地址
|
|
||||||
VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
|
|
||||||
|
|
||||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||||
VITE_COMPRESS=none
|
VITE_COMPRESS=none
|
||||||
|
|
||||||
@ -10,7 +7,22 @@ VITE_COMPRESS=none
|
|||||||
VITE_PWA=false
|
VITE_PWA=false
|
||||||
|
|
||||||
# vue-router 的模式
|
# vue-router 的模式
|
||||||
VITE_ROUTER_HISTORY=hash
|
VITE_ROUTER_HISTORY=history
|
||||||
|
|
||||||
# 是否注入全局loading
|
# 是否注入全局loading
|
||||||
VITE_INJECT_APP_LOADING=true
|
VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
|
# 后台请求路径 具体在vite.config.mts配置代理
|
||||||
|
VITE_GLOB_API_URL=/prod-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=6afcaa29272b14c1c87264950c726ef4
|
||||||
|
|
||||||
|
# 开启WEBSOCKET
|
||||||
|
VITE_GLOB_WEBSOCKET_ENABLE=false
|
||||||
|
|||||||
9
apps/web-antd/.vscode/settings.json
vendored
Normal file
9
apps/web-antd/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": "explicit",
|
||||||
|
"source.fixAll.stylelint": "explicit"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,19 +14,6 @@
|
|||||||
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||||
<title><%= VITE_APP_TITLE %></title>
|
<title><%= VITE_APP_TITLE %></title>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<script>
|
|
||||||
// 生产环境下注入百度统计
|
|
||||||
if (window._VBEN_ADMIN_PRO_APP_CONF_) {
|
|
||||||
var _hmt = _hmt || [];
|
|
||||||
(function () {
|
|
||||||
var hm = document.createElement('script');
|
|
||||||
hm.src =
|
|
||||||
'https://hm.baidu.com/hm.js?d20a01273820422b6aa2ee41b6c9414d';
|
|
||||||
var s = document.getElementsByTagName('script')[0];
|
|
||||||
s.parentNode.insertBefore(hm, s);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@ -42,9 +42,17 @@
|
|||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "^10.11.0",
|
"@vueuse/core": "^10.11.0",
|
||||||
"ant-design-vue": "^4.2.3",
|
"ant-design-vue": "^4.2.3",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.12",
|
"dayjs": "^1.11.12",
|
||||||
|
"echarts": "^5.5.1",
|
||||||
|
"jsencrypt": "^3.3.2",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
"pinia": "2.2.0",
|
"pinia": "2.2.0",
|
||||||
"vue": "^3.4.35",
|
"vue": "^3.4.35",
|
||||||
"vue-router": "^4.4.2"
|
"vue-router": "^4.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
|
"@types/lodash-es": "^4.17.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,25 @@
|
|||||||
|
import { useAppConfig } from '@vben/hooks';
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
import { requestClient } from '#/api/request';
|
||||||
|
|
||||||
|
const { clientId } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
|
||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
/** 登录接口参数 */
|
/** 登录接口参数 */
|
||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
|
code?: string;
|
||||||
|
grantType: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
tenantId: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
uuid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
accessToken: string;
|
access_token: string;
|
||||||
desc: string;
|
client_id: string;
|
||||||
realName: string;
|
expire_in: number;
|
||||||
refreshToken: string;
|
|
||||||
userId: string;
|
|
||||||
username: string;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,12 +27,46 @@ export namespace AuthApi {
|
|||||||
* 登录
|
* 登录
|
||||||
*/
|
*/
|
||||||
export async function login(data: AuthApi.LoginParams) {
|
export async function login(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
return requestClient.post<AuthApi.LoginResult>(
|
||||||
|
'/auth/login',
|
||||||
|
{ ...data, clientId },
|
||||||
|
{
|
||||||
|
encrypt: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户权限码
|
* 用户登出
|
||||||
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export async function getAccessCodes() {
|
export function doLogout() {
|
||||||
return requestClient.get<string[]>('/auth/codes');
|
return requestClient.post<void>('/auth/logout');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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>('/auth/tenant/list');
|
||||||
}
|
}
|
||||||
|
|||||||
42
apps/web-antd/src/api/core/captcha.ts
Normal file
42
apps/web-antd/src/api/core/captcha.ts
Normal 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 captchaEnabled 是否开启
|
||||||
|
* @param uuid 验证码ID
|
||||||
|
*/
|
||||||
|
export interface CaptchaResponse {
|
||||||
|
captchaEnabled: boolean;
|
||||||
|
img: string;
|
||||||
|
uuid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片验证码
|
||||||
|
* @returns resp
|
||||||
|
*/
|
||||||
|
export function captchaImage() {
|
||||||
|
return requestClient.get<CaptchaResponse>('/auth/code');
|
||||||
|
}
|
||||||
@ -1,10 +1,45 @@
|
|||||||
import type { RouteRecordStringComponent } from '@vben/types';
|
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
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 组件名称 Laout
|
||||||
|
* @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 getAllMenus() {
|
export async function getAllMenus() {
|
||||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
return requestClient.get<Menu[]>('/system/menu/getRouters');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,45 @@
|
|||||||
import type { UserInfo } from '@vben/types';
|
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
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 {
|
||||||
|
permissions: string[];
|
||||||
|
roles: string[];
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户信息
|
* 获取用户信息
|
||||||
*/
|
*/
|
||||||
export async function getUserInfo() {
|
export async function getUserInfo() {
|
||||||
return requestClient.get<UserInfo>('/user/info');
|
return requestClient.get<UserInfoResp>('/system/user/getInfo');
|
||||||
}
|
}
|
||||||
|
|||||||
69
apps/web-antd/src/api/helper.ts
Normal file
69
apps/web-antd/src/api/helper.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { isObject, isString } from '@vben/utils';
|
||||||
|
|
||||||
|
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
|
export function joinTimestamp<T extends boolean>(
|
||||||
|
join: boolean,
|
||||||
|
restful: T,
|
||||||
|
): T extends true ? string : object;
|
||||||
|
|
||||||
|
export function joinTimestamp(join: boolean, restful = false): object | string {
|
||||||
|
if (!join) {
|
||||||
|
return restful ? '' : {};
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
if (restful) {
|
||||||
|
return `?_t=${now}`;
|
||||||
|
}
|
||||||
|
return { _t: now };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: Format request parameter time
|
||||||
|
*/
|
||||||
|
export function formatRequestDate(params: Record<string, any>) {
|
||||||
|
if (Object.prototype.toString.call(params) !== '[object Object]') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in params) {
|
||||||
|
const format = params[key]?.format ?? null;
|
||||||
|
if (format && typeof format === 'function') {
|
||||||
|
params[key] = params[key].format(DATE_TIME_FORMAT);
|
||||||
|
}
|
||||||
|
if (isString(key)) {
|
||||||
|
const value = params[key];
|
||||||
|
if (value) {
|
||||||
|
try {
|
||||||
|
params[key] = isString(value) ? value.trim() : value;
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isObject(params[key])) {
|
||||||
|
formatRequestDate(params[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the object as a parameter to the URL
|
||||||
|
* @param baseUrl url
|
||||||
|
* @param obj
|
||||||
|
* @returns {string}
|
||||||
|
* eg:
|
||||||
|
* let obj = {a: '3', b: '4'}
|
||||||
|
* setObjToUrlParams('www.baidu.com', obj)
|
||||||
|
* ==>www.baidu.com?a=3&b=4
|
||||||
|
*/
|
||||||
|
export function setObjToUrlParams(baseUrl: string, obj: any): string {
|
||||||
|
let parameters = '';
|
||||||
|
for (const key in obj) {
|
||||||
|
parameters += `${key}=${encodeURIComponent(obj[key])}&`;
|
||||||
|
}
|
||||||
|
parameters = parameters.replace(/&$/, '');
|
||||||
|
return /\?$/.test(baseUrl)
|
||||||
|
? baseUrl + parameters
|
||||||
|
: baseUrl.replace(/\/?$/, '?') + parameters;
|
||||||
|
}
|
||||||
24
apps/web-antd/src/api/monitor/cache/index.ts
vendored
Normal file
24
apps/web-antd/src/api/monitor/cache/index.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns redis信息
|
||||||
|
*/
|
||||||
|
export function redisCacheInfo() {
|
||||||
|
return requestClient.get<CacheInfo>('/monitor/cache');
|
||||||
|
}
|
||||||
@ -4,19 +4,51 @@
|
|||||||
import type { HttpResponse } from '@vben/request';
|
import type { HttpResponse } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { useAppConfig } from '@vben/hooks';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { RequestClient } from '@vben/request';
|
import { RequestClient } from '@vben/request';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
import { isString } from '@vben/utils';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message, Modal } from 'ant-design-vue';
|
||||||
|
import { isEmpty, isNull } from 'lodash-es';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
import {
|
||||||
|
decryptBase64,
|
||||||
|
decryptWithAes,
|
||||||
|
encryptBase64,
|
||||||
|
encryptWithAes,
|
||||||
|
generateAesKey,
|
||||||
|
} from '#/utils/encryption/crypto';
|
||||||
|
import * as encryptUtil from '#/utils/encryption/jsencrypt';
|
||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
import { formatRequestDate, joinTimestamp, setObjToUrlParams } from './helper';
|
||||||
|
|
||||||
|
const { apiURL, clientId, enableEncrypt } = useAppConfig(
|
||||||
|
import.meta.env,
|
||||||
|
import.meta.env.PROD,
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 控制是否弹窗 防止登录超时请求多个api会弹窗多次 */
|
||||||
|
let showTimeoutToast = true;
|
||||||
|
|
||||||
function createRequestClient(baseURL: string) {
|
function createRequestClient(baseURL: string) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
// 后端地址
|
||||||
baseURL,
|
baseURL,
|
||||||
|
// 消息提示类型
|
||||||
|
errorMessageMode: 'message',
|
||||||
|
// 格式化提交参数时间
|
||||||
|
formatDate: true,
|
||||||
|
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||||
|
isReturnNativeResponse: false,
|
||||||
|
// 需要对返回数据进行处理
|
||||||
|
isTransformResponse: true,
|
||||||
|
// post请求的时候添加参数到url
|
||||||
|
joinParamsToUrl: false,
|
||||||
|
// 是否加入时间戳
|
||||||
|
joinTime: false,
|
||||||
// 为每个请求携带 Authorization
|
// 为每个请求携带 Authorization
|
||||||
makeAuthorization: () => {
|
makeAuthorization: () => {
|
||||||
return {
|
return {
|
||||||
@ -43,23 +75,192 @@ function createRequestClient(baseURL: string) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* http状态码不为200会走到这里
|
||||||
|
* 其他会走到addResponseInterceptor
|
||||||
|
* @param msg
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
makeErrorMessage: (msg) => message.error(msg),
|
makeErrorMessage: (msg) => message.error(msg),
|
||||||
|
|
||||||
makeRequestHeaders: () => {
|
makeRequestHeaders: () => {
|
||||||
|
/**
|
||||||
|
* locale跟后台不一致 需要转换
|
||||||
|
*/
|
||||||
|
const language = preferences.app.locale.replace('-', '_');
|
||||||
return {
|
return {
|
||||||
// 为每个请求携带 Accept-Language
|
// 为每个请求携带 Accept-Language
|
||||||
'Accept-Language': preferences.app.locale,
|
'Accept-Language': language,
|
||||||
|
clientId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
client.addResponseInterceptor<HttpResponse>((response) => {
|
|
||||||
const { data: responseData, status } = response;
|
|
||||||
|
|
||||||
const { code, data, message: msg } = responseData;
|
client.addRequestInterceptor((config) => {
|
||||||
if (status >= 200 && status < 400 && code === 0) {
|
const { encrypt, formatDate, joinParamsToUrl, joinTime = true } = config;
|
||||||
return data;
|
const params = config.params || {};
|
||||||
|
const data = config.data || false;
|
||||||
|
formatDate && data && !isString(data) && formatRequestDate(data);
|
||||||
|
if (config.method?.toUpperCase() === 'GET') {
|
||||||
|
if (isString(params)) {
|
||||||
|
// 兼容restful风格
|
||||||
|
config.url = `${config.url + params}${joinTimestamp(joinTime, true)}`;
|
||||||
|
config.params = undefined;
|
||||||
|
} else {
|
||||||
|
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
|
||||||
|
config.params = Object.assign(
|
||||||
|
params || {},
|
||||||
|
joinTimestamp(joinTime, false),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isString(params)) {
|
||||||
|
// 兼容restful风格
|
||||||
|
config.url = config.url + params;
|
||||||
|
config.params = undefined;
|
||||||
|
} else {
|
||||||
|
formatDate && formatRequestDate(params);
|
||||||
|
if (
|
||||||
|
Reflect.has(config, 'data') &&
|
||||||
|
config.data &&
|
||||||
|
(Object.keys(config.data).length > 0 ||
|
||||||
|
config.data instanceof FormData)
|
||||||
|
) {
|
||||||
|
config.data = data;
|
||||||
|
config.params = params;
|
||||||
|
} else {
|
||||||
|
// 非GET请求如果没有提供data,则将params视为data
|
||||||
|
config.data = params;
|
||||||
|
config.params = undefined;
|
||||||
|
}
|
||||||
|
if (joinParamsToUrl) {
|
||||||
|
config.url = setObjToUrlParams(
|
||||||
|
config.url as string,
|
||||||
|
Object.assign({}, config.params, config.data),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw new Error(msg);
|
console.log('请求参数', 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;
|
||||||
|
});
|
||||||
|
|
||||||
|
client.addResponseInterceptor<HttpResponse>((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;
|
||||||
|
}
|
||||||
|
// 不进行任何处理,直接返回
|
||||||
|
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||||
|
if (!isTransformResponse) {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const axiosResponseData = response.data;
|
||||||
|
if (!axiosResponseData) {
|
||||||
|
throw new Error($t('fallback.http.apiRequestFailed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ruoyi-plus没有采用严格的{code, msg, data}模式
|
||||||
|
const { code, data, msg, ...other } = axiosResponseData;
|
||||||
|
|
||||||
|
// 这里逻辑可以根据项目进行修改
|
||||||
|
const hasSuccess = Reflect.has(axiosResponseData, 'code') && code === 200;
|
||||||
|
if (hasSuccess) {
|
||||||
|
let successMsg = msg;
|
||||||
|
|
||||||
|
if (isNull(successMsg) || isEmpty(successMsg)) {
|
||||||
|
successMsg = $t(`fallback.http.operationSuccess`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.config.successMessageMode === 'modal') {
|
||||||
|
Modal.success({
|
||||||
|
content: successMsg,
|
||||||
|
title: $t('fallback.http.successTip'),
|
||||||
|
});
|
||||||
|
} else if (response.config.successMessageMode === 'message') {
|
||||||
|
message.success(successMsg);
|
||||||
|
}
|
||||||
|
// ruoyi-plus没有采用严格的{code, msg, data}模式
|
||||||
|
// 如果有data 直接返回data 没有data将剩余参数(...other)封装为data返回
|
||||||
|
// 需要考虑data为null的情况(比如查询为空)
|
||||||
|
if (data !== undefined) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
// 在此处根据自己项目的实际情况对不同的code执行不同的操作
|
||||||
|
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
|
||||||
|
let timeoutMsg = '';
|
||||||
|
switch (code) {
|
||||||
|
case 401: {
|
||||||
|
const _msg = '登录超时, 请重新登录';
|
||||||
|
const userStore = useAuthStore();
|
||||||
|
userStore.logout().then(() => {
|
||||||
|
/** 只弹窗一次 */
|
||||||
|
if (showTimeoutToast) {
|
||||||
|
showTimeoutToast = false;
|
||||||
|
message.error(_msg);
|
||||||
|
/** 定时器 3s后再开启弹窗 */
|
||||||
|
setTimeout(() => {
|
||||||
|
showTimeoutToast = true;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 不再执行下面逻辑
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (msg) {
|
||||||
|
timeoutMsg = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
||||||
|
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
|
||||||
|
if (response.config.errorMessageMode === 'modal') {
|
||||||
|
Modal.error({
|
||||||
|
content: timeoutMsg,
|
||||||
|
title: $t('fallback.http.errorTip'),
|
||||||
|
});
|
||||||
|
} else if (response.config.errorMessageMode === 'message') {
|
||||||
|
message.error(timeoutMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(timeoutMsg || $t('fallback.http.apiRequestFailed'));
|
||||||
});
|
});
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { NotificationItem } from '@vben/layouts';
|
import { computed, onMounted } from 'vue';
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||||
import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
||||||
import {
|
import {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
@ -14,55 +11,18 @@ import {
|
|||||||
UserDropdown,
|
UserDropdown,
|
||||||
} from '@vben/layouts';
|
} from '@vben/layouts';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
resetAllStores,
|
|
||||||
storeToRefs,
|
|
||||||
useAccessStore,
|
|
||||||
useUserStore,
|
|
||||||
} from '@vben/stores';
|
|
||||||
import { openWindow } from '@vben/utils';
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { resetRoutes } from '#/router';
|
import { resetRoutes } from '#/router';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore, useNotifyStore } from '#/store';
|
||||||
|
|
||||||
const notifications = ref<NotificationItem[]>([
|
|
||||||
{
|
|
||||||
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
|
|
||||||
date: '3小时前',
|
|
||||||
isRead: true,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '收到了 14 份新周报',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatar: 'https://avatar.vercel.sh/1',
|
|
||||||
date: '刚刚',
|
|
||||||
isRead: false,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '朱偏右 回复了你',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatar: 'https://avatar.vercel.sh/1',
|
|
||||||
date: '2024-01-01',
|
|
||||||
isRead: false,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '曲丽丽 评论了你',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatar: 'https://avatar.vercel.sh/satori',
|
|
||||||
date: '1天前',
|
|
||||||
isRead: false,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '代办提醒',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const showDot = computed(() =>
|
|
||||||
notifications.value.some((item) => !item.isRead),
|
|
||||||
);
|
|
||||||
|
|
||||||
const menus = computed(() => [
|
const menus = computed(() => [
|
||||||
{
|
{
|
||||||
@ -100,20 +60,18 @@ const avatar = computed(() => {
|
|||||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
resetAllStores();
|
// resetAllStores();
|
||||||
resetRoutes();
|
resetRoutes();
|
||||||
await router.replace(LOGIN_PATH);
|
// await router.replace(LOGIN_PATH);
|
||||||
|
authStore.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNoticeClear() {
|
const notifyStore = useNotifyStore();
|
||||||
notifications.value = [];
|
onMounted(() => notifyStore.startListeningMessage());
|
||||||
}
|
|
||||||
|
|
||||||
function handleMakeAll() {
|
function handleViewAll() {
|
||||||
notifications.value.forEach((item) => (item.isRead = true));
|
message.warning('暂未开放');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -131,10 +89,12 @@ function handleMakeAll() {
|
|||||||
</template>
|
</template>
|
||||||
<template #notification>
|
<template #notification>
|
||||||
<Notification
|
<Notification
|
||||||
:dot="showDot"
|
:dot="notifyStore.showDot"
|
||||||
:notifications="notifications"
|
:notifications="notifyStore.notificationList"
|
||||||
@clear="handleNoticeClear"
|
@clear="notifyStore.clearAllMessage"
|
||||||
@make-all="handleMakeAll"
|
@make-all="notifyStore.setAllRead"
|
||||||
|
@read="notifyStore.setRead"
|
||||||
|
@view-all="handleViewAll"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
|
|||||||
@ -24,10 +24,10 @@ const localesMap = loadLocalesMap(modules);
|
|||||||
*/
|
*/
|
||||||
async function loadMessages(lang: SupportedLanguagesType) {
|
async function loadMessages(lang: SupportedLanguagesType) {
|
||||||
const [appLocaleMessages] = await Promise.all([
|
const [appLocaleMessages] = await Promise.all([
|
||||||
localesMap[lang]?.(),
|
localesMap[lang](),
|
||||||
loadThirdPartyMessage(lang),
|
loadThirdPartyMessage(lang),
|
||||||
]);
|
]);
|
||||||
return appLocaleMessages?.default;
|
return appLocaleMessages.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -7,6 +7,18 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
|||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
app: {
|
app: {
|
||||||
|
/**
|
||||||
|
* 不要动这里 后端路由模式
|
||||||
|
*/
|
||||||
|
accessMode: 'backend',
|
||||||
name: import.meta.env.VITE_APP_TITLE,
|
name: import.meta.env.VITE_APP_TITLE,
|
||||||
},
|
},
|
||||||
|
tabbar: {
|
||||||
|
/**
|
||||||
|
* 标签tab 持久化 关闭
|
||||||
|
*/
|
||||||
|
persist: false,
|
||||||
|
styleType: 'card',
|
||||||
|
},
|
||||||
|
theme: {},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,18 +1,88 @@
|
|||||||
import type {
|
import type {
|
||||||
ComponentRecordType,
|
ComponentRecordType,
|
||||||
GenerateMenuAndRoutesOptions,
|
GenerateMenuAndRoutesOptions,
|
||||||
|
RouteRecordStringComponent,
|
||||||
} from '@vben/types';
|
} from '@vben/types';
|
||||||
|
|
||||||
import { generateAccessible } from '@vben/access';
|
import { generateAccessible } from '@vben/access';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import { getAllMenus } from '#/api';
|
import { getAllMenus, type Menu } from '#/api';
|
||||||
import { BasicLayout, IFrameView } from '#/layouts';
|
import { BasicLayout, IFrameView } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||||
|
const NotFoundComponent = () => import('#/views/_core/fallback/not-found.vue');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这里放本地路由
|
||||||
|
*/
|
||||||
|
const localMenuList: RouteRecordStringComponent[] = [
|
||||||
|
{
|
||||||
|
component: 'BasicLayout',
|
||||||
|
meta: {
|
||||||
|
order: -1,
|
||||||
|
title: 'page.dashboard.title',
|
||||||
|
},
|
||||||
|
name: 'Dashboard',
|
||||||
|
path: '/',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'VbenDocument',
|
||||||
|
path: '/vben-admin/document',
|
||||||
|
component: 'IFrameView',
|
||||||
|
meta: {
|
||||||
|
icon: 'lucide:book-open-text',
|
||||||
|
iframeSrc: 'https://dapdap.top',
|
||||||
|
keepAlive: true,
|
||||||
|
title: $t('page.vben.document'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'BasicLayout',
|
||||||
|
meta: {
|
||||||
|
hideChildrenInMenu: true,
|
||||||
|
icon: 'lucide:copyright',
|
||||||
|
order: 9999,
|
||||||
|
title: $t('page.vben.about'),
|
||||||
|
},
|
||||||
|
name: 'About',
|
||||||
|
path: '/about',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
component: '/_core/vben/about/index',
|
||||||
|
meta: {
|
||||||
|
title: $t('page.vben.about'),
|
||||||
|
},
|
||||||
|
name: 'VbenAbout',
|
||||||
|
path: '/vben-admin/about',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||||
@ -20,16 +90,164 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
|||||||
const layoutMap: ComponentRecordType = {
|
const layoutMap: ComponentRecordType = {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
IFrameView,
|
IFrameView,
|
||||||
|
NotFoundComponent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后台路由转vben路由
|
||||||
|
*
|
||||||
|
* todo 需要重构
|
||||||
|
* @param menuList 后台菜单
|
||||||
|
* @param parentPath 上级目录
|
||||||
|
* @returns vben路由
|
||||||
|
*/
|
||||||
|
function backMenuToVbenMenu(
|
||||||
|
menuList: Menu[],
|
||||||
|
parentPath = '',
|
||||||
|
): RouteRecordStringComponent[] {
|
||||||
|
const resultList: RouteRecordStringComponent[] = [];
|
||||||
|
menuList.forEach((menu) => {
|
||||||
|
// 根目录为菜单形式
|
||||||
|
// 固定有一个children children为当前菜单
|
||||||
|
if (menu.path === '/' && menu.children && menu.children.length === 1) {
|
||||||
|
menu.meta = menu.children[0].meta;
|
||||||
|
/**
|
||||||
|
* todo 先写死 后续再优化
|
||||||
|
*/
|
||||||
|
menu.path = '/root_menu';
|
||||||
|
menu.component = 'RootMenu';
|
||||||
|
}
|
||||||
|
// 外链: http开头 & 组件为Layout || ParentView
|
||||||
|
// 正则判断是否为http://或者https://开头
|
||||||
|
if (
|
||||||
|
/^http(s)?:\/\//.test(menu.path) &&
|
||||||
|
(menu.component === 'Layout' || menu.component === 'ParentView')
|
||||||
|
) {
|
||||||
|
menu.component = 'Link';
|
||||||
|
}
|
||||||
|
// 内嵌iframe 组件为InnerLink
|
||||||
|
if (menu.meta?.link && menu.component === 'InnerLink') {
|
||||||
|
menu.component = 'IFrameView';
|
||||||
|
}
|
||||||
|
|
||||||
|
// path
|
||||||
|
if (parentPath) {
|
||||||
|
menu.path = `${parentPath}/${menu.path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const vbenRoute: RouteRecordStringComponent = {
|
||||||
|
component: menu.component,
|
||||||
|
meta: {
|
||||||
|
// 当前路由不在菜单显示 但是可以通过链接访问
|
||||||
|
// 不可访问的路由由后端控制隐藏(不返回对应路由)
|
||||||
|
hideMenu: menu.hidden,
|
||||||
|
icon: menu.meta?.icon,
|
||||||
|
keepAlive: !menu.meta?.noCache,
|
||||||
|
title: menu.meta?.title,
|
||||||
|
},
|
||||||
|
name: menu.name,
|
||||||
|
path: menu.path,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理不同组件
|
||||||
|
*/
|
||||||
|
switch (menu.component) {
|
||||||
|
case 'Layout': {
|
||||||
|
vbenRoute.component = 'BasicLayout';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* iframe内嵌
|
||||||
|
*/
|
||||||
|
case 'IFrameView': {
|
||||||
|
vbenRoute.component = 'IFrameView';
|
||||||
|
if (vbenRoute.meta) {
|
||||||
|
vbenRoute.meta.iframeSrc = menu.meta.link;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 需要判断特殊情况 比如vue的hash是带#的
|
||||||
|
* 比如链接 aaa.com/#/bbb path会转换为 aaa/com/#/bbb
|
||||||
|
* 比如链接 aaa.com/?bbb=xxx
|
||||||
|
* 需要去除# 否则无法被添加到路由
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* todo 不优雅 考虑别的方案
|
||||||
|
*/
|
||||||
|
if (vbenRoute.path.includes('/#/')) {
|
||||||
|
vbenRoute.path = vbenRoute.path.replace('/#/', '');
|
||||||
|
}
|
||||||
|
if (vbenRoute.path.includes('#')) {
|
||||||
|
vbenRoute.path = vbenRoute.path.replace('#', '');
|
||||||
|
}
|
||||||
|
if (vbenRoute.path.includes('?') || vbenRoute.path.includes('&')) {
|
||||||
|
vbenRoute.path = vbenRoute.path.replace('?', '');
|
||||||
|
vbenRoute.path = vbenRoute.path.replace('&', '');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 外链 新窗口打开
|
||||||
|
*/
|
||||||
|
case 'Link': {
|
||||||
|
if (vbenRoute.meta) {
|
||||||
|
vbenRoute.meta.link = menu.meta.link;
|
||||||
|
}
|
||||||
|
vbenRoute.component = 'BasicLayout';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 根目录菜单
|
||||||
|
*/
|
||||||
|
case 'RootMenu': {
|
||||||
|
if (vbenRoute.meta) {
|
||||||
|
vbenRoute.meta.hideChildrenInMenu = true;
|
||||||
|
}
|
||||||
|
vbenRoute.component = 'BasicLayout';
|
||||||
|
console.log('RootMenu', vbenRoute);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 不能为layout 会套两层BasicLayout
|
||||||
|
*/
|
||||||
|
case 'ParentView': {
|
||||||
|
vbenRoute.component = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 其他自定义组件 如system/user/index 拼接/
|
||||||
|
*/
|
||||||
|
default: {
|
||||||
|
vbenRoute.component = `/${menu.component}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// children处理
|
||||||
|
if (menu.children && menu.children.length > 0) {
|
||||||
|
vbenRoute.children = backMenuToVbenMenu(menu.children, menu.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
resultList.push(vbenRoute);
|
||||||
|
});
|
||||||
|
return resultList;
|
||||||
|
}
|
||||||
|
|
||||||
return await generateAccessible(preferences.app.accessMode, {
|
return await generateAccessible(preferences.app.accessMode, {
|
||||||
...options,
|
...options,
|
||||||
fetchMenuListAsync: async () => {
|
fetchMenuListAsync: async () => {
|
||||||
message.loading({
|
message.loading({
|
||||||
content: `${$t('common.loadingMenu')}...`,
|
content: `${$t('common.loadingMenu')}...`,
|
||||||
duration: 1.5,
|
duration: 1,
|
||||||
});
|
});
|
||||||
return await getAllMenus();
|
// 后台返回路由/菜单
|
||||||
|
const backMenuList = await getAllMenus();
|
||||||
|
// 转换为vben能用的路由
|
||||||
|
const vbenMenuList = backMenuToVbenMenu(backMenuList);
|
||||||
|
// 特别注意 这里要深拷贝
|
||||||
|
const menuList = [...cloneDeep(localMenuList), ...vbenMenuList];
|
||||||
|
console.log('menuList', menuList);
|
||||||
|
return menuList;
|
||||||
},
|
},
|
||||||
// 可以指定没有权限跳转403页面
|
// 可以指定没有权限跳转403页面
|
||||||
forbiddenComponent,
|
forbiddenComponent,
|
||||||
|
|||||||
@ -62,20 +62,14 @@ function setupAccessGuard(router: Router) {
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
// 基本路由,这些路由不需要进入权限拦截
|
|
||||||
if (coreRouteNames.includes(to.name as string)) {
|
|
||||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
|
||||||
return decodeURIComponent(
|
|
||||||
(to.query?.redirect as string) || DEFAULT_HOME_PATH,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// accessToken 检查
|
// accessToken 检查
|
||||||
if (!accessStore.accessToken) {
|
if (!accessStore.accessToken) {
|
||||||
// 明确声明忽略权限访问权限,则可以访问
|
if (
|
||||||
if (to.meta.ignoreAccess) {
|
// 基本路由,这些路由不需要进入权限拦截
|
||||||
|
coreRouteNames.includes(to.name as string) ||
|
||||||
|
// 明确声明忽略权限访问权限,则可以访问
|
||||||
|
to.meta.ignoreAccess
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +87,15 @@ function setupAccessGuard(router: Router) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accessRoutes = accessStore.accessRoutes;
|
const accessRoutes = accessStore.accessRoutes;
|
||||||
|
/**
|
||||||
|
* 已经登录 前往登录页 跳转到首页
|
||||||
|
*/
|
||||||
|
if (to.path === LOGIN_PATH) {
|
||||||
|
return {
|
||||||
|
path: DEFAULT_HOME_PATH,
|
||||||
|
replace: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 是否已经生成过动态路由
|
// 是否已经生成过动态路由
|
||||||
if (accessRoutes && accessRoutes.length > 0) {
|
if (accessRoutes && accessRoutes.length > 0) {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
|||||||
import { notification } from 'ant-design-vue';
|
import { notification } from 'ant-design-vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { getAccessCodes, getUserInfo, login } from '#/api';
|
import { doLogout, getUserInfo, login } from '#/api';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
@ -33,40 +33,35 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
let userInfo: null | UserInfo = null;
|
let userInfo: null | UserInfo = null;
|
||||||
try {
|
try {
|
||||||
loginLoading.value = true;
|
loginLoading.value = true;
|
||||||
const { accessToken, refreshToken } = await login(params);
|
const { access_token } = await login(params);
|
||||||
|
|
||||||
// 如果成功获取到 accessToken
|
// 将 accessToken 存储到 accessStore 中
|
||||||
if (accessToken) {
|
accessStore.setAccessToken(access_token);
|
||||||
// 将 accessToken 存储到 accessStore 中
|
accessStore.setRefreshToken(access_token);
|
||||||
accessStore.setAccessToken(accessToken);
|
|
||||||
accessStore.setRefreshToken(refreshToken);
|
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
// 获取用户信息并存储到 accessStore 中
|
||||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
userInfo = await fetchUserInfo();
|
||||||
fetchUserInfo(),
|
/**
|
||||||
getAccessCodes(),
|
* 设置用户信息
|
||||||
]);
|
*/
|
||||||
|
userStore.setUserInfo(userInfo);
|
||||||
|
/**
|
||||||
|
* 在这里设置权限
|
||||||
|
*/
|
||||||
|
accessStore.setAccessCodes(userInfo.permissions);
|
||||||
|
|
||||||
userInfo = fetchUserInfoResult;
|
if (accessStore.loginExpired) {
|
||||||
|
accessStore.setLoginExpired(false);
|
||||||
|
} else {
|
||||||
|
onSuccess ? await onSuccess?.() : await router.push(DEFAULT_HOME_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
userStore.setUserInfo(userInfo);
|
if (userInfo?.realName) {
|
||||||
accessStore.setAccessCodes(accessCodes);
|
notification.success({
|
||||||
|
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
||||||
if (accessStore.loginExpired) {
|
duration: 3,
|
||||||
accessStore.setLoginExpired(false);
|
message: $t('authentication.loginSuccess'),
|
||||||
} else {
|
});
|
||||||
onSuccess
|
|
||||||
? await onSuccess?.()
|
|
||||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
|
||||||
notification.success({
|
|
||||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
|
||||||
duration: 3,
|
|
||||||
message: $t('authentication.loginSuccess'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loginLoading.value = false;
|
loginLoading.value = false;
|
||||||
@ -78,21 +73,38 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
resetAllStores();
|
try {
|
||||||
accessStore.setLoginExpired(false);
|
await doLogout();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
resetAllStores();
|
||||||
|
accessStore.setLoginExpired(false);
|
||||||
|
|
||||||
// 回登陆页带上当前路由地址
|
// 回登陆页带上当前路由地址
|
||||||
await router.replace({
|
await router.replace({
|
||||||
path: LOGIN_PATH,
|
path: LOGIN_PATH,
|
||||||
query: {
|
query: {
|
||||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
async function fetchUserInfo() {
|
||||||
let userInfo: null | UserInfo = null;
|
const { permissions = [], roles = [], user } = await getUserInfo();
|
||||||
userInfo = await getUserInfo();
|
|
||||||
|
/**
|
||||||
|
* 从后台user -> vben user转换
|
||||||
|
*/
|
||||||
|
const userInfo: UserInfo = {
|
||||||
|
avatar: user.avatar ?? '',
|
||||||
|
permissions,
|
||||||
|
realName: user.nickName,
|
||||||
|
roles,
|
||||||
|
userId: user.userId,
|
||||||
|
username: user.userName,
|
||||||
|
};
|
||||||
userStore.setUserInfo(userInfo);
|
userStore.setUserInfo(userInfo);
|
||||||
return userInfo;
|
return userInfo;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export * from './auth';
|
export * from './auth';
|
||||||
|
export * from './notify';
|
||||||
|
|||||||
119
apps/web-antd/src/store/notify.ts
Normal file
119
apps/web-antd/src/store/notify.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { useAppConfig } from '@vben/hooks';
|
||||||
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import { useEventSource } from '@vueuse/core';
|
||||||
|
import { notification } from 'ant-design-vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { random } from 'lodash-es';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
const { apiURL, clientId } = useAppConfig(
|
||||||
|
import.meta.env,
|
||||||
|
import.meta.env.PROD,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useNotifyStore = defineStore(
|
||||||
|
'app-notify',
|
||||||
|
() => {
|
||||||
|
const notificationList = ref<NotificationItem[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始监听sse消息
|
||||||
|
*/
|
||||||
|
function startListeningMessage() {
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
const token = accessStore.accessToken;
|
||||||
|
|
||||||
|
const sseAddr = `${apiURL}/resource/sse?clientid=${clientId}&Authorization=Bearer ${token}`;
|
||||||
|
|
||||||
|
const { data } = useEventSource(sseAddr, [], {
|
||||||
|
autoReconnect: {
|
||||||
|
delay: 1000,
|
||||||
|
onFailed() {
|
||||||
|
console.log('sse重连失败.');
|
||||||
|
},
|
||||||
|
retries: 3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(data, (message) => {
|
||||||
|
if (!message) return;
|
||||||
|
console.log(`接收到消息: ${message}`);
|
||||||
|
|
||||||
|
notification.success({
|
||||||
|
description: message,
|
||||||
|
duration: 3,
|
||||||
|
message: '收到新消息',
|
||||||
|
});
|
||||||
|
|
||||||
|
notificationList.value.push({
|
||||||
|
// 随机头像
|
||||||
|
avatar: `https://api.multiavatar.com/${random(0, 10_000)}.png`,
|
||||||
|
date: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
isRead: false,
|
||||||
|
message,
|
||||||
|
title: '消息',
|
||||||
|
});
|
||||||
|
|
||||||
|
data.value = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置全部已读
|
||||||
|
*/
|
||||||
|
function setAllRead() {
|
||||||
|
notificationList.value.forEach((item) => {
|
||||||
|
item.isRead = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单条消息已读
|
||||||
|
* @param item 通知
|
||||||
|
*/
|
||||||
|
function setRead(item: NotificationItem) {
|
||||||
|
!item.isRead && (item.isRead = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空全部消息
|
||||||
|
*/
|
||||||
|
function clearAllMessage() {
|
||||||
|
notificationList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只需要空实现即可
|
||||||
|
* 否则会在退出登录清空所有
|
||||||
|
*/
|
||||||
|
function $reset() {
|
||||||
|
// notificationList.value = [];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 显示小圆点
|
||||||
|
*/
|
||||||
|
const showDot = computed(() =>
|
||||||
|
notificationList.value.some((item) => !item.isRead),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
$reset,
|
||||||
|
clearAllMessage,
|
||||||
|
notificationList,
|
||||||
|
setAllRead,
|
||||||
|
setRead,
|
||||||
|
showDot,
|
||||||
|
startListeningMessage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
persist: {
|
||||||
|
paths: ['notificationList'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
80
apps/web-antd/src/utils/encryption/crypto.ts
Normal file
80
apps/web-antd/src/utils/encryption/crypto.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
|
function randomUUID() {
|
||||||
|
const chars = [
|
||||||
|
...'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
||||||
|
];
|
||||||
|
const uuid = Array.from({ length: 36 });
|
||||||
|
let rnd = 0;
|
||||||
|
let r: number;
|
||||||
|
for (let i = 0; i < 36; i++) {
|
||||||
|
if (i === 8 || i === 13 || i === 18 || i === 23) {
|
||||||
|
uuid[i] = '-';
|
||||||
|
} else if (i === 14) {
|
||||||
|
uuid[i] = '4';
|
||||||
|
} else {
|
||||||
|
if (rnd <= 0x02)
|
||||||
|
rnd = Math.trunc(0x2_00_00_00 + Math.random() * 0x1_00_00_00);
|
||||||
|
r = rnd & 16;
|
||||||
|
rnd = rnd >> 4;
|
||||||
|
uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuid.join('').replaceAll('-', '').toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成aes 密钥
|
||||||
|
*
|
||||||
|
* @returns aes 密钥
|
||||||
|
*/
|
||||||
|
export function generateAesKey() {
|
||||||
|
return CryptoJS.enc.Utf8.parse(randomUUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* base64编码
|
||||||
|
* @param str
|
||||||
|
* @returns base64编码
|
||||||
|
*/
|
||||||
|
export function encryptBase64(str: CryptoJS.lib.WordArray) {
|
||||||
|
return CryptoJS.enc.Base64.stringify(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用公钥加密
|
||||||
|
* @param message 加密内容
|
||||||
|
* @param aesKey aesKey
|
||||||
|
* @returns 使用公钥加密
|
||||||
|
*/
|
||||||
|
export function encryptWithAes(
|
||||||
|
message: string,
|
||||||
|
aesKey: CryptoJS.lib.WordArray,
|
||||||
|
) {
|
||||||
|
const encrypted = CryptoJS.AES.encrypt(message, aesKey, {
|
||||||
|
mode: CryptoJS.mode.ECB,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
return encrypted.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密base64
|
||||||
|
*/
|
||||||
|
export function decryptBase64(str: string) {
|
||||||
|
return CryptoJS.enc.Base64.parse(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用密钥对数据进行解密
|
||||||
|
*/
|
||||||
|
export function decryptWithAes(
|
||||||
|
message: string,
|
||||||
|
aesKey: CryptoJS.lib.WordArray,
|
||||||
|
) {
|
||||||
|
const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
|
||||||
|
mode: CryptoJS.mode.ECB,
|
||||||
|
padding: CryptoJS.pad.Pkcs7,
|
||||||
|
});
|
||||||
|
return decrypted.toString(CryptoJS.enc.Utf8);
|
||||||
|
}
|
||||||
31
apps/web-antd/src/utils/encryption/jsencrypt.ts
Normal file
31
apps/web-antd/src/utils/encryption/jsencrypt.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 密钥对生成 http://web.chacuo.net/netrsakeypair
|
||||||
|
import { useAppConfig } from '@vben/hooks';
|
||||||
|
|
||||||
|
import JSEncrypt from 'jsencrypt';
|
||||||
|
|
||||||
|
const { rsaPrivateKey, rsaPublicKey } = useAppConfig(
|
||||||
|
import.meta.env,
|
||||||
|
import.meta.env.PROD,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密
|
||||||
|
* @param txt 需要加密的数据
|
||||||
|
* @returns 加密后的数据
|
||||||
|
*/
|
||||||
|
export function encrypt(txt: string) {
|
||||||
|
const instance = new JSEncrypt();
|
||||||
|
instance.setPublicKey(rsaPublicKey);
|
||||||
|
return instance.encrypt(txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密
|
||||||
|
* @param txt 需要解密的数据
|
||||||
|
* @returns 解密后的数据
|
||||||
|
*/
|
||||||
|
export function decrypt(txt: string) {
|
||||||
|
const instance = new JSEncrypt();
|
||||||
|
instance.setPrivateKey(rsaPrivateKey);
|
||||||
|
return instance.decrypt(txt);
|
||||||
|
}
|
||||||
@ -16,7 +16,6 @@ const loading = ref(false);
|
|||||||
* @param values 登录表单数据
|
* @param values 登录表单数据
|
||||||
*/
|
*/
|
||||||
async function handleLogin(values: LoginCodeParams) {
|
async function handleLogin(values: LoginCodeParams) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(values);
|
console.log(values);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -9,7 +9,6 @@ defineOptions({ name: 'ForgetPassword' });
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
function handleSubmit(value: string) {
|
function handleSubmit(value: string) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('reset email:', value);
|
console.log('reset email:', value);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,18 +1,91 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLogin } from '@vben/common-ui';
|
import { AuthenticationLogin } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { omit } from 'lodash-es';
|
||||||
|
|
||||||
|
import { tenantList, type TenantResp } from '#/api';
|
||||||
|
import { captchaImage, type CaptchaResponse } from '#/api/core/captcha';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
defineOptions({ name: 'Login' });
|
defineOptions({ name: 'Login' });
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const captchaInfo = ref<CaptchaResponse>({
|
||||||
|
captchaEnabled: false,
|
||||||
|
img: '',
|
||||||
|
uuid: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadCaptcha() {
|
||||||
|
const resp = await captchaImage();
|
||||||
|
if (resp.captchaEnabled) {
|
||||||
|
resp.img = `data:image/png;base64,${resp.img}`;
|
||||||
|
}
|
||||||
|
captchaInfo.value = resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenantInfo = ref<TenantResp>({
|
||||||
|
tenantEnabled: false,
|
||||||
|
voList: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadTenant() {
|
||||||
|
const resp = await tenantList();
|
||||||
|
tenantInfo.value = resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadCaptcha();
|
||||||
|
loadTenant();
|
||||||
|
});
|
||||||
|
|
||||||
|
interface LoginForm {
|
||||||
|
code?: string;
|
||||||
|
grantType: string;
|
||||||
|
password: string;
|
||||||
|
tenantId: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginRef = ref<InstanceType<typeof AuthenticationLogin>>();
|
||||||
|
async function handleAccountLogin(values: LoginForm) {
|
||||||
|
try {
|
||||||
|
const requestParam: any = omit(values, ['code']);
|
||||||
|
// 验证码
|
||||||
|
if (captchaInfo.value.captchaEnabled) {
|
||||||
|
requestParam.code = values.code;
|
||||||
|
requestParam.uuid = captchaInfo.value.uuid;
|
||||||
|
}
|
||||||
|
// 登录
|
||||||
|
await authStore.authLogin(requestParam);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
// 处理验证码错误
|
||||||
|
if (error instanceof Error) {
|
||||||
|
const message = error.message;
|
||||||
|
if (message.includes('captcha') || message.includes('验证码')) {
|
||||||
|
// 刷新验证码
|
||||||
|
loginRef.value?.resetCaptcha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationLogin
|
<AuthenticationLogin
|
||||||
|
ref="loginRef"
|
||||||
|
:captcha-base64="captchaInfo.img"
|
||||||
:loading="authStore.loginLoading"
|
:loading="authStore.loginLoading"
|
||||||
password-placeholder="123456"
|
:tenant-options="tenantInfo.voList"
|
||||||
username-placeholder="vben"
|
:use-captcha="captchaInfo.captchaEnabled"
|
||||||
@submit="authStore.authLogin"
|
:use-tenant="tenantInfo.tenantEnabled"
|
||||||
|
password-placeholder="密码"
|
||||||
|
username-placeholder="用户名"
|
||||||
|
@captcha-click="loadCaptcha"
|
||||||
|
@submit="handleAccountLogin"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -11,7 +11,6 @@ defineOptions({ name: 'Register' });
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
function handleSubmit(value: LoginAndRegisterParams) {
|
function handleSubmit(value: LoginAndRegisterParams) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('register submit:', value);
|
console.log('register submit:', value);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
18
apps/web-antd/src/views/common.tsx
Normal file
18
apps/web-antd/src/views/common.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import { Fallback } from '@vben/common-ui';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'CommonSkeleton',
|
||||||
|
setup() {
|
||||||
|
return () => (
|
||||||
|
<div class="flex h-[600px] w-full items-center justify-center">
|
||||||
|
<Fallback
|
||||||
|
description="等待官方组件中"
|
||||||
|
status="coming-soon"
|
||||||
|
title="Coming Soon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
9
apps/web-antd/src/views/demo/demo/index.vue
Normal file
9
apps/web-antd/src/views/demo/demo/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/demo/tree/index.vue
Normal file
9
apps/web-antd/src/views/demo/tree/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -41,11 +41,9 @@ async function changeAccount(role: string) {
|
|||||||
|
|
||||||
const account = accounts[role];
|
const account = accounts[role];
|
||||||
resetAllStores();
|
resetAllStores();
|
||||||
if (account) {
|
await authStore.authLogin(account, async () => {
|
||||||
await authStore.authLogin(account, async () => {
|
router.go(0);
|
||||||
router.go(0);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -41,17 +41,12 @@ async function changeAccount(role: string) {
|
|||||||
|
|
||||||
const account = accounts[role];
|
const account = accounts[role];
|
||||||
resetAllStores();
|
resetAllStores();
|
||||||
if (account) {
|
await accessStore.authLogin(account, async () => {
|
||||||
await accessStore.authLogin(account, async () => {
|
router.go(0);
|
||||||
router.go(0);
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleToggleAccessMode() {
|
async function handleToggleAccessMode() {
|
||||||
if (!accounts.super) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await toggleAccessMode();
|
await toggleAccessMode();
|
||||||
resetAllStores();
|
resetAllStores();
|
||||||
|
|
||||||
|
|||||||
6
apps/web-antd/src/views/monitor/admin/index.vue
Normal file
6
apps/web-antd/src/views/monitor/admin/index.vue
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<template>
|
||||||
|
<iframe
|
||||||
|
class="size-full"
|
||||||
|
src="http://localhost:9090/admin/applications"
|
||||||
|
></iframe>
|
||||||
|
</template>
|
||||||
83
apps/web-antd/src/views/monitor/cache/components/CommandChart.vue
vendored
Normal file
83
apps/web-antd/src/views/monitor/cache/components/CommandChart.vue
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { EChartsOption } from 'echarts';
|
||||||
|
|
||||||
|
import { defineComponent, onMounted, ref, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'CommandChart',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
default: () => [],
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { expose }) {
|
||||||
|
expose({});
|
||||||
|
|
||||||
|
const commandHtmlRef = ref<HTMLDivElement>();
|
||||||
|
const echartsInstance = shallowRef<echarts.ECharts | null>(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
if (!commandHtmlRef.value) return;
|
||||||
|
setEchartsOption(props.data);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
echartsInstance.value = echarts.init(
|
||||||
|
commandHtmlRef.value,
|
||||||
|
preferences.theme.mode,
|
||||||
|
);
|
||||||
|
setEchartsOption(props.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => preferences.theme.mode,
|
||||||
|
(mode) => {
|
||||||
|
echartsInstance.value?.dispose();
|
||||||
|
echartsInstance.value = echarts.init(commandHtmlRef.value, mode);
|
||||||
|
setEchartsOption(props.data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function setEchartsOption(data: any[]) {
|
||||||
|
const option: EChartsOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
animationDuration: 1000,
|
||||||
|
animationEasing: 'cubicInOut',
|
||||||
|
center: ['50%', '38%'],
|
||||||
|
data,
|
||||||
|
name: '命令',
|
||||||
|
radius: [15, 95],
|
||||||
|
roseType: 'radius',
|
||||||
|
type: 'pie',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
formatter: '{a} <br/>{b} : {c} ({d}%)',
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
echartsInstance.value?.setOption(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
commandHtmlRef,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="commandHtmlRef" class="h-[400px] w-full"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
102
apps/web-antd/src/views/monitor/cache/components/MemoryChart.vue
vendored
Normal file
102
apps/web-antd/src/views/monitor/cache/components/MemoryChart.vue
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { EChartsOption } from 'echarts';
|
||||||
|
|
||||||
|
import { defineComponent, onMounted, ref, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
|
import { preferences } from '@vben/preferences';
|
||||||
|
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MemoryChart',
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
default: '0',
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { expose }) {
|
||||||
|
expose({});
|
||||||
|
|
||||||
|
const memoryHtmlRef = ref<HTMLDivElement>();
|
||||||
|
const echartsInstance = shallowRef<echarts.ECharts | null>(null);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
() => {
|
||||||
|
if (!memoryHtmlRef.value) return;
|
||||||
|
setEchartsOption(props.data);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
echartsInstance.value = echarts.init(
|
||||||
|
memoryHtmlRef.value,
|
||||||
|
preferences.theme.mode,
|
||||||
|
);
|
||||||
|
setEchartsOption(props.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => preferences.theme.mode,
|
||||||
|
(mode) => {
|
||||||
|
echartsInstance.value?.dispose();
|
||||||
|
echartsInstance.value = echarts.init(memoryHtmlRef.value, mode);
|
||||||
|
setEchartsOption(props.data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function getNearestPowerOfTen(num: number) {
|
||||||
|
let power = 10;
|
||||||
|
while (power <= num) {
|
||||||
|
power *= 10;
|
||||||
|
}
|
||||||
|
return power;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEchartsOption(value: string) {
|
||||||
|
// x10
|
||||||
|
const formattedValue = Math.floor(Number.parseFloat(value));
|
||||||
|
// 最大值 10以内取10 100以内取100 以此类推
|
||||||
|
const max = getNearestPowerOfTen(formattedValue);
|
||||||
|
const options: EChartsOption = {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
animation: true,
|
||||||
|
animationDuration: 1000,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: '内存消耗',
|
||||||
|
value: Number.parseFloat(value),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
detail: {
|
||||||
|
formatter: `${value}M`,
|
||||||
|
valueAnimation: true,
|
||||||
|
},
|
||||||
|
max,
|
||||||
|
min: 0,
|
||||||
|
name: '峰值',
|
||||||
|
type: 'gauge',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
formatter: `{b} <br/>{a} : ${value}M`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
echartsInstance.value?.setOption(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
memoryHtmlRef,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="memoryHtmlRef" class="h-[400px] w-full"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
65
apps/web-antd/src/views/monitor/cache/components/RedisDescription.vue
vendored
Normal file
65
apps/web-antd/src/views/monitor/cache/components/RedisDescription.vue
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { RedisInfo } from '#/api/monitor/cache';
|
||||||
|
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
|
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
|
||||||
|
|
||||||
|
interface IRedisInfo extends RedisInfo {
|
||||||
|
dbSize: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
data: {
|
||||||
|
required: true,
|
||||||
|
type: Object as PropType<IRedisInfo>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Descriptions
|
||||||
|
:column="{ xs: 1, sm: 1, md: 3, lg: 4, xl: 4 }"
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<DescriptionsItem label="redis版本">
|
||||||
|
{{ data.redis_version }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="redis模式">
|
||||||
|
{{ data.redis_mode === 'standalone' ? '单机模式' : '集群模式' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="tcp端口">
|
||||||
|
{{ data.tcp_port }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="客户端数">
|
||||||
|
{{ data.connected_clients }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="运行时间">
|
||||||
|
{{ `${data.uptime_in_days}天` }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="使用内存">
|
||||||
|
{{ data.used_memory_human }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="使用CPU">
|
||||||
|
{{ parseFloat(data.used_cpu_user_children!).toFixed(2) }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="内存配置">
|
||||||
|
{{ data.maxmemory_human }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="AOF是否开启">
|
||||||
|
{{ data.aof_enabled === '0' ? '否' : '是' }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="RDB是否成功">
|
||||||
|
{{ data.rdb_last_bgsave_status }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="Key数量">
|
||||||
|
{{ data.dbSize }}
|
||||||
|
</DescriptionsItem>
|
||||||
|
<DescriptionsItem label="网络入口/出口">
|
||||||
|
{{
|
||||||
|
`${data.instantaneous_input_kbps}kps/${data.instantaneous_output_kbps}kps`
|
||||||
|
}}
|
||||||
|
</DescriptionsItem>
|
||||||
|
</Descriptions>
|
||||||
|
</template>
|
||||||
94
apps/web-antd/src/views/monitor/cache/index.vue
vendored
Normal file
94
apps/web-antd/src/views/monitor/cache/index.vue
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Button, Card, Col, Row } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { redisCacheInfo, type RedisInfo } from '#/api/monitor/cache';
|
||||||
|
|
||||||
|
import CommandChart from './components/CommandChart.vue';
|
||||||
|
import MemoryChart from './components/MemoryChart.vue';
|
||||||
|
import RedisDescription from './components/RedisDescription.vue';
|
||||||
|
|
||||||
|
const baseSpan = { lg: 12, md: 24, sm: 24, xl: 12, xs: 24 };
|
||||||
|
|
||||||
|
const chartData = reactive<{ command: any[]; memory: string }>({
|
||||||
|
command: [],
|
||||||
|
memory: '0',
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IRedisInfo extends RedisInfo {
|
||||||
|
dbSize: string;
|
||||||
|
}
|
||||||
|
const redisInfo = ref<IRedisInfo>();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadInfo() {
|
||||||
|
try {
|
||||||
|
const ret = await redisCacheInfo();
|
||||||
|
|
||||||
|
// 单位MB 保留两位小数
|
||||||
|
const usedMemory = (
|
||||||
|
Number.parseInt(ret.info.used_memory!) /
|
||||||
|
1024 /
|
||||||
|
1024
|
||||||
|
).toFixed(2);
|
||||||
|
chartData.memory = usedMemory;
|
||||||
|
// 命令统计
|
||||||
|
chartData.command = ret.commandStats;
|
||||||
|
// redis信息
|
||||||
|
redisInfo.value = { ...ret.info, dbSize: String(ret.dbSize) };
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="m-[16px]">
|
||||||
|
<Row :gutter="[15, 15]">
|
||||||
|
<Col :span="24">
|
||||||
|
<Card size="small">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-start gap-[6px]">
|
||||||
|
<span class="icon-[logos--redis]"></span>
|
||||||
|
<span>redis信息</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<Button size="small" @click="loadInfo">
|
||||||
|
<div class="flex">
|
||||||
|
<span class="icon-[charm--refresh]"></span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
<RedisDescription v-if="redisInfo" :data="redisInfo" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col v-bind="baseSpan">
|
||||||
|
<Card size="small">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center gap-[6px]">
|
||||||
|
<span class="icon-[flat-color-icons--command-line]"></span>
|
||||||
|
<span>命令统计</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<CommandChart :data="chartData.command" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col v-bind="baseSpan">
|
||||||
|
<Card size="small">
|
||||||
|
<template #title>
|
||||||
|
<div class="flex items-center justify-start gap-[6px]">
|
||||||
|
<span class="icon-[la--memory]"></span>
|
||||||
|
<span>内存占用</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<MemoryChart :data="chartData.memory" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/monitor/logininfor/index.vue
Normal file
9
apps/web-antd/src/views/monitor/logininfor/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/monitor/online/index.vue
Normal file
9
apps/web-antd/src/views/monitor/online/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/monitor/operlog/index.vue
Normal file
9
apps/web-antd/src/views/monitor/operlog/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
3
apps/web-antd/src/views/monitor/snailjob/index.vue
Normal file
3
apps/web-antd/src/views/monitor/snailjob/index.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<iframe class="size-full" src="http://localhost:8800/snail-job"></iframe>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/client/index.vue
Normal file
9
apps/web-antd/src/views/system/client/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/config/index.vue
Normal file
9
apps/web-antd/src/views/system/config/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/dept/index.vue
Normal file
9
apps/web-antd/src/views/system/dept/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/dict/index.vue
Normal file
9
apps/web-antd/src/views/system/dict/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/menu/index.vue
Normal file
9
apps/web-antd/src/views/system/menu/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/notice/index.vue
Normal file
9
apps/web-antd/src/views/system/notice/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/oss/index.vue
Normal file
9
apps/web-antd/src/views/system/oss/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/post/index.vue
Normal file
9
apps/web-antd/src/views/system/post/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/role/index.vue
Normal file
9
apps/web-antd/src/views/system/role/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/tenant/index.vue
Normal file
9
apps/web-antd/src/views/system/tenant/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/system/tenantPackage/index.vue
Normal file
9
apps/web-antd/src/views/system/tenantPackage/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
apps/web-antd/src/views/system/user/index.vue
Normal file
17
apps/web-antd/src/views/system/user/index.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
|
import { Button } from 'ant-design-vue';
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('keepAlive测试 -> 挂载了');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="m-[8px]">
|
||||||
|
<Button type="primary" v-access:code="['system:user:list']">
|
||||||
|
测试按钮可见
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/tool/gen/index.vue
Normal file
9
apps/web-antd/src/views/tool/gen/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/category/index.vue
Normal file
9
apps/web-antd/src/views/workflow/category/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/formManage/index.vue
Normal file
9
apps/web-antd/src/views/workflow/formManage/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/leave/index.vue
Normal file
9
apps/web-antd/src/views/workflow/leave/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/model/index.vue
Normal file
9
apps/web-antd/src/views/workflow/model/index.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/task/allTaskWaiting.vue
Normal file
9
apps/web-antd/src/views/workflow/task/allTaskWaiting.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/task/myDocument.vue
Normal file
9
apps/web-antd/src/views/workflow/task/myDocument.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/task/taskCopyList.vue
Normal file
9
apps/web-antd/src/views/workflow/task/taskCopyList.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/task/taskFinish.vue
Normal file
9
apps/web-antd/src/views/workflow/task/taskFinish.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
9
apps/web-antd/src/views/workflow/task/taskWaiting.vue
Normal file
9
apps/web-antd/src/views/workflow/task/taskWaiting.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CommonSkeleton from '#/views/common';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<CommonSkeleton />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -10,7 +10,7 @@ export default defineConfig(async () => {
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||||
// mock代理目标地址
|
// mock代理目标地址
|
||||||
target: 'http://localhost:5320/api',
|
target: 'http://localhost:8080',
|
||||||
ws: true,
|
ws: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -19,6 +19,10 @@ const customConfig: Linter.Config[] = [
|
|||||||
files: ['apps/**/**'],
|
files: ['apps/**/**'],
|
||||||
ignores: restrictedImportIgnores,
|
ignores: restrictedImportIgnores,
|
||||||
rules: {
|
rules: {
|
||||||
|
// 允许使用void类型
|
||||||
|
'@typescript-eslint/no-invalid-void-type': 'off',
|
||||||
|
// 关闭 不允许使用console
|
||||||
|
'no-console': 'off',
|
||||||
'no-restricted-imports': [
|
'no-restricted-imports': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
@ -46,6 +50,7 @@ const customConfig: Linter.Config[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'regexp/no-unused-capturing-group': 'off',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -86,6 +91,13 @@ const customConfig: Linter.Config[] = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ['packages/effects/access/**/**'],
|
||||||
|
ignores: restrictedImportIgnores,
|
||||||
|
rules: {
|
||||||
|
'regexp/no-unused-capturing-group': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// 不能引入@vben/*里面的包
|
// 不能引入@vben/*里面的包
|
||||||
files: [
|
files: [
|
||||||
|
|||||||
8
packages/@core/base/typings/src/basic.d.ts
vendored
8
packages/@core/base/typings/src/basic.d.ts
vendored
@ -12,6 +12,10 @@ interface BasicUserInfo {
|
|||||||
* 头像
|
* 头像
|
||||||
*/
|
*/
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
/**
|
||||||
|
* 用户权限
|
||||||
|
*/
|
||||||
|
permissions: string[];
|
||||||
/**
|
/**
|
||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@ -19,11 +23,11 @@ interface BasicUserInfo {
|
|||||||
/**
|
/**
|
||||||
* 用户角色
|
* 用户角色
|
||||||
*/
|
*/
|
||||||
roles?: string[];
|
roles: string[];
|
||||||
/**
|
/**
|
||||||
* 用户id
|
* 用户id
|
||||||
*/
|
*/
|
||||||
userId: string;
|
userId: number | string;
|
||||||
/**
|
/**
|
||||||
* 用户名
|
* 用户名
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -88,7 +88,7 @@ const defaultPreferences: Preferences = {
|
|||||||
colorPrimary: 'hsl(231 98% 65%)',
|
colorPrimary: 'hsl(231 98% 65%)',
|
||||||
colorSuccess: 'hsl(144 57% 58%)',
|
colorSuccess: 'hsl(144 57% 58%)',
|
||||||
colorWarning: 'hsl(42 84% 61%)',
|
colorWarning: 'hsl(42 84% 61%)',
|
||||||
mode: 'dark',
|
mode: 'auto',
|
||||||
radius: '0.5',
|
radius: '0.5',
|
||||||
semiDarkMenu: true,
|
semiDarkMenu: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,6 +24,12 @@ async function generateAccessible(
|
|||||||
|
|
||||||
// 动态添加到router实例内
|
// 动态添加到router实例内
|
||||||
accessibleRoutes.forEach((route) => {
|
accessibleRoutes.forEach((route) => {
|
||||||
|
/**
|
||||||
|
* 外链不应该被添加到路由 由menu处理
|
||||||
|
*/
|
||||||
|
if (/^http(s)?:\/\//.test(route.path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
router.addRoute(route);
|
router.addRoute(route);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,13 @@ function useAccess() {
|
|||||||
*/
|
*/
|
||||||
function hasAccessByCodes(codes: string[]) {
|
function hasAccessByCodes(codes: string[]) {
|
||||||
const userCodesSet = new Set(accessStore.accessCodes);
|
const userCodesSet = new Set(accessStore.accessCodes);
|
||||||
|
/**
|
||||||
|
* 管理员权限
|
||||||
|
*/
|
||||||
|
if (userCodesSet.has('*:*:*')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 其他 判断是否存在
|
||||||
const intersection = codes.filter((item) => userCodesSet.has(item));
|
const intersection = codes.filter((item) => userCodesSet.has(item));
|
||||||
return intersection.length > 0;
|
return intersection.length > 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AuthenticationProps, LoginEmits } from './typings';
|
import type { AuthenticationProps, LoginEmits } from './typings';
|
||||||
|
|
||||||
import { computed, reactive } from 'vue';
|
import { computed, reactive, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import {
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
VbenButton,
|
VbenButton,
|
||||||
VbenCheckbox,
|
VbenCheckbox,
|
||||||
VbenInput,
|
VbenInput,
|
||||||
@ -15,13 +21,31 @@ import {
|
|||||||
import Title from './auth-title.vue';
|
import Title from './auth-title.vue';
|
||||||
import ThirdPartyLogin from './third-party-login.vue';
|
import ThirdPartyLogin from './third-party-login.vue';
|
||||||
|
|
||||||
interface Props extends AuthenticationProps {}
|
interface Props extends AuthenticationProps {
|
||||||
|
/**
|
||||||
|
* @zh_CN 验证码图片base64
|
||||||
|
*/
|
||||||
|
captchaBase64?: string;
|
||||||
|
/**
|
||||||
|
* 租户信息options
|
||||||
|
*/
|
||||||
|
tenantOptions?: { companyName: string; domain?: string; tenantId: string }[];
|
||||||
|
/**
|
||||||
|
* @zh_CN 是否启用验证码
|
||||||
|
*/
|
||||||
|
useCaptcha?: boolean;
|
||||||
|
/**
|
||||||
|
* @zh_CN 是否启用租户
|
||||||
|
*/
|
||||||
|
useTenant?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'AuthenticationLogin',
|
name: 'AuthenticationLogin',
|
||||||
});
|
});
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
captchaBase64: '',
|
||||||
codeLoginPath: '/auth/code-login',
|
codeLoginPath: '/auth/code-login',
|
||||||
forgetPasswordPath: '/auth/forget-password',
|
forgetPasswordPath: '/auth/forget-password',
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -35,11 +59,16 @@ withDefaults(defineProps<Props>(), {
|
|||||||
showRememberMe: true,
|
showRememberMe: true,
|
||||||
showThirdPartyLogin: true,
|
showThirdPartyLogin: true,
|
||||||
subTitle: '',
|
subTitle: '',
|
||||||
|
tenantOptions: () => [],
|
||||||
title: '',
|
title: '',
|
||||||
usernamePlaceholder: '',
|
usernamePlaceholder: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
/**
|
||||||
|
* 验证码点击
|
||||||
|
*/
|
||||||
|
captchaClick: [];
|
||||||
submit: LoginEmits['submit'];
|
submit: LoginEmits['submit'];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@ -47,15 +76,28 @@ const router = useRouter();
|
|||||||
|
|
||||||
const REMEMBER_ME_KEY = `REMEMBER_ME_USERNAME_${location.hostname}`;
|
const REMEMBER_ME_KEY = `REMEMBER_ME_USERNAME_${location.hostname}`;
|
||||||
|
|
||||||
const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || '';
|
const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || 'admin';
|
||||||
|
|
||||||
const formState = reactive({
|
const formState = reactive({
|
||||||
password: '',
|
code: '',
|
||||||
|
password: 'admin123',
|
||||||
rememberMe: !!localUsername,
|
rememberMe: !!localUsername,
|
||||||
submitted: false,
|
submitted: false,
|
||||||
|
// 默认租户
|
||||||
|
tenantId: '000000',
|
||||||
username: localUsername,
|
username: localUsername,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认选中第一项租户
|
||||||
|
*/
|
||||||
|
const stop = watch(props.tenantOptions, (options) => {
|
||||||
|
if (options.length > 0) {
|
||||||
|
formState.tenantId = options[0]!.tenantId;
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const usernameStatus = computed(() => {
|
const usernameStatus = computed(() => {
|
||||||
return formState.submitted && !formState.username ? 'error' : 'default';
|
return formState.submitted && !formState.username ? 'error' : 'default';
|
||||||
});
|
});
|
||||||
@ -64,6 +106,10 @@ const passwordStatus = computed(() => {
|
|||||||
return formState.submitted && !formState.password ? 'error' : 'default';
|
return formState.submitted && !formState.password ? 'error' : 'default';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const captchaStatus = computed(() => {
|
||||||
|
return formState.submitted && !formState.code ? 'error' : 'default';
|
||||||
|
});
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
formState.submitted = true;
|
formState.submitted = true;
|
||||||
|
|
||||||
@ -74,13 +120,21 @@ function handleSubmit() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证码
|
||||||
|
if (props.useCaptcha && captchaStatus.value !== 'default') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
REMEMBER_ME_KEY,
|
REMEMBER_ME_KEY,
|
||||||
formState.rememberMe ? formState.username : '',
|
formState.rememberMe ? formState.username : '',
|
||||||
);
|
);
|
||||||
|
|
||||||
emit('submit', {
|
emit('submit', {
|
||||||
|
code: formState.code,
|
||||||
|
grantType: 'password',
|
||||||
password: formState.password,
|
password: formState.password,
|
||||||
|
tenantId: formState.tenantId,
|
||||||
username: formState.username,
|
username: formState.username,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -88,6 +142,18 @@ function handleSubmit() {
|
|||||||
function handleGo(path: string) {
|
function handleGo(path: string) {
|
||||||
router.push(path);
|
router.push(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置验证码
|
||||||
|
*/
|
||||||
|
function resetCaptcha() {
|
||||||
|
emit('captchaClick');
|
||||||
|
formState.code = '';
|
||||||
|
// todo 获取焦点
|
||||||
|
// VbenInput并没有提供focus方法
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ resetCaptcha });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -101,6 +167,26 @@ function handleGo(path: string) {
|
|||||||
</template>
|
</template>
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
|
<!-- 租户 -->
|
||||||
|
<div v-if="useTenant" class="mb-6">
|
||||||
|
<Select v-model="formState.tenantId">
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="选择公司" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem
|
||||||
|
v-for="item in tenantOptions"
|
||||||
|
:key="item.tenantId"
|
||||||
|
:value="item.tenantId"
|
||||||
|
>
|
||||||
|
{{ item.companyName }}
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<VbenInput
|
<VbenInput
|
||||||
v-model="formState.username"
|
v-model="formState.username"
|
||||||
:autofocus="false"
|
:autofocus="false"
|
||||||
@ -123,6 +209,27 @@ function handleGo(path: string) {
|
|||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 图片验证码 -->
|
||||||
|
<div v-if="useCaptcha" class="flex">
|
||||||
|
<div class="flex-1">
|
||||||
|
<VbenInput
|
||||||
|
v-model="formState.code"
|
||||||
|
:error-tip="$t('authentication.captchaTip')"
|
||||||
|
:label="$t('authentication.captcha')"
|
||||||
|
:placeholder="$t('authentication.captcha')"
|
||||||
|
:status="captchaStatus"
|
||||||
|
name="code"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
:src="captchaBase64"
|
||||||
|
class="h-[38px] w-[115px] rounded-r-md"
|
||||||
|
@click="emit('captchaClick')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-6 mt-4 flex justify-between">
|
<div class="mb-6 mt-4 flex justify-between">
|
||||||
<div v-if="showRememberMe" class="flex-center">
|
<div v-if="showRememberMe" class="flex-center">
|
||||||
<VbenCheckbox v-model:checked="formState.rememberMe" name="rememberMe">
|
<VbenCheckbox v-model:checked="formState.rememberMe" name="rememberMe">
|
||||||
|
|||||||
@ -3,6 +3,7 @@ interface AuthenticationProps {
|
|||||||
* @zh_CN 验证码登录路径
|
* @zh_CN 验证码登录路径
|
||||||
*/
|
*/
|
||||||
codeLoginPath?: string;
|
codeLoginPath?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 忘记密码路径
|
* @zh_CN 忘记密码路径
|
||||||
*/
|
*/
|
||||||
@ -32,6 +33,7 @@ interface AuthenticationProps {
|
|||||||
* @zh_CN 是否显示验证码登录
|
* @zh_CN 是否显示验证码登录
|
||||||
*/
|
*/
|
||||||
showCodeLogin?: boolean;
|
showCodeLogin?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 是否显示忘记密码
|
* @zh_CN 是否显示忘记密码
|
||||||
*/
|
*/
|
||||||
@ -66,7 +68,6 @@ interface AuthenticationProps {
|
|||||||
* @zh_CN 登录框标题
|
* @zh_CN 登录框标题
|
||||||
*/
|
*/
|
||||||
title?: string;
|
title?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 用户名占位符
|
* @zh_CN 用户名占位符
|
||||||
*/
|
*/
|
||||||
@ -74,8 +75,12 @@ interface AuthenticationProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface LoginAndRegisterParams {
|
interface LoginAndRegisterParams {
|
||||||
|
code?: string;
|
||||||
|
grantType: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
tenantId: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
uuid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoginCodeParams {
|
interface LoginCodeParams {
|
||||||
|
|||||||
@ -15,9 +15,26 @@ export function useAppConfig(
|
|||||||
? window._VBEN_ADMIN_PRO_APP_CONF_
|
? window._VBEN_ADMIN_PRO_APP_CONF_
|
||||||
: (env as VbenAdminProAppConfigRaw);
|
: (env as VbenAdminProAppConfigRaw);
|
||||||
|
|
||||||
const { VITE_GLOB_API_URL } = config;
|
const {
|
||||||
|
VITE_GLOB_API_URL,
|
||||||
|
VITE_GLOB_APP_CLIENT_ID,
|
||||||
|
VITE_GLOB_ENABLE_ENCRYPT,
|
||||||
|
VITE_GLOB_RSA_PRIVATE_KEY,
|
||||||
|
VITE_GLOB_RSA_PUBLIC_KEY,
|
||||||
|
VITE_GLOB_WEBSOCKET_ENABLE,
|
||||||
|
} = config;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
// 后端地址
|
||||||
apiURL: VITE_GLOB_API_URL,
|
apiURL: VITE_GLOB_API_URL,
|
||||||
|
// 客户端key
|
||||||
|
clientId: VITE_GLOB_APP_CLIENT_ID,
|
||||||
|
enableEncrypt: VITE_GLOB_ENABLE_ENCRYPT === 'true',
|
||||||
|
// RSA私钥
|
||||||
|
rsaPrivateKey: VITE_GLOB_RSA_PRIVATE_KEY,
|
||||||
|
// RSA公钥
|
||||||
|
rsaPublicKey: VITE_GLOB_RSA_PUBLIC_KEY,
|
||||||
|
// 是否开启websocket
|
||||||
|
websocketEnable: VITE_GLOB_WEBSOCKET_ENABLE === 'true',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,7 +99,8 @@ class RequestClient {
|
|||||||
const authorization = this.makeAuthorization?.(config);
|
const authorization = this.makeAuthorization?.(config);
|
||||||
if (authorization) {
|
if (authorization) {
|
||||||
const { token } = authorization.tokenHandler?.() ?? {};
|
const { token } = authorization.tokenHandler?.() ?? {};
|
||||||
config.headers[authorization.key || 'Authorization'] = token;
|
config.headers[authorization.key || 'Authorization'] =
|
||||||
|
`Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestHeader = this.makeRequestHeaders?.(config);
|
const requestHeader = this.makeRequestHeaders?.(config);
|
||||||
|
|||||||
@ -43,13 +43,9 @@ interface RequestClientOptions extends CreateAxiosDefaults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface HttpResponse<T = any> {
|
interface HttpResponse<T = any> {
|
||||||
/**
|
|
||||||
* 0 表示成功 其他表示失败
|
|
||||||
* 0 means success, others means fail
|
|
||||||
*/
|
|
||||||
code: number;
|
code: number;
|
||||||
data: T;
|
data: T;
|
||||||
message: string;
|
msg: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
@ -60,3 +56,44 @@ export type {
|
|||||||
RequestClientOptions,
|
RequestClientOptions,
|
||||||
RequestContentType,
|
RequestContentType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ErrorMessageMode = 'message' | 'modal' | 'none' | undefined;
|
||||||
|
export type SuccessMessageMode = ErrorMessageMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拓展axios的请求配置
|
||||||
|
*/
|
||||||
|
declare module 'axios' {
|
||||||
|
interface AxiosRequestConfig {
|
||||||
|
/** 是否加密请求参数 */
|
||||||
|
encrypt?: boolean;
|
||||||
|
/**
|
||||||
|
* 错误弹窗类型
|
||||||
|
*/
|
||||||
|
errorMessageMode?: ErrorMessageMode;
|
||||||
|
/**
|
||||||
|
* 是否格式化日期
|
||||||
|
*/
|
||||||
|
formatDate?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否返回原生axios响应
|
||||||
|
*/
|
||||||
|
isReturnNativeResponse?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否需要转换响应 即只获取{code, msg, data}中的data
|
||||||
|
*/
|
||||||
|
isTransformResponse?: boolean;
|
||||||
|
/**
|
||||||
|
* param添加到url后
|
||||||
|
*/
|
||||||
|
joinParamsToUrl?: boolean;
|
||||||
|
/**
|
||||||
|
* 加入时间戳
|
||||||
|
*/
|
||||||
|
joinTime?: boolean;
|
||||||
|
/**
|
||||||
|
* 成功弹窗类型
|
||||||
|
*/
|
||||||
|
successMessageMode?: SuccessMessageMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -51,7 +51,12 @@
|
|||||||
"unauthorized": "Unauthorized. Please log in to continue.",
|
"unauthorized": "Unauthorized. Please log in to continue.",
|
||||||
"forbidden": "Forbidden. You do not have permission to access this resource.",
|
"forbidden": "Forbidden. You do not have permission to access this resource.",
|
||||||
"notFound": "Not Found. The requested resource could not be found.",
|
"notFound": "Not Found. The requested resource could not be found.",
|
||||||
"internalServerError": "Internal Server Error. Something went wrong on our end. Please try again later."
|
"internalServerError": "Internal Server Error. Something went wrong on our end. Please try again later.",
|
||||||
|
"apiRequestFailed": "The interface request failed, please try again later!",
|
||||||
|
"operationSuccess": "Operation Success",
|
||||||
|
"operationFailed": "Operation failed",
|
||||||
|
"errorTip": "Error Tip",
|
||||||
|
"successTip": "Success Tip"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
@ -95,8 +100,10 @@
|
|||||||
"loginSubtitle": "Enter your account details to manage your projects",
|
"loginSubtitle": "Enter your account details to manage your projects",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
|
"captcha": "Captcha",
|
||||||
"usernameTip": "Please enter username",
|
"usernameTip": "Please enter username",
|
||||||
"passwordTip": "Please enter password",
|
"passwordTip": "Please enter password",
|
||||||
|
"captchaTip": "Please enter captcha",
|
||||||
"rememberMe": "Remember Me",
|
"rememberMe": "Remember Me",
|
||||||
"createAnAccount": "Create an Account",
|
"createAnAccount": "Create an Account",
|
||||||
"createAccount": "Create Account",
|
"createAccount": "Create Account",
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"page": {
|
"page": {
|
||||||
"core": {
|
"core": {
|
||||||
"login": "登陆",
|
"login": "登录",
|
||||||
"register": "注册",
|
"register": "注册",
|
||||||
"codeLogin": "验证码登陆",
|
"codeLogin": "验证码登录",
|
||||||
"qrcodeLogin": "二维码登陆",
|
"qrcodeLogin": "二维码登录",
|
||||||
"forgetPassword": "忘记密码"
|
"forgetPassword": "忘记密码"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
@ -51,7 +51,12 @@
|
|||||||
"unauthorized": "登录认证过期。请重新登录后继续。",
|
"unauthorized": "登录认证过期。请重新登录后继续。",
|
||||||
"forbidden": "禁止访问, 您没有权限访问此资源。",
|
"forbidden": "禁止访问, 您没有权限访问此资源。",
|
||||||
"notFound": "未找到, 请求的资源不存在。",
|
"notFound": "未找到, 请求的资源不存在。",
|
||||||
"internalServerError": "内部服务器错误,请稍后再试。"
|
"internalServerError": "内部服务器错误,请稍后再试。",
|
||||||
|
"apiRequestFailed": "请求出错,请稍候重试",
|
||||||
|
"operationSuccess": "操作成功",
|
||||||
|
"operationFailed": "操作失败",
|
||||||
|
"errorTip": "错误提示",
|
||||||
|
"successTip": "成功提示"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
@ -95,8 +100,10 @@
|
|||||||
"loginSubtitle": "请输入您的帐户信息以开始管理您的项目",
|
"loginSubtitle": "请输入您的帐户信息以开始管理您的项目",
|
||||||
"username": "账号",
|
"username": "账号",
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
|
"captcha": "验证码",
|
||||||
"usernameTip": "请输入用户名",
|
"usernameTip": "请输入用户名",
|
||||||
"passwordTip": "请输入密码",
|
"passwordTip": "请输入密码",
|
||||||
|
"captchaTip": "请输入验证码",
|
||||||
"rememberMe": "记住账号",
|
"rememberMe": "记住账号",
|
||||||
"createAnAccount": "创建一个账号",
|
"createAnAccount": "创建一个账号",
|
||||||
"createAccount": "创建账号",
|
"createAccount": "创建账号",
|
||||||
|
|||||||
@ -5,6 +5,10 @@ interface BasicUserInfo {
|
|||||||
* 头像
|
* 头像
|
||||||
*/
|
*/
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
/**
|
||||||
|
* 用户权限
|
||||||
|
*/
|
||||||
|
permissions: string[];
|
||||||
/**
|
/**
|
||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@ -12,11 +16,11 @@ interface BasicUserInfo {
|
|||||||
/**
|
/**
|
||||||
* 用户角色
|
* 用户角色
|
||||||
*/
|
*/
|
||||||
roles?: string[];
|
roles: string[];
|
||||||
/**
|
/**
|
||||||
* 用户id
|
* 用户id
|
||||||
*/
|
*/
|
||||||
userId: string;
|
userId: number | string;
|
||||||
/**
|
/**
|
||||||
* 用户名
|
* 用户名
|
||||||
*/
|
*/
|
||||||
|
|||||||
22
packages/types/global.d.ts
vendored
22
packages/types/global.d.ts
vendored
@ -8,11 +8,33 @@ declare module 'vue-router' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface VbenAdminProAppConfigRaw {
|
export interface VbenAdminProAppConfigRaw {
|
||||||
|
// 后端接口地址
|
||||||
VITE_GLOB_API_URL: string;
|
VITE_GLOB_API_URL: string;
|
||||||
|
// 客户端ID
|
||||||
|
VITE_GLOB_APP_CLIENT_ID: string;
|
||||||
|
// # 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
|
||||||
|
VITE_GLOB_ENABLE_ENCRYPT: string;
|
||||||
|
// RSA请求解密私钥
|
||||||
|
VITE_GLOB_RSA_PRIVATE_KEY: string;
|
||||||
|
// RSA请求加密公钥
|
||||||
|
VITE_GLOB_RSA_PUBLIC_KEY: string;
|
||||||
|
// 是否开启websocket 注意从配置文件获取的类型为string
|
||||||
|
VITE_GLOB_WEBSOCKET_ENABLE: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplicationConfig {
|
export interface ApplicationConfig {
|
||||||
|
// 后端接口地址
|
||||||
apiURL: string;
|
apiURL: string;
|
||||||
|
// 客户端key
|
||||||
|
clientId: string;
|
||||||
|
// 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
|
||||||
|
enableEncrypt: boolean;
|
||||||
|
// RSA响应解密私钥
|
||||||
|
rsaPrivateKey: string;
|
||||||
|
// RSA请求加密公钥
|
||||||
|
rsaPublicKey: string;
|
||||||
|
// 是否开启websocket
|
||||||
|
websocketEnable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
13
packages/types/src/user.d.ts
vendored
13
packages/types/src/user.d.ts
vendored
@ -3,18 +3,9 @@ import type { BasicUserInfo } from '@vben-core/typings';
|
|||||||
/** 用户信息 */
|
/** 用户信息 */
|
||||||
interface UserInfo extends BasicUserInfo {
|
interface UserInfo extends BasicUserInfo {
|
||||||
/**
|
/**
|
||||||
* 用户描述
|
* 拓展使用
|
||||||
*/
|
*/
|
||||||
desc: string;
|
[key: string]: any;
|
||||||
/**
|
|
||||||
* 首页地址
|
|
||||||
*/
|
|
||||||
homePath: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* accessToken
|
|
||||||
*/
|
|
||||||
token: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { UserInfo };
|
export type { UserInfo };
|
||||||
|
|||||||
@ -61,6 +61,11 @@ function convertRoutes(
|
|||||||
? normalizePath
|
? normalizePath
|
||||||
: `${normalizePath}.vue`
|
: `${normalizePath}.vue`
|
||||||
];
|
];
|
||||||
|
if (!route.component) {
|
||||||
|
console.error(`未找到对应组件: ${component}`);
|
||||||
|
// 默认为404页面
|
||||||
|
route.component = layoutMap.NotFoundComponent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return route;
|
return route;
|
||||||
|
|||||||
@ -160,9 +160,21 @@ importers:
|
|||||||
ant-design-vue:
|
ant-design-vue:
|
||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3(vue@3.4.35(typescript@5.5.4))
|
version: 4.2.3(vue@3.4.35(typescript@5.5.4))
|
||||||
|
crypto-js:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.12
|
specifier: ^1.11.12
|
||||||
version: 1.11.12
|
version: 1.11.12
|
||||||
|
echarts:
|
||||||
|
specifier: ^5.5.1
|
||||||
|
version: 5.5.1
|
||||||
|
jsencrypt:
|
||||||
|
specifier: ^3.3.2
|
||||||
|
version: 3.3.2
|
||||||
|
lodash-es:
|
||||||
|
specifier: ^4.17.21
|
||||||
|
version: 4.17.21
|
||||||
pinia:
|
pinia:
|
||||||
specifier: 2.2.0
|
specifier: 2.2.0
|
||||||
version: 2.2.0(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))
|
version: 2.2.0(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))
|
||||||
@ -172,6 +184,13 @@ importers:
|
|||||||
vue-router:
|
vue-router:
|
||||||
specifier: ^4.4.2
|
specifier: ^4.4.2
|
||||||
version: 4.4.2(vue@3.4.35(typescript@5.5.4))
|
version: 4.4.2(vue@3.4.35(typescript@5.5.4))
|
||||||
|
devDependencies:
|
||||||
|
'@types/crypto-js':
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
|
'@types/lodash-es':
|
||||||
|
specifier: ^4.17.12
|
||||||
|
version: 4.17.12
|
||||||
|
|
||||||
apps/web-ele:
|
apps/web-ele:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -1219,36 +1238,42 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@ast-grep/napi-linux-arm64-gnu@0.22.6':
|
'@ast-grep/napi-linux-arm64-gnu@0.22.6':
|
||||||
resolution: {integrity: sha512-9PAqNJlAQfFm1RW0DVCM/S4gFHdppxUTWacB3qEeJZXgdLnoH0KGQa4z3Xo559SPYDKZy0VnY02mZ3XJ+v6/Vw==}
|
resolution: {integrity: sha512-9PAqNJlAQfFm1RW0DVCM/S4gFHdppxUTWacB3qEeJZXgdLnoH0KGQa4z3Xo559SPYDKZy0VnY02mZ3XJ+v6/Vw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@ast-grep/napi-linux-x64-gnu@0.21.4':
|
'@ast-grep/napi-linux-x64-gnu@0.21.4':
|
||||||
resolution: {integrity: sha512-U7jl8RGpxKV+pjFstY0y5qD+D+wm9dXNO7NBbIOnETgTMizTFiUuQWT7SOlIklhcxxuXqWzfwhNN1qwI0tGNWw==}
|
resolution: {integrity: sha512-U7jl8RGpxKV+pjFstY0y5qD+D+wm9dXNO7NBbIOnETgTMizTFiUuQWT7SOlIklhcxxuXqWzfwhNN1qwI0tGNWw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@ast-grep/napi-linux-x64-gnu@0.22.6':
|
'@ast-grep/napi-linux-x64-gnu@0.22.6':
|
||||||
resolution: {integrity: sha512-nZf+gxXVrZqvP1LN6HwzOMA4brF3umBXfMequQzv8S6HeJ4c34P23F0Tw8mHtQpVYP9PQWJUvt3LJQ8Xvd5Hiw==}
|
resolution: {integrity: sha512-nZf+gxXVrZqvP1LN6HwzOMA4brF3umBXfMequQzv8S6HeJ4c34P23F0Tw8mHtQpVYP9PQWJUvt3LJQ8Xvd5Hiw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@ast-grep/napi-linux-x64-musl@0.21.4':
|
'@ast-grep/napi-linux-x64-musl@0.21.4':
|
||||||
resolution: {integrity: sha512-SOGR93kGomRR+Vh87+jXI3pJLR+J+dekCI8a4S22kGX9iAen8/+Ew++lFouDueKLyszmmhCrIk1WnJvYPuSFBw==}
|
resolution: {integrity: sha512-SOGR93kGomRR+Vh87+jXI3pJLR+J+dekCI8a4S22kGX9iAen8/+Ew++lFouDueKLyszmmhCrIk1WnJvYPuSFBw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@ast-grep/napi-linux-x64-musl@0.22.6':
|
'@ast-grep/napi-linux-x64-musl@0.22.6':
|
||||||
resolution: {integrity: sha512-gcJeBMgJQf2pZZo0lgH0Vg4ycyujM7Am8VlomXhavC/dPpkddA1tiHSIC4fCNneLU1EqHITy3ALSmM4GLdsjBw==}
|
resolution: {integrity: sha512-gcJeBMgJQf2pZZo0lgH0Vg4ycyujM7Am8VlomXhavC/dPpkddA1tiHSIC4fCNneLU1EqHITy3ALSmM4GLdsjBw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@ast-grep/napi-win32-arm64-msvc@0.21.4':
|
'@ast-grep/napi-win32-arm64-msvc@0.21.4':
|
||||||
resolution: {integrity: sha512-ciGaTbkPjbCGqUyLwIPvcNeftNXjSG3cXE+5NiLThRbDhh2yUOE8YJkElUQcu0xQCdSlXnb4l/imEED/65jGfw==}
|
resolution: {integrity: sha512-ciGaTbkPjbCGqUyLwIPvcNeftNXjSG3cXE+5NiLThRbDhh2yUOE8YJkElUQcu0xQCdSlXnb4l/imEED/65jGfw==}
|
||||||
@ -3281,7 +3306,6 @@ packages:
|
|||||||
|
|
||||||
'@ls-lint/ls-lint@2.2.3':
|
'@ls-lint/ls-lint@2.2.3':
|
||||||
resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==}
|
resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==}
|
||||||
cpu: [x64, arm64, s390x]
|
|
||||||
os: [darwin, linux, win32]
|
os: [darwin, linux, win32]
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@ -3384,30 +3408,35 @@ packages:
|
|||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.4.1':
|
'@parcel/watcher-linux-arm64-glibc@2.4.1':
|
||||||
resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
|
resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.4.1':
|
'@parcel/watcher-linux-arm64-musl@2.4.1':
|
||||||
resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
|
resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.4.1':
|
'@parcel/watcher-linux-x64-glibc@2.4.1':
|
||||||
resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
|
resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.4.1':
|
'@parcel/watcher-linux-x64-musl@2.4.1':
|
||||||
resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
|
resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@parcel/watcher-wasm@2.4.1':
|
'@parcel/watcher-wasm@2.4.1':
|
||||||
resolution: {integrity: sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA==}
|
resolution: {integrity: sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA==}
|
||||||
@ -3607,91 +3636,109 @@ packages:
|
|||||||
resolution: {integrity: sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==}
|
resolution: {integrity: sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-gnueabihf@4.20.0':
|
'@rollup/rollup-linux-arm-gnueabihf@4.20.0':
|
||||||
resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==}
|
resolution: {integrity: sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.19.2':
|
'@rollup/rollup-linux-arm-musleabihf@4.19.2':
|
||||||
resolution: {integrity: sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==}
|
resolution: {integrity: sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.20.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.20.0':
|
||||||
resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==}
|
resolution: {integrity: sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.19.2':
|
'@rollup/rollup-linux-arm64-gnu@4.19.2':
|
||||||
resolution: {integrity: sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==}
|
resolution: {integrity: sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.20.0':
|
'@rollup/rollup-linux-arm64-gnu@4.20.0':
|
||||||
resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==}
|
resolution: {integrity: sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.19.2':
|
'@rollup/rollup-linux-arm64-musl@4.19.2':
|
||||||
resolution: {integrity: sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==}
|
resolution: {integrity: sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.20.0':
|
'@rollup/rollup-linux-arm64-musl@4.20.0':
|
||||||
resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==}
|
resolution: {integrity: sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.19.2':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.19.2':
|
||||||
resolution: {integrity: sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==}
|
resolution: {integrity: sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.20.0':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.20.0':
|
||||||
resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==}
|
resolution: {integrity: sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.19.2':
|
'@rollup/rollup-linux-riscv64-gnu@4.19.2':
|
||||||
resolution: {integrity: sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==}
|
resolution: {integrity: sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.20.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.20.0':
|
||||||
resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==}
|
resolution: {integrity: sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.19.2':
|
'@rollup/rollup-linux-s390x-gnu@4.19.2':
|
||||||
resolution: {integrity: sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==}
|
resolution: {integrity: sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.20.0':
|
'@rollup/rollup-linux-s390x-gnu@4.20.0':
|
||||||
resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==}
|
resolution: {integrity: sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.19.2':
|
'@rollup/rollup-linux-x64-gnu@4.19.2':
|
||||||
resolution: {integrity: sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==}
|
resolution: {integrity: sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.20.0':
|
'@rollup/rollup-linux-x64-gnu@4.20.0':
|
||||||
resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==}
|
resolution: {integrity: sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.19.2':
|
'@rollup/rollup-linux-x64-musl@4.19.2':
|
||||||
resolution: {integrity: sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==}
|
resolution: {integrity: sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.20.0':
|
'@rollup/rollup-linux-x64-musl@4.20.0':
|
||||||
resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==}
|
resolution: {integrity: sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.19.2':
|
'@rollup/rollup-win32-arm64-msvc@4.19.2':
|
||||||
resolution: {integrity: sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==}
|
resolution: {integrity: sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==}
|
||||||
@ -3819,6 +3866,9 @@ packages:
|
|||||||
'@types/conventional-commits-parser@5.0.0':
|
'@types/conventional-commits-parser@5.0.0':
|
||||||
resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==}
|
resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==}
|
||||||
|
|
||||||
|
'@types/crypto-js@4.2.2':
|
||||||
|
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||||
|
|
||||||
'@types/eslint@8.56.11':
|
'@types/eslint@8.56.11':
|
||||||
resolution: {integrity: sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==}
|
resolution: {integrity: sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==}
|
||||||
|
|
||||||
@ -4905,6 +4955,9 @@ packages:
|
|||||||
uWebSockets.js:
|
uWebSockets.js:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
|
||||||
crypto-random-string@2.0.0:
|
crypto-random-string@2.0.0:
|
||||||
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
|
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -6558,6 +6611,9 @@ packages:
|
|||||||
canvas:
|
canvas:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
jsencrypt@3.3.2:
|
||||||
|
resolution: {integrity: sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==}
|
||||||
|
|
||||||
jsesc@0.5.0:
|
jsesc@0.5.0:
|
||||||
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
|
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -12489,6 +12545,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.1.0
|
'@types/node': 22.1.0
|
||||||
|
|
||||||
|
'@types/crypto-js@4.2.2': {}
|
||||||
|
|
||||||
'@types/eslint@8.56.11':
|
'@types/eslint@8.56.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
@ -13775,6 +13833,8 @@ snapshots:
|
|||||||
|
|
||||||
crossws@0.2.4: {}
|
crossws@0.2.4: {}
|
||||||
|
|
||||||
|
crypto-js@4.2.0: {}
|
||||||
|
|
||||||
crypto-random-string@2.0.0: {}
|
crypto-random-string@2.0.0: {}
|
||||||
|
|
||||||
cspell-config-lib@8.13.1:
|
cspell-config-lib@8.13.1:
|
||||||
@ -15689,6 +15749,8 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
|
jsencrypt@3.3.2: {}
|
||||||
|
|
||||||
jsesc@0.5.0: {}
|
jsesc@0.5.0: {}
|
||||||
|
|
||||||
jsesc@2.5.2: {}
|
jsesc@2.5.2: {}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user