Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin
This commit is contained in:
commit
445489bc38
@ -4,6 +4,7 @@ import type {
|
|||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
@ -61,6 +62,16 @@ export type FormComponentType =
|
|||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| 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组件内部
|
// 初始化表单组件,并注册到form组件内部
|
||||||
setupVbenForm<FormComponentType>({
|
setupVbenForm<FormComponentType>({
|
||||||
components: {
|
components: {
|
||||||
@ -77,21 +88,21 @@ setupVbenForm<FormComponentType>({
|
|||||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||||
},
|
},
|
||||||
Divider,
|
Divider,
|
||||||
Input,
|
Input: withDefaultPlaceholder(Input, 'input'),
|
||||||
InputNumber,
|
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||||
InputPassword,
|
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||||
Mentions,
|
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
RangePicker,
|
RangePicker,
|
||||||
Rate,
|
Rate,
|
||||||
RichTextarea,
|
RichTextarea,
|
||||||
Select,
|
Select: withDefaultPlaceholder(Select, 'select'),
|
||||||
Space,
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Textarea,
|
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||||
TimePicker,
|
TimePicker,
|
||||||
TreeSelect,
|
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||||
Upload,
|
Upload,
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
@ -4,6 +4,7 @@ import type {
|
|||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
@ -42,6 +43,16 @@ export type FormComponentType =
|
|||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| 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组件内部
|
// 初始化表单组件,并注册到form组件内部
|
||||||
setupVbenForm<FormComponentType>({
|
setupVbenForm<FormComponentType>({
|
||||||
components: {
|
components: {
|
||||||
@ -56,14 +67,14 @@ setupVbenForm<FormComponentType>({
|
|||||||
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
|
||||||
},
|
},
|
||||||
Divider: ElDivider,
|
Divider: ElDivider,
|
||||||
Input: ElInput,
|
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||||
InputNumber: ElInputNumber,
|
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||||
RadioGroup: ElRadioGroup,
|
RadioGroup: ElRadioGroup,
|
||||||
Select: ElSelect,
|
Select: withDefaultPlaceholder(ElSelect, 'select'),
|
||||||
Space: ElSpace,
|
Space: ElSpace,
|
||||||
Switch: ElSwitch,
|
Switch: ElSwitch,
|
||||||
TimePicker: ElTimePicker,
|
TimePicker: ElTimePicker,
|
||||||
TreeSelect: ElTreeSelect,
|
TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
|
||||||
Upload: ElUpload,
|
Upload: ElUpload,
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
@ -4,6 +4,7 @@ import type {
|
|||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
@ -43,6 +44,16 @@ export type FormComponentType =
|
|||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| 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组件内部
|
// 初始化表单组件,并注册到form组件内部
|
||||||
setupVbenForm<FormComponentType>({
|
setupVbenForm<FormComponentType>({
|
||||||
components: {
|
components: {
|
||||||
@ -62,17 +73,18 @@ setupVbenForm<FormComponentType>({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
Divider: NDivider,
|
Divider: NDivider,
|
||||||
Input: NInput,
|
Input: withDefaultPlaceholder(NInput, 'input'),
|
||||||
InputNumber: NInputNumber,
|
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
|
||||||
RadioGroup: NRadioGroup,
|
RadioGroup: NRadioGroup,
|
||||||
Select: NSelect,
|
Select: withDefaultPlaceholder(NSelect, 'select'),
|
||||||
Space: NSpace,
|
Space: NSpace,
|
||||||
Switch: NSwitch,
|
Switch: NSwitch,
|
||||||
TimePicker: NTimePicker,
|
TimePicker: NTimePicker,
|
||||||
TreeSelect: NTreeSelect,
|
TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
|
||||||
Upload: NUpload,
|
Upload: NUpload,
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
disabledOnChangeListener: true,
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
Checkbox: 'checked',
|
Checkbox: 'checked',
|
||||||
|
@ -25,7 +25,6 @@ const routes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:shield-key-outline',
|
|
||||||
title: $t('page.demos.table'),
|
title: $t('page.demos.table'),
|
||||||
},
|
},
|
||||||
name: 'Table',
|
name: 'Table',
|
||||||
|
@ -31,6 +31,7 @@ import type {
|
|||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
@ -84,6 +85,16 @@ export type FormComponentType =
|
|||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| 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组件内部
|
// 初始化表单组件,并注册到form组件内部
|
||||||
setupVbenForm<FormComponentType>({
|
setupVbenForm<FormComponentType>({
|
||||||
components: {
|
components: {
|
||||||
@ -100,26 +111,27 @@ setupVbenForm<FormComponentType>({
|
|||||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||||
},
|
},
|
||||||
Divider,
|
Divider,
|
||||||
Input,
|
Input: withDefaultPlaceholder(Input, 'input'),
|
||||||
InputNumber,
|
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||||
InputPassword,
|
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||||
Mentions,
|
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
RangePicker,
|
RangePicker,
|
||||||
Rate,
|
Rate,
|
||||||
Select,
|
Select: withDefaultPlaceholder(Select, 'select'),
|
||||||
Space,
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Textarea,
|
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||||
TimePicker,
|
TimePicker,
|
||||||
TreeSelect,
|
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||||
Upload,
|
Upload,
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
// 是否禁用onChange事件监听,naive ui组件库默认不需要监听onChange事件,否则会在控制台报错
|
||||||
|
disabledOnChangeListener: true,
|
||||||
// ant design vue组件库默认都是 v-model:value
|
// ant design vue组件库默认都是 v-model:value
|
||||||
baseModelPropName: 'value',
|
baseModelPropName: 'value',
|
||||||
|
|
||||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
Checkbox: 'checked',
|
Checkbox: 'checked',
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
"node": ">=20.10.0",
|
"node": ">=20.10.0",
|
||||||
"pnpm": ">=9.5.0"
|
"pnpm": ">=9.5.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.11.0",
|
"packageManager": "pnpm@9.12.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import type { BaseFormComponentType, VbenFormAdapterOptions } from './types';
|
import type {
|
||||||
|
BaseFormComponentType,
|
||||||
|
FormCommonConfig,
|
||||||
|
VbenFormAdapterOptions,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
@ -17,6 +21,8 @@ import { defineRule } from 'vee-validate';
|
|||||||
|
|
||||||
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
|
const DEFAULT_MODEL_PROP_NAME = 'modelValue';
|
||||||
|
|
||||||
|
export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
|
||||||
|
|
||||||
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
|
export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
|
||||||
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
|
DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
|
||||||
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
|
DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
|
||||||
@ -39,6 +45,9 @@ export function setupVbenForm<
|
|||||||
>(options: VbenFormAdapterOptions<T>) {
|
>(options: VbenFormAdapterOptions<T>) {
|
||||||
const { components, config, defineRules } = options;
|
const { components, config, defineRules } = options;
|
||||||
|
|
||||||
|
DEFAULT_FORM_COMMON_CONFIG.disabledOnChangeListener =
|
||||||
|
config?.disabledOnChangeListener ?? false;
|
||||||
|
|
||||||
if (defineRules) {
|
if (defineRules) {
|
||||||
for (const key of Object.keys(defineRules)) {
|
for (const key of Object.keys(defineRules)) {
|
||||||
defineRule(key, defineRules[key as never]);
|
defineRule(key, defineRules[key as never]);
|
||||||
|
@ -3,7 +3,7 @@ import type { ZodType } from 'zod';
|
|||||||
|
|
||||||
import type { FormSchema, MaybeComponentProps } from '../types';
|
import type { FormSchema, MaybeComponentProps } from '../types';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed, nextTick, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -32,6 +32,7 @@ const {
|
|||||||
dependencies,
|
dependencies,
|
||||||
description,
|
description,
|
||||||
disabled,
|
disabled,
|
||||||
|
disabledOnChangeListener,
|
||||||
fieldName,
|
fieldName,
|
||||||
formFieldProps,
|
formFieldProps,
|
||||||
label,
|
label,
|
||||||
@ -49,6 +50,7 @@ const { componentBindEventMap, componentMap, isVertical } = useFormContext();
|
|||||||
const formRenderProps = injectRenderFormProps();
|
const formRenderProps = injectRenderFormProps();
|
||||||
const values = useFormValues();
|
const values = useFormValues();
|
||||||
const errors = useFieldError(fieldName);
|
const errors = useFieldError(fieldName);
|
||||||
|
const fieldComponentRef = useTemplateRef<HTMLInputElement>('fieldComponentRef');
|
||||||
const formApi = formRenderProps.form;
|
const formApi = formRenderProps.form;
|
||||||
|
|
||||||
const isInValid = computed(() => errors.value?.length > 0);
|
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(() => {
|
const shouldDisabled = computed(() => {
|
||||||
return isDisabled.value || disabled || computedProps.value?.disabled;
|
return isDisabled.value || disabled || computedProps.value?.disabled;
|
||||||
});
|
});
|
||||||
@ -177,7 +191,7 @@ const fieldProps = computed(() => {
|
|||||||
keepValue: true,
|
keepValue: true,
|
||||||
label,
|
label,
|
||||||
...(rules ? { rules } : {}),
|
...(rules ? { rules } : {}),
|
||||||
...formFieldProps,
|
...(formFieldProps as Record<string, any>),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -200,13 +214,14 @@ function fieldBindEvent(slotProps: Record<string, any>) {
|
|||||||
return {
|
return {
|
||||||
[`onUpdate:${bindEventField}`]: handler,
|
[`onUpdate:${bindEventField}`]: handler,
|
||||||
[bindEventField]: value,
|
[bindEventField]: value,
|
||||||
onChange: (e: Record<string, any>) => {
|
onChange: disabledOnChangeListener
|
||||||
|
? undefined
|
||||||
|
: (e: Record<string, any>) => {
|
||||||
const shouldUnwrap = isEventObjectLike(e);
|
const shouldUnwrap = isEventObjectLike(e);
|
||||||
const onChange = slotProps?.componentField?.onChange;
|
const onChange = slotProps?.componentField?.onChange;
|
||||||
if (!shouldUnwrap) {
|
if (!shouldUnwrap) {
|
||||||
return onChange?.(e);
|
return onChange?.(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return onChange?.(e?.target?.[bindEventField] ?? e);
|
return onChange?.(e?.target?.[bindEventField] ?? e);
|
||||||
},
|
},
|
||||||
onInput: () => {},
|
onInput: () => {},
|
||||||
@ -226,6 +241,17 @@ function createComponentProps(slotProps: Record<string, any>) {
|
|||||||
|
|
||||||
return binds;
|
return binds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function autofocus() {
|
||||||
|
if (
|
||||||
|
fieldComponentRef.value &&
|
||||||
|
isFunction(fieldComponentRef.value.focus) &&
|
||||||
|
// 检查当前是否有元素被聚焦
|
||||||
|
document.activeElement !== fieldComponentRef.value
|
||||||
|
) {
|
||||||
|
fieldComponentRef.value?.focus?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -275,6 +301,7 @@ function createComponentProps(slotProps: Record<string, any>) {
|
|||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="fieldComponent"
|
:is="fieldComponent"
|
||||||
|
ref="fieldComponentRef"
|
||||||
:class="{
|
:class="{
|
||||||
'border-destructive focus:border-destructive hover:border-destructive/80 focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
|
'border-destructive focus:border-destructive hover:border-destructive/80 focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
|
||||||
isInValid,
|
isInValid,
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ZodTypeAny } from 'zod';
|
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 { computed } from 'vue';
|
||||||
|
|
||||||
import { Form } from '@vben-core/shadcn-ui';
|
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';
|
import { type GenericObject } from 'vee-validate';
|
||||||
|
|
||||||
@ -17,12 +22,16 @@ import { getBaseRules, getDefaultValueInZodStack } from './helper';
|
|||||||
|
|
||||||
interface Props extends FormRenderProps {}
|
interface Props extends FormRenderProps {}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(
|
||||||
|
defineProps<{ globalCommonConfig?: FormCommonConfig } & Props>(),
|
||||||
|
{
|
||||||
collapsedRows: 1,
|
collapsedRows: 1,
|
||||||
commonConfig: () => ({}),
|
commonConfig: () => ({}),
|
||||||
|
globalCommonConfig: () => ({}),
|
||||||
showCollapseButton: false,
|
showCollapseButton: false,
|
||||||
wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
|
wrapperClass: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
submit: [event: any];
|
submit: [event: any];
|
||||||
@ -77,6 +86,7 @@ const computedSchema = computed(
|
|||||||
componentProps = {},
|
componentProps = {},
|
||||||
controlClass = '',
|
controlClass = '',
|
||||||
disabled,
|
disabled,
|
||||||
|
disabledOnChangeListener = false,
|
||||||
formFieldProps = {},
|
formFieldProps = {},
|
||||||
formItemClass = '',
|
formItemClass = '',
|
||||||
hideLabel = false,
|
hideLabel = false,
|
||||||
@ -84,7 +94,7 @@ const computedSchema = computed(
|
|||||||
labelClass = '',
|
labelClass = '',
|
||||||
labelWidth = 100,
|
labelWidth = 100,
|
||||||
wrapperClass = '',
|
wrapperClass = '',
|
||||||
} = props.commonConfig;
|
} = mergeWithArrayOverride(props.commonConfig, props.globalCommonConfig);
|
||||||
return (props.schema || []).map((schema, index) => {
|
return (props.schema || []).map((schema, index) => {
|
||||||
const keepIndex = keepFormItemIndex.value;
|
const keepIndex = keepFormItemIndex.value;
|
||||||
|
|
||||||
@ -96,6 +106,7 @@ const computedSchema = computed(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
disabled,
|
disabled,
|
||||||
|
disabledOnChangeListener,
|
||||||
hideLabel,
|
hideLabel,
|
||||||
hideRequiredMark,
|
hideRequiredMark,
|
||||||
labelWidth,
|
labelWidth,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { VbenButtonProps } from '@vben-core/shadcn-ui';
|
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 { ZodTypeAny } from 'zod';
|
||||||
|
|
||||||
import type { FormApi } from './form-api';
|
import type { FormApi } from './form-api';
|
||||||
@ -33,6 +33,15 @@ export type FormItemClassType =
|
|||||||
| (Record<never, never> & string)
|
| (Record<never, never> & string)
|
||||||
| WrapperClassType;
|
| WrapperClassType;
|
||||||
|
|
||||||
|
export type FormFieldOptions = Partial<
|
||||||
|
{
|
||||||
|
validateOnBlur?: boolean;
|
||||||
|
validateOnChange?: boolean;
|
||||||
|
validateOnInput?: boolean;
|
||||||
|
validateOnModelUpdate?: boolean;
|
||||||
|
} & FieldOptions
|
||||||
|
>;
|
||||||
|
|
||||||
export interface FormShape {
|
export interface FormShape {
|
||||||
/** 默认值 */
|
/** 默认值 */
|
||||||
default?: any;
|
default?: any;
|
||||||
@ -139,11 +148,16 @@ export interface FormCommonConfig {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
/**
|
||||||
|
* 是否禁用所有表单项的change事件监听
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
disabledOnChangeListener?: boolean;
|
||||||
/**
|
/**
|
||||||
* 所有表单项的控件样式
|
* 所有表单项的控件样式
|
||||||
* @default {}
|
* @default {}
|
||||||
*/
|
*/
|
||||||
formFieldProps?: Partial<typeof Field>;
|
formFieldProps?: FormFieldOptions;
|
||||||
/**
|
/**
|
||||||
* 所有表单项的栅格布局
|
* 所有表单项的栅格布局
|
||||||
* @default ""
|
* @default ""
|
||||||
@ -317,6 +331,7 @@ export interface VbenFormAdapterOptions<
|
|||||||
components: Partial<Record<T, Component>>;
|
components: Partial<Record<T, Component>>;
|
||||||
config?: {
|
config?: {
|
||||||
baseModelPropName?: string;
|
baseModelPropName?: string;
|
||||||
|
disabledOnChangeListener?: boolean;
|
||||||
modelPropNameMap?: Partial<Record<T, string>>;
|
modelPropNameMap?: Partial<Record<T, string>>;
|
||||||
};
|
};
|
||||||
defineRules?: {
|
defineRules?: {
|
||||||
|
@ -6,7 +6,11 @@ import { ref, watchEffect } from 'vue';
|
|||||||
import { useForwardPropsEmits } from '@vben-core/composables';
|
import { useForwardPropsEmits } from '@vben-core/composables';
|
||||||
|
|
||||||
import FormActions from './components/form-actions.vue';
|
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 { Form } from './form-render';
|
||||||
import { provideFormProps, useFormInitial } from './use-form-context';
|
import { provideFormProps, useFormInitial } from './use-form-context';
|
||||||
|
|
||||||
@ -51,6 +55,7 @@ watchEffect(() => {
|
|||||||
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
|
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
|
||||||
:component-map="COMPONENT_MAP"
|
:component-map="COMPONENT_MAP"
|
||||||
:form="form"
|
:form="form"
|
||||||
|
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-for="slotName in delegatedSlots"
|
v-for="slotName in delegatedSlots"
|
||||||
|
@ -4,10 +4,13 @@ import type { ExtendedFormApi, VbenFormProps } from './types';
|
|||||||
import { useForwardPriorityValues } from '@vben-core/composables';
|
import { useForwardPriorityValues } from '@vben-core/composables';
|
||||||
|
|
||||||
import FormActions from './components/form-actions.vue';
|
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 { Form } from './form-render';
|
||||||
import { provideFormProps, useFormInitial } from './use-form-context';
|
import { provideFormProps, useFormInitial } from './use-form-context';
|
||||||
|
|
||||||
// 通过 extends 会导致热更新卡死,所以重复写了一遍
|
// 通过 extends 会导致热更新卡死,所以重复写了一遍
|
||||||
interface Props extends VbenFormProps {
|
interface Props extends VbenFormProps {
|
||||||
formApi: ExtendedFormApi;
|
formApi: ExtendedFormApi;
|
||||||
@ -36,6 +39,7 @@ const handleUpdateCollapsed = (value: boolean) => {
|
|||||||
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
|
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
|
||||||
:component-map="COMPONENT_MAP"
|
:component-map="COMPONENT_MAP"
|
||||||
:form="form"
|
:form="form"
|
||||||
|
:global-common-config="DEFAULT_FORM_COMMON_CONFIG"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-for="slotName in delegatedSlots"
|
v-for="slotName in delegatedSlots"
|
||||||
|
@ -64,6 +64,10 @@
|
|||||||
"required": "Please enter {0}",
|
"required": "Please enter {0}",
|
||||||
"selectRequired": "Please select {0}"
|
"selectRequired": "Please select {0}"
|
||||||
},
|
},
|
||||||
|
"placeholder": {
|
||||||
|
"input": "Please enter",
|
||||||
|
"select": "Please select"
|
||||||
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
"document": "Document",
|
"document": "Document",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
|
@ -64,6 +64,10 @@
|
|||||||
"required": "请输入{0}",
|
"required": "请输入{0}",
|
||||||
"selectRequired": "请选择{0}"
|
"selectRequired": "请选择{0}"
|
||||||
},
|
},
|
||||||
|
"placeholder": {
|
||||||
|
"input": "请输入",
|
||||||
|
"select": "请选择"
|
||||||
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
"document": "文档",
|
"document": "文档",
|
||||||
"profile": "个人中心",
|
"profile": "个人中心",
|
||||||
|
@ -4,6 +4,7 @@ import type {
|
|||||||
VbenFormProps,
|
VbenFormProps,
|
||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
|
|
||||||
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
@ -57,6 +58,16 @@ export type FormComponentType =
|
|||||||
| 'Upload'
|
| 'Upload'
|
||||||
| BaseFormComponentType;
|
| 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组件内部
|
// 初始化表单组件,并注册到form组件内部
|
||||||
setupVbenForm<FormComponentType>({
|
setupVbenForm<FormComponentType>({
|
||||||
components: {
|
components: {
|
||||||
@ -73,20 +84,20 @@ setupVbenForm<FormComponentType>({
|
|||||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||||
},
|
},
|
||||||
Divider,
|
Divider,
|
||||||
Input,
|
Input: withDefaultPlaceholder(Input, 'input'),
|
||||||
InputNumber,
|
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||||
InputPassword,
|
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||||
Mentions,
|
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
RangePicker,
|
RangePicker,
|
||||||
Rate,
|
Rate,
|
||||||
Select,
|
Select: withDefaultPlaceholder(Select, 'select'),
|
||||||
Space,
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Textarea,
|
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||||
TimePicker,
|
TimePicker,
|
||||||
TreeSelect,
|
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||||
Upload,
|
Upload,
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
@ -223,6 +223,75 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
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>) {
|
function onSubmit(values: Record<string, any>) {
|
||||||
message.success({
|
message.success({
|
||||||
content: `form values: ${JSON.stringify(values)}`,
|
content: `form values: ${JSON.stringify(values)}`,
|
||||||
@ -256,6 +325,7 @@ function handleSetFormValue() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page
|
<Page
|
||||||
|
content-class="flex flex-col gap-4"
|
||||||
description="表单组件基础示例,请注意,该页面用到的参数代码会添加一些简单注释,方便理解,请仔细查看。"
|
description="表单组件基础示例,请注意,该页面用到的参数代码会添加一些简单注释,方便理解,请仔细查看。"
|
||||||
title="表单组件"
|
title="表单组件"
|
||||||
>
|
>
|
||||||
@ -265,5 +335,8 @@ function handleSetFormValue() {
|
|||||||
</template>
|
</template>
|
||||||
<BaseForm />
|
<BaseForm />
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card title="使用tailwind自定义布局">
|
||||||
|
<CustomLayoutForm />
|
||||||
|
</Card>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
@ -175,6 +175,47 @@ const [Form, formApi] = useVbenForm({
|
|||||||
label: '密码',
|
label: '密码',
|
||||||
rules: 'required',
|
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: '用户名已存在',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
|
1924
pnpm-lock.yaml
1924
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@ catalog:
|
|||||||
'@commitlint/config-conventional': ^19.5.0
|
'@commitlint/config-conventional': ^19.5.0
|
||||||
'@ctrl/tinycolor': ^4.1.0
|
'@ctrl/tinycolor': ^4.1.0
|
||||||
'@eslint/js': ^9.11.1
|
'@eslint/js': ^9.11.1
|
||||||
'@iconify/json': ^2.2.254
|
'@iconify/json': ^2.2.255
|
||||||
'@iconify/tailwind': ^1.1.3
|
'@iconify/tailwind': ^1.1.3
|
||||||
'@iconify/vue': ^4.1.2
|
'@iconify/vue': ^4.1.2
|
||||||
'@intlify/core-base': ^10.0.3
|
'@intlify/core-base': ^10.0.3
|
||||||
@ -33,7 +33,7 @@ catalog:
|
|||||||
'@stylistic/stylelint-plugin': ^3.1.0
|
'@stylistic/stylelint-plugin': ^3.1.0
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.15
|
'@tailwindcss/typography': ^0.5.15
|
||||||
'@tanstack/vue-query': ^5.56.2
|
'@tanstack/vue-query': ^5.59.0
|
||||||
'@tanstack/vue-store': ^0.5.5
|
'@tanstack/vue-store': ^0.5.5
|
||||||
'@types/archiver': ^6.0.2
|
'@types/archiver': ^6.0.2
|
||||||
'@types/chalk': ^2.2.0
|
'@types/chalk': ^2.2.0
|
||||||
@ -46,8 +46,8 @@ catalog:
|
|||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@typescript-eslint/eslint-plugin': ^8.7.0
|
'@typescript-eslint/eslint-plugin': ^8.8.0
|
||||||
'@typescript-eslint/parser': ^8.7.0
|
'@typescript-eslint/parser': ^8.8.0
|
||||||
'@vee-validate/zod': ^4.13.2
|
'@vee-validate/zod': ^4.13.2
|
||||||
'@vite-pwa/vitepress': ^0.5.3
|
'@vite-pwa/vitepress': ^0.5.3
|
||||||
'@vitejs/plugin-vue': ^5.1.4
|
'@vitejs/plugin-vue': ^5.1.4
|
||||||
@ -82,15 +82,15 @@ catalog:
|
|||||||
echarts: ^5.5.1
|
echarts: ^5.5.1
|
||||||
element-plus: ^2.8.4
|
element-plus: ^2.8.4
|
||||||
eslint: ^9.11.1
|
eslint: ^9.11.1
|
||||||
eslint-config-turbo: ^2.1.2
|
eslint-config-turbo: ^2.1.3
|
||||||
eslint-plugin-command: ^0.2.6
|
eslint-plugin-command: ^0.2.6
|
||||||
eslint-plugin-eslint-comments: ^3.2.0
|
eslint-plugin-eslint-comments: ^3.2.0
|
||||||
eslint-plugin-import-x: ^4.3.0
|
eslint-plugin-import-x: ^4.3.1
|
||||||
eslint-plugin-jsdoc: ^50.3.0
|
eslint-plugin-jsdoc: ^50.3.1
|
||||||
eslint-plugin-jsonc: ^2.16.0
|
eslint-plugin-jsonc: ^2.16.0
|
||||||
eslint-plugin-n: ^17.10.3
|
eslint-plugin-n: ^17.10.3
|
||||||
eslint-plugin-no-only-tests: ^3.3.0
|
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-prettier: ^5.2.1
|
||||||
eslint-plugin-regexp: ^2.6.0
|
eslint-plugin-regexp: ^2.6.0
|
||||||
eslint-plugin-unicorn: ^55.0.0
|
eslint-plugin-unicorn: ^55.0.0
|
||||||
@ -100,7 +100,7 @@ catalog:
|
|||||||
execa: ^9.4.0
|
execa: ^9.4.0
|
||||||
find-up: ^7.0.0
|
find-up: ^7.0.0
|
||||||
get-port: ^7.1.0
|
get-port: ^7.1.0
|
||||||
globals: ^15.9.0
|
globals: ^15.10.0
|
||||||
h3: ^1.12.0
|
h3: ^1.12.0
|
||||||
happy-dom: ^15.7.4
|
happy-dom: ^15.7.4
|
||||||
html-minifier-terser: ^7.2.0
|
html-minifier-terser: ^7.2.0
|
||||||
@ -110,7 +110,7 @@ catalog:
|
|||||||
jsonwebtoken: ^9.0.2
|
jsonwebtoken: ^9.0.2
|
||||||
lint-staged: ^15.2.10
|
lint-staged: ^15.2.10
|
||||||
lodash.clonedeep: ^4.5.0
|
lodash.clonedeep: ^4.5.0
|
||||||
lucide-vue-next: ^0.446.0
|
lucide-vue-next: ^0.447.0
|
||||||
medium-zoom: ^1.1.0
|
medium-zoom: ^1.1.0
|
||||||
naive-ui: ^2.40.1
|
naive-ui: ^2.40.1
|
||||||
nanoid: ^5.0.7
|
nanoid: ^5.0.7
|
||||||
@ -134,7 +134,7 @@ catalog:
|
|||||||
radix-vue: ^1.9.6
|
radix-vue: ^1.9.6
|
||||||
resolve.exports: ^2.0.2
|
resolve.exports: ^2.0.2
|
||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
rollup: ^4.22.5
|
rollup: ^4.24.0
|
||||||
rollup-plugin-visualizer: ^5.12.0
|
rollup-plugin-visualizer: ^5.12.0
|
||||||
sass: ^1.79.4
|
sass: ^1.79.4
|
||||||
sortablejs: ^1.15.3
|
sortablejs: ^1.15.3
|
||||||
@ -151,7 +151,7 @@ catalog:
|
|||||||
tailwindcss: ^3.4.13
|
tailwindcss: ^3.4.13
|
||||||
tailwindcss-animate: ^1.0.7
|
tailwindcss-animate: ^1.0.7
|
||||||
theme-colors: ^0.1.0
|
theme-colors: ^0.1.0
|
||||||
turbo: ^2.1.2
|
turbo: ^2.1.3
|
||||||
typescript: ^5.6.2
|
typescript: ^5.6.2
|
||||||
unbuild: ^2.0.0
|
unbuild: ^2.0.0
|
||||||
unplugin-element-plus: ^0.8.0
|
unplugin-element-plus: ^0.8.0
|
||||||
@ -165,7 +165,7 @@ catalog:
|
|||||||
vite-plugin-vue-devtools: ^7.4.6
|
vite-plugin-vue-devtools: ^7.4.6
|
||||||
vitepress: ^1.3.4
|
vitepress: ^1.3.4
|
||||||
vitepress-plugin-group-icons: ^1.2.4
|
vitepress-plugin-group-icons: ^1.2.4
|
||||||
vitest: ^2.1.1
|
vitest: ^2.1.2
|
||||||
vue: ^3.5.10
|
vue: ^3.5.10
|
||||||
vue-eslint-parser: ^9.4.3
|
vue-eslint-parser: ^9.4.3
|
||||||
vue-i18n: ^10.0.3
|
vue-i18n: ^10.0.3
|
||||||
|
Loading…
Reference in New Issue
Block a user