This commit is contained in:
dap 2024-10-03 15:20:39 +08:00
commit 445489bc38
19 changed files with 1577 additions and 738 deletions

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -61,6 +62,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@ -77,21 +88,21 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio,
RadioGroup,
RangePicker,
Rate,
RichTextarea,
Select,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
},
config: {

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -42,6 +43,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@ -56,14 +67,14 @@ setupVbenForm<FormComponentType>({
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
},
Divider: ElDivider,
Input: ElInput,
InputNumber: ElInputNumber,
Input: withDefaultPlaceholder(ElInput, 'input'),
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
RadioGroup: ElRadioGroup,
Select: ElSelect,
Select: withDefaultPlaceholder(ElSelect, 'select'),
Space: ElSpace,
Switch: ElSwitch,
TimePicker: ElTimePicker,
TreeSelect: ElTreeSelect,
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
Upload: ElUpload,
},
config: {

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -43,6 +44,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@ -62,17 +73,18 @@ setupVbenForm<FormComponentType>({
);
},
Divider: NDivider,
Input: NInput,
InputNumber: NInputNumber,
Input: withDefaultPlaceholder(NInput, 'input'),
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
RadioGroup: NRadioGroup,
Select: NSelect,
Select: withDefaultPlaceholder(NSelect, 'select'),
Space: NSpace,
Switch: NSwitch,
TimePicker: NTimePicker,
TreeSelect: NTreeSelect,
TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
Upload: NUpload,
},
config: {
disabledOnChangeListener: true,
baseModelPropName: 'value',
modelPropNameMap: {
Checkbox: 'checked',

View File

@ -25,7 +25,6 @@ const routes: RouteRecordRaw[] = [
},
{
meta: {
icon: 'mdi:shield-key-outline',
title: $t('page.demos.table'),
},
name: 'Table',

View File

@ -31,6 +31,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -84,6 +85,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@ -100,26 +111,27 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
},
config: {
// 是否禁用onChange事件监听naive ui组件库默认不需要监听onChange事件否则会在控制台报错
disabledOnChangeListener: true,
// ant design vue组件库默认都是 v-model:value
baseModelPropName: 'value',
// 一些组件是 v-model:checked 或者 v-model:fileList
modelPropNameMap: {
Checkbox: 'checked',

View File

@ -99,7 +99,7 @@
"node": ">=20.10.0",
"pnpm": ">=9.5.0"
},
"packageManager": "pnpm@9.11.0",
"packageManager": "pnpm@9.12.0",
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {

View File

@ -1,4 +1,8 @@
import type { BaseFormComponentType, VbenFormAdapterOptions } from './types';
import type {
BaseFormComponentType,
FormCommonConfig,
VbenFormAdapterOptions,
} from './types';
import type { Component } from 'vue';
import { h } from 'vue';
@ -17,6 +21,8 @@ import { defineRule } from 'vee-validate';
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
@ -39,6 +45,9 @@ export function setupVbenForm<
>(options: VbenFormAdapterOptions<T>) {
const { components, config, defineRules } = options;
DEFAULT_FORM_COMMON_CONFIG.disabledOnChangeListener =
config?.disabledOnChangeListener ?? false;
if (defineRules) {
for (const key of Object.keys(defineRules)) {
defineRule(key, defineRules[key as never]);

View File

@ -3,7 +3,7 @@ import type { ZodType } from 'zod';
import type { FormSchema, MaybeComponentProps } from '../types';
import { computed } from 'vue';
import { computed, nextTick, useTemplateRef, watch } from 'vue';
import {
FormControl,
@ -32,6 +32,7 @@ const {
dependencies,
description,
disabled,
disabledOnChangeListener,
fieldName,
formFieldProps,
label,
@ -49,6 +50,7 @@ const { componentBindEventMap, componentMap, isVertical } = useFormContext();
const formRenderProps = injectRenderFormProps();
const values = useFormValues();
const errors = useFieldError(fieldName);
const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
const formApi = formRenderProps.form;
const isInValid = computed(() => errors.value?.length > 0);
@ -156,6 +158,18 @@ const computedProps = computed(() => {
};
});
watch(
() => computedProps.value?.autofocus,
(value) => {
if (value === true) {
nextTick(() => {
autofocus();
});
}
},
{ immediate: true },
);
const shouldDisabled = computed(() => {
return isDisabled.value || disabled || computedProps.value?.disabled;
});
@ -177,7 +191,7 @@ const fieldProps = computed(() => {
keepValue: true,
label,
...(rules ? { rules } : {}),
...formFieldProps,
...(formFieldProps as Record<string, any>),
};
});
@ -200,13 +214,14 @@ function fieldBindEvent(slotProps: Record<string, any>) {
return {
[`onUpdate:${bindEventField}`]: handler,
[bindEventField]: value,
onChange: (e: Record<string, any>) => {
onChange: disabledOnChangeListener
? undefined
: (e: Record<string, any>) => {
const shouldUnwrap = isEventObjectLike(e);
const onChange = slotProps?.componentField?.onChange;
if (!shouldUnwrap) {
return onChange?.(e);
}
return onChange?.(e?.target?.[bindEventField] ?? e);
},
onInput: () => {},
@ -226,6 +241,17 @@ function createComponentProps(slotProps: Record<string, any>) {
return binds;
}
function autofocus() {
if (
fieldComponentRef.value &&
isFunction(fieldComponentRef.value.focus) &&
//
document.activeElement !== fieldComponentRef.value
) {
fieldComponentRef.value?.focus?.();
}
}
</script>
<template>
@ -275,6 +301,7 @@ function createComponentProps(slotProps: Record<string, any>) {
>
<component
:is="fieldComponent"
ref="fieldComponentRef"
:class="{
'border-destructive focus:border-destructive hover:border-destructive/80 focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
isInValid,

View File

@ -1,12 +1,17 @@
<script setup lang="ts">
import type { ZodTypeAny } from 'zod';
import type { FormRenderProps, FormSchema, FormShape } from '../types';
import type {
FormCommonConfig,
FormRenderProps,
FormSchema,
FormShape,
} from '../types';
import { computed } from 'vue';
import { Form } from '@vben-core/shadcn-ui';
import { cn, isString } from '@vben-core/shared/utils';
import { cn, isString, mergeWithArrayOverride } from '@vben-core/shared/utils';
import { type GenericObject } from 'vee-validate';
@ -17,12 +22,16 @@ import { getBaseRules, getDefaultValueInZodStack } from './helper';
interface Props extends FormRenderProps {}
const props = withDefaults(defineProps<Props>(), {
const props = withDefaults(
defineProps<{ globalCommonConfig?: FormCommonConfig } & Props>(),
{
collapsedRows: 1,
commonConfig: () => ({}),
globalCommonConfig: () => ({}),
showCollapseButton: false,
wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
});
},
);
const emits = defineEmits<{
submit: [event: any];
@ -77,6 +86,7 @@ const computedSchema = computed(
componentProps = {},
controlClass = '',
disabled,
disabledOnChangeListener = false,
formFieldProps = {},
formItemClass = '',
hideLabel = false,
@ -84,7 +94,7 @@ const computedSchema = computed(
labelClass = '',
labelWidth = 100,
wrapperClass = '',
} = props.commonConfig;
} = mergeWithArrayOverride(props.commonConfig, props.globalCommonConfig);
return (props.schema || []).map((schema, index) => {
const keepIndex = keepFormItemIndex.value;
@ -96,6 +106,7 @@ const computedSchema = computed(
return {
disabled,
disabledOnChangeListener,
hideLabel,
hideRequiredMark,
labelWidth,

View File

@ -1,5 +1,5 @@
import type { VbenButtonProps } from '@vben-core/shadcn-ui';
import type { Field, FormContext, GenericObject } from 'vee-validate';
import type { FieldOptions, FormContext, GenericObject } from 'vee-validate';
import type { ZodTypeAny } from 'zod';
import type { FormApi } from './form-api';
@ -33,6 +33,15 @@ export type FormItemClassType =
| (Record<never, never> & string)
| WrapperClassType;
export type FormFieldOptions = Partial<
{
validateOnBlur?: boolean;
validateOnChange?: boolean;
validateOnInput?: boolean;
validateOnModelUpdate?: boolean;
} & FieldOptions
>;
export interface FormShape {
/** 默认值 */
default?: any;
@ -139,11 +148,16 @@ export interface FormCommonConfig {
* @default false
*/
disabled?: boolean;
/**
* change事件监听
* @default false
*/
disabledOnChangeListener?: boolean;
/**
*
* @default {}
*/
formFieldProps?: Partial<typeof Field>;
formFieldProps?: FormFieldOptions;
/**
*
* @default ""
@ -317,6 +331,7 @@ export interface VbenFormAdapterOptions<
components: Partial<Record<T, Component>>;
config?: {
baseModelPropName?: string;
disabledOnChangeListener?: boolean;
modelPropNameMap?: Partial<Record<T, string>>;
};
defineRules?: {

View File

@ -6,7 +6,11 @@ import { ref, watchEffect } from 'vue';
import { useForwardPropsEmits } from '@vben-core/composables';
import FormActions from './components/form-actions.vue';
import { COMPONENT_BIND_EVENT_MAP, COMPONENT_MAP } from './config';
import {
COMPONENT_BIND_EVENT_MAP,
COMPONENT_MAP,
DEFAULT_FORM_COMMON_CONFIG,
} from './config';
import { Form } from './form-render';
import { provideFormProps, useFormInitial } from './use-form-context';
@ -51,6 +55,7 @@ watchEffect(() => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP"
:form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
>
<template
v-for="slotName in delegatedSlots"

View File

@ -4,10 +4,13 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
import { useForwardPriorityValues } from '@vben-core/composables';
import FormActions from './components/form-actions.vue';
import { COMPONENT_BIND_EVENT_MAP, COMPONENT_MAP } from './config';
import {
COMPONENT_BIND_EVENT_MAP,
COMPONENT_MAP,
DEFAULT_FORM_COMMON_CONFIG,
} from './config';
import { Form } from './form-render';
import { provideFormProps, useFormInitial } from './use-form-context';
// extends
interface Props extends VbenFormProps {
formApi: ExtendedFormApi;
@ -36,6 +39,7 @@ const handleUpdateCollapsed = (value: boolean) => {
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
:component-map="COMPONENT_MAP"
:form="form"
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
>
<template
v-for="slotName in delegatedSlots"

View File

@ -64,6 +64,10 @@
"required": "Please enter {0}",
"selectRequired": "Please select {0}"
},
"placeholder": {
"input": "Please enter",
"select": "Please select"
},
"widgets": {
"document": "Document",
"profile": "Profile",

View File

@ -64,6 +64,10 @@
"required": "请输入{0}",
"selectRequired": "请选择{0}"
},
"placeholder": {
"input": "请输入",
"select": "请选择"
},
"widgets": {
"document": "文档",
"profile": "个人中心",

View File

@ -4,6 +4,7 @@ import type {
VbenFormProps,
} from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
@ -57,6 +58,16 @@ export type FormComponentType =
| 'Upload'
| BaseFormComponentType;
const withDefaultPlaceholder = <T extends Component>(
component: T,
type: 'input' | 'select',
) => {
return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
const placeholder = props?.placeholder || $t(`placeholder.${type}`);
return h(component, { ...props, ...attrs, placeholder }, slots);
};
};
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
@ -73,20 +84,20 @@ setupVbenForm<FormComponentType>({
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Input: withDefaultPlaceholder(Input, 'input'),
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
Mentions: withDefaultPlaceholder(Mentions, 'input'),
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Select: withDefaultPlaceholder(Select, 'select'),
Space,
Switch,
Textarea,
Textarea: withDefaultPlaceholder(Textarea, 'input'),
TimePicker,
TreeSelect,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
},
config: {

View File

@ -223,6 +223,75 @@ const [BaseForm, baseFormApi] = useVbenForm({
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
});
const [CustomLayoutForm] = useVbenForm({
//
commonConfig: {
//
componentProps: {
class: 'w-full',
},
},
layout: 'horizontal',
schema: [
{
component: 'Select',
fieldName: 'field1',
label: '字符串',
},
{
component: 'TreeSelect',
fieldName: 'field2',
label: '字符串',
},
{
component: 'Mentions',
fieldName: 'field3',
label: '字符串',
},
{
component: 'Input',
fieldName: 'field4',
label: '字符串',
},
{
component: 'InputNumber',
fieldName: 'field5',
//
formItemClass: 'col-start-3',
label: '前面空了一列',
},
{
component: 'Textarea',
fieldName: 'field6',
// 线
formItemClass: 'col-span-3 items-baseline',
label: '占满三列',
},
{
component: 'Input',
fieldName: 'field7',
// 2
formItemClass: 'col-span-2 col-start-2',
label: '占满2列',
},
{
component: 'Input',
fieldName: 'field8',
//
formItemClass: 'col-start-2',
label: '左右留空',
},
{
component: 'InputPassword',
fieldName: 'field9',
formItemClass: 'col-start-1',
label: '字符串',
},
],
//
wrapperClass: 'grid-cols-3',
});
function onSubmit(values: Record<string, any>) {
message.success({
content: `form values: ${JSON.stringify(values)}`,
@ -256,6 +325,7 @@ function handleSetFormValue() {
<template>
<Page
content-class="flex flex-col gap-4"
description="表单组件基础示例,请注意,该页面用到的参数代码会添加一些简单注释,方便理解,请仔细查看。"
title="表单组件"
>
@ -265,5 +335,8 @@ function handleSetFormValue() {
</template>
<BaseForm />
</Card>
<Card title="使用tailwind自定义布局">
<CustomLayoutForm />
</Card>
</Page>
</template>

View File

@ -175,6 +175,47 @@ const [Form, formApi] = useVbenForm({
label: '密码',
rules: 'required',
},
{
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: 'input-blur',
formFieldProps: {
validateOnChange: false,
validateOnModelUpdate: false,
},
help: 'blur时才会触发校验',
label: 'blur触发',
rules: 'required',
},
{
component: 'Input',
componentProps: {
placeholder: '请输入',
},
fieldName: 'input-async',
label: '异步校验',
rules: z
.string()
.min(3, '用户名至少需要3个字符')
.refine(
async (username) => {
//
const checkUsernameExists = async (
username: string,
): Promise<boolean> => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return username === 'existingUser';
};
const exists = await checkUsernameExists(username);
return !exists;
},
{
message: '用户名已存在',
},
),
},
],
// 321
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ catalog:
'@commitlint/config-conventional': ^19.5.0
'@ctrl/tinycolor': ^4.1.0
'@eslint/js': ^9.11.1
'@iconify/json': ^2.2.254
'@iconify/json': ^2.2.255
'@iconify/tailwind': ^1.1.3
'@iconify/vue': ^4.1.2
'@intlify/core-base': ^10.0.3
@ -33,7 +33,7 @@ catalog:
'@stylistic/stylelint-plugin': ^3.1.0
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
'@tailwindcss/typography': ^0.5.15
'@tanstack/vue-query': ^5.56.2
'@tanstack/vue-query': ^5.59.0
'@tanstack/vue-store': ^0.5.5
'@types/archiver': ^6.0.2
'@types/chalk': ^2.2.0
@ -46,8 +46,8 @@ catalog:
'@types/postcss-import': ^14.0.3
'@types/qrcode': ^1.5.5
'@types/sortablejs': ^1.15.8
'@typescript-eslint/eslint-plugin': ^8.7.0
'@typescript-eslint/parser': ^8.7.0
'@typescript-eslint/eslint-plugin': ^8.8.0
'@typescript-eslint/parser': ^8.8.0
'@vee-validate/zod': ^4.13.2
'@vite-pwa/vitepress': ^0.5.3
'@vitejs/plugin-vue': ^5.1.4
@ -82,15 +82,15 @@ catalog:
echarts: ^5.5.1
element-plus: ^2.8.4
eslint: ^9.11.1
eslint-config-turbo: ^2.1.2
eslint-config-turbo: ^2.1.3
eslint-plugin-command: ^0.2.6
eslint-plugin-eslint-comments: ^3.2.0
eslint-plugin-import-x: ^4.3.0
eslint-plugin-jsdoc: ^50.3.0
eslint-plugin-import-x: ^4.3.1
eslint-plugin-jsdoc: ^50.3.1
eslint-plugin-jsonc: ^2.16.0
eslint-plugin-n: ^17.10.3
eslint-plugin-no-only-tests: ^3.3.0
eslint-plugin-perfectionist: ^3.7.0
eslint-plugin-perfectionist: ^3.8.0
eslint-plugin-prettier: ^5.2.1
eslint-plugin-regexp: ^2.6.0
eslint-plugin-unicorn: ^55.0.0
@ -100,7 +100,7 @@ catalog:
execa: ^9.4.0
find-up: ^7.0.0
get-port: ^7.1.0
globals: ^15.9.0
globals: ^15.10.0
h3: ^1.12.0
happy-dom: ^15.7.4
html-minifier-terser: ^7.2.0
@ -110,7 +110,7 @@ catalog:
jsonwebtoken: ^9.0.2
lint-staged: ^15.2.10
lodash.clonedeep: ^4.5.0
lucide-vue-next: ^0.446.0
lucide-vue-next: ^0.447.0
medium-zoom: ^1.1.0
naive-ui: ^2.40.1
nanoid: ^5.0.7
@ -134,7 +134,7 @@ catalog:
radix-vue: ^1.9.6
resolve.exports: ^2.0.2
rimraf: ^6.0.1
rollup: ^4.22.5
rollup: ^4.24.0
rollup-plugin-visualizer: ^5.12.0
sass: ^1.79.4
sortablejs: ^1.15.3
@ -151,7 +151,7 @@ catalog:
tailwindcss: ^3.4.13
tailwindcss-animate: ^1.0.7
theme-colors: ^0.1.0
turbo: ^2.1.2
turbo: ^2.1.3
typescript: ^5.6.2
unbuild: ^2.0.0
unplugin-element-plus: ^0.8.0
@ -165,7 +165,7 @@ catalog:
vite-plugin-vue-devtools: ^7.4.6
vitepress: ^1.3.4
vitepress-plugin-group-icons: ^1.2.4
vitest: ^2.1.1
vitest: ^2.1.2
vue: ^3.5.10
vue-eslint-parser: ^9.4.3
vue-i18n: ^10.0.3