1
0
Эх сурвалжийг харах

docs: update docs (#4466)

* chore: fixed known issues with form components

* docs: add vben form doc
Vben 7 сар өмнө
parent
commit
5ce3a18785
36 өөрчлөгдсөн 1938 нэмэгдсэн , 139 устгасан
  1. 10 3
      apps/web-antd/src/adapter/form.ts
  2. 1 1
      apps/web-ele/src/adapter/form.ts
  3. 1 1
      apps/web-naive/src/adapter/form.ts
  4. 10 8
      docs/.vitepress/components/demo-preview.vue
  5. 7 3
      docs/.vitepress/config/zh.mts
  6. 64 2
      docs/.vitepress/theme/components/site-layout.vue
  7. 4 0
      docs/.vitepress/theme/styles/base.css
  8. 7 0
      docs/package.json
  9. 127 0
      docs/src/_env/adapter/form.ts
  10. 1 0
      docs/src/_env/adapter/index.ts
  11. 52 0
      docs/src/components/common-ui/vben-count-to-animator.md
  12. 436 3
      docs/src/components/common-ui/vben-form.md
  13. 6 0
      docs/src/demos/vben-count-to-animator/basic/index.vue
  14. 12 0
      docs/src/demos/vben-count-to-animator/custom/index.vue
  15. 236 0
      docs/src/demos/vben-form/api/index.vue
  16. 231 0
      docs/src/demos/vben-form/basic/index.vue
  17. 68 0
      docs/src/demos/vben-form/custom/index.vue
  18. 168 0
      docs/src/demos/vben-form/dynamic/index.vue
  19. 94 0
      docs/src/demos/vben-form/query/index.vue
  20. 189 0
      docs/src/demos/vben-form/rules/index.vue
  21. 6 0
      docs/tsconfig.json
  22. 16 0
      packages/@core/base/shared/src/utils/__tests__/inference.test.ts
  23. 10 0
      packages/@core/base/shared/src/utils/inference.ts
  24. 7 1
      packages/@core/ui-kit/form-ui/src/form-api.ts
  25. 9 1
      packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts
  26. 12 0
      packages/@core/ui-kit/form-ui/src/form-render/form-field.vue
  27. 6 5
      packages/@core/ui-kit/form-ui/src/types.ts
  28. 2 2
      packages/effects/common-ui/src/components/captcha/point-selection-captcha/index.vue
  29. 8 1
      packages/effects/common-ui/src/components/index.ts
  30. 7 1
      packages/effects/layouts/src/widgets/notification/notification.vue
  31. 10 3
      playground/src/adapter/form.ts
  32. 1 0
      playground/src/views/examples/form/api.vue
  33. 3 3
      playground/src/views/examples/form/custom.vue
  34. 11 0
      playground/src/views/examples/form/dynamic.vue
  35. 99 94
      pnpm-lock.yaml
  36. 7 7
      pnpm-workspace.yaml

+ 10 - 3
apps/web-antd/src/adapter/form.ts

@@ -27,13 +27,13 @@ import {
   Select,
   Space,
   Switch,
+  Textarea,
   TimePicker,
   TreeSelect,
   Upload,
 } from 'ant-design-vue';
 
-// 业务表单组件适配
-
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
 export type FormComponentType =
   | 'AutoComplete'
   | 'Checkbox'
@@ -51,6 +51,7 @@ export type FormComponentType =
   | 'Select'
   | 'Space'
   | 'Switch'
+  | 'Textarea'
   | 'TimePicker'
   | 'TreeSelect'
   | 'Upload'
@@ -83,12 +84,16 @@ setupVbenForm<FormComponentType>({
     Select,
     Space,
     Switch,
+    Textarea,
     TimePicker,
     TreeSelect,
     Upload,
   },
   config: {
+    // ant design vue组件库默认都是 v-model:value
     baseModelPropName: 'value',
+
+    // 一些组件是 v-model:checked 或者 v-model:fileList
     modelPropNameMap: {
       Checkbox: 'checked',
       Radio: 'checked',
@@ -97,12 +102,14 @@ setupVbenForm<FormComponentType>({
     },
   },
   defineRules: {
+    // 输入项目必填国际化适配
     required: (value, _params, ctx) => {
-      if ((!value && value !== 0) || value.length === 0) {
+      if (value === undefined || value === null || value.length === 0) {
         return $t('formRules.required', [ctx.label]);
       }
       return true;
     },
+    // 选择项目必填国际化适配
     selectRequired: (value, _params, ctx) => {
       if (value === undefined || value === null) {
         return $t('formRules.selectRequired', [ctx.label]);

+ 1 - 1
apps/web-ele/src/adapter/form.ts

@@ -73,7 +73,7 @@ setupVbenForm<FormComponentType>({
   },
   defineRules: {
     required: (value, _params, ctx) => {
-      if ((!value && value !== 0) || value.length === 0) {
+      if (value === undefined || value === null || value.length === 0) {
         return $t('formRules.required', [ctx.label]);
       }
       return true;

+ 1 - 1
apps/web-naive/src/adapter/form.ts

@@ -82,7 +82,7 @@ setupVbenForm<FormComponentType>({
   },
   defineRules: {
     required: (value, _params, ctx) => {
-      if ((!value && value !== 0) || value.length === 0) {
+      if (value === undefined || value === null || value.length === 0) {
         return $t('formRules.required', [ctx.label]);
       }
       return true;

+ 10 - 8
docs/.vitepress/components/demo-preview.vue

@@ -24,14 +24,16 @@ const parsedFiles = computed(() => {
       class="not-prose relative w-full overflow-x-auto rounded-t-lg px-4 py-6"
     >
       <div class="flex w-full max-w-[700px] px-2">
-        <slot v-if="parsedFiles.length > 0"></slot>
-        <div v-else class="text-destructive text-sm">
-          <span class="bg-destructive text-foreground rounded-sm px-1 py-1">
-            ERROR:
-          </span>
-          The preview directory does not exist. Please check the 'dir'
-          parameter.
-        </div>
+        <ClientOnly>
+          <slot v-if="parsedFiles.length > 0"></slot>
+          <div v-else class="text-destructive text-sm">
+            <span class="bg-destructive text-foreground rounded-sm px-1 py-1">
+              ERROR:
+            </span>
+            The preview directory does not exist. Please check the 'dir'
+            parameter.
+          </div>
+        </ClientOnly>
       </div>
     </div>
     <PreviewGroup v-if="parsedFiles.length > 0" :files="parsedFiles">

+ 7 - 3
docs/.vitepress/config/zh.mts

@@ -154,15 +154,19 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
       items: [
         {
           link: 'common-ui/vben-modal',
-          text: 'Vben Modal 模态框',
+          text: 'Modal 模态框',
         },
         {
           link: 'common-ui/vben-drawer',
-          text: 'Vben Drawer 抽屉',
+          text: 'Drawer 抽屉',
         },
         {
           link: 'common-ui/vben-form',
-          text: 'Vben Form 表单',
+          text: 'Form 表单',
+        },
+        {
+          link: 'common-ui/vben-count-to-animator',
+          text: 'CountToAnimator 数字动画',
         },
       ],
     },

+ 64 - 2
docs/.vitepress/theme/components/site-layout.vue

@@ -1,30 +1,92 @@
 <script lang="ts" setup>
-import { nextTick, onMounted, watch } from 'vue';
+import {
+  computed,
+  nextTick,
+  onBeforeUnmount,
+  onMounted,
+  ref,
+  watch,
+} from 'vue';
 
+// import { useAntdDesignTokens } from '@vben/hooks';
+// import { initPreferences } from '@vben/preferences';
+
+import { ConfigProvider, theme } from 'ant-design-vue';
 import mediumZoom from 'medium-zoom';
 import { useRoute } from 'vitepress';
 import DefaultTheme from 'vitepress/theme';
 
 const { Layout } = DefaultTheme;
 const route = useRoute();
+// const { tokens } = useAntdDesignTokens();
 
 const initZoom = () => {
   // mediumZoom('[data-zoomable]', { background: 'var(--vp-c-bg)' });
   mediumZoom('.VPContent img', { background: 'var(--vp-c-bg)' });
 };
 
+const isDark = ref(true);
+
 watch(
   () => route.path,
   () => nextTick(() => initZoom()),
 );
 
+// initPreferences({
+//   namespace: 'docs',
+// });
+
 onMounted(() => {
   initZoom();
 });
+
+// 使用该函数
+const observer = watchDarkModeChange((dark) => {
+  isDark.value = dark;
+});
+
+onBeforeUnmount(() => {
+  observer?.disconnect();
+});
+
+function watchDarkModeChange(callback: (isDark: boolean) => void) {
+  if (typeof window === 'undefined') {
+    return;
+  }
+  const htmlElement = document.documentElement;
+
+  const observer = new MutationObserver(() => {
+    const isDark = htmlElement.classList.contains('dark');
+    callback(isDark);
+  });
+
+  observer.observe(htmlElement, {
+    attributeFilter: ['class'],
+    attributes: true,
+  });
+
+  const initialIsDark = htmlElement.classList.contains('dark');
+  callback(initialIsDark);
+
+  return observer;
+}
+
+const tokenTheme = computed(() => {
+  const algorithm = isDark.value
+    ? [theme.darkAlgorithm]
+    : [theme.defaultAlgorithm];
+
+  return {
+    algorithm,
+    // token: tokens,
+  };
+});
 </script>
 
 <template>
-  <Layout />
+  <ConfigProvider :theme="tokenTheme">
+    <Layout />
+  </ConfigProvider>
 </template>
 
 <style>

+ 4 - 0
docs/.vitepress/theme/styles/base.css

@@ -5,3 +5,7 @@ html.dark {
 .dark .VPContent {
   /* background-color: #14161a; */
 }
+
+.form-valid-error p {
+  margin: 0;
+}

+ 7 - 0
docs/package.json

@@ -7,10 +7,17 @@
     "dev": "vitepress dev",
     "docs:preview": "vitepress preview"
   },
+  "imports": {
+    "#/*": "./src/_env/*"
+  },
   "dependencies": {
     "@vben-core/shadcn-ui": "workspace:*",
     "@vben/common-ui": "workspace:*",
+    "@vben/hooks": "workspace:*",
+    "@vben/locales": "workspace:*",
+    "@vben/preferences": "workspace:*",
     "@vben/styles": "workspace:*",
+    "ant-design-vue": "catalog:",
     "lucide-vue-next": "catalog:",
     "medium-zoom": "catalog:",
     "radix-vue": "catalog:",

+ 127 - 0
docs/src/_env/adapter/form.ts

@@ -0,0 +1,127 @@
+import type {
+  BaseFormComponentType,
+  VbenFormSchema as FormSchema,
+  VbenFormProps,
+} from '@vben/common-ui';
+
+import { h } from 'vue';
+
+import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import {
+  AutoComplete,
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  DatePicker,
+  Divider,
+  Input,
+  InputNumber,
+  InputPassword,
+  Mentions,
+  Radio,
+  RadioGroup,
+  RangePicker,
+  Rate,
+  Select,
+  Space,
+  Switch,
+  Textarea,
+  TimePicker,
+  TreeSelect,
+  Upload,
+} from 'ant-design-vue';
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type FormComponentType =
+  | 'AutoComplete'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'Divider'
+  | 'Input'
+  | 'InputNumber'
+  | 'InputPassword'
+  | 'Mentions'
+  | 'Radio'
+  | 'RadioGroup'
+  | 'RangePicker'
+  | 'Rate'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'Textarea'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+// 初始化表单组件,并注册到form组件内部
+setupVbenForm<FormComponentType>({
+  components: {
+    AutoComplete,
+    Checkbox,
+    CheckboxGroup,
+    DatePicker,
+    // 自定义默认的重置按钮
+    DefaultResetActionButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'default' }, slots);
+    },
+    // 自定义默认的提交按钮
+    DefaultSubmitActionButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Divider,
+    Input,
+    InputNumber,
+    InputPassword,
+    Mentions,
+    Radio,
+    RadioGroup,
+    RangePicker,
+    Rate,
+    Select,
+    Space,
+    Switch,
+    Textarea,
+    TimePicker,
+    TreeSelect,
+    Upload,
+  },
+  config: {
+    // ant design vue组件库默认都是 v-model:value
+    baseModelPropName: 'value',
+
+    // 一些组件是 v-model:checked 或者 v-model:fileList
+    modelPropNameMap: {
+      Checkbox: 'checked',
+      Radio: 'checked',
+      Switch: 'checked',
+      Upload: 'fileList',
+    },
+  },
+  defineRules: {
+    // 输入项目必填国际化适配
+    required: (value, _params, ctx) => {
+      if (value === undefined || value === null || value.length === 0) {
+        return $t('formRules.required', [ctx.label]);
+      }
+      return true;
+    },
+    // 选择项目必填国际化适配
+    selectRequired: (value, _params, ctx) => {
+      if (value === undefined || value === null) {
+        return $t('formRules.selectRequired', [ctx.label]);
+      }
+      return true;
+    },
+  },
+});
+
+const useVbenForm = useForm<FormComponentType>;
+
+export { useVbenForm, z };
+
+export type VbenFormSchema = FormSchema<FormComponentType>;
+export type { VbenFormProps };

+ 1 - 0
docs/src/_env/adapter/index.ts

@@ -0,0 +1 @@
+export * from './form';

+ 52 - 0
docs/src/components/common-ui/vben-count-to-animator.md

@@ -0,0 +1,52 @@
+---
+outline: deep
+---
+
+# Vben CountToAnimator 数字动画
+
+框架提供的数字动画组件,支持数字动画效果。
+
+> 如果文档内没有参数说明,可以尝试在在线示例内寻找
+
+::: info 写在前面
+
+如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
+
+:::
+
+## 基础用法
+
+通过 `start-val` 和 `end-val`设置数字动画的开始值和结束值, 持续时间`3000`ms。
+
+<DemoPreview dir="demos/vben-count-to-animator/basic" />
+
+## 自定义前缀及分隔符
+
+通过 `prefix` 和 `separator` 设置数字动画的前缀和分隔符。
+
+<DemoPreview dir="demos/vben-count-to-animator/custom" />
+
+### Props
+
+| 属性名     | 描述           | 类型      | 默认值   |
+| ---------- | -------------- | --------- | -------- |
+| startVal   | 起始值         | `number`  | `0`      |
+| endVal     | 结束值         | `number`  | `2021`   |
+| duration   | 动画持续时间   | `number`  | `1500`   |
+| autoplay   | 自动执行       | `boolean` | `true`   |
+| prefix     | 前缀           | `string`  | -        |
+| suffix     | 后缀           | `string`  | -        |
+| separator  | 分隔符         | `string`  | `,`      |
+| color      | 字体颜色       | `string`  | -        |
+| useEasing  | 是否开启动画   | `boolean` | `true`   |
+| transition | 动画效果       | `string`  | `linear` |
+| decimals   | 保留小数点位数 | `number`  | `0`      |
+
+### Methods
+
+以下事件,只有在 `useVbenModal({onCancel:()=>{}})` 中传入才会生效。
+
+| 事件名 | 描述         | 类型       |
+| ------ | ------------ | ---------- |
+| start  | 开始执行动画 | `()=>void` |
+| reset  | 重置         | `()=>void` |

+ 436 - 3
docs/src/components/common-ui/vben-form.md

@@ -4,8 +4,441 @@ outline: deep
 
 # Vben Form 表单
 
-框架提供的表单组件,可适配 `Element Plus`、`Ant Design Vue`、`Naive`UI 框架。
+框架提供的表单组件,可适配 `Element Plus`、`Ant Design Vue`、`Naive UI` 等框架。
 
-# 使用
+> 如果文档内没有参数说明,可以尝试在在线示例内寻找
 
-TODO
+::: info 写在前面
+
+如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
+
+:::
+
+## 适配器
+
+表单底层使用 [vee-validate](https://vee-validate.logaretm.com/v4/) 进行表单验证,所以你可以使用 `vee-validate` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架。
+
+### 适配器说明
+
+每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明:
+
+::: details ant design 适配器
+
+```ts
+import type {
+  BaseFormComponentType,
+  VbenFormSchema as FormSchema,
+  VbenFormProps,
+} from '@vben/common-ui';
+
+import { h } from 'vue';
+
+import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import {
+  AutoComplete,
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  DatePicker,
+  Divider,
+  Input,
+  InputNumber,
+  InputPassword,
+  Mentions,
+  Radio,
+  RadioGroup,
+  RangePicker,
+  Rate,
+  Select,
+  Space,
+  Switch,
+  Textarea,
+  TimePicker,
+  TreeSelect,
+  Upload,
+} from 'ant-design-vue';
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type FormComponentType =
+  | 'AutoComplete'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'Divider'
+  | 'Input'
+  | 'InputNumber'
+  | 'InputPassword'
+  | 'Mentions'
+  | 'Radio'
+  | 'RadioGroup'
+  | 'RangePicker'
+  | 'Rate'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'Textarea'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+// 初始化表单组件,并注册到form组件内部
+setupVbenForm<FormComponentType>({
+  components: {
+    AutoComplete,
+    Checkbox,
+    CheckboxGroup,
+    DatePicker,
+    // 自定义默认的重置按钮
+    DefaultResetActionButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'default' }, slots);
+    },
+    // 自定义默认的提交按钮
+    DefaultSubmitActionButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Divider,
+    Input,
+    InputNumber,
+    InputPassword,
+    Mentions,
+    Radio,
+    RadioGroup,
+    RangePicker,
+    Rate,
+    Select,
+    Space,
+    Switch,
+    Textarea,
+    TimePicker,
+    TreeSelect,
+    Upload,
+  },
+  config: {
+    // ant design vue组件库默认都是 v-model:value
+    baseModelPropName: 'value',
+
+    // 一些组件是 v-model:checked 或者 v-model:fileList
+    modelPropNameMap: {
+      Checkbox: 'checked',
+      Radio: 'checked',
+      Switch: 'checked',
+      Upload: 'fileList',
+    },
+  },
+  defineRules: {
+    // 输入项目必填国际化适配
+    required: (value, _params, ctx) => {
+      if (value === undefined || value === null || value.length === 0) {
+        return $t('formRules.required', [ctx.label]);
+      }
+      return true;
+    },
+    // 选择项目必填国际化适配
+    selectRequired: (value, _params, ctx) => {
+      if (value === undefined || value === null) {
+        return $t('formRules.selectRequired', [ctx.label]);
+      }
+      return true;
+    },
+  },
+});
+
+const useVbenForm = useForm<FormComponentType>;
+
+export { useVbenForm, z };
+
+export type VbenFormSchema = FormSchema<FormComponentType>;
+export type { VbenFormProps };
+```
+
+:::
+
+## 基础用法
+
+::: tip README
+
+下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
+
+:::
+
+使用 `useVbenForm` 创建最基础的表单。
+
+<DemoPreview dir="demos/vben-form/basic" />
+
+## 查询表单
+
+查询表单是一种特殊的表单,用于查询数据。查询表单不会触发表单验证,只会触发查询事件。
+
+<DemoPreview dir="demos/vben-form/query" />
+
+## 表单校验
+
+表单校验是一个非常重要的功能,可以通过 `rules` 属性进行校验。
+
+<DemoPreview dir="demos/vben-form/rules" />
+
+## 表单联动
+
+表单联动是一个非常常见的功能,可以通过 `dependencies` 属性进行联动。
+
+_注意_ 需要指定 `dependencies` 的 `triggerFields` 属性,设置由谁的改动来触发,以便表单组件能够正确的联动。
+
+<DemoPreview dir="demos/vben-form/dynamic" />
+
+## 自定义组件
+
+如果你的业务组件库没有提供某个组件,你可以自行封装一个组件,然后加到表单内部。
+
+<DemoPreview dir="demos/vben-form/custom" />
+
+## 操作
+
+一些常见的表单操作。
+
+<DemoPreview dir="demos/vben-form/api" />
+
+## API
+
+`useVbenForm` 返回一个数组,第一个元素是表单组件,第二个元素是表单的方法。
+
+```vue
+<script setup lang="ts">
+import { useVbenForm } from '#/adapter';
+
+// Form 为弹窗组件
+// formApi 为弹窗的方法
+const [Form, formApi] = useVbenForm({
+  // 属性
+  // 事件
+});
+</script>
+
+<template>
+  <Form />
+</template>
+```
+
+### FormApi
+
+useVbenForm 返回的第二个参数,是一个对象,包含了一些表单的方法。
+
+| 方法名 | 描述 | 类型 |
+| --- | --- | --- |
+| submitForm | 提交表单 | `(e:Event)=>Promise<Record<string,any>>` |
+| resetForm | 重置表单 | `()=>Promise<void>` |
+| setValues | 设置表单值 | `()=>Promise<Record<string,any>>` |
+| getValues | 获取表单值 | `(fields:Record<string, any>,shouldValidate: boolean = false)=>Promise<void>` |
+| validate | 表单校验 | `()=>Promise<void>` |
+| resetValidate | 重置表单校验 | `()=>Promise<void>` |
+| updateSchema | 更新formSchema | `(schema:FormSchema[])=>void` |
+| setFieldValue | 设置字段值 | `(field: string, value: any, shouldValidate?: boolean)=>Promise<void>` |
+| setState | 设置组件状态(props) | `(stateOrFn:\| ((prev: VbenFormProps) => Partial<VbenFormProps>)\| Partial<VbenFormProps>)=>Promise<void>` |
+| getState | 获取组件状态(props) | `()=>Promise<VbenFormProps>` |
+| form | 表单对象实例,可以操作表单,见 [useForm](https://vee-validate.logaretm.com/v4/api/use-form/) | - |
+
+## Props
+
+所有属性都可以传入 `useVbenForm` 的第一个参数中。
+
+| 属性名 | 描述 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| layout | 表单项布局 | `'horizontal' \| 'vertical'` | `horizontal` |
+| showCollapseButton | 是否显示折叠按钮 | `boolean` | `false` |
+| wrapperClass | 表单的布局,基于tailwindcss | `any` | - |
+| actionWrapperClass | 表单操作区域class | `any` | - |
+| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
+| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
+| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
+| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
+| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
+| collapsed | 是否折叠,在`是否展开,在showCollapseButton=true`时生效 | `boolean` | `false` |
+| collapsedRows | 折叠时保持的行数 | `number` | `1` |
+| commonConfig | 表单项的通用配置,每个配置都会传递到每个表单项,表单项可覆盖 | `FormCommonConfig` | - |
+| schema | 表单项的每一项配置 | `FormSchema` | - |
+
+### TS 类型说明
+
+::: details ActionButtonOptions
+
+```ts
+export interface ActionButtonOptions {
+  /** 样式 */
+  class?: any;
+  /** 是否禁用 */
+  disabled?: boolean;
+  /** 是否加载中 */
+  loading?: boolean;
+  /** 按钮大小 */
+  size?: ButtonVariantSize;
+  /** 按钮类型 */
+  variant?: ButtonVariants;
+  /** 是否显示 */
+  show?: boolean;
+  /** 按钮文本 */
+  text?: string;
+}
+```
+
+:::
+
+::: details FormCommonConfig
+
+```ts
+export interface FormCommonConfig {
+  /**
+   * 所有表单项的props
+   */
+  componentProps?: ComponentProps;
+  /**
+   * 所有表单项的控件样式
+   */
+  controlClass?: string;
+  /**
+   * 所有表单项的禁用状态
+   * @default false
+   */
+  disabled?: boolean;
+  /**
+   * 所有表单项的控件样式
+   * @default {}
+   */
+  formFieldProps?: Partial<typeof Field>;
+  /**
+   * 所有表单项的栅格布局
+   * @default ""
+   */
+  formItemClass?: string;
+  /**
+   * 隐藏所有表单项label
+   * @default false
+   */
+  hideLabel?: boolean;
+  /**
+   * 是否隐藏必填标记
+   * @default false
+   */
+  hideRequiredMark?: boolean;
+  /**
+   * 所有表单项的label样式
+   * @default ""
+   */
+  labelClass?: string;
+  /**
+   * 所有表单项的label宽度
+   */
+  labelWidth?: number;
+  /**
+   * 所有表单项的wrapper样式
+   */
+  wrapperClass?: string;
+}
+```
+
+:::
+
+::: details FormSchema
+
+```ts
+export interface FormSchema<
+  T extends BaseFormComponentType = BaseFormComponentType,
+> extends FormCommonConfig {
+  /** 组件 */
+  component: Component | T;
+  /** 组件参数 */
+  componentProps?: ComponentProps;
+  /** 默认值 */
+  defaultValue?: any;
+  /** 依赖 */
+  dependencies?: FormItemDependencies;
+  /** 描述 */
+  description?: string;
+  /** 字段名 */
+  fieldName: string;
+  /** 帮助信息 */
+  help?: string;
+  /** 表单项 */
+  label?: string;
+  // 自定义组件内部渲染
+  renderComponentContent?: RenderComponentContentType;
+  /** 字段规则 */
+  rules?: FormSchemaRuleType;
+  /** 后缀 */
+  suffix?: CustomRenderType;
+}
+```
+
+:::
+
+### 表单联动
+
+表单联动需要通过 schema 内的 `dependencies` 属性进行联动,允许您添加字段之间的依赖项,以根据其他字段的值控制字段。
+
+```ts
+dependencies: {
+  // 只有当 name 字段的值变化时,才会触发联动
+  triggerFields: ['name'],
+  // 动态判断当前字段是否需要显示,不显示则直接销毁
+  if(values,formApi){},
+  // 动态判断当前字段是否需要显示,不显示用css隐藏
+  show(values,formApi){},
+  // 动态判断当前字段是否需要禁用
+  disabled(values,formApi){},
+  // 字段变更时,都会触发该函数
+  trigger(values,formApi){},
+  // 动态rules
+  rules(values,formApi){},
+  // 动态必填
+  required(values,formApi){},
+  // 动态组件参数
+  componentProps(values,formApi){},
+}
+```
+
+### 表单校验
+
+表单联动需要通过 schema 内的 `rules` 属性进行配置。
+
+rules的值可以是一个字符串,也可以是一个zod的schema。
+
+#### 字符串
+
+```ts
+// 表示字段必填,默认会根据适配器的required进行国际化
+{
+  rules: 'required';
+}
+
+// 表示字段必填,默认会根据适配器的required进行国际化,用于下拉选择之类
+{
+  rules: 'selectRequired';
+}
+```
+
+#### zod
+
+rules也支持 zod 的 schema,可以进行更复杂的校验,zod 的使用请查看 [zod文档](https://zod.dev/)。
+
+```ts
+import { z } from '#/adapter';
+
+// 基础类型
+{
+  rules: z.string().min(1, { message: '请输入字符串' });
+}
+
+// 可选,并且携带默认值
+{
+   rules: z.string().default('默认值').optional(),
+}
+
+// 复杂校验
+{
+   z.string().min(1, { message: "请输入" })
+            .refine((value) => value === "123", {
+              message: "值必须为123",
+            });
+}
+```

+ 6 - 0
docs/src/demos/vben-count-to-animator/basic/index.vue

@@ -0,0 +1,6 @@
+<script lang="ts" setup>
+import { VbenCountToAnimator } from '@vben/common-ui';
+</script>
+<template>
+  <VbenCountToAnimator :duration="3000" :end-val="30000" :start-val="1" />
+</template>

+ 12 - 0
docs/src/demos/vben-count-to-animator/custom/index.vue

@@ -0,0 +1,12 @@
+<script lang="ts" setup>
+import { VbenCountToAnimator } from '@vben/common-ui';
+</script>
+<template>
+  <VbenCountToAnimator
+    :duration="3000"
+    :end-val="2000000"
+    :start-val="1"
+    prefix="$"
+    separator="/"
+  />
+</template>

+ 236 - 0
docs/src/demos/vben-form/api/index.vue

@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import { Button, message, Space } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+
+const [BaseForm, formApi] = useVbenForm({
+  // 所有表单项共用,可单独在表单内覆盖
+  commonConfig: {
+    // 所有表单项
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  // 使用 tailwindcss grid布局
+  // 提交函数
+  handleSubmit: onSubmit,
+  // 垂直布局,label和input在不同行,值为vertical
+  layout: 'horizontal',
+  // 水平布局,label和input在同一行
+  schema: [
+    {
+      // 组件需要在 #/adapter.ts内注册,并加上类型
+      component: 'Input',
+      // 对应组件的参数
+      componentProps: {
+        placeholder: '请输入用户名',
+      },
+      // 字段名
+      fieldName: 'field1',
+      // 界面显示的label
+      label: 'field1',
+    },
+    {
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        filterOption: true,
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+        placeholder: '请选择',
+        showSearch: true,
+      },
+      fieldName: 'fieldOptions',
+      label: '下拉选',
+    },
+  ],
+  wrapperClass: 'grid-cols-1 md:grid-cols-2',
+});
+
+function onSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form values: ${JSON.stringify(values)}`,
+  });
+}
+
+function handleClick(
+  action:
+    | 'batchAddSchema'
+    | 'batchDeleteSchema'
+    | 'disabled'
+    | 'hiddenAction'
+    | 'hiddenResetButton'
+    | 'hiddenSubmitButton'
+    | 'labelWidth'
+    | 'resetDisabled'
+    | 'resetLabelWidth'
+    | 'showAction'
+    | 'showResetButton'
+    | 'showSubmitButton'
+    | 'updateActionAlign'
+    | 'updateResetButton'
+    | 'updateSchema'
+    | 'updateSubmitButton',
+) {
+  switch (action) {
+    case 'updateSchema': {
+      formApi.updateSchema([
+        {
+          componentProps: {
+            options: [
+              {
+                label: '选项1',
+                value: '1',
+              },
+              {
+                label: '选项2',
+                value: '2',
+              },
+              {
+                label: '选项3',
+                value: '3',
+              },
+            ],
+          },
+          fieldName: 'fieldOptions',
+        },
+      ]);
+      message.success('字段 `fieldOptions` 下拉选项更新成功。');
+      break;
+    }
+
+    case 'labelWidth': {
+      formApi.setState({
+        commonConfig: {
+          labelWidth: 150,
+        },
+      });
+      break;
+    }
+    case 'resetLabelWidth': {
+      formApi.setState({
+        commonConfig: {
+          labelWidth: 100,
+        },
+      });
+      break;
+    }
+    case 'disabled': {
+      formApi.setState({ commonConfig: { disabled: true } });
+      break;
+    }
+    case 'resetDisabled': {
+      formApi.setState({ commonConfig: { disabled: false } });
+      break;
+    }
+    case 'hiddenAction': {
+      formApi.setState({ showDefaultActions: false });
+      break;
+    }
+    case 'showAction': {
+      formApi.setState({ showDefaultActions: true });
+      break;
+    }
+    case 'hiddenResetButton': {
+      formApi.setState({ resetButtonOptions: { show: false } });
+      break;
+    }
+    case 'showResetButton': {
+      formApi.setState({ resetButtonOptions: { show: true } });
+      break;
+    }
+    case 'hiddenSubmitButton': {
+      formApi.setState({ submitButtonOptions: { show: false } });
+      break;
+    }
+    case 'showSubmitButton': {
+      formApi.setState({ submitButtonOptions: { show: true } });
+      break;
+    }
+    case 'updateResetButton': {
+      formApi.setState({
+        resetButtonOptions: { disabled: true },
+      });
+      break;
+    }
+    case 'updateSubmitButton': {
+      formApi.setState({
+        submitButtonOptions: { loading: true },
+      });
+      break;
+    }
+    case 'updateActionAlign': {
+      formApi.setState({
+        // 可以自行调整class
+        actionWrapperClass: 'text-center',
+      });
+      break;
+    }
+    case 'batchAddSchema': {
+      formApi.setState((prev) => {
+        const currentSchema = prev?.schema ?? [];
+        const newSchema = [];
+        for (let i = 0; i < 2; i++) {
+          newSchema.push({
+            component: 'Input',
+            componentProps: {
+              placeholder: '请输入',
+            },
+            fieldName: `field${i}${Date.now()}`,
+            label: `field+`,
+          });
+        }
+        return {
+          schema: [...currentSchema, ...newSchema],
+        };
+      });
+      break;
+    }
+    case 'batchDeleteSchema': {
+      formApi.setState((prev) => {
+        const currentSchema = prev?.schema ?? [];
+        return {
+          schema: currentSchema.slice(0, -2),
+        };
+      });
+      break;
+    }
+  }
+}
+</script>
+
+<template>
+  <div>
+    <Space class="mb-5 flex-wrap">
+      <Button @click="handleClick('updateSchema')">updateSchema</Button>
+      <Button @click="handleClick('labelWidth')">更改labelWidth</Button>
+      <Button @click="handleClick('resetLabelWidth')">还原labelWidth</Button>
+      <Button @click="handleClick('disabled')">禁用表单</Button>
+      <Button @click="handleClick('resetDisabled')">解除禁用</Button>
+      <Button @click="handleClick('hiddenAction')">隐藏操作按钮</Button>
+      <Button @click="handleClick('showAction')">显示操作按钮</Button>
+      <Button @click="handleClick('hiddenResetButton')">隐藏重置按钮</Button>
+      <Button @click="handleClick('showResetButton')">显示重置按钮</Button>
+      <Button @click="handleClick('hiddenSubmitButton')">隐藏提交按钮</Button>
+      <Button @click="handleClick('showSubmitButton')">显示提交按钮</Button>
+      <Button @click="handleClick('updateResetButton')">修改重置按钮</Button>
+      <Button @click="handleClick('updateSubmitButton')">修改提交按钮</Button>
+      <Button @click="handleClick('updateActionAlign')">
+        调整操作按钮位置
+      </Button>
+      <Button @click="handleClick('batchAddSchema')"> 批量添加表单项 </Button>
+      <Button @click="handleClick('batchDeleteSchema')">
+        批量删除表单项
+      </Button>
+    </Space>
+    <BaseForm />
+  </div>
+</template>

+ 231 - 0
docs/src/demos/vben-form/basic/index.vue

@@ -0,0 +1,231 @@
+<script lang="ts" setup>
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+
+const [BaseForm] = useVbenForm({
+  // 所有表单项共用,可单独在表单内覆盖
+  commonConfig: {
+    // 所有表单项
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  // 提交函数
+  handleSubmit: onSubmit,
+  // 垂直布局,label和input在不同行,值为vertical
+  // 水平布局,label和input在同一行
+  layout: 'horizontal',
+  schema: [
+    {
+      // 组件需要在 #/adapter.ts内注册,并加上类型
+      component: 'Input',
+      // 对应组件的参数
+      componentProps: {
+        placeholder: '请输入用户名',
+      },
+      // 字段名
+      fieldName: 'username',
+      // 界面显示的label
+      label: '字符串',
+    },
+    {
+      component: 'InputPassword',
+      componentProps: {
+        placeholder: '请输入密码',
+      },
+      fieldName: 'password',
+      label: '密码',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'number',
+      label: '数字(带后缀)',
+      suffix: () => '¥',
+    },
+    {
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        filterOption: true,
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+        placeholder: '请选择',
+        showSearch: true,
+      },
+      fieldName: 'options',
+      label: '下拉选',
+    },
+    {
+      component: 'RadioGroup',
+      componentProps: {
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+      },
+      fieldName: 'radioGroup',
+      label: '单选组',
+    },
+    {
+      component: 'Radio',
+      fieldName: 'radio',
+      label: '',
+      renderComponentContent: () => {
+        return {
+          default: () => ['Radio'],
+        };
+      },
+    },
+    {
+      component: 'CheckboxGroup',
+      componentProps: {
+        name: 'cname',
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+      },
+      fieldName: 'checkboxGroup',
+      label: '多选组',
+    },
+    {
+      component: 'Checkbox',
+      fieldName: 'checkbox',
+      label: '',
+      renderComponentContent: () => {
+        return {
+          default: () => ['我已阅读并同意'],
+        };
+      },
+    },
+    {
+      component: 'Mentions',
+      componentProps: {
+        options: [
+          {
+            label: 'afc163',
+            value: 'afc163',
+          },
+          {
+            label: 'zombieJ',
+            value: 'zombieJ',
+          },
+        ],
+        placeholder: '请输入',
+      },
+      fieldName: 'mentions',
+      label: '提及',
+    },
+    {
+      component: 'Rate',
+      fieldName: 'rate',
+      label: '评分',
+    },
+    {
+      component: 'Switch',
+      componentProps: {
+        class: 'w-auto',
+      },
+      fieldName: 'switch',
+      label: '开关',
+    },
+    {
+      component: 'DatePicker',
+      fieldName: 'datePicker',
+      label: '日期选择框',
+    },
+    {
+      component: 'RangePicker',
+      fieldName: 'rangePicker',
+      label: '范围选择器',
+    },
+    {
+      component: 'TimePicker',
+      fieldName: 'timePicker',
+      label: '时间选择框',
+    },
+    {
+      component: 'TreeSelect',
+      componentProps: {
+        allowClear: true,
+        placeholder: '请选择',
+        showSearch: true,
+        treeData: [
+          {
+            label: 'root 1',
+            value: 'root 1',
+            children: [
+              {
+                label: 'parent 1',
+                value: 'parent 1',
+                children: [
+                  {
+                    label: 'parent 1-0',
+                    value: 'parent 1-0',
+                    children: [
+                      {
+                        label: 'my leaf',
+                        value: 'leaf1',
+                      },
+                      {
+                        label: 'your leaf',
+                        value: 'leaf2',
+                      },
+                    ],
+                  },
+                  {
+                    label: 'parent 1-1',
+                    value: 'parent 1-1',
+                  },
+                ],
+              },
+              {
+                label: 'parent 2',
+                value: 'parent 2',
+              },
+            ],
+          },
+        ],
+        treeNodeFilterProp: 'label',
+      },
+      fieldName: 'treeSelect',
+      label: '树选择',
+    },
+  ],
+  wrapperClass: 'grid-cols-1',
+});
+
+function onSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form values: ${JSON.stringify(values)}`,
+  });
+}
+</script>
+
+<template>
+  <BaseForm />
+</template>

+ 68 - 0
docs/src/demos/vben-form/custom/index.vue

@@ -0,0 +1,68 @@
+<script lang="ts" setup>
+import { h } from 'vue';
+
+import { Input, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+
+const [Form] = useVbenForm({
+  // 所有表单项共用,可单独在表单内覆盖
+  commonConfig: {
+    // 所有表单项
+    componentProps: {
+      class: 'w-full',
+    },
+    labelClass: 'w-2/6',
+  },
+  // 提交函数
+  handleSubmit: onSubmit,
+  // 垂直布局,label和input在不同行,值为vertical
+  // 水平布局,label和input在同一行
+  layout: 'horizontal',
+  schema: [
+    {
+      // 组件需要在 #/adapter.ts内注册,并加上类型
+      component: 'Input',
+      fieldName: 'field',
+      label: '自定义后缀',
+      suffix: () => h('span', { class: 'text-red-600' }, '元'),
+    },
+    {
+      component: 'Input',
+      fieldName: 'field1',
+      label: '自定义组件slot',
+      renderComponentContent: () => ({
+        prefix: () => 'prefix',
+        suffix: () => 'suffix',
+      }),
+    },
+    {
+      component: h(Input, { placeholder: '请输入' }),
+      fieldName: 'field2',
+      label: '自定义组件',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      fieldName: 'field3',
+      label: '自定义组件(slot)',
+      rules: 'required',
+    },
+  ],
+  wrapperClass: 'grid-cols-1',
+});
+
+function onSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form values: ${JSON.stringify(values)}`,
+  });
+}
+</script>
+
+<template>
+  <Form>
+    <template #field3="slotProps">
+      <Input placeholder="请输入" v-bind="slotProps" />
+    </template>
+  </Form>
+</template>

+ 168 - 0
docs/src/demos/vben-form/dynamic/index.vue

@@ -0,0 +1,168 @@
+<script lang="ts" setup>
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+
+const [Form] = useVbenForm({
+  // 提交函数
+  handleSubmit: onSubmit,
+  schema: [
+    {
+      component: 'Input',
+      defaultValue: 'hidden value',
+      dependencies: {
+        show: false,
+        // 随意一个字段改变时,都会触发
+        triggerFields: ['field1Switch'],
+      },
+      fieldName: 'hiddenField',
+      label: '隐藏字段',
+    },
+    {
+      component: 'Switch',
+      defaultValue: true,
+      fieldName: 'field1Switch',
+      help: '通过Dom控制销毁',
+      label: '显示字段1',
+    },
+    {
+      component: 'Switch',
+      defaultValue: true,
+      fieldName: 'field2Switch',
+      help: '通过css控制隐藏',
+      label: '显示字段2',
+    },
+    {
+      component: 'Switch',
+      fieldName: 'field3Switch',
+      label: '禁用字段3',
+    },
+    {
+      component: 'Switch',
+      fieldName: 'field4Switch',
+      label: '字段4必填',
+    },
+    {
+      component: 'Input',
+      dependencies: {
+        if(values) {
+          return !!values.field1Switch;
+        },
+        // 只有指定的字段改变时,才会触发
+        triggerFields: ['field1Switch'],
+      },
+      // 字段名
+      fieldName: 'field1',
+      // 界面显示的label
+      label: '字段1',
+    },
+    {
+      component: 'Input',
+      dependencies: {
+        show(values) {
+          return !!values.field2Switch;
+        },
+        triggerFields: ['field2Switch'],
+      },
+      fieldName: 'field2',
+      label: '字段2',
+    },
+    {
+      component: 'Input',
+      dependencies: {
+        disabled(values) {
+          return !!values.field3Switch;
+        },
+        triggerFields: ['field3Switch'],
+      },
+      fieldName: 'field3',
+      label: '字段3',
+    },
+    {
+      component: 'Input',
+      dependencies: {
+        required(values) {
+          return !!values.field4Switch;
+        },
+        triggerFields: ['field4Switch'],
+      },
+      fieldName: 'field4',
+      label: '字段4',
+    },
+    {
+      component: 'Input',
+      dependencies: {
+        rules(values) {
+          if (values.field1 === '123') {
+            return 'required';
+          }
+          return null;
+        },
+        triggerFields: ['field1'],
+      },
+      fieldName: 'field5',
+      help: '当字段1的值为`123`时,必填',
+      label: '动态rules',
+    },
+    {
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        class: 'w-full',
+        filterOption: true,
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+        placeholder: '请选择',
+        showSearch: true,
+      },
+      dependencies: {
+        componentProps(values) {
+          if (values.field2 === '123') {
+            return {
+              options: [
+                {
+                  label: '选项1',
+                  value: '1',
+                },
+                {
+                  label: '选项2',
+                  value: '2',
+                },
+                {
+                  label: '选项3',
+                  value: '3',
+                },
+              ],
+            };
+          }
+          return {};
+        },
+        triggerFields: ['field2'],
+      },
+      fieldName: 'field6',
+      help: '当字段2的值为`123`时,更改下拉选项',
+      label: '动态配置',
+    },
+  ],
+  // 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
+  wrapperClass: 'grid-cols-1 md:grid-cols-2',
+});
+
+function onSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form values: ${JSON.stringify(values)}`,
+  });
+}
+</script>
+
+<template>
+  <Form />
+</template>

+ 94 - 0
docs/src/demos/vben-form/query/index.vue

@@ -0,0 +1,94 @@
+<script lang="ts" setup>
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+
+const [QueryForm] = useVbenForm({
+  // 默认展开
+  collapsed: false,
+  // 所有表单项共用,可单独在表单内覆盖
+  commonConfig: {
+    // 所有表单项
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  // 提交函数
+  handleSubmit: onSubmit,
+  // 垂直布局,label和input在不同行,值为vertical
+  // 水平布局,label和input在同一行
+  layout: 'horizontal',
+  schema: [
+    {
+      // 组件需要在 #/adapter.ts内注册,并加上类型
+      component: 'Input',
+      // 对应组件的参数
+      componentProps: {
+        placeholder: '请输入用户名',
+      },
+      // 字段名
+      fieldName: 'username',
+      // 界面显示的label
+      label: '字符串',
+    },
+    {
+      component: 'InputPassword',
+      componentProps: {
+        placeholder: '请输入密码',
+      },
+      fieldName: 'password',
+      label: '密码',
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'number',
+      label: '数字(带后缀)',
+      suffix: () => '¥',
+    },
+    {
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        filterOption: true,
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+        placeholder: '请选择',
+        showSearch: true,
+      },
+      fieldName: 'options',
+      label: '下拉选',
+    },
+    {
+      component: 'DatePicker',
+      fieldName: 'datePicker',
+      label: '日期选择框',
+    },
+  ],
+  // 是否可展开
+  showCollapseButton: true,
+  submitButtonOptions: {
+    text: '查询',
+  },
+  wrapperClass: 'grid-cols-1 md:grid-cols-2',
+});
+function onSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form values: ${JSON.stringify(values)}`,
+  });
+}
+</script>
+
+<template>
+  <QueryForm />
+</template>

+ 189 - 0
docs/src/demos/vben-form/rules/index.vue

@@ -0,0 +1,189 @@
+<script lang="ts" setup>
+import { message } from 'ant-design-vue';
+
+import { useVbenForm, z } from '#/adapter';
+
+const [Form] = useVbenForm({
+  // 所有表单项共用,可单独在表单内覆盖
+  commonConfig: {
+    // 所有表单项
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  // 提交函数
+  handleSubmit: onSubmit,
+  // 垂直布局,label和input在不同行,值为vertical
+  // 水平布局,label和input在同一行
+  layout: 'horizontal',
+  schema: [
+    {
+      // 组件需要在 #/adapter.ts内注册,并加上类型
+      component: 'Input',
+      // 对应组件的参数
+      componentProps: {
+        placeholder: '请输入',
+      },
+      // 字段名
+      fieldName: 'field1',
+      // 界面显示的label
+      label: '字段1',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      defaultValue: '默认值',
+      fieldName: 'field2',
+      label: '默认值(必填)',
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'field3',
+      label: '默认值(非必填)',
+      rules: z.string().default('默认值').optional(),
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'field31',
+      label: '自定义信息',
+      rules: z.string().min(1, { message: '最少输入1个字符' }),
+    },
+    {
+      component: 'Input',
+      // 对应组件的参数
+      componentProps: {
+        placeholder: '请输入',
+      },
+      // 字段名
+      fieldName: 'field4',
+      // 界面显示的label
+      label: '邮箱',
+      rules: z.string().email('请输入正确的邮箱'),
+    },
+    {
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'number',
+      label: '数字',
+      rules: 'required',
+    },
+    {
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        filterOption: true,
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+        placeholder: '请选择',
+        showSearch: true,
+      },
+      defaultValue: undefined,
+      fieldName: 'options',
+      label: '下拉选',
+      rules: 'selectRequired',
+    },
+    {
+      component: 'RadioGroup',
+      componentProps: {
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+      },
+      fieldName: 'radioGroup',
+      label: '单选组',
+      rules: 'selectRequired',
+    },
+    {
+      component: 'CheckboxGroup',
+      componentProps: {
+        name: 'cname',
+        options: [
+          {
+            label: '选项1',
+            value: '1',
+          },
+          {
+            label: '选项2',
+            value: '2',
+          },
+        ],
+      },
+      fieldName: 'checkboxGroup',
+      label: '多选组',
+      rules: 'selectRequired',
+    },
+    {
+      component: 'Checkbox',
+      fieldName: 'checkbox',
+      label: '',
+      renderComponentContent: () => {
+        return {
+          default: () => ['我已阅读并同意'],
+        };
+      },
+      rules: 'selectRequired',
+    },
+    {
+      component: 'DatePicker',
+      defaultValue: undefined,
+      fieldName: 'datePicker',
+      label: '日期选择框',
+      rules: 'selectRequired',
+    },
+    {
+      component: 'RangePicker',
+      defaultValue: undefined,
+      fieldName: 'rangePicker',
+      label: '区间选择框',
+      rules: 'selectRequired',
+    },
+    {
+      component: 'InputPassword',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'password',
+      label: '密码',
+      rules: 'required',
+    },
+  ],
+  wrapperClass: 'grid-cols-1',
+});
+
+function onSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form values: ${JSON.stringify(values)}`,
+  });
+}
+</script>
+
+<template>
+  <Form />
+</template>

+ 6 - 0
docs/tsconfig.json

@@ -1,6 +1,12 @@
 {
   "$schema": "https://json.schemastore.org/tsconfig",
   "extends": "@vben/tsconfig/web.json",
+  "compilerOptions": {
+    "baseUrl": ".",
+    "paths": {
+      "#/*": ["./src/_env/*"]
+    }
+  },
   "include": [
     ".vitepress/*.mts",
     ".vitepress/**/*.ts",

+ 16 - 0
packages/@core/base/shared/src/utils/__tests__/inference.test.ts

@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
 
 import {
   getFirstNonNullOrUndefined,
+  isBoolean,
   isEmpty,
   isHttpUrl,
   isObject,
@@ -96,6 +97,21 @@ describe('isWindow', () => {
   });
 });
 
+describe('isBoolean', () => {
+  it('should return true for boolean values', () => {
+    expect(isBoolean(true)).toBe(true);
+    expect(isBoolean(false)).toBe(true);
+  });
+
+  it('should return false for non-boolean values', () => {
+    expect(isBoolean(null)).toBe(false);
+    expect(isBoolean(42)).toBe(false);
+    expect(isBoolean('string')).toBe(false);
+    expect(isBoolean({})).toBe(false);
+    expect(isBoolean([])).toBe(false);
+  });
+});
+
 describe('isObject', () => {
   it('should return true for objects', () => {
     expect(isObject({})).toBe(true);

+ 10 - 0
packages/@core/base/shared/src/utils/inference.ts

@@ -10,6 +10,15 @@ function isUndefined(value?: unknown): value is undefined {
   return value === undefined;
 }
 
+/**
+ * 检查传入的值是否为boolean
+ * @param value
+ * @returns 如果值是布尔值,返回true,否则返回false。
+ */
+function isBoolean(value: unknown): value is boolean {
+  return typeof value === 'boolean';
+}
+
 /**
  * 检查传入的值是否为空。
  *
@@ -141,6 +150,7 @@ function getFirstNonNullOrUndefined<T>(
 
 export {
   getFirstNonNullOrUndefined,
+  isBoolean,
   isEmpty,
   isFunction,
   isHttpUrl,

+ 7 - 1
packages/@core/ui-kit/form-ui/src/form-api.ts

@@ -219,6 +219,12 @@ export class FormApi {
 
   async validate(opts?: Partial<ValidationOptions>) {
     const form = await this.getForm();
-    return await form.validate(opts);
+
+    const validateResult = await form.validate(opts);
+
+    if (Object.keys(validateResult?.errors ?? {}).length > 0) {
+      console.error('validate error', validateResult?.errors);
+    }
+    return validateResult;
   }
 }

+ 9 - 1
packages/@core/ui-kit/form-ui/src/form-render/dependencies.ts

@@ -6,7 +6,7 @@ import type {
 
 import { computed, ref, watch } from 'vue';
 
-import { isFunction } from '@vben-core/shared/utils';
+import { isBoolean, isFunction } from '@vben-core/shared/utils';
 
 import { useFormValues } from 'vee-validate';
 
@@ -74,12 +74,18 @@ export default function useDependencies(
         isIf.value = !!(await whenIf(formValues, formApi));
         // 不渲染
         if (!isIf.value) return;
+      } else if (isBoolean(whenIf)) {
+        isIf.value = whenIf;
+        if (!isIf.value) return;
       }
 
       // 2. 判断show,如果show为false,则隐藏
       if (isFunction(show)) {
         isShow.value = !!(await show(formValues, formApi));
         if (!isShow.value) return;
+      } else if (isBoolean(show)) {
+        isShow.value = show;
+        if (!isShow.value) return;
       }
 
       if (isFunction(componentProps)) {
@@ -92,6 +98,8 @@ export default function useDependencies(
 
       if (isFunction(disabled)) {
         isDisabled.value = !!(await disabled(formValues, formApi));
+      } else if (isBoolean(disabled)) {
+        isDisabled.value = disabled;
       }
 
       if (isFunction(required)) {

+ 12 - 0
packages/@core/ui-kit/form-ui/src/form-render/form-field.vue

@@ -85,7 +85,15 @@ const currentRules = computed(() => {
   return dynamicRules.value || rules;
 });
 
+const visible = computed(() => {
+  return isIf.value && isShow.value;
+});
+
 const shouldRequired = computed(() => {
+  if (!visible.value) {
+    return false;
+  }
+
   if (!currentRules.value) {
     return isRequired.value;
   }
@@ -113,6 +121,10 @@ const shouldRequired = computed(() => {
 });
 
 const fieldRules = computed(() => {
+  if (!visible.value) {
+    return null;
+  }
+
   let rules = currentRules.value;
   if (!rules) {
     return isRequired.value ? 'required' : null;

+ 6 - 5
packages/@core/ui-kit/form-ui/src/types.ts

@@ -88,12 +88,12 @@ export interface FormItemDependencies {
    * 是否禁用
    * @returns 是否禁用
    */
-  disabled?: FormItemDependenciesCondition;
+  disabled?: boolean | FormItemDependenciesCondition;
   /**
    * 是否渲染(删除dom)
    * @returns 是否渲染
    */
-  if?: FormItemDependenciesCondition;
+  if?: boolean | FormItemDependenciesCondition;
   /**
    * 是否必填
    * @returns 是否必填
@@ -107,7 +107,7 @@ export interface FormItemDependencies {
    * 是否隐藏(Css)
    * @returns 是否隐藏
    */
-  show?: FormItemDependenciesCondition;
+  show?: boolean | FormItemDependenciesCondition;
   /**
    * 任意触发都会执行
    */
@@ -141,7 +141,7 @@ export interface FormCommonConfig {
   disabled?: boolean;
   /**
    * 所有表单项的控件样式
-   * @default ""
+   * @default {}
    */
   formFieldProps?: Partial<typeof Field>;
   /**
@@ -161,7 +161,7 @@ export interface FormCommonConfig {
   hideRequiredMark?: boolean;
   /**
    * 所有表单项的label样式
-   * @default "w-[100px]"
+   * @default ""
    */
   labelClass?: string;
   /**
@@ -295,6 +295,7 @@ export interface VbenFormProps<
 
   /**
    * 是否显示默认操作按钮
+   * @default true
    */
   showDefaultActions?: boolean;
 

+ 2 - 2
packages/effects/common-ui/src/components/captcha/point-selection-captcha/index.vue

@@ -162,11 +162,11 @@ function handleConfirm() {
         v-if="hintImage"
         :alt="$t('ui.captcha.alt')"
         :src="hintImage"
-        class="h-10 w-full rounded border border-solid border-slate-200"
+        class="border-border h-10 w-full rounded border"
       />
       <div
         v-else-if="hintText"
-        class="flex h-10 w-full items-center justify-center rounded border border-solid border-slate-200"
+        class="border-border flex-center h-10 w-full rounded border"
       >
         {{ `${$t('ui.captcha.clickInOrder')}` + `【${hintText}】` }}
       </div>

+ 8 - 1
packages/effects/common-ui/src/components/index.ts

@@ -5,4 +5,11 @@ export * from '@vben-core/form-ui';
 export * from '@vben-core/popup-ui';
 
 // 给文档用
-export { VbenButton } from '@vben-core/shadcn-ui';
+export {
+  VbenButton,
+  VbenCountToAnimator,
+  VbenInputPassword,
+  VbenLoading,
+  VbenPinInput,
+  VbenSpinner,
+} from '@vben-core/shadcn-ui';

+ 7 - 1
packages/effects/layouts/src/widgets/notification/notification.vue

@@ -81,6 +81,7 @@ function handleClick(item: NotificationItem) {
       <div class="flex items-center justify-between p-4 py-3">
         <div class="text-foreground">{{ $t('widgets.notifications') }}</div>
         <VbenIconButton
+          :disabled="notifications.length <= 0"
           :tooltip="$t('widgets.markAllAsRead')"
           @click="handleMakeAll"
         >
@@ -131,7 +132,12 @@ function handleClick(item: NotificationItem) {
       <div
         class="border-border flex items-center justify-between border-t px-4 py-3"
       >
-        <VbenButton size="sm" variant="ghost" @click="handleClear">
+        <VbenButton
+          :disabled="notifications.length <= 0"
+          size="sm"
+          variant="ghost"
+          @click="handleClear"
+        >
           {{ $t('widgets.clearNotifications') }}
         </VbenButton>
         <VbenButton size="sm" @click="handleViewAll">

+ 10 - 3
playground/src/adapter/form.ts

@@ -27,13 +27,13 @@ import {
   Select,
   Space,
   Switch,
+  Textarea,
   TimePicker,
   TreeSelect,
   Upload,
 } from 'ant-design-vue';
 
-// 业务表单组件适配
-
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
 export type FormComponentType =
   | 'AutoComplete'
   | 'Checkbox'
@@ -51,6 +51,7 @@ export type FormComponentType =
   | 'Select'
   | 'Space'
   | 'Switch'
+  | 'Textarea'
   | 'TimePicker'
   | 'TreeSelect'
   | 'Upload'
@@ -83,12 +84,16 @@ setupVbenForm<FormComponentType>({
     Select,
     Space,
     Switch,
+    Textarea,
     TimePicker,
     TreeSelect,
     Upload,
   },
   config: {
+    // ant design vue组件库默认都是 v-model:value
     baseModelPropName: 'value',
+
+    // 一些组件是 v-model:checked 或者 v-model:fileList
     modelPropNameMap: {
       Checkbox: 'checked',
       Radio: 'checked',
@@ -97,12 +102,14 @@ setupVbenForm<FormComponentType>({
     },
   },
   defineRules: {
+    // 输入项目必填国际化适配
     required: (value, _params, ctx) => {
-      if ((!value && value !== 0) || value.length === 0) {
+      if (value === undefined || value === null || value.length === 0) {
         return $t('formRules.required', [ctx.label]);
       }
       return true;
     },
+    // 选择项目必填国际化适配
     selectRequired: (value, _params, ctx) => {
       if (value === undefined || value === null) {
         return $t('formRules.selectRequired', [ctx.label]);

+ 1 - 0
playground/src/views/examples/form/api.vue

@@ -114,6 +114,7 @@ function handleClick(
           fieldName: 'fieldOptions',
         },
       ]);
+      message.success('字段 `fieldOptions` 下拉选项更新成功。');
       break;
     }
 

+ 3 - 3
playground/src/views/examples/form/custom.vue

@@ -7,7 +7,7 @@ import { Card, Input, message } from 'ant-design-vue';
 
 import { useVbenForm } from '#/adapter';
 
-const [BaseForm] = useVbenForm({
+const [Form] = useVbenForm({
   // 所有表单项共用,可单独在表单内覆盖
   commonConfig: {
     // 所有表单项
@@ -65,11 +65,11 @@ function onSubmit(values: Record<string, any>) {
 <template>
   <Page description="表单组件自定义示例" title="表单组件">
     <Card title="基础示例">
-      <BaseForm>
+      <Form>
         <template #field3="slotProps">
           <Input placeholder="请输入" v-bind="slotProps" />
         </template>
-      </BaseForm>
+      </Form>
     </Card>
   </Page>
 </template>

+ 11 - 0
playground/src/views/examples/form/dynamic.vue

@@ -9,6 +9,17 @@ const [Form, formApi] = useVbenForm({
   // 提交函数
   handleSubmit: onSubmit,
   schema: [
+    {
+      component: 'Input',
+      defaultValue: 'hidden value',
+      dependencies: {
+        show: false,
+        // 随意一个字段改变时,都会触发
+        triggerFields: ['field1Switch'],
+      },
+      fieldName: 'hiddenField',
+      label: '隐藏字段',
+    },
     {
       component: 'Switch',
       defaultValue: true,

+ 99 - 94
pnpm-lock.yaml

@@ -37,8 +37,8 @@ catalogs:
       specifier: ^4.1.2
       version: 4.1.2
     '@intlify/core-base':
-      specifier: ^10.0.1
-      version: 10.0.1
+      specifier: ^10.0.2
+      version: 10.0.2
     '@intlify/unplugin-vue-i18n':
       specifier: ^5.0.0
       version: 5.0.0
@@ -124,11 +124,11 @@ catalogs:
       specifier: ^4.0.1
       version: 4.0.1
     '@vue/reactivity':
-      specifier: ^3.5.7
-      version: 3.5.7
+      specifier: ^3.5.8
+      version: 3.5.8
     '@vue/shared':
-      specifier: ^3.5.7
-      version: 3.5.7
+      specifier: ^3.5.8
+      version: 3.5.8
     '@vue/test-utils':
       specifier: ^2.4.6
       version: 2.4.6
@@ -280,8 +280,8 @@ catalogs:
       specifier: ^3.0.1
       version: 3.0.1
     jsdom:
-      specifier: ^25.0.0
-      version: 25.0.0
+      specifier: ^25.0.1
+      version: 25.0.1
     jsonc-eslint-parser:
       specifier: ^2.4.0
       version: 2.4.0
@@ -376,8 +376,8 @@ catalogs:
       specifier: ^16.9.0
       version: 16.9.0
     stylelint-config-recess-order:
-      specifier: ^5.1.0
-      version: 5.1.0
+      specifier: ^5.1.1
+      version: 5.1.1
     stylelint-config-recommended:
       specifier: ^14.0.1
       version: 14.0.1
@@ -460,8 +460,8 @@ catalogs:
       specifier: ^9.4.3
       version: 9.4.3
     vue-i18n:
-      specifier: ^10.0.1
-      version: 10.0.1
+      specifier: ^10.0.2
+      version: 10.0.2
     vue-router:
       specifier: ^4.4.5
       version: 4.4.5
@@ -553,7 +553,7 @@ importers:
         version: 3.0.1
       jsdom:
         specifier: 'catalog:'
-        version: 25.0.0
+        version: 25.0.1
       lint-staged:
         specifier: 'catalog:'
         version: 15.2.10
@@ -577,7 +577,7 @@ importers:
         version: 5.4.7(@types/node@22.5.5)(less@4.2.0)(sass@1.79.3)(terser@5.33.0)
       vitest:
         specifier: 'catalog:'
-        version: 2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(less@4.2.0)(sass@1.79.3)(terser@5.33.0)
+        version: 2.1.1(@types/node@22.5.5)(jsdom@25.0.1)(less@4.2.0)(sass@1.79.3)(terser@5.33.0)
       vue:
         specifier: 3.5.7
         version: 3.5.7(typescript@5.6.2)
@@ -799,9 +799,21 @@ importers:
       '@vben/common-ui':
         specifier: workspace:*
         version: link:../packages/effects/common-ui
+      '@vben/hooks':
+        specifier: workspace:*
+        version: link:../packages/effects/hooks
+      '@vben/locales':
+        specifier: workspace:*
+        version: link:../packages/locales
+      '@vben/preferences':
+        specifier: workspace:*
+        version: link:../packages/preferences
       '@vben/styles':
         specifier: workspace:*
         version: link:../packages/styles
+      ant-design-vue:
+        specifier: 'catalog:'
+        version: 4.2.5(vue@3.5.7(typescript@5.6.2))
       lucide-vue-next:
         specifier: 'catalog:'
         version: 0.445.0(vue@3.5.7(typescript@5.6.2))
@@ -911,7 +923,7 @@ importers:
         version: 4.1.4(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))
       eslint-plugin-vitest:
         specifier: 'catalog:'
-        version: 0.5.4(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(less@4.2.0)(sass@1.79.3)(terser@5.33.0))
+        version: 0.5.4(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.1)(less@4.2.0)(sass@1.79.3)(terser@5.33.0))
       eslint-plugin-vue:
         specifier: 'catalog:'
         version: 9.28.0(eslint@9.11.0(jiti@1.21.6))
@@ -941,7 +953,7 @@ importers:
         version: 3.0.1(stylelint@16.9.0(typescript@5.6.2))
       stylelint-config-recess-order:
         specifier: 'catalog:'
-        version: 5.1.0(stylelint@16.9.0(typescript@5.6.2))
+        version: 5.1.1(stylelint@16.9.0(typescript@5.6.2))
       stylelint-scss:
         specifier: 'catalog:'
         version: 6.7.0(stylelint@16.9.0(typescript@5.6.2))
@@ -1082,7 +1094,7 @@ importers:
     dependencies:
       '@intlify/unplugin-vue-i18n':
         specifier: 'catalog:'
-        version: 5.0.0(@vue/compiler-dom@3.5.7)(eslint@9.11.0(jiti@1.21.6))(rollup@4.22.4)(typescript@5.6.2)(vue-i18n@10.0.1(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))
+        version: 5.0.0(@vue/compiler-dom@3.5.7)(eslint@9.11.0(jiti@1.21.6))(rollup@4.22.4)(typescript@5.6.2)(vue-i18n@10.0.2(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))
       '@jspm/generator':
         specifier: 'catalog:'
         version: 2.3.1
@@ -1181,10 +1193,10 @@ importers:
         version: 0.5.5(vue@3.5.7(typescript@5.6.2))
       '@vue/reactivity':
         specifier: 'catalog:'
-        version: 3.5.7
+        version: 3.5.8
       '@vue/shared':
         specifier: 'catalog:'
-        version: 3.5.7
+        version: 3.5.8
       clsx:
         specifier: 2.1.1
         version: 2.1.1
@@ -1608,7 +1620,7 @@ importers:
     dependencies:
       '@intlify/core-base':
         specifier: 'catalog:'
-        version: 10.0.1
+        version: 10.0.2
       '@vben-core/composables':
         specifier: workspace:*
         version: link:../@core/composables
@@ -1617,7 +1629,7 @@ importers:
         version: 3.5.7(typescript@5.6.2)
       vue-i18n:
         specifier: 'catalog:'
-        version: 10.0.1(vue@3.5.7(typescript@5.6.2))
+        version: 10.0.2(vue@3.5.7(typescript@5.6.2))
 
   packages/preferences:
     dependencies:
@@ -3903,24 +3915,24 @@ packages:
       vue-i18n:
         optional: true
 
-  '@intlify/core-base@10.0.1':
-    resolution: {integrity: sha512-6kpRGjhos95ph7QmEtP4tnWFTW102s71CLQAQwfsIGqOAcoJhzcYFpzIQ0gKXzqAIXsMD/hwM5qJ4ewqMHw3gg==}
+  '@intlify/core-base@10.0.2':
+    resolution: {integrity: sha512-0Ewg801c3f5ukktJVi/BVwxPVbX2lvGflK0G6QxTatbWaMt2YA1QheDGTXS2Nz/PupSDPNOabADsTG9LCoKA1A==}
     engines: {node: '>= 16'}
 
   '@intlify/message-compiler@10.0.0':
     resolution: {integrity: sha512-OcaWc63NC/9p1cMdgoNKBj4d61BH8sUW1Hfs6YijTd9656ZR4rNqXAlRnBrfS5ABq0vjQjpa8VnyvH9hK49yBw==}
     engines: {node: '>= 16'}
 
-  '@intlify/message-compiler@10.0.1':
-    resolution: {integrity: sha512-fPeykrcgVT5eOIlshTHiPCN8FV3AZyBOdMS3XaXzfQ6eL5wqfc29I/EdIv5YXVW5X8e/BgYeWjBC0Cuznsl/2g==}
+  '@intlify/message-compiler@10.0.2':
+    resolution: {integrity: sha512-PHFnGFEKknuk+RwcafKAjS537Ln0ptSfqmvVdsHKWBytTbiKqZZFX57pmfio2ln+ZLeHuyudcqbTi1zGJUNIcA==}
     engines: {node: '>= 16'}
 
   '@intlify/shared@10.0.0':
     resolution: {integrity: sha512-6ngLfI7DOTew2dcF9WMJx+NnMWghMBhIiHbGg+wRvngpzD5KZJZiJVuzMsUQE1a5YebEmtpTEfUrDp/NqVGdiw==}
     engines: {node: '>= 16'}
 
-  '@intlify/shared@10.0.1':
-    resolution: {integrity: sha512-b4h7IWdZl710DnAhET8lgfgZ4Y9A2IZx/gbli3Ec/zHtYCoPqLHmiM7kUNBrSZj7d/SSjcMMZHuz5I09x3PYZw==}
+  '@intlify/shared@10.0.2':
+    resolution: {integrity: sha512-sIF9cqB0CwUWLtDb1QDnl4PlTggE4GSnR1aA44thT9Y18rUVdWO2z4w2Ow6O60tsdNzC0WnUd6fU0VXyKQ6WBA==}
     engines: {node: '>= 16'}
 
   '@intlify/unplugin-vue-i18n@5.0.0':
@@ -4843,6 +4855,9 @@ packages:
   '@vue/reactivity@3.5.7':
     resolution: {integrity: sha512-yF0EpokpOHRNXyn/h6abXc9JFIzfdAf0MJHIi92xxCWS0mqrXH6+2aZ+A6EbSrspGzX5MHTd5N8iBA28HnXu9g==}
 
+  '@vue/reactivity@3.5.8':
+    resolution: {integrity: sha512-mlgUyFHLCUZcAYkqvzYnlBRCh0t5ZQfLYit7nukn1GR96gc48Bp4B7OIcSfVSvlG1k3BPfD+p22gi1t2n9tsXg==}
+
   '@vue/runtime-core@3.5.7':
     resolution: {integrity: sha512-OzLpBpKbZEaZVSNfd+hQbfBrDKux+b7Yl5hYhhWWWhHD7fEpF+CdI3Brm5k5GsufHEfvMcjruPxwQZuBN6nFYQ==}
 
@@ -4857,6 +4872,9 @@ packages:
   '@vue/shared@3.5.7':
     resolution: {integrity: sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g==}
 
+  '@vue/shared@3.5.8':
+    resolution: {integrity: sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==}
+
   '@vue/test-utils@2.4.6':
     resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==}
 
@@ -7310,8 +7328,8 @@ packages:
     resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
     engines: {node: '>=12.0.0'}
 
-  jsdom@25.0.0:
-    resolution: {integrity: sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==}
+  jsdom@25.0.1:
+    resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==}
     engines: {node: '>=18'}
     peerDependencies:
       canvas: ^2.11.2
@@ -8778,9 +8796,6 @@ packages:
   pseudomap@1.0.2:
     resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==}
 
-  psl@1.9.0:
-    resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
-
   publint@0.2.11:
     resolution: {integrity: sha512-/kxbd+sD/uEG515N/ZYpC6gYs8h89cQ4UIsAq1y6VT4qlNh8xmiSwcP2xU2MbzXFl8J0l2IdONKFweLfYoqhcA==}
     engines: {node: '>=16'}
@@ -8799,9 +8814,6 @@ packages:
     engines: {node: '>=10.13.0'}
     hasBin: true
 
-  querystringify@2.2.0:
-    resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
-
   queue-microtask@1.2.3:
     resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
 
@@ -8952,9 +8964,6 @@ packages:
   require-package-name@2.0.1:
     resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==}
 
-  requires-port@1.0.0:
-    resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
-
   resize-observer-polyfill@1.5.1:
     resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
 
@@ -9437,8 +9446,8 @@ packages:
       postcss-html: ^1.0.0
       stylelint: '>=14.0.0'
 
-  stylelint-config-recess-order@5.1.0:
-    resolution: {integrity: sha512-ddapCF6B/kEtQYIFhQFReQ0dvK1ZdgJDM/SGFtIyeooYDbqaJqcOlGkRRGaVErCQYJY/bPSPsLRS2LdQtLJUVQ==}
+  stylelint-config-recess-order@5.1.1:
+    resolution: {integrity: sha512-eDAHWVBelzDbMbdMj15pSw0Ycykv5eLeriJdbGCp0zd44yvhgZLI+wyVHegzXp5NrstxTPSxl0fuOVKdMm0XLA==}
     peerDependencies:
       stylelint: '>=16'
 
@@ -9653,6 +9662,13 @@ packages:
     resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
     engines: {node: '>=14.0.0'}
 
+  tldts-core@6.1.47:
+    resolution: {integrity: sha512-6SWyFMnlst1fEt7GQVAAu16EGgFK0cLouH/2Mk6Ftlwhv3Ol40L0dlpGMcnnNiiOMyD2EV/aF3S+U2nKvvLvrA==}
+
+  tldts@6.1.47:
+    resolution: {integrity: sha512-R/K2tZ5MiY+mVrnSkNJkwqYT2vUv1lcT6wJvd2emGaMJ7PHUGRY4e3tUsdFCXgqxi2QgbHjL3yJgXCo40v9Hxw==}
+    hasBin: true
+
   tmp@0.0.33:
     resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
     engines: {node: '>=0.6.0'}
@@ -9673,9 +9689,9 @@ packages:
     resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
     engines: {node: '>=6'}
 
-  tough-cookie@4.1.4:
-    resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
-    engines: {node: '>=6'}
+  tough-cookie@5.0.0:
+    resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==}
+    engines: {node: '>=16'}
 
   tr46@0.0.3:
     resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -9887,10 +9903,6 @@ packages:
     resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
     engines: {node: '>= 4.0.0'}
 
-  universalify@0.2.0:
-    resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
-    engines: {node: '>= 4.0.0'}
-
   universalify@2.0.1:
     resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
     engines: {node: '>= 10.0.0'}
@@ -9983,9 +9995,6 @@ packages:
   uri-js@4.4.1:
     resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
 
-  url-parse@1.5.10:
-    resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
-
   urlpattern-polyfill@8.0.2:
     resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
 
@@ -10181,8 +10190,8 @@ packages:
     peerDependencies:
       eslint: '>=6.0.0'
 
-  vue-i18n@10.0.1:
-    resolution: {integrity: sha512-SQVlSm/1S6AaG1wexvwq3ebXUrrkx75ZHD78UAs4/rYD/X3tsQxfm6ElpT4ZPegJQEgRtOJjGripqSrfqAENtg==}
+  vue-i18n@10.0.2:
+    resolution: {integrity: sha512-osoes79ecpqdzYYhywp/pDLlPMoyQ5MHJvjAitQLmXiaCj/ejC8YIeWIwIsDdqWcvkrVFmROYDLcGgGCVn7g0Q==}
     engines: {node: '>= 16'}
     peerDependencies:
       vue: 3.5.7
@@ -12662,7 +12671,7 @@ snapshots:
     dependencies:
       '@swc/helpers': 0.5.13
 
-  '@intlify/bundle-utils@9.0.0-beta.0(vue-i18n@10.0.1(vue@3.5.7(typescript@5.6.2)))':
+  '@intlify/bundle-utils@9.0.0-beta.0(vue-i18n@10.0.2(vue@3.5.7(typescript@5.6.2)))':
     dependencies:
       '@intlify/message-compiler': 10.0.0
       '@intlify/shared': 10.0.0
@@ -12674,33 +12683,33 @@ snapshots:
       source-map-js: 1.2.1
       yaml-eslint-parser: 1.2.3
     optionalDependencies:
-      vue-i18n: 10.0.1(vue@3.5.7(typescript@5.6.2))
+      vue-i18n: 10.0.2(vue@3.5.7(typescript@5.6.2))
 
-  '@intlify/core-base@10.0.1':
+  '@intlify/core-base@10.0.2':
     dependencies:
-      '@intlify/message-compiler': 10.0.1
-      '@intlify/shared': 10.0.1
+      '@intlify/message-compiler': 10.0.2
+      '@intlify/shared': 10.0.2
 
   '@intlify/message-compiler@10.0.0':
     dependencies:
       '@intlify/shared': 10.0.0
       source-map-js: 1.2.1
 
-  '@intlify/message-compiler@10.0.1':
+  '@intlify/message-compiler@10.0.2':
     dependencies:
-      '@intlify/shared': 10.0.1
+      '@intlify/shared': 10.0.2
       source-map-js: 1.2.1
 
   '@intlify/shared@10.0.0': {}
 
-  '@intlify/shared@10.0.1': {}
+  '@intlify/shared@10.0.2': {}
 
-  '@intlify/unplugin-vue-i18n@5.0.0(@vue/compiler-dom@3.5.7)(eslint@9.11.0(jiti@1.21.6))(rollup@4.22.4)(typescript@5.6.2)(vue-i18n@10.0.1(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))':
+  '@intlify/unplugin-vue-i18n@5.0.0(@vue/compiler-dom@3.5.7)(eslint@9.11.0(jiti@1.21.6))(rollup@4.22.4)(typescript@5.6.2)(vue-i18n@10.0.2(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))':
     dependencies:
       '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0(jiti@1.21.6))
-      '@intlify/bundle-utils': 9.0.0-beta.0(vue-i18n@10.0.1(vue@3.5.7(typescript@5.6.2)))
+      '@intlify/bundle-utils': 9.0.0-beta.0(vue-i18n@10.0.2(vue@3.5.7(typescript@5.6.2)))
       '@intlify/shared': 10.0.0
-      '@intlify/vue-i18n-extensions': 6.2.0(@intlify/shared@10.0.0)(@vue/compiler-dom@3.5.7)(vue-i18n@10.0.1(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))
+      '@intlify/vue-i18n-extensions': 6.2.0(@intlify/shared@10.0.0)(@vue/compiler-dom@3.5.7)(vue-i18n@10.0.2(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))
       '@rollup/pluginutils': 5.1.0(rollup@4.22.4)
       '@typescript-eslint/scope-manager': 7.18.0
       '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.2)
@@ -12715,7 +12724,7 @@ snapshots:
       unplugin: 1.14.1
       vue: 3.5.7(typescript@5.6.2)
     optionalDependencies:
-      vue-i18n: 10.0.1(vue@3.5.7(typescript@5.6.2))
+      vue-i18n: 10.0.2(vue@3.5.7(typescript@5.6.2))
     transitivePeerDependencies:
       - '@vue/compiler-dom'
       - eslint
@@ -12724,14 +12733,14 @@ snapshots:
       - typescript
       - webpack-sources
 
-  '@intlify/vue-i18n-extensions@6.2.0(@intlify/shared@10.0.0)(@vue/compiler-dom@3.5.7)(vue-i18n@10.0.1(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))':
+  '@intlify/vue-i18n-extensions@6.2.0(@intlify/shared@10.0.0)(@vue/compiler-dom@3.5.7)(vue-i18n@10.0.2(vue@3.5.7(typescript@5.6.2)))(vue@3.5.7(typescript@5.6.2))':
     dependencies:
       '@babel/parser': 7.25.6
     optionalDependencies:
       '@intlify/shared': 10.0.0
       '@vue/compiler-dom': 3.5.7
       vue: 3.5.7(typescript@5.6.2)
-      vue-i18n: 10.0.1(vue@3.5.7(typescript@5.6.2))
+      vue-i18n: 10.0.2(vue@3.5.7(typescript@5.6.2))
 
   '@ioredis/commands@1.2.0': {}
 
@@ -13929,6 +13938,10 @@ snapshots:
     dependencies:
       '@vue/shared': 3.5.7
 
+  '@vue/reactivity@3.5.8':
+    dependencies:
+      '@vue/shared': 3.5.8
+
   '@vue/runtime-core@3.5.7':
     dependencies:
       '@vue/reactivity': 3.5.7
@@ -13949,6 +13962,8 @@ snapshots:
 
   '@vue/shared@3.5.7': {}
 
+  '@vue/shared@3.5.8': {}
+
   '@vue/test-utils@2.4.6':
     dependencies:
       js-beautify: 1.15.1
@@ -15713,13 +15728,13 @@ snapshots:
     optionalDependencies:
       '@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)
 
-  eslint-plugin-vitest@0.5.4(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(less@4.2.0)(sass@1.79.3)(terser@5.33.0)):
+  eslint-plugin-vitest@0.5.4(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)(vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.1)(less@4.2.0)(sass@1.79.3)(terser@5.33.0)):
     dependencies:
       '@typescript-eslint/utils': 7.18.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)
       eslint: 9.11.0(jiti@1.21.6)
     optionalDependencies:
       '@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)
-      vitest: 2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(less@4.2.0)(sass@1.79.3)(terser@5.33.0)
+      vitest: 2.1.1(@types/node@22.5.5)(jsdom@25.0.1)(less@4.2.0)(sass@1.79.3)(terser@5.33.0)
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -16738,7 +16753,7 @@ snapshots:
 
   jsdoc-type-pratt-parser@4.1.0: {}
 
-  jsdom@25.0.0:
+  jsdom@25.0.1:
     dependencies:
       cssstyle: 4.1.0
       data-urls: 5.0.0
@@ -16753,7 +16768,7 @@ snapshots:
       rrweb-cssom: 0.7.1
       saxes: 6.0.0
       symbol-tree: 3.2.4
-      tough-cookie: 4.1.4
+      tough-cookie: 5.0.0
       w3c-xmlserializer: 5.0.0
       webidl-conversions: 7.0.0
       whatwg-encoding: 3.1.1
@@ -18273,8 +18288,6 @@ snapshots:
 
   pseudomap@1.0.2: {}
 
-  psl@1.9.0: {}
-
   publint@0.2.11:
     dependencies:
       npm-packlist: 5.1.3
@@ -18293,8 +18306,6 @@ snapshots:
       pngjs: 5.0.0
       yargs: 15.4.1
 
-  querystringify@2.2.0: {}
-
   queue-microtask@1.2.3: {}
 
   queue-tick@1.0.1: {}
@@ -18471,8 +18482,6 @@ snapshots:
 
   require-package-name@2.0.1: {}
 
-  requires-port@1.0.0: {}
-
   resize-observer-polyfill@1.5.1: {}
 
   resolve-dir@1.0.1:
@@ -18980,7 +18989,7 @@ snapshots:
       postcss-html: 1.7.0
       stylelint: 16.9.0(typescript@5.6.2)
 
-  stylelint-config-recess-order@5.1.0(stylelint@16.9.0(typescript@5.6.2)):
+  stylelint-config-recess-order@5.1.1(stylelint@16.9.0(typescript@5.6.2)):
     dependencies:
       stylelint: 16.9.0(typescript@5.6.2)
       stylelint-order: 6.0.4(stylelint@16.9.0(typescript@5.6.2))
@@ -19260,6 +19269,12 @@ snapshots:
 
   tinyspy@3.0.2: {}
 
+  tldts-core@6.1.47: {}
+
+  tldts@6.1.47:
+    dependencies:
+      tldts-core: 6.1.47
+
   tmp@0.0.33:
     dependencies:
       os-tmpdir: 1.0.2
@@ -19274,12 +19289,9 @@ snapshots:
 
   totalist@3.0.1: {}
 
-  tough-cookie@4.1.4:
+  tough-cookie@5.0.0:
     dependencies:
-      psl: 1.9.0
-      punycode: 2.3.1
-      universalify: 0.2.0
-      url-parse: 1.5.10
+      tldts: 6.1.47
 
   tr46@0.0.3: {}
 
@@ -19524,8 +19536,6 @@ snapshots:
 
   universalify@0.1.2: {}
 
-  universalify@0.2.0: {}
-
   universalify@2.0.1: {}
 
   unplugin-element-plus@0.8.0(rollup@4.22.4):
@@ -19616,11 +19626,6 @@ snapshots:
     dependencies:
       punycode: 2.3.1
 
-  url-parse@1.5.10:
-    dependencies:
-      querystringify: 2.2.0
-      requires-port: 1.0.0
-
   urlpattern-polyfill@8.0.2: {}
 
   util-deprecate@1.0.2: {}
@@ -19849,7 +19854,7 @@ snapshots:
       - typescript
       - universal-cookie
 
-  vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.0)(less@4.2.0)(sass@1.79.3)(terser@5.33.0):
+  vitest@2.1.1(@types/node@22.5.5)(jsdom@25.0.1)(less@4.2.0)(sass@1.79.3)(terser@5.33.0):
     dependencies:
       '@vitest/expect': 2.1.1
       '@vitest/mocker': 2.1.1(@vitest/spy@2.1.1)(vite@5.4.7(@types/node@22.5.5)(less@4.2.0)(sass@1.79.3)(terser@5.33.0))
@@ -19872,7 +19877,7 @@ snapshots:
       why-is-node-running: 2.3.0
     optionalDependencies:
       '@types/node': 22.5.5
-      jsdom: 25.0.0
+      jsdom: 25.0.1
     transitivePeerDependencies:
       - less
       - lightningcss
@@ -19912,10 +19917,10 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  vue-i18n@10.0.1(vue@3.5.7(typescript@5.6.2)):
+  vue-i18n@10.0.2(vue@3.5.7(typescript@5.6.2)):
     dependencies:
-      '@intlify/core-base': 10.0.1
-      '@intlify/shared': 10.0.1
+      '@intlify/core-base': 10.0.2
+      '@intlify/shared': 10.0.2
       '@vue/devtools-api': 6.6.4
       vue: 3.5.7(typescript@5.6.2)
 

+ 7 - 7
pnpm-workspace.yaml

@@ -24,7 +24,7 @@ catalog:
   '@iconify/json': ^2.2.251
   '@iconify/tailwind': ^1.1.3
   '@iconify/vue': ^4.1.2
-  '@intlify/core-base': ^10.0.1
+  '@intlify/core-base': ^10.0.2
   '@intlify/unplugin-vue-i18n': ^5.0.0
   '@jspm/generator': ^2.3.1
   '@manypkg/get-packages': ^2.2.2
@@ -53,8 +53,8 @@ catalog:
   '@vite-pwa/vitepress': ^0.5.3
   '@vitejs/plugin-vue': ^5.1.4
   '@vitejs/plugin-vue-jsx': ^4.0.1
-  '@vue/reactivity': ^3.5.7
-  '@vue/shared': ^3.5.7
+  '@vue/reactivity': ^3.5.8
+  '@vue/shared': ^3.5.8
   '@vue/test-utils': ^2.4.6
   '@vueuse/core': ^11.1.0
   '@vueuse/integrations': ^11.1.0
@@ -106,7 +106,7 @@ catalog:
   html-minifier-terser: ^7.2.0
   husky: ^9.1.6
   is-ci: ^3.0.1
-  jsdom: ^25.0.0
+  jsdom: ^25.0.1
   jsonc-eslint-parser: ^2.4.0
   jsonwebtoken: ^9.0.2
   lint-staged: ^15.2.10
@@ -139,7 +139,7 @@ catalog:
   sass: ^1.79.3
   sortablejs: ^1.15.3
   stylelint: ^16.9.0
-  stylelint-config-recess-order: ^5.1.0
+  stylelint-config-recess-order: ^5.1.1
   stylelint-config-recommended: ^14.0.1
   stylelint-config-recommended-scss: ^14.1.0
   stylelint-config-recommended-vue: ^1.5.0
@@ -166,9 +166,9 @@ catalog:
   vitepress: ^1.3.4
   vitepress-plugin-group-icons: ^1.2.4
   vitest: ^2.1.1
-  vue: ^3.5.7
+  vue: ^3.5.8
   vue-eslint-parser: ^9.4.3
-  vue-i18n: ^10.0.1
+  vue-i18n: ^10.0.2
   vue-router: ^4.4.5
   vue-tsc: ^2.1.6
   watermark-js-plus: ^1.5.6