Compare commits

..

13 Commits

Author SHA1 Message Date
Jin Mao
382918b8f0 Merge branch 'main' into alova 2025-08-25 21:32:20 +08:00
Ken Hai
b0d7b177be fix: 角色管理,授权的树形组件,取消勾选叶子节点,父级节点状态错误 (#6680)
Co-authored-by: haiyinlong <haiyinlong@uhigame.com>
2025-08-25 21:32:01 +08:00
LinaBell
566d3bdf7c fix: appendToMain warning (#6684) 2025-08-25 21:25:29 +08:00
螃蟹
adbf793e79 fix(@vben/web-ele): the main color tone for switching between dark and light themes has been reset (#6678) 2025-08-25 11:03:54 +08:00
Jin Mao
962141b778 refactor(auth): 注释掉设置 accessToken 的代码
- 在 auth.ts 文件中,注释掉了设置 accessToken 的代码行
- 这可能是为了暂时禁用该功能或进行调试
2025-08-25 10:48:51 +08:00
Jin Mao
625837d9f7 refactor(api): 重构 auth.ts 中的 logoutApi 方法
-移除了未使用的 import 语句
- 重新实现了 logoutApi 方法,使用 client 替代 baseRequestClient
- 为 logoutApi 方法添加了 meta 信息,指定 authRole 为 'logout'
- 删除了未使用的 withCredentials 配置项
2025-08-25 06:44:36 +08:00
Jin Mao
679231dbc8 refactor(playground): 重构 API 请求和响应处理
- 添加 ResponseData 接口,用于统一响应数据格式
- 修改 login 方法,增加状态码和错误信息处理- 添加 logout 方法,暂未实现具体逻辑
- 优化 refreshTokenOnSuccess 处理逻辑
-重构 response success interceptor,增加状态码和错误信息处理
2025-08-25 06:44:11 +08:00
Jin Mao
9b3cc2deb5 refactor(alova): 重构响应成功拦截器处理逻辑
-移除自定义 ResponseData 接口,使用 alova 的 Response接口
- 重新定义 responseSuccessInterceptor 类型为 ResponseSuccessHandler
- 优化响应成功拦截器的执行流程
- 移除不必要的错误抛出和 JSON 解析
2025-08-25 06:43:59 +08:00
Jin Mao
63ccda1bbc feat(api): 添加服务端认证和国际化支持
- 引入 createServerTokenAuthentication 用于服务端认证
- 在请求头中添加 Accept-Language 以支持国际化
-优化 client 实例创建方式,使用解构赋值
2025-08-23 23:05:58 +08:00
Jin Mao
34c73b73d9 refactor(alova): 重构 AlovaClient 类并添加泛型支持
- 为 AlovaClient 类添加泛型参数 T,默认为 AlovaGenerics
- 重构请求拦截器和响应拦截器的类型定义
- 新增 ResponseData接口定义标准响应数据结构- 新增 ResponseSuccessMethod 类型定义请求成功拦截器方法
- 优化构造函数,支持传入自定义认证选项
- 为类方法添加 JSDoc 注释,提高代码可读性
- 更新 pnpm-lock.yaml,添加 alova 依赖
2025-08-23 23:05:47 +08:00
Jin Mao
0b6b4e00ef refactor(auth): 优化刷新令牌的响应格式
- 引入 useResponseSuccess 函数以统一响应格式
- 修改返回值为包含 accessToken 的对象
- 删除多余的空行
2025-08-23 18:54:25 +08:00
Jin Mao
2a06d3aa06 feat(api): 集成 Alova 请求库
- 引入 AlovaClient 并配置登录、刷新 token 等功能
- 重构 auth 和 user API,使用新的 AlovaClient
- 添加请求成功拦截器,统一处理错误码
2025-08-23 18:53:46 +08:00
Jin Mao
d6a0ddea4c feat(effects): 添加 Alova 请求库支持
- 新增 Alova 客户端类,提供请求拦截器和响应处理功能
- 在 package.json 中添加 Alova相关配置
- 更新 pnpm-workspace.yaml,添加 Alova依赖
2025-08-23 18:53:33 +08:00
13 changed files with 2550 additions and 2090 deletions

View File

@@ -6,7 +6,7 @@ import {
} from '~/utils/cookie-utils';
import { generateAccessToken, verifyRefreshToken } from '~/utils/jwt-utils';
import { MOCK_USERS } from '~/utils/mock-data';
import { forbiddenResponse } from '~/utils/response';
import { forbiddenResponse, useResponseSuccess } from '~/utils/response';
export default defineEventHandler(async (event) => {
const refreshToken = getRefreshTokenFromCookie(event);
@@ -30,6 +30,7 @@ export default defineEventHandler(async (event) => {
const accessToken = generateAccessToken(findUser);
setRefreshTokenCookie(event, refreshToken);
return accessToken;
return useResponseSuccess({
accessToken,
});
});

View File

@@ -82,7 +82,7 @@ defineExpose({
</script>
<template>
<Teleport :to="appendTo">
<Teleport defer :to="appendTo">
<Transition name="fade">
<DialogOverlay
v-if="open && modal"

View File

@@ -73,7 +73,7 @@ function onAnimationEnd(event: AnimationEvent) {
</script>
<template>
<Teleport :to="appendTo">
<Teleport defer :to="appendTo">
<Transition name="fade">
<SheetOverlay
v-if="open && modal"

View File

@@ -103,10 +103,15 @@ function updateTreeValue() {
treeValue.value = undefined;
} else {
if (Array.isArray(val)) {
const filteredValues = val.filter((v) => {
let filteredValues = val.filter((v) => {
const item = getItemByValue(v);
return item && !get(item, props.disabledField);
});
if (!props.checkStrictly && props.autoCheckParent) {
filteredValues = processParentSelection(filteredValues);
}
treeValue.value = filteredValues.map((v) => getItemByValue(v));
if (filteredValues.length !== val.length) {
@@ -123,7 +128,35 @@ function updateTreeValue() {
}
}
}
function processParentSelection(
selectedValues: Array<number | string>,
): Array<number | string> {
if (props.checkStrictly) return selectedValues;
const result = [...selectedValues];
for (let i = result.length - 1; i >= 0; i--) {
const currentValue = result[i];
if (currentValue === undefined) continue;
const currentItem = getItemByValue(currentValue);
if (!currentItem) continue;
const children = get(currentItem, props.childrenField);
if (Array.isArray(children) && children.length > 0) {
const hasSelectedChildren = children.some((child) => {
const childValue = get(child, props.valueField);
return result.includes(childValue);
});
if (!hasSelectedChildren) {
result.splice(i, 1);
}
}
}
return result;
}
function updateModelValue(val: Arrayable<Recordable<any>>) {
if (Array.isArray(val)) {
const filteredVal = val.filter((v) => !get(v, props.disabledField));

View File

@@ -104,7 +104,7 @@ function selectColor() {
watch(
() => [modelValue.value, props.isDark] as [BuiltinThemeType, boolean],
([themeType, isDark]) => {
([themeType, isDark], [_, isDarkPrev]) => {
const theme = builtinThemePresets.value.find(
(item) => item.type === themeType,
);
@@ -113,7 +113,9 @@ watch(
? theme.darkPrimaryColor || theme.primaryColor
: theme.primaryColor;
themeColorPrimary.value = primaryColor || theme.color;
if (!(theme.type === 'custom' && isDark !== isDarkPrev)) {
themeColorPrimary.value = primaryColor || theme.color;
}
}
},
);

View File

@@ -25,6 +25,10 @@
"./motion": {
"types": "./src/motion/index.ts",
"default": "./src/motion/index.ts"
},
"./alova": {
"types": "./src/alova/index.ts",
"default": "./src/alova/index.ts"
}
},
"dependencies": {
@@ -41,6 +45,7 @@
"@vueuse/motion": "catalog:",
"echarts": "catalog:",
"vue": "catalog:",
"alova": "catalog:",
"vxe-pc-ui": "catalog:",
"vxe-table": "catalog:"
}

View File

@@ -0,0 +1,254 @@
import type {
Alova,
AlovaGenerics,
AlovaMethodCommonConfig,
AlovaMethodCreateConfig,
AlovaOptions,
Method,
RequestBody,
RespondedAlovaGenerics,
ResponseCompleteHandler,
ResponseErrorHandler,
ResponseSuccessHandler,
StatesHook,
} from 'alova';
import type {
AlovaRequestAdapterUnified,
TokenAuthenticationResult,
} from 'alova/client';
import { createAlova } from 'alova';
import adapterFetch from 'alova/fetch';
import VueHook from 'alova/vue';
/**
* 请求拦截器方法类型
*/
type RequestMethod<T extends AlovaGenerics = AlovaGenerics> = (
method: Method<T>,
) => Promise<void> | void;
/**
* Alova HTTP客户端封装类
* @template T 泛型参数继承自AlovaGenerics
*/
class AlovaClient<T extends AlovaGenerics = AlovaGenerics> {
public readonly instance: Alova<T>;
/**
* 请求拦截器数组
*/
public requestInterceptor: RequestMethod<T>[] = [];
/**
* 请求完成拦截器数组
*/
public responseCompleteInterceptor: ResponseCompleteHandler<T>[] = [];
/**
* 请求错误拦截器数组
*/
public responseErrorInterceptor: ResponseErrorHandler<T>[] = [];
/**
* 请求成功拦截器数组
*/
public responseSuccessInterceptor: ResponseSuccessHandler<T>[] = [];
/**
* 构造函数
* @param options Alova配置选项
* @param authOptions 认证选项
*/
constructor(
options: AlovaOptions<T>,
authOptions?: TokenAuthenticationResult<
StatesHook<any>,
AlovaRequestAdapterUnified
>,
) {
const { onAuthRequired, onResponseRefreshToken } = authOptions || {};
const beforeRequest = async (method: Method<T>) => {
for (const interceptor of this.requestInterceptor) {
await interceptor(method);
}
};
const responded = {
// 请求成功的拦截器
// 当使用 `alova/fetch` 请求适配器时第一个参数接收Response对象
// 第二个参数为当前请求的method实例你可以用它同步请求前后的配置信息
onSuccess: async (response: Response, method: Method<T>) => {
let result: any = null;
for (const interceptor of this.responseSuccessInterceptor) {
result = await interceptor(response, method);
if (result) {
return result;
}
}
},
onError: async (err: Error, method: Method<T>) => {
for (const interceptor of this.responseErrorInterceptor) {
await interceptor(err, method);
}
},
// 请求完成的拦截器
// 当你需要在请求不论是成功、失败、还是命中缓存都需要执行的逻辑时可以在创建alova实例时指定全局的`onComplete`拦截器,例如关闭请求 loading 状态。
// 接收当前请求的method实例
onComplete: async (method: Method<T>) => {
// 处理请求完成逻辑
for (const interceptor of this.responseCompleteInterceptor) {
await interceptor(method);
}
},
};
this.instance = createAlova({
requestAdapter: adapterFetch(),
statesHook: VueHook,
beforeRequest: onAuthRequired
? onAuthRequired(beforeRequest)
: beforeRequest,
// 使用 responded 对象分别指定请求成功的拦截器和请求失败的拦截器
responded: onResponseRefreshToken
? onResponseRefreshToken(responded)
: responded,
...options,
});
}
/**
* 添加请求拦截器
* @param method 拦截器方法
*/
public addRequestInterceptor(method: RequestMethod<T>) {
this.requestInterceptor.push(method);
}
/**
* 添加请求完成拦截器
* @param method 拦截器方法
*/
public addResponseCompleteInterceptor(method: ResponseCompleteHandler<T>) {
this.responseCompleteInterceptor.push(method);
}
/**
* 添加请求错误拦截器
* @param method 拦截器方法
*/
public addResponseErrorInterceptor(method: ResponseErrorHandler<T>) {
this.responseErrorInterceptor.push(method);
}
/**
* 添加请求成功拦截器
* @param method 拦截器方法
*/
public addResponseSuccessInterceptor(method: ResponseSuccessMethod) {
this.responseSuccessInterceptor.push(method);
}
/**
* 发送DELETE请求
* @param url 请求地址
* @param data 请求数据
* @param config 请求配置
* @returns Method实例
*/
public delete<Responded = unknown, Transformed = unknown>(
url: string,
data?: RequestBody,
config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Delete(url, config, data);
}
/**
* 发送GET请求
* @param url 请求地址
* @param config 请求配置
* @returns Method实例
*/
public get<Responded = unknown, Transformed = unknown>(
url: string,
config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Get(url, config);
}
/**
* 发送HEAD请求
* @param url 请求地址
* @param config 请求配置
* @returns Method实例
*/
public head<Responded = unknown, Transformed = unknown>(
url: string,
config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Head(url, config);
}
/**
* 发送OPTIONS请求
* @param url 请求地址
* @param config 请求配置
* @returns Method实例
*/
public options<Responded = unknown, Transformed = unknown>(
url: string,
config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Options(url, config);
}
/**
* 发送PATCH请求
* @param url 请求地址
* @param data 请求数据
* @param config 请求配置
* @returns Method实例
*/
public patch<Responded = unknown, Transformed = unknown>(
url: string,
data?: RequestBody,
config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Patch(url, data, config);
}
/**
* 发送POST请求
* @param url 请求地址
* @param data 请求数据
* @param config 请求配置
* @returns Method实例
*/
public post<Responded = unknown, Transformed = unknown>(
url: string,
data?: RequestBody,
config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Post(url, data, config);
}
/**
* 发送PUT请求
* @param url 请求地址
* @param data 请求数据
* @param config 请求配置
* @returns Method实例
*/
public put<Responded = unknown, Transformed = unknown>(
url: string,
data?: RequestBody,
config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Put(url, data, config);
}
/**
* 发送自定义请求
* @param config 请求配置
* @returns Method实例
*/
public request<Responded = unknown, Transformed = unknown>(
config: AlovaMethodCommonConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Request(config);
}
}
export { AlovaClient, VueHook };
export * from 'alova';
export * from 'alova/client';

View File

@@ -1,4 +1,4 @@
import { baseRequestClient, requestClient } from '#/api/request';
import { client, requestClient } from '#/api/request';
export namespace AuthApi {
/** 登录接口参数 */
@@ -21,32 +21,54 @@ export namespace AuthApi {
/**
* 登录
*/
export async function loginApi(data: AuthApi.LoginParams) {
return requestClient.post<AuthApi.LoginResult>('/auth/login', data, {
withCredentials: true,
});
// export async function loginApi(data: AuthApi.LoginParams) {
// return requestClient.post<AuthApi.LoginResult>('/auth/login', data, {
// withCredentials: true,
// });
// }
export async function loginApi(params: AuthApi.LoginParams) {
const method = client.post<AuthApi.LoginResult>('/auth/login', params);
method.meta = {
authRole: 'login',
};
return method;
}
/**
* 刷新accessToken
*/
// export async function refreshTokenApi() {
// return baseRequestClient.post<AuthApi.RefreshTokenResult>(
// '/auth/refresh',
// null,
// {
// withCredentials: true,
// },
// );
// }
export async function refreshTokenApi() {
return baseRequestClient.post<AuthApi.RefreshTokenResult>(
'/auth/refresh',
null,
{
withCredentials: true,
},
);
const method = client.post<AuthApi.RefreshTokenResult>('/auth/refresh');
method.meta = {
authRole: 'refreshToken',
};
return method;
}
/**
* 退出登录
*/
// export async function logoutApi() {
// return baseRequestClient.post('/auth/logout', null, {
// withCredentials: true,
// });
// }
export async function logoutApi() {
return baseRequestClient.post('/auth/logout', null, {
withCredentials: true,
});
const method = client.post('/auth/logout');
method.meta = {
authRole: 'logout',
};
return method;
}
/**

View File

@@ -1,10 +1,13 @@
import type { UserInfo } from '@vben/types';
import { requestClient } from '#/api/request';
import { client } from '#/api/request';
/**
* 获取用户信息
*/
// export async function getUserInfoApi() {
// return requestClient.get<UserInfo>('/user/info');
// }
export async function getUserInfoApi() {
return requestClient.get<UserInfo>('/user/info');
return client.get<UserInfo>('/user/info');
}

View File

@@ -4,6 +4,11 @@
import type { AxiosResponseHeaders, RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks';
import {
AlovaClient,
createServerTokenAuthentication,
VueHook,
} from '@vben/plugins/alova';
import { preferences } from '@vben/preferences';
import {
authenticateResponseInterceptor,
@@ -127,3 +132,76 @@ export interface PageFetchParams {
pageNo?: number;
pageSize?: number;
}
let count = 0;
interface ResponseData<T = unknown> {
[key: string]: unknown;
code?: number;
data?: T;
message?: string;
}
const { onAuthRequired, onResponseRefreshToken } =
createServerTokenAuthentication<typeof VueHook>({
async login(response) {
// const accessStore = useAccessStore();
if (response.status >= 400) {
throw new Error(response.statusText);
}
const json = await response.clone().json();
if (json.code !== 0) {
throw new Error(json.message);
}
// accessStore.setAccessToken(json.access_token);
},
assignToken: (method) => {
const accessStore = useAccessStore();
const accessToken = accessStore.accessToken;
method.config.headers['Accept-Language'] = preferences.app.locale;
method.config.headers.Authorization = accessToken
? `Bearer ${accessToken}`
: null;
},
logout(response, method) {
console.log(response, method);
},
refreshTokenOnSuccess: {
// 在请求前触发将接收到method参数并返回boolean表示token是否过期
isExpired: (response) => {
if (count > 5) {
return false;
}
count++;
return response.status === 401;
},
// 当token过期时触发在此函数中触发刷新token
handler: async (method) => {
try {
const accessStore = useAccessStore();
const { accessToken } = await refreshTokenApi();
accessStore.setAccessToken(accessToken);
} catch (error) {
// token刷新失败跳转回登录页
location.href = '/login';
// 并抛出错误
throw error;
}
},
},
});
export const client = new AlovaClient<ResponseData>(
{ baseURL: apiURL },
{ onAuthRequired, onResponseRefreshToken },
);
client.addResponseSuccessInterceptor(async (response, method) => {
if (response.status >= 400) {
throw new Error(response.statusText);
}
const json = await response.clone().json();
if (json.code !== 0) {
throw new Error(json.message);
}
return json.data;
});

View File

@@ -38,7 +38,7 @@ export const useAuthStore = defineStore('auth', () => {
// 如果成功获取到 accessToken
if (accessToken) {
accessStore.setAccessToken(accessToken);
// accessStore.setAccessToken(accessToken);
// 获取用户信息并存储到 accessStore 中
const [fetchUserInfoResult, accessCodes] = await Promise.all([

4095
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,31 +14,31 @@ packages:
- playground
catalog:
'@ast-grep/napi': ^0.39.4
'@ast-grep/napi': ^0.37.0
'@changesets/changelog-github': ^0.5.1
'@changesets/cli': ^2.29.6
'@changesets/cli': ^2.29.5
'@changesets/git': ^3.0.4
'@clack/prompts': ^0.11.0
'@clack/prompts': ^0.10.1
'@commitlint/cli': ^19.8.1
'@commitlint/config-conventional': ^19.8.1
'@ctrl/tinycolor': ^4.1.0
'@eslint/js': ^9.33.0
'@eslint/js': ^9.30.1
'@faker-js/faker': ^9.9.0
'@iconify/json': ^2.2.377
'@iconify/json': ^2.2.354
'@iconify/tailwind': ^1.2.0
'@iconify/vue': ^5.0.0
'@intlify/core-base': ^11.1.11
'@intlify/core-base': ^11.1.7
'@intlify/unplugin-vue-i18n': ^6.0.8
'@jspm/generator': ^2.6.3
'@jspm/generator': ^2.6.2
'@manypkg/get-packages': ^3.0.0
'@nolebase/vitepress-plugin-git-changelog': ^2.18.2
'@playwright/test': ^1.55.0
'@pnpm/workspace.read-manifest': ^1000.2.2
'@nolebase/vitepress-plugin-git-changelog': ^2.18.0
'@playwright/test': ^1.53.2
'@pnpm/workspace.read-manifest': ^1000.2.0
'@stylistic/stylelint-plugin': ^3.1.3
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
'@tailwindcss/typography': ^0.5.16
'@tanstack/vue-query': ^5.85.5
'@tanstack/vue-store': ^0.7.4
'@tanstack/vue-query': ^5.81.5
'@tanstack/vue-store': ^0.7.1
'@types/archiver': ^6.0.3
'@types/eslint': ^9.6.1
'@types/html-minifier-terser': ^7.0.2
@@ -54,14 +54,14 @@ catalog:
'@types/qrcode': ^1.5.5
'@types/qs': ^6.14.0
'@types/sortablejs': ^1.15.8
'@typescript-eslint/eslint-plugin': ^8.40.0
'@typescript-eslint/parser': ^8.40.0
'@typescript-eslint/eslint-plugin': ^8.35.1
'@typescript-eslint/parser': ^8.35.1
'@vee-validate/zod': ^4.15.1
'@vite-pwa/vitepress': ^1.0.0
'@vitejs/plugin-vue': ^6.0.1
'@vitejs/plugin-vue-jsx': ^5.0.1
'@vue/reactivity': ^3.5.17
'@vue/shared': ^3.5.19
'@vue/shared': ^3.5.17
'@vue/test-utils': ^2.4.6
'@vueuse/core': ^13.4.0
'@vueuse/integrations': ^13.4.0
@@ -69,11 +69,12 @@ catalog:
ant-design-vue: ^4.2.6
archiver: ^7.0.1
autoprefixer: ^10.4.21
axios: ^1.11.0
axios: ^1.10.0
axios-mock-adapter: ^2.1.0
cac: ^6.7.14
chalk: ^5.4.1
cheerio: ^1.1.2
alova: ^3.3.4
cheerio: ^1.1.0
circular-dependency-scanner: ^2.3.0
class-variance-authority: ^0.7.1
clsx: ^2.1.1
@@ -81,57 +82,57 @@ catalog:
consola: ^3.4.2
cross-env: ^7.0.3
cspell: ^8.19.4
cssnano: ^7.1.1
cz-git: ^1.12.0
czg: ^1.12.0
cssnano: ^7.0.7
cz-git: ^1.11.2
czg: ^1.11.1
dayjs: ^1.11.13
defu: ^6.1.4
depcheck: ^1.4.7
dotenv: ^16.6.1
echarts: ^5.6.0
element-plus: ^2.11.0
eslint: ^9.33.0
eslint-config-turbo: ^2.5.6
element-plus: ^2.10.2
eslint: ^9.30.1
eslint-config-turbo: ^2.5.4
eslint-plugin-command: ^3.3.1
eslint-plugin-eslint-comments: ^3.2.0
eslint-plugin-import-x: ^4.16.1
eslint-plugin-jsdoc: ^50.8.0
eslint-plugin-jsonc: ^2.20.1
eslint-plugin-n: ^17.21.3
eslint-plugin-n: ^17.20.0
eslint-plugin-no-only-tests: ^3.3.0
eslint-plugin-perfectionist: ^4.15.0
eslint-plugin-prettier: ^5.5.4
eslint-plugin-regexp: ^2.10.0
eslint-plugin-prettier: ^5.5.1
eslint-plugin-regexp: ^2.9.0
eslint-plugin-unicorn: ^59.0.1
eslint-plugin-unused-imports: ^4.2.0
eslint-plugin-unused-imports: ^4.1.4
eslint-plugin-vitest: ^0.5.4
eslint-plugin-vue: ^10.4.0
eslint-plugin-vue: ^10.2.0
execa: ^9.6.0
find-up: ^7.0.0
get-port: ^7.1.0
globals: ^16.3.0
h3: ^1.15.4
h3: ^1.15.3
happy-dom: ^17.6.3
html-minifier-terser: ^7.2.0
is-ci: ^4.1.0
json-bigint: ^1.0.0
jsonc-eslint-parser: ^2.4.0
jsonwebtoken: ^9.0.2
lefthook: ^1.12.3
lefthook: ^1.11.14
lodash.clonedeep: ^4.5.0
lodash.get: ^4.4.2
lodash.isequal: ^4.5.0
lodash.set: ^4.3.2
lucide-vue-next: ^0.541.0
lucide-vue-next: ^0.507.0
medium-zoom: ^1.1.0
naive-ui: ^2.42.0
nitropack: ^2.12.4
nitropack: ^2.11.13
nprogress: ^0.2.0
ora: ^8.2.0
pinia: ^3.0.3
pinia-plugin-persistedstate: ^4.5.0
pinia-plugin-persistedstate: ^4.4.1
pkg-types: ^2.2.0
playwright: ^1.55.0
playwright: ^1.53.2
postcss: ^8.5.6
postcss-antd-fixes: ^0.2.0
postcss-html: ^1.8.0
@@ -139,7 +140,7 @@ catalog:
postcss-preset-env: ^10.2.4
postcss-scss: ^4.0.9
prettier: ^3.6.2
prettier-plugin-tailwindcss: ^0.6.14
prettier-plugin-tailwindcss: ^0.6.13
publint: ^0.3.12
qrcode: ^1.5.4
qs: ^6.14.0
@@ -148,10 +149,10 @@ catalog:
rimraf: ^6.0.1
rollup: ^4.44.1
rollup-plugin-visualizer: ^5.14.0
sass: ^1.90.0
sass: ^1.89.2
secure-ls: ^2.0.0
sortablejs: ^1.15.6
stylelint: ^16.23.1
stylelint: ^16.21.0
stylelint-config-recess-order: ^6.1.0
stylelint-config-recommended: ^16.0.0
stylelint-config-recommended-scss: ^14.1.0
@@ -165,8 +166,8 @@ catalog:
tailwindcss-animate: ^1.0.7
theme-colors: ^0.1.0
tippy.js: ^6.3.7
turbo: ^2.5.6
typescript: ^5.9.2
turbo: ^2.5.4
typescript: ^5.8.3
unbuild: ^3.6.1
unplugin-element-plus: ^0.10.0
vee-validate: ^4.15.1
@@ -175,20 +176,20 @@ catalog:
vite-plugin-dts: ^4.5.4
vite-plugin-html: ^3.2.2
vite-plugin-lazy-import: ^1.0.7
vite-plugin-pwa: ^1.0.3
vite-plugin-pwa: ^1.0.1
vite-plugin-vue-devtools: ^7.7.7
vitepress: ^1.6.4
vitepress-plugin-group-icons: ^1.6.3
vitepress: ^1.6.3
vitepress-plugin-group-icons: ^1.6.1
vitest: ^3.2.4
vue: ^3.5.19
vue: ^3.5.17
vue-eslint-parser: ^10.2.0
vue-i18n: ^11.1.11
vue-i18n: ^11.1.7
vue-json-viewer: ^3.0.4
vue-router: ^4.5.1
vue-tippy: ^6.7.1
vue-tsc: 2.2.10
vxe-pc-ui: ^4.8.24
vxe-table: ^4.15.10
watermark-js-plus: ^1.6.3
vxe-pc-ui: ^4.7.12
vxe-table: ^4.14.4
watermark-js-plus: ^1.6.2
zod: ^3.25.67
zod-defaults: ^0.1.3