feat:【framework 框架】增加 api 加解密能力

pull/201/head
YunaiV 2025-08-16 17:09:08 +08:00
parent 9cac5a2937
commit 2920dabb99
13 changed files with 605 additions and 2 deletions

View File

@ -24,3 +24,12 @@ VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'
# API 加解密
VITE_APP_API_ENCRYPT_ENABLE = true
VITE_APP_API_ENCRYPT_HEADER = X-Api-Encrypt
VITE_APP_API_ENCRYPT_ALGORITHM = AES
VITE_APP_API_ENCRYPT_REQUEST_KEY = 52549111389893486934626385991395
VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883
# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB
# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ==

View File

@ -50,6 +50,7 @@
"crypto-js": "catalog:",
"dayjs": "catalog:",
"highlight.js": "catalog:",
"jsencrypt": "catalog:",
"pinia": "catalog:",
"vue": "catalog:",
"vue-dompurify-html": "catalog:",

View File

@ -64,7 +64,11 @@ export namespace AuthApi {
/** 登录 */
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data);
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data, {
headers: {
isEncrypt: true,
},
});
}
/** 刷新 accessToken */

View File

@ -16,6 +16,7 @@ import { useAccessStore } from '@vben/stores';
import { message } from 'ant-design-vue';
import { useAuthStore } from '#/store';
import { ApiEncrypt } from '#/utils/encrypt';
import { refreshTokenApi } from './core';
@ -84,10 +85,46 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
config.headers['visit-tenant-id'] = tenantEnable
? accessStore.visitTenantId
: undefined;
// 是否 API 加密
if ((config.headers || {}).isEncrypt) {
try {
// 加密请求数据
if (config.data) {
config.data = ApiEncrypt.encryptRequest(config.data);
// 设置加密标识头
config.headers[ApiEncrypt.getEncryptHeader()] = 'true';
}
} catch (error) {
console.error('请求数据加密失败:', error);
throw error;
}
}
return config;
},
});
// API 解密响应拦截器
client.addResponseInterceptor({
fulfilled: (response) => {
// 检查是否需要解密响应数据
const encryptHeader = ApiEncrypt.getEncryptHeader();
const isEncryptResponse =
response.headers[encryptHeader] === 'true' ||
response.headers[encryptHeader.toLowerCase()] === 'true';
if (isEncryptResponse && typeof response.data === 'string') {
try {
// 解密响应数据
response.data = ApiEncrypt.decryptResponse(response.data);
} catch (error) {
console.error('响应数据解密失败:', error);
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
}
}
return response;
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({

View File

@ -0,0 +1,240 @@
import CryptoJS from 'crypto-js';
import { JSEncrypt } from 'jsencrypt';
/**
* API
* AES RSA
*/
// 从环境变量获取配置
const API_ENCRYPT_ENABLE =
import.meta.env.VITE_APP_API_ENCRYPT_ENABLE === 'true';
const API_ENCRYPT_HEADER =
import.meta.env.VITE_APP_API_ENCRYPT_HEADER || 'X-Api-Encrypt';
const API_ENCRYPT_ALGORITHM =
import.meta.env.VITE_APP_API_ENCRYPT_ALGORITHM || 'AES';
const API_ENCRYPT_REQUEST_KEY =
import.meta.env.VITE_APP_API_ENCRYPT_REQUEST_KEY || ''; // AES密钥 或 RSA公钥
const API_ENCRYPT_RESPONSE_KEY =
import.meta.env.VITE_APP_API_ENCRYPT_RESPONSE_KEY || ''; // AES密钥 或 RSA私钥
/**
* AES
*/
export const AES = {
/**
* AES
* @param data
* @param key
* @returns
*/
encrypt(data: string, key: string): string {
try {
if (!key) {
throw new Error('AES 加密密钥不能为空');
}
if (key.length !== 32) {
throw new Error(
`AES 加密密钥长度必须为 32 位,当前长度: ${key.length}`,
);
}
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
} catch (error) {
console.error('AES 加密失败:', error);
throw error;
}
},
/**
* AES
* @param encryptedData
* @param key
* @returns
*/
decrypt(encryptedData: string, key: string): string {
try {
if (!key) {
throw new Error('AES 解密密钥不能为空');
}
if (key.length !== 32) {
throw new Error(
`AES 解密密钥长度必须为 32 位,当前长度: ${key.length}`,
);
}
if (!encryptedData) {
throw new Error('AES 解密数据不能为空');
}
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const decrypted = CryptoJS.AES.decrypt(encryptedData, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
const result = decrypted.toString(CryptoJS.enc.Utf8);
if (!result) {
throw new Error('AES 解密结果为空,可能是密钥错误或数据损坏');
}
return result;
} catch (error) {
console.error('AES 解密失败:', error);
throw error;
}
},
};
/**
* RSA
*/
export const RSA = {
/**
* RSA
* @param data
* @param publicKey
* @returns
*/
encrypt(data: string, publicKey: string): false | string {
try {
if (!publicKey) {
throw new Error('RSA 公钥不能为空');
}
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
const result = encryptor.encrypt(data);
if (result === false) {
throw new Error('RSA 加密失败,可能是公钥格式错误或数据过长');
}
return result;
} catch (error) {
console.error('RSA 加密失败:', error);
throw error;
}
},
/**
* RSA
* @param encryptedData
* @param privateKey
* @returns
*/
decrypt(encryptedData: string, privateKey: string): false | string {
try {
if (!privateKey) {
throw new Error('RSA 私钥不能为空');
}
if (!encryptedData) {
throw new Error('RSA 解密数据不能为空');
}
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(privateKey);
const result = encryptor.decrypt(encryptedData);
if (result === false) {
throw new Error('RSA 解密失败,可能是私钥错误或数据损坏');
}
return result;
} catch (error) {
console.error('RSA 解密失败:', error);
throw error;
}
},
};
/**
* API
*/
export const ApiEncrypt = {
/**
*
*/
getEncryptHeader(): string {
return API_ENCRYPT_HEADER;
},
/**
*
* @param data
* @returns
*/
encryptRequest(data: any): string {
if (!API_ENCRYPT_ENABLE) {
return data;
}
try {
const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
if (!API_ENCRYPT_REQUEST_KEY) {
throw new Error('AES 请求加密密钥未配置');
}
return AES.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
if (!API_ENCRYPT_REQUEST_KEY) {
throw new Error('RSA 公钥未配置');
}
const result = RSA.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
if (result === false) {
throw new Error('RSA 加密失败');
}
return result;
} else {
throw new Error(`不支持的加密算法: ${API_ENCRYPT_ALGORITHM}`);
}
} catch (error) {
console.error('请求数据加密失败:', error);
throw error;
}
},
/**
*
* @param encryptedData
* @returns
*/
decryptResponse(encryptedData: string): any {
if (!API_ENCRYPT_ENABLE) {
return encryptedData;
}
try {
let decryptedData: false | string = '';
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
if (!API_ENCRYPT_RESPONSE_KEY) {
throw new Error('AES 响应解密密钥未配置');
}
decryptedData = AES.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
if (!API_ENCRYPT_RESPONSE_KEY) {
throw new Error('RSA 私钥未配置');
}
decryptedData = RSA.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
if (decryptedData === false) {
throw new Error('RSA 解密失败');
}
} else {
throw new Error(`不支持的解密算法: ${API_ENCRYPT_ALGORITHM}`);
}
if (!decryptedData) {
throw new Error('解密结果为空');
}
// 尝试解析为 JSON如果失败则返回原字符串
try {
return JSON.parse(decryptedData);
} catch {
return decryptedData;
}
} catch (error) {
console.error('响应数据解密失败:', error);
throw error;
}
},
};

View File

@ -24,3 +24,12 @@ VITE_APP_BAIDU_CODE = b79d8f49e2d38b26503b92810b740f45
# GoView域名
VITE_GOVIEW_URL='http://127.0.0.1:3000'
# API 加解密
VITE_APP_API_ENCRYPT_ENABLE = true
VITE_APP_API_ENCRYPT_HEADER = X-Api-Encrypt
VITE_APP_API_ENCRYPT_ALGORITHM = AES
VITE_APP_API_ENCRYPT_REQUEST_KEY = 52549111389893486934626385991395
VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883
# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB
# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ==

View File

@ -49,6 +49,7 @@
"dayjs": "catalog:",
"element-plus": "catalog:",
"highlight.js": "catalog:",
"jsencrypt": "catalog:",
"pinia": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"

View File

@ -64,7 +64,11 @@ export namespace AuthApi {
/** 登录 */
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data);
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data, {
headers: {
isEncrypt: true,
},
});
}
/** 刷新 accessToken */

View File

@ -16,6 +16,7 @@ import { useAccessStore } from '@vben/stores';
import { ElMessage } from 'element-plus';
import { useAuthStore } from '#/store';
import { ApiEncrypt } from '#/utils/encrypt';
import { refreshTokenApi } from './core';
@ -84,10 +85,46 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
config.headers['visit-tenant-id'] = tenantEnable
? accessStore.visitTenantId
: undefined;
// 是否 API 加密
if ((config.headers || {}).isEncrypt) {
try {
// 加密请求数据
if (config.data) {
config.data = ApiEncrypt.encryptRequest(config.data);
// 设置加密标识头
config.headers[ApiEncrypt.getEncryptHeader()] = 'true';
}
} catch (error) {
console.error('请求数据加密失败:', error);
throw error;
}
}
return config;
},
});
// API 解密响应拦截器
client.addResponseInterceptor({
fulfilled: (response) => {
// 检查是否需要解密响应数据
const encryptHeader = ApiEncrypt.getEncryptHeader();
const isEncryptResponse =
response.headers[encryptHeader] === 'true' ||
response.headers[encryptHeader.toLowerCase()] === 'true';
if (isEncryptResponse && typeof response.data === 'string') {
try {
// 解密响应数据
response.data = ApiEncrypt.decryptResponse(response.data);
} catch (error) {
console.error('响应数据解密失败:', error);
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
}
}
return response;
},
});
// 处理返回的响应数据格式
client.addResponseInterceptor(
defaultResponseInterceptor({

View File

@ -0,0 +1,240 @@
import CryptoJS from 'crypto-js';
import { JSEncrypt } from 'jsencrypt';
/**
* API
* AES RSA
*/
// 从环境变量获取配置
const API_ENCRYPT_ENABLE =
import.meta.env.VITE_APP_API_ENCRYPT_ENABLE === 'true';
const API_ENCRYPT_HEADER =
import.meta.env.VITE_APP_API_ENCRYPT_HEADER || 'X-Api-Encrypt';
const API_ENCRYPT_ALGORITHM =
import.meta.env.VITE_APP_API_ENCRYPT_ALGORITHM || 'AES';
const API_ENCRYPT_REQUEST_KEY =
import.meta.env.VITE_APP_API_ENCRYPT_REQUEST_KEY || ''; // AES密钥 或 RSA公钥
const API_ENCRYPT_RESPONSE_KEY =
import.meta.env.VITE_APP_API_ENCRYPT_RESPONSE_KEY || ''; // AES密钥 或 RSA私钥
/**
* AES
*/
export const AES = {
/**
* AES
* @param data
* @param key
* @returns
*/
encrypt(data: string, key: string): string {
try {
if (!key) {
throw new Error('AES 加密密钥不能为空');
}
if (key.length !== 32) {
throw new Error(
`AES 加密密钥长度必须为 32 位,当前长度: ${key.length}`,
);
}
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
} catch (error) {
console.error('AES 加密失败:', error);
throw error;
}
},
/**
* AES
* @param encryptedData
* @param key
* @returns
*/
decrypt(encryptedData: string, key: string): string {
try {
if (!key) {
throw new Error('AES 解密密钥不能为空');
}
if (key.length !== 32) {
throw new Error(
`AES 解密密钥长度必须为 32 位,当前长度: ${key.length}`,
);
}
if (!encryptedData) {
throw new Error('AES 解密数据不能为空');
}
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const decrypted = CryptoJS.AES.decrypt(encryptedData, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
const result = decrypted.toString(CryptoJS.enc.Utf8);
if (!result) {
throw new Error('AES 解密结果为空,可能是密钥错误或数据损坏');
}
return result;
} catch (error) {
console.error('AES 解密失败:', error);
throw error;
}
},
};
/**
* RSA
*/
export const RSA = {
/**
* RSA
* @param data
* @param publicKey
* @returns
*/
encrypt(data: string, publicKey: string): false | string {
try {
if (!publicKey) {
throw new Error('RSA 公钥不能为空');
}
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
const result = encryptor.encrypt(data);
if (result === false) {
throw new Error('RSA 加密失败,可能是公钥格式错误或数据过长');
}
return result;
} catch (error) {
console.error('RSA 加密失败:', error);
throw error;
}
},
/**
* RSA
* @param encryptedData
* @param privateKey
* @returns
*/
decrypt(encryptedData: string, privateKey: string): false | string {
try {
if (!privateKey) {
throw new Error('RSA 私钥不能为空');
}
if (!encryptedData) {
throw new Error('RSA 解密数据不能为空');
}
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(privateKey);
const result = encryptor.decrypt(encryptedData);
if (result === false) {
throw new Error('RSA 解密失败,可能是私钥错误或数据损坏');
}
return result;
} catch (error) {
console.error('RSA 解密失败:', error);
throw error;
}
},
};
/**
* API
*/
export const ApiEncrypt = {
/**
*
*/
getEncryptHeader(): string {
return API_ENCRYPT_HEADER;
},
/**
*
* @param data
* @returns
*/
encryptRequest(data: any): string {
if (!API_ENCRYPT_ENABLE) {
return data;
}
try {
const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
if (!API_ENCRYPT_REQUEST_KEY) {
throw new Error('AES 请求加密密钥未配置');
}
return AES.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
if (!API_ENCRYPT_REQUEST_KEY) {
throw new Error('RSA 公钥未配置');
}
const result = RSA.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
if (result === false) {
throw new Error('RSA 加密失败');
}
return result;
} else {
throw new Error(`不支持的加密算法: ${API_ENCRYPT_ALGORITHM}`);
}
} catch (error) {
console.error('请求数据加密失败:', error);
throw error;
}
},
/**
*
* @param encryptedData
* @returns
*/
decryptResponse(encryptedData: string): any {
if (!API_ENCRYPT_ENABLE) {
return encryptedData;
}
try {
let decryptedData: false | string = '';
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
if (!API_ENCRYPT_RESPONSE_KEY) {
throw new Error('AES 响应解密密钥未配置');
}
decryptedData = AES.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
if (!API_ENCRYPT_RESPONSE_KEY) {
throw new Error('RSA 私钥未配置');
}
decryptedData = RSA.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
if (decryptedData === false) {
throw new Error('RSA 解密失败');
}
} else {
throw new Error(`不支持的解密算法: ${API_ENCRYPT_ALGORITHM}`);
}
if (!decryptedData) {
throw new Error('解密结果为空');
}
// 尝试解析为 JSON如果失败则返回原字符串
try {
return JSON.parse(decryptedData);
} catch {
return decryptedData;
}
} catch (error) {
console.error('响应数据解密失败:', error);
throw error;
}
},
};

View File

@ -11,6 +11,12 @@ export interface VbenAdminProAppConfigRaw {
VITE_GLOB_API_URL: string;
VITE_GLOB_AUTH_DINGDING_CLIENT_ID: string;
VITE_GLOB_AUTH_DINGDING_CORP_ID: string;
// API 加解密相关配置
VITE_APP_API_ENCRYPT_ENABLE: string;
VITE_APP_API_ENCRYPT_HEADER: string;
VITE_APP_API_ENCRYPT_ALGORITHM: string;
VITE_APP_API_ENCRYPT_REQUEST_KEY: string;
VITE_APP_API_ENCRYPT_RESPONSE_KEY: string;
}
interface AuthConfig {

View File

@ -330,6 +330,9 @@ catalogs:
is-ci:
specifier: ^4.1.0
version: 4.1.0
jsencrypt:
specifier: ^3.3.2
version: 3.5.4
json-bigint:
specifier: ^1.0.0
version: 1.0.0
@ -782,6 +785,9 @@ importers:
highlight.js:
specifier: 'catalog:'
version: 11.11.1
jsencrypt:
specifier: 'catalog:'
version: 3.5.4
pinia:
specifier: ^3.0.3
version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
@ -873,6 +879,9 @@ importers:
highlight.js:
specifier: 'catalog:'
version: 11.11.1
jsencrypt:
specifier: 'catalog:'
version: 3.5.4
pinia:
specifier: ^3.0.3
version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
@ -8074,6 +8083,9 @@ packages:
resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
engines: {node: '>=12.0.0'}
jsencrypt@3.5.4:
resolution: {integrity: sha512-kNjfYEMNASxrDGsmcSQh/rUTmcoRfSUkxnAz+MMywM8jtGu+fFEZ3nJjHM58zscVnwR0fYmG9sGkTDjqUdpiwA==}
jsesc@3.0.2:
resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
engines: {node: '>=6'}
@ -18550,6 +18562,8 @@ snapshots:
jsdoc-type-pratt-parser@4.1.0: {}
jsencrypt@3.5.4: {}
jsesc@3.0.2: {}
jsesc@3.1.0: {}

View File

@ -92,6 +92,7 @@ catalog:
cropperjs: ^1.6.2
crypto-js: ^4.2.0
cspell: ^8.19.4
jsencrypt: ^3.3.2
cssnano: ^7.0.7
cz-git: ^1.11.2
czg: ^1.11.1