Explorar o código

refactor(adapter): separate form and component adapters so that components adapt to components other than the form (#4628)

* refactor: global components can be customized

* refactor: remove use Toast and reconstruct the form adapter
Vben hai 6 meses
pai
achega
24d14c2841
Modificáronse 63 ficheiros con 1093 adicións e 1268 borrados
  1. 1 1
      README.md
  2. 127 0
      apps/web-antd/src/adapter/component/index.ts
  3. 4 95
      apps/web-antd/src/adapter/form.ts
  4. 4 0
      apps/web-antd/src/bootstrap.ts
  5. 1 0
      apps/web-antd/src/router/routes/core.ts
  6. 104 0
      apps/web-ele/src/adapter/component/index.ts
  7. 4 72
      apps/web-ele/src/adapter/form.ts
  8. 3 0
      apps/web-ele/src/bootstrap.ts
  9. 1 0
      apps/web-ele/src/router/routes/core.ts
  10. 103 0
      apps/web-naive/src/adapter/component/index.ts
  11. 4 74
      apps/web-naive/src/adapter/form.ts
  12. 3 0
      apps/web-naive/src/bootstrap.ts
  13. 1 0
      apps/web-naive/src/router/routes/core.ts
  14. 4 0
      docs/.vitepress/config/zh.mts
  15. 127 0
      docs/src/_env/adapter/component.ts
  16. 10 88
      docs/src/_env/adapter/form.ts
  17. 1 1
      docs/src/commercial/community.md
  18. 6 0
      docs/src/components/common-ui/vben-drawer.md
  19. 99 59
      docs/src/components/common-ui/vben-form.md
  20. 6 0
      docs/src/components/common-ui/vben-modal.md
  21. 7 0
      docs/src/components/common-ui/vben-vxe-table.md
  22. 1 1
      docs/src/friend-links/index.md
  23. 0 1
      packages/@core/base/design/src/css/global.css
  24. 0 0
      packages/@core/base/design/src/design-tokens/dark.css
  25. 0 0
      packages/@core/base/design/src/design-tokens/default.css
  26. 2 2
      packages/@core/base/design/src/design-tokens/index.ts
  27. 1 0
      packages/@core/base/shared/build.config.ts
  28. 12 2
      packages/@core/base/shared/package.json
  29. 45 0
      packages/@core/base/shared/src/global-state.ts
  30. 0 7
      packages/@core/composables/src/use-sortable.ts
  31. 2 2
      packages/@core/ui-kit/form-ui/src/components/form-actions.vue
  32. 6 3
      packages/@core/ui-kit/form-ui/src/config.ts
  33. 2 3
      packages/@core/ui-kit/form-ui/src/types.ts
  34. 10 4
      packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue
  35. 10 4
      packages/@core/ui-kit/popup-ui/src/modal/modal.vue
  36. 0 1
      packages/@core/ui-kit/shadcn-ui/src/ui/index.ts
  37. 0 35
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/Toast.vue
  38. 0 29
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastAction.vue
  39. 0 34
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastClose.vue
  40. 0 24
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastDescription.vue
  41. 0 11
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastProvider.vue
  42. 0 24
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastTitle.vue
  43. 0 27
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastViewport.vue
  44. 0 36
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/Toaster.vue
  45. 0 11
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/index.ts
  46. 0 27
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/toast.ts
  47. 0 168
      packages/@core/ui-kit/shadcn-ui/src/ui/toast/use-toast.ts
  48. 2 0
      packages/effects/common-ui/src/components/index.ts
  49. 0 1
      packages/effects/common-ui/src/index.ts
  50. 1 2
      packages/effects/layouts/src/basic/layout.vue
  51. 29 37
      packages/effects/layouts/src/widgets/check-updates/check-updates.vue
  52. 8 10
      packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue
  53. 1 1
      packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue
  54. 1 1
      packages/locales/src/i18n.ts
  55. 1 0
      packages/locales/src/langs/en-US.json
  56. 1 0
      packages/locales/src/langs/zh-CN.json
  57. 2 1
      packages/stores/src/modules/tabbar.ts
  58. 0 1
      packages/stores/src/setup.ts
  59. 127 0
      playground/src/adapter/component/index.ts
  60. 4 97
      playground/src/adapter/form.ts
  61. 4 0
      playground/src/bootstrap.ts
  62. 1 0
      playground/src/router/routes/core.ts
  63. 200 271
      pnpm-lock.yaml

+ 1 - 1
README.md

@@ -134,7 +134,7 @@ If you think this project is helpful to you, you can help the author buy a cup o
 
 ![donate](https://unpkg.com/@vbenjs/static-source@0.1.7/source/sponsor.png)
 
-<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
+<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a>
 
 ## Contributor
 

+ 127 - 0
apps/web-antd/src/adapter/component/index.ts

@@ -0,0 +1,127 @@
+/**
+ * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
+ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
+ */
+
+import type { BaseFormComponentType } from '@vben/common-ui';
+
+import type { Component, SetupContext } from 'vue';
+import { h } from 'vue';
+
+import { globalShareState } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import {
+  AutoComplete,
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  DatePicker,
+  Divider,
+  Input,
+  InputNumber,
+  InputPassword,
+  Mentions,
+  notification,
+  Radio,
+  RadioGroup,
+  RangePicker,
+  Rate,
+  Select,
+  Space,
+  Switch,
+  Textarea,
+  TimePicker,
+  TreeSelect,
+  Upload,
+} from 'ant-design-vue';
+
+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);
+  };
+};
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type ComponentType =
+  | 'AutoComplete'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'DefaultButton'
+  | 'Divider'
+  | 'Input'
+  | 'InputNumber'
+  | 'InputPassword'
+  | 'Mentions'
+  | 'PrimaryButton'
+  | 'Radio'
+  | 'RadioGroup'
+  | 'RangePicker'
+  | 'Rate'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'Textarea'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+async function initComponentAdapter() {
+  const components: Partial<Record<ComponentType, Component>> = {
+    // 如果你的组件体积比较大,可以使用异步加载
+    // Button: () =>
+    // import('xxx').then((res) => res.Button),
+
+    AutoComplete,
+    Checkbox,
+    CheckboxGroup,
+    DatePicker,
+    // 自定义默认按钮
+    DefaultButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'default' }, slots);
+    },
+    Divider,
+    Input: withDefaultPlaceholder(Input, 'input'),
+    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
+    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
+    Mentions: withDefaultPlaceholder(Mentions, 'input'),
+    // 自定义主要按钮
+    PrimaryButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Radio,
+    RadioGroup,
+    RangePicker,
+    Rate,
+    Select: withDefaultPlaceholder(Select, 'select'),
+    Space,
+    Switch,
+    Textarea: withDefaultPlaceholder(Textarea, 'input'),
+    TimePicker,
+    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
+    Upload,
+  };
+
+  // 将组件注册到全局共享状态中
+  globalShareState.setComponents(components);
+
+  // 定义全局共享状态中的消息提示
+  globalShareState.defineMessage({
+    // 复制成功消息提示
+    copyPreferencesSuccess: (title, content) => {
+      notification.success({
+        description: content,
+        message: title,
+        placement: 'bottomRight',
+      });
+    },
+  });
+}
+
+export { initComponentAdapter };

+ 4 - 95
apps/web-antd/src/adapter/form.ts

@@ -1,105 +1,14 @@
 import type {
-  BaseFormComponentType,
   VbenFormSchema as FormSchema,
   VbenFormProps,
 } from '@vben/common-ui';
 
-import type { Component, SetupContext } from 'vue';
-import { h } from 'vue';
+import type { ComponentType } from './component';
 
 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;
-
-const withDefaultPlaceholder = <T extends Component>(
-  component: T,
-  type: 'input' | 'select',
-) => {
-  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
-    const placeholder = props?.placeholder || $t(`placeholder.${type}`);
-    return h(component, { ...props, ...attrs, placeholder }, slots);
-  };
-};
-
-// 初始化表单组件,并注册到form组件内部
-setupVbenForm<FormComponentType>({
-  components: {
-    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: withDefaultPlaceholder(Input, 'input'),
-    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
-    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
-    Mentions: withDefaultPlaceholder(Mentions, 'input'),
-    Radio,
-    RadioGroup,
-    RangePicker,
-    Rate,
-    Select: withDefaultPlaceholder(Select, 'select'),
-    Space,
-    Switch,
-    Textarea: withDefaultPlaceholder(Textarea, 'input'),
-    TimePicker,
-    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
-    Upload,
-  },
+setupVbenForm<ComponentType>({
   config: {
     // ant design vue组件库默认都是 v-model:value
     baseModelPropName: 'value',
@@ -130,9 +39,9 @@ setupVbenForm<FormComponentType>({
   },
 });
 
-const useVbenForm = useForm<FormComponentType>;
+const useVbenForm = useForm<ComponentType>;
 
 export { useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<FormComponentType>;
+export type VbenFormSchema = FormSchema<ComponentType>;
 export type { VbenFormProps };

+ 4 - 0
apps/web-antd/src/bootstrap.ts

@@ -7,10 +7,14 @@ import '@vben/styles/antd';
 
 import { setupI18n } from '#/locales';
 
+import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
 import { router } from './router';
 
 async function bootstrap(namespace: string) {
+  // 初始化组件适配器
+  await initComponentAdapter();
+
   const app = createApp(App);
 
   // 国际化 i18n 配置

+ 1 - 0
apps/web-antd/src/router/routes/core.ts

@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
   {
     component: AuthPageLayout,
     meta: {
+      hideInTab: true,
       title: 'Authentication',
     },
     name: 'Authentication',

+ 104 - 0
apps/web-ele/src/adapter/component/index.ts

@@ -0,0 +1,104 @@
+/**
+ * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
+ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
+ */
+
+import type { BaseFormComponentType } from '@vben/common-ui';
+
+import type { Component, SetupContext } from 'vue';
+import { h } from 'vue';
+
+import { globalShareState } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import {
+  ElButton,
+  ElCheckbox,
+  ElCheckboxGroup,
+  ElDivider,
+  ElInput,
+  ElInputNumber,
+  ElNotification,
+  ElRadioGroup,
+  ElSelect,
+  ElSpace,
+  ElSwitch,
+  ElTimePicker,
+  ElTreeSelect,
+  ElUpload,
+} from 'element-plus';
+
+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);
+  };
+};
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type ComponentType =
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'Divider'
+  | 'Input'
+  | 'InputNumber'
+  | 'RadioGroup'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+async function initComponentAdapter() {
+  const components: Partial<Record<ComponentType, Component>> = {
+    // 如果你的组件体积比较大,可以使用异步加载
+    // Button: () =>
+    // import('xxx').then((res) => res.Button),
+
+    Checkbox: ElCheckbox,
+    CheckboxGroup: ElCheckboxGroup,
+    // 自定义默认按钮
+    DefaulButton: (props, { attrs, slots }) => {
+      return h(ElButton, { ...props, attrs, type: 'info' }, slots);
+    },
+    // 自定义主要按钮
+    PrimaryButton: (props, { attrs, slots }) => {
+      return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Divider: ElDivider,
+    Input: withDefaultPlaceholder(ElInput, 'input'),
+    InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
+    RadioGroup: ElRadioGroup,
+    Select: withDefaultPlaceholder(ElSelect, 'select'),
+    Space: ElSpace,
+    Switch: ElSwitch,
+    TimePicker: ElTimePicker,
+    TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
+    Upload: ElUpload,
+  };
+
+  // 将组件注册到全局共享状态中
+  globalShareState.setComponents(components);
+
+  // 定义全局共享状态中的消息提示
+  globalShareState.defineMessage({
+    // 复制成功消息提示
+    copyPreferencesSuccess: (title, content) => {
+      ElNotification({
+        title,
+        message: content,
+        position: 'bottom-right',
+        duration: 0,
+        type: 'success',
+      });
+    },
+  });
+}
+
+export { initComponentAdapter };

+ 4 - 72
apps/web-ele/src/adapter/form.ts

@@ -1,82 +1,14 @@
 import type {
-  BaseFormComponentType,
   VbenFormSchema as FormSchema,
   VbenFormProps,
 } from '@vben/common-ui';
 
-import type { Component, SetupContext } from 'vue';
-import { h } from 'vue';
+import type { ComponentType } from './component';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
-import {
-  ElButton,
-  ElCheckbox,
-  ElCheckboxGroup,
-  ElDivider,
-  ElInput,
-  ElInputNumber,
-  ElRadioGroup,
-  ElSelect,
-  ElSpace,
-  ElSwitch,
-  ElTimePicker,
-  ElTreeSelect,
-  ElUpload,
-} from 'element-plus';
-// 业务表单组件适配
-
-export type FormComponentType =
-  | 'Checkbox'
-  | 'CheckboxGroup'
-  | 'DatePicker'
-  | 'Divider'
-  | 'Input'
-  | 'InputNumber'
-  | 'RadioGroup'
-  | 'Select'
-  | 'Space'
-  | 'Switch'
-  | 'TimePicker'
-  | 'TreeSelect'
-  | 'Upload'
-  | BaseFormComponentType;
-
-const withDefaultPlaceholder = <T extends Component>(
-  component: T,
-  type: 'input' | 'select',
-) => {
-  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
-    const placeholder = props?.placeholder || $t(`placeholder.${type}`);
-    return h(component, { ...props, ...attrs, placeholder }, slots);
-  };
-};
-
-// 初始化表单组件,并注册到form组件内部
-setupVbenForm<FormComponentType>({
-  components: {
-    Checkbox: ElCheckbox,
-    CheckboxGroup: ElCheckboxGroup,
-    // 自定义默认的重置按钮
-    DefaultResetActionButton: (props, { attrs, slots }) => {
-      return h(ElButton, { ...props, attrs, type: 'info' }, slots);
-    },
-    // 自定义默认的提交按钮
-    DefaultSubmitActionButton: (props, { attrs, slots }) => {
-      return h(ElButton, { ...props, attrs, type: 'primary' }, slots);
-    },
-    Divider: ElDivider,
-    Input: withDefaultPlaceholder(ElInput, 'input'),
-    InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
-    RadioGroup: ElRadioGroup,
-    Select: withDefaultPlaceholder(ElSelect, 'select'),
-    Space: ElSpace,
-    Switch: ElSwitch,
-    TimePicker: ElTimePicker,
-    TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'),
-    Upload: ElUpload,
-  },
+setupVbenForm<ComponentType>({
   config: {
     modelPropNameMap: {
       Upload: 'fileList',
@@ -98,9 +30,9 @@ setupVbenForm<FormComponentType>({
   },
 });
 
-const useVbenForm = useForm<FormComponentType>;
+const useVbenForm = useForm<ComponentType>;
 
 export { useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<FormComponentType>;
+export type VbenFormSchema = FormSchema<ComponentType>;
 export type { VbenFormProps };

+ 3 - 0
apps/web-ele/src/bootstrap.ts

@@ -7,10 +7,13 @@ import '@vben/styles/ele';
 
 import { setupI18n } from '#/locales';
 
+import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
 import { router } from './router';
 
 async function bootstrap(namespace: string) {
+  // 初始化组件适配器
+  await initComponentAdapter();
   const app = createApp(App);
 
   // 国际化 i18n 配置

+ 1 - 0
apps/web-ele/src/router/routes/core.ts

@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
   {
     component: AuthPageLayout,
     meta: {
+      hideInTab: true,
       title: 'Authentication',
     },
     name: 'Authentication',

+ 103 - 0
apps/web-naive/src/adapter/component/index.ts

@@ -0,0 +1,103 @@
+/**
+ * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
+ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
+ */
+
+import type { BaseFormComponentType } from '@vben/common-ui';
+
+import type { Component, SetupContext } from 'vue';
+import { h } from 'vue';
+
+import { globalShareState } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import {
+  NButton,
+  NCheckbox,
+  NCheckboxGroup,
+  NDatePicker,
+  NDivider,
+  NInput,
+  NInputNumber,
+  NRadioGroup,
+  NSelect,
+  NSpace,
+  NSwitch,
+  NTimePicker,
+  NTreeSelect,
+  NUpload,
+} from 'naive-ui';
+
+import { message } from '#/adapter/naive';
+
+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);
+  };
+};
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type ComponentType =
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'Divider'
+  | 'Input'
+  | 'InputNumber'
+  | 'RadioGroup'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+async function initComponentAdapter() {
+  const components: Partial<Record<ComponentType, Component>> = {
+    // 如果你的组件体积比较大,可以使用异步加载
+    // Button: () =>
+    // import('xxx').then((res) => res.Button),
+
+    Checkbox: NCheckbox,
+    CheckboxGroup: NCheckboxGroup,
+    DatePicker: NDatePicker,
+    // 自定义默认按钮
+    DefaultButton: (props, { attrs, slots }) => {
+      return h(NButton, { ...props, attrs, type: 'info' }, slots);
+    },
+    // 自定义主要按钮
+    PrimaryButton: (props, { attrs, slots }) => {
+      return h(NButton, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Divider: NDivider,
+    Input: withDefaultPlaceholder(NInput, 'input'),
+    InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
+    RadioGroup: NRadioGroup,
+    Select: withDefaultPlaceholder(NSelect, 'select'),
+    Space: NSpace,
+    Switch: NSwitch,
+    TimePicker: NTimePicker,
+    TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
+    Upload: NUpload,
+  };
+
+  // 将组件注册到全局共享状态中
+  globalShareState.setComponents(components);
+
+  // 定义全局共享状态中的消息提示
+  globalShareState.defineMessage({
+    // 复制成功消息提示
+    copyPreferencesSuccess: (title, content) => {
+      message.success(content || title, {
+        duration: 0,
+      });
+    },
+  });
+}
+
+export { initComponentAdapter };

+ 4 - 74
apps/web-naive/src/adapter/form.ts

@@ -1,84 +1,14 @@
 import type {
-  BaseFormComponentType,
   VbenFormSchema as FormSchema,
   VbenFormProps,
 } from '@vben/common-ui';
 
-import type { Component, SetupContext } from 'vue';
-import { h } from 'vue';
+import type { ComponentType } from './component';
 
 import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
-import {
-  NButton,
-  NCheckbox,
-  NCheckboxGroup,
-  NDatePicker,
-  NDivider,
-  NInput,
-  NInputNumber,
-  NRadioGroup,
-  NSelect,
-  NSpace,
-  NSwitch,
-  NTimePicker,
-  NTreeSelect,
-  NUpload,
-} from 'naive-ui';
-// 业务表单组件适配
-
-export type FormComponentType =
-  | 'Checkbox'
-  | 'CheckboxGroup'
-  | 'DatePicker'
-  | 'Divider'
-  | 'Input'
-  | 'InputNumber'
-  | 'RadioGroup'
-  | 'Select'
-  | 'Space'
-  | 'Switch'
-  | 'TimePicker'
-  | 'TreeSelect'
-  | 'Upload'
-  | BaseFormComponentType;
-
-const withDefaultPlaceholder = <T extends Component>(
-  component: T,
-  type: 'input' | 'select',
-) => {
-  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
-    const placeholder = props?.placeholder || $t(`placeholder.${type}`);
-    return h(component, { ...props, ...attrs, placeholder }, slots);
-  };
-};
-
-// 初始化表单组件,并注册到form组件内部
-setupVbenForm<FormComponentType>({
-  components: {
-    Checkbox: NCheckbox,
-    CheckboxGroup: NCheckboxGroup,
-    DatePicker: NDatePicker,
-    // 自定义默认的重置按钮
-    DefaultResetActionButton: (props, { attrs, slots }) => {
-      return h(NButton, { ...props, attrs, type: 'info' }, slots);
-    },
-    // 自定义默认的提交按钮
-    DefaultSubmitActionButton: (props, { attrs, slots }) => {
-      return h(NButton, { ...props, attrs, type: 'primary' }, slots);
-    },
-    Divider: NDivider,
-    Input: withDefaultPlaceholder(NInput, 'input'),
-    InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
-    RadioGroup: NRadioGroup,
-    Select: withDefaultPlaceholder(NSelect, 'select'),
-    Space: NSpace,
-    Switch: NSwitch,
-    TimePicker: NTimePicker,
-    TreeSelect: withDefaultPlaceholder(NTreeSelect, 'select'),
-    Upload: NUpload,
-  },
+setupVbenForm<ComponentType>({
   config: {
     // naive-ui组件不接受onChang事件,所以需要禁用
     disabledOnChangeListener: true,
@@ -107,9 +37,9 @@ setupVbenForm<FormComponentType>({
   },
 });
 
-const useVbenForm = useForm<FormComponentType>;
+const useVbenForm = useForm<ComponentType>;
 
 export { useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<FormComponentType>;
+export type VbenFormSchema = FormSchema<ComponentType>;
 export type { VbenFormProps };

+ 3 - 0
apps/web-naive/src/bootstrap.ts

@@ -6,10 +6,13 @@ import '@vben/styles';
 
 import { setupI18n } from '#/locales';
 
+import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
 import { router } from './router';
 
 async function bootstrap(namespace: string) {
+  // 初始化组件适配器
+  initComponentAdapter();
   const app = createApp(App);
 
   // 国际化 i18n 配置

+ 1 - 0
apps/web-naive/src/router/routes/core.ts

@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
   {
     component: AuthPageLayout,
     meta: {
+      hideInTab: true,
       title: 'Authentication',
     },
     name: 'Authentication',

+ 4 - 0
docs/.vitepress/config/zh.mts

@@ -164,6 +164,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
           link: 'common-ui/vben-form',
           text: 'Form 表单',
         },
+        {
+          link: 'common-ui/vben-vxe-table',
+          text: 'Vxe Table 表格',
+        },
         {
           link: 'common-ui/vben-count-to-animator',
           text: 'CountToAnimator 数字动画',

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

@@ -0,0 +1,127 @@
+/**
+ * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
+ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
+ */
+
+import type { BaseFormComponentType } from '@vben/common-ui';
+
+import type { Component, SetupContext } from 'vue';
+import { h } from 'vue';
+
+import { globalShareState } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import {
+  AutoComplete,
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  DatePicker,
+  Divider,
+  Input,
+  InputNumber,
+  InputPassword,
+  Mentions,
+  notification,
+  Radio,
+  RadioGroup,
+  RangePicker,
+  Rate,
+  Select,
+  Space,
+  Switch,
+  Textarea,
+  TimePicker,
+  TreeSelect,
+  Upload,
+} from 'ant-design-vue';
+
+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);
+  };
+};
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type ComponentType =
+  | 'AutoComplete'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'DefaultButton'
+  | 'Divider'
+  | 'Input'
+  | 'InputNumber'
+  | 'InputPassword'
+  | 'Mentions'
+  | 'PrimaryButton'
+  | 'Radio'
+  | 'RadioGroup'
+  | 'RangePicker'
+  | 'Rate'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'Textarea'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+async function initComponentAdapter() {
+  const components: Partial<Record<ComponentType, Component>> = {
+    // 如果你的组件体积比较大,可以使用异步加载
+    // Button: () =>
+    // import('xxx').then((res) => res.Button),
+
+    AutoComplete,
+    Checkbox,
+    CheckboxGroup,
+    DatePicker,
+    // 自定义默认按钮
+    DefaultButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'default' }, slots);
+    },
+    Divider,
+    Input: withDefaultPlaceholder(Input, 'input'),
+    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
+    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
+    Mentions: withDefaultPlaceholder(Mentions, 'input'),
+    // 自定义主要按钮
+    PrimaryButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Radio,
+    RadioGroup,
+    RangePicker,
+    Rate,
+    Select: withDefaultPlaceholder(Select, 'select'),
+    Space,
+    Switch,
+    Textarea: withDefaultPlaceholder(Textarea, 'input'),
+    TimePicker,
+    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
+    Upload,
+  };
+
+  // 将组件注册到全局共享状态中
+  globalShareState.setComponents(components);
+
+  // 定义全局共享状态中的消息提示
+  globalShareState.defineMessage({
+    // 复制成功消息提示
+    copyPreferencesSuccess: (title, content) => {
+      notification.success({
+        description: content,
+        message: title,
+        placement: 'bottomRight',
+      });
+    },
+  });
+}
+
+export { initComponentAdapter };

+ 10 - 88
docs/src/_env/adapter/form.ts

@@ -1,99 +1,23 @@
 import type {
-  BaseFormComponentType,
   VbenFormSchema as FormSchema,
   VbenFormProps,
 } from '@vben/common-ui';
 
-import { h } from 'vue';
+import type { ComponentType } from './component';
 
 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';
+import { initComponentAdapter } from './component';
 
-// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
-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,
-  },
+initComponentAdapter();
+setupVbenForm<ComponentType>({
   config: {
-    // ant design vue组件库默认都是 v-model:value
     baseModelPropName: 'value',
-
-    // 一些组件是 v-model:checked 或者 v-model:fileList
+    // naive-ui组件不接受onChang事件,所以需要禁用
+    disabledOnChangeListener: true,
+    // naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
+    emptyStateValue: null,
     modelPropNameMap: {
       Checkbox: 'checked',
       Radio: 'checked',
@@ -102,14 +26,12 @@ setupVbenForm<FormComponentType>({
     },
   },
   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]);
@@ -119,9 +41,9 @@ setupVbenForm<FormComponentType>({
   },
 });
 
-const useVbenForm = useForm<FormComponentType>;
+const useVbenForm = useForm<ComponentType>;
 
 export { useVbenForm, z };
 
-export type VbenFormSchema = FormSchema<FormComponentType>;
+export type VbenFormSchema = FormSchema<ComponentType>;
 export type { VbenFormProps };

+ 1 - 1
docs/src/commercial/community.md

@@ -3,7 +3,7 @@
 社区交流群主要是为了方便大家交流,提问,解答问题,分享经验等。偏自助方式,如果你有问题,可以通过以下方式加入社区交流群:
 
 - [QQ频道](https://pd.qq.com/s/16p8lvvob):推荐!!!主要提供问题解答,分享经验等。
-- QQ群:[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),[老群](https://qm.qq.com/q/MEmHoCLbG0),主要使用者的交流群。
+- QQ群:[大群](https://qm.qq.com/q/MEmHoCLbG0),[1群](https://qm.qq.com/q/YacMHPYAMu)、[2群](https://qm.qq.com/q/ajVKZvFICk)、[3群](https://qm.qq.com/q/36zdwThP2E),[4群](https://qm.qq.com/q/sCzSlm3504),主要使用者的交流群。
 - [Discord](https://discord.com/invite/VU62jTecad): 主要提供问题解答,分享经验等。
 
 ::: tip

+ 6 - 0
docs/src/components/common-ui/vben-drawer.md

@@ -14,6 +14,12 @@ outline: deep
 
 :::
 
+::: tip README
+
+下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
+
+:::
+
 ## 基础用法
 
 使用 `useVbenDrawer` 创建最基础的模态框。

+ 99 - 59
docs/src/components/common-ui/vben-form.md

@@ -20,21 +20,74 @@ outline: deep
 
 ### 适配器说明
 
-每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明:
+每个应用都有不同的 UI 框架,所以在应用的 `src/adapter/form` 和 `src/adapter/component` 内部,你可以根据自己的需求,进行组件适配。下面是 `Ant Design Vue` 的适配器示例代码,可根据注释查看说明:
 
-::: details ant design 适配器
+::: details ant design vue 表单适配器
 
 ```ts
 import type {
-  BaseFormComponentType,
   VbenFormSchema as FormSchema,
   VbenFormProps,
 } from '@vben/common-ui';
 
+import type { ComponentType } from './component';
+
+import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+setupVbenForm<ComponentType>({
+  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<ComponentType>;
+
+export { useVbenForm, z };
+export type VbenFormSchema = FormSchema<ComponentType>;
+export type { VbenFormProps };
+```
+
+:::
+
+::: details ant design vue 组件适配器
+
+```ts
+/**
+ * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
+ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
+ */
+
+import type { BaseFormComponentType } from '@vben/common-ui';
+
 import type { Component, SetupContext } from 'vue';
 import { h } from 'vue';
 
-import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
+import { globalShareState } from '@vben/common-ui';
 import { $t } from '@vben/locales';
 
 import {
@@ -48,6 +101,7 @@ import {
   InputNumber,
   InputPassword,
   Mentions,
+  notification,
   Radio,
   RadioGroup,
   RangePicker,
@@ -61,17 +115,29 @@ import {
   Upload,
 } from 'ant-design-vue';
 
+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);
+  };
+};
+
 // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
-export type FormComponentType =
+export type ComponentType =
   | 'AutoComplete'
   | 'Checkbox'
   | 'CheckboxGroup'
   | 'DatePicker'
+  | 'DefaultButton'
   | 'Divider'
   | 'Input'
   | 'InputNumber'
   | 'InputPassword'
   | 'Mentions'
+  | 'PrimaryButton'
   | 'Radio'
   | 'RadioGroup'
   | 'RangePicker'
@@ -85,36 +151,29 @@ export type FormComponentType =
   | 'Upload'
   | BaseFormComponentType;
 
-const withDefaultPlaceholder = <T extends Component>(
-  component: T,
-  type: 'input' | 'select',
-) => {
-  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
-    const placeholder = props?.placeholder || $t(`placeholder.${type}`);
-    return h(component, { ...props, ...attrs, placeholder }, slots);
-  };
-};
+async function initComponentAdapter() {
+  const components: Partial<Record<ComponentType, Component>> = {
+    // 如果你的组件体积比较大,可以使用异步加载
+    // Button: () =>
+    // import('xxx').then((res) => res.Button),
 
-// 初始化表单组件,并注册到form组件内部
-setupVbenForm<FormComponentType>({
-  components: {
     AutoComplete,
     Checkbox,
     CheckboxGroup,
     DatePicker,
-    // 自定义默认的重置按钮
-    DefaultResetActionButton: (props, { attrs, slots }) => {
+    // 自定义默认按钮
+    DefaultButton: (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: withDefaultPlaceholder(Input, 'input'),
     InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
     InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
     Mentions: withDefaultPlaceholder(Mentions, 'input'),
+    // 自定义主要按钮
+    PrimaryButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'primary' }, slots);
+    },
     Radio,
     RadioGroup,
     RangePicker,
@@ -126,44 +185,25 @@ setupVbenForm<FormComponentType>({
     TimePicker,
     TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
     Upload,
-  },
-  config: {
-    // 是否禁用onChange事件监听,naive ui组件库默认不需要监听onChange事件,否则会在控制台报错
-    disabledOnChangeListener: true,
-    // ant design vue组件库默认都是 v-model:value
-    baseModelPropName: 'value',
-    // 一些组件是 v-model:checked 或者 v-model:fileList
-    modelPropNameMap: {
-      Checkbox: 'checked',
-      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 };
+  // 将组件注册到全局共享状态中
+  globalShareState.setComponents(components);
+
+  // 定义全局共享状态中的消息提示
+  globalShareState.defineMessage({
+    // 复制成功消息提示
+    copyPreferencesSuccess: (title, content) => {
+      notification.success({
+        description: content,
+        message: title,
+        placement: 'bottomRight',
+      });
+    },
+  });
+}
 
-export type VbenFormSchema = FormSchema<FormComponentType>;
-export type { VbenFormProps };
+export { initComponentAdapter };
 ```
 
 :::

+ 6 - 0
docs/src/components/common-ui/vben-modal.md

@@ -14,6 +14,12 @@ outline: deep
 
 :::
 
+::: tip README
+
+下方示例代码中的,存在一些国际化、主题色未适配问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
+
+:::
+
 ## 基础用法
 
 使用 `useVbenModal` 创建最基础的模态框。

+ 7 - 0
docs/src/components/common-ui/vben-vxe-table.md

@@ -0,0 +1,7 @@
+---
+outline: deep
+---
+
+# Vben Vxe Table 表格
+
+TODO

+ 1 - 1
docs/src/friend-links/index.md

@@ -1,6 +1,6 @@
 # 友情链接
 
-如果您的网站是与 Vben Admin 相关的,或者也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。
+如果您的网站是与 Vben Admin 相关的,也属于开源项目,欢迎联系我们,我们会将与您的网站加入交换友情链接。
 
 ## 交换方式
 

+ 0 - 1
packages/@core/base/design/src/css/global.css

@@ -94,7 +94,6 @@
   }
 
   /* 只有非mac下才进行调整,mac下使用默认滚动条 */
-
   html:not([data-platform='macOs']) {
     ::-webkit-scrollbar {
       @apply h-[10px] w-[10px];

+ 0 - 0
packages/@core/base/design/src/design-tokens/dark/index.css → packages/@core/base/design/src/design-tokens/dark.css


+ 0 - 0
packages/@core/base/design/src/design-tokens/default/index.css → packages/@core/base/design/src/design-tokens/default.css


+ 2 - 2
packages/@core/base/design/src/design-tokens/index.ts

@@ -1,4 +1,4 @@
-import './default/index.css';
-import './dark/index.css';
+import './default.css';
+import './dark.css';
 
 export {};

+ 1 - 0
packages/@core/base/shared/build.config.ts

@@ -9,5 +9,6 @@ export default defineBuildConfig({
     'src/utils/index',
     'src/color/index',
     'src/cache/index',
+    'src/global-state',
   ],
 });

+ 12 - 2
packages/@core/base/shared/package.json

@@ -11,7 +11,8 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild"
+    "build": "pnpm unbuild",
+    "stub": "pnpm unbuild --stub"
   },
   "files": [
     "dist"
@@ -42,6 +43,11 @@
       "types": "./src/store.ts",
       "development": "./src/store.ts",
       "default": "./dist/store.mjs"
+    },
+    "./global-state": {
+      "types": "./dist/global-state.d.ts",
+      "development": "./src/global-state.ts",
+      "default": "./dist/global-state.mjs"
     }
   },
   "publishConfig": {
@@ -63,8 +69,12 @@
         "default": "./dist/cache/index.mjs"
       },
       "./store": {
-        "types": "./dist/store/index.d.ts",
+        "types": "./dist/store.d.ts",
         "default": "./dist/store.mjs"
+      },
+      "./global-state": {
+        "types": "./dist/global-state.d.ts",
+        "default": "./dist/global-state.mjs"
       }
     }
   },

+ 45 - 0
packages/@core/base/shared/src/global-state.ts

@@ -0,0 +1,45 @@
+/**
+ * 全局复用的变量、组件、配置,各个模块之间共享
+ * 通过单例模式实现,单例必须注意不受请求影响,例如用户信息这些需要根据请求获取的。后续如果有ssr需求,也不会影响
+ */
+
+interface ComponentsState {
+  [key: string]: any;
+}
+
+interface MessageState {
+  copyPreferencesSuccess?: (title: string, content?: string) => void;
+}
+
+export interface IGlobalSharedState {
+  components: ComponentsState;
+  message: MessageState;
+}
+
+class GlobalShareState {
+  #components: ComponentsState = {};
+  #message: MessageState = {};
+
+  /**
+   * 定义框架内部各个场景的消息提示
+   */
+  public defineMessage({ copyPreferencesSuccess }: MessageState) {
+    this.#message = {
+      copyPreferencesSuccess,
+    };
+  }
+
+  public getComponents(): ComponentsState {
+    return this.#components;
+  }
+
+  public getMessage(): MessageState {
+    return this.#message;
+  }
+
+  public setComponents(value: ComponentsState) {
+    this.#components = value;
+  }
+}
+
+export const globalShareState = new GlobalShareState();

+ 0 - 7
packages/@core/composables/src/use-sortable.ts

@@ -10,13 +10,6 @@ function useSortable<T extends HTMLElement>(
       // @ts-expect-error - This is a dynamic import
       'sortablejs/modular/sortable.complete.esm.js'
     );
-    // const { AutoScroll } = await import(
-    //   // @ts-expect-error - This is a dynamic import
-    //   'sortablejs/modular/sortable.core.esm.js'
-    // );
-
-    // Sortable?.default?.mount?.(AutoScroll);
-
     const sortable = Sortable?.default?.create?.(sortableContainer, {
       animation: 300,
       delay: 400,

+ 2 - 2
packages/@core/ui-kit/form-ui/src/components/form-actions.vue

@@ -84,7 +84,7 @@ watch(
     :style="queryFormStyle"
   >
     <component
-      :is="COMPONENT_MAP.DefaultResetActionButton"
+      :is="COMPONENT_MAP.DefaultButton"
       v-if="resetButtonOptions.show"
       class="mr-3"
       type="button"
@@ -95,7 +95,7 @@ watch(
     </component>
 
     <component
-      :is="COMPONENT_MAP.DefaultSubmitActionButton"
+      :is="COMPONENT_MAP.PrimaryButton"
       v-if="submitButtonOptions.show"
       type="button"
       @click="handleSubmit"

+ 6 - 3
packages/@core/ui-kit/form-ui/src/config.ts

@@ -15,6 +15,7 @@ import {
   VbenPinInput,
   VbenSelect,
 } from '@vben-core/shadcn-ui';
+import { globalShareState } from '@vben-core/shared/global-state';
 
 import { defineRule } from 'vee-validate';
 
@@ -23,8 +24,8 @@ const DEFAULT_MODEL_PROP_NAME = 'modelValue';
 export const DEFAULT_FORM_COMMON_CONFIG: FormCommonConfig = {};
 
 export const COMPONENT_MAP: Record<BaseFormComponentType, Component> = {
-  DefaultResetActionButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
-  DefaultSubmitActionButton: h(VbenButton, { size: 'sm', variant: 'default' }),
+  DefaultButton: h(VbenButton, { size: 'sm', variant: 'outline' }),
+  PrimaryButton: h(VbenButton, { size: 'sm', variant: 'default' }),
   VbenCheckbox,
   VbenInput,
   VbenInputPassword,
@@ -41,7 +42,7 @@ export const COMPONENT_BIND_EVENT_MAP: Partial<
 export function setupVbenForm<
   T extends BaseFormComponentType = BaseFormComponentType,
 >(options: VbenFormAdapterOptions<T>) {
-  const { components, config, defineRules } = options;
+  const { config, defineRules } = options;
 
   const { disabledOnChangeListener = false, emptyStateValue = undefined } =
     (config || {}) as FormCommonConfig;
@@ -63,6 +64,8 @@ export function setupVbenForm<
     | Record<BaseFormComponentType, string>
     | undefined;
 
+  const components = globalShareState.getComponents();
+
   for (const component of Object.keys(components)) {
     const key = component as BaseFormComponentType;
     COMPONENT_MAP[key] = components[component as never];

+ 2 - 3
packages/@core/ui-kit/form-ui/src/types.ts

@@ -9,8 +9,8 @@ import type { Component, HtmlHTMLAttributes, Ref } from 'vue';
 export type FormLayout = 'horizontal' | 'vertical';
 
 export type BaseFormComponentType =
-  | 'DefaultResetActionButton'
-  | 'DefaultSubmitActionButton'
+  | 'DefaultButton'
+  | 'PrimaryButton'
   | 'VbenCheckbox'
   | 'VbenInput'
   | 'VbenInputPassword'
@@ -341,7 +341,6 @@ export type ExtendedFormApi = {
 export interface VbenFormAdapterOptions<
   T extends BaseFormComponentType = BaseFormComponentType,
 > {
-  components: Partial<Record<T, Component>>;
   config?: {
     baseModelPropName?: string;
     disabledOnChangeListener?: boolean;

+ 10 - 4
packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue

@@ -23,6 +23,7 @@ import {
   VbenLoading,
   VisuallyHidden,
 } from '@vben-core/shadcn-ui';
+import { globalShareState } from '@vben-core/shared/global-state';
 import { cn } from '@vben-core/shared/utils';
 
 interface Props extends DrawerProps {
@@ -33,6 +34,8 @@ const props = withDefaults(defineProps<Props>(), {
   drawerApi: undefined,
 });
 
+const components = globalShareState.getComponents();
+
 const id = useId();
 provide('DISMISSABLE_DRAWER_ID', id);
 
@@ -187,7 +190,8 @@ function handleFocusOutside(e: Event) {
       >
         <slot name="prepend-footer"></slot>
         <slot name="footer">
-          <VbenButton
+          <component
+            :is="components.DefaultButton || VbenButton"
             v-if="showCancelButton"
             variant="ghost"
             @click="() => drawerApi?.onCancel()"
@@ -195,8 +199,10 @@ function handleFocusOutside(e: Event) {
             <slot name="cancelText">
               {{ cancelText || $t('cancel') }}
             </slot>
-          </VbenButton>
-          <VbenButton
+          </component>
+
+          <component
+            :is="components.PrimaryButton || VbenButton"
             v-if="showConfirmButton"
             :loading="confirmLoading"
             @click="() => drawerApi?.onConfirm()"
@@ -204,7 +210,7 @@ function handleFocusOutside(e: Event) {
             <slot name="confirmText">
               {{ confirmText || $t('confirm') }}
             </slot>
-          </VbenButton>
+          </component>
         </slot>
         <slot name="append-footer"></slot>
       </SheetFooter>

+ 10 - 4
packages/@core/ui-kit/popup-ui/src/modal/modal.vue

@@ -22,6 +22,7 @@ import {
   VbenLoading,
   VisuallyHidden,
 } from '@vben-core/shadcn-ui';
+import { globalShareState } from '@vben-core/shared/global-state';
 import { cn } from '@vben-core/shared/utils';
 
 import { useModalDraggable } from './use-modal-draggable';
@@ -34,6 +35,8 @@ const props = withDefaults(defineProps<Props>(), {
   modalApi: undefined,
 });
 
+const components = globalShareState.getComponents();
+
 const contentRef = ref();
 const wrapperRef = ref<HTMLElement>();
 const dialogRef = ref();
@@ -256,7 +259,8 @@ function handleFocusOutside(e: Event) {
       >
         <slot name="prepend-footer"></slot>
         <slot name="footer">
-          <VbenButton
+          <component
+            :is="components.DefaultButton || VbenButton"
             v-if="showCancelButton"
             variant="ghost"
             @click="() => modalApi?.onCancel()"
@@ -264,8 +268,10 @@ function handleFocusOutside(e: Event) {
             <slot name="cancelText">
               {{ cancelText || $t('cancel') }}
             </slot>
-          </VbenButton>
-          <VbenButton
+          </component>
+
+          <component
+            :is="components.PrimaryButton || VbenButton"
             v-if="showConfirmButton"
             :loading="confirmLoading"
             @click="() => modalApi?.onConfirm()"
@@ -273,7 +279,7 @@ function handleFocusOutside(e: Event) {
             <slot name="confirmText">
               {{ confirmText || $t('confirm') }}
             </slot>
-          </VbenButton>
+          </component>
         </slot>
         <slot name="append-footer"></slot>
       </DialogFooter>

+ 0 - 1
packages/@core/ui-kit/shadcn-ui/src/ui/index.ts

@@ -23,7 +23,6 @@ export * from './sheet';
 export * from './switch';
 export * from './tabs';
 export * from './textarea';
-export * from './toast';
 export * from './toggle';
 export * from './toggle-group';
 export * from './tooltip';

+ 0 - 35
packages/@core/ui-kit/shadcn-ui/src/ui/toast/Toast.vue

@@ -1,35 +0,0 @@
-<script setup lang="ts">
-import { computed } from 'vue';
-
-import { cn } from '@vben-core/shared/utils';
-
-import {
-  ToastRoot,
-  type ToastRootEmits,
-  useForwardPropsEmits,
-} from 'radix-vue';
-
-import { type ToastProps, toastVariants } from './toast';
-
-const props = defineProps<ToastProps>();
-
-const emits = defineEmits<ToastRootEmits>();
-
-const delegatedProps = computed(() => {
-  const { class: _, ...delegated } = props;
-
-  return delegated;
-});
-
-const forwarded = useForwardPropsEmits(delegatedProps, emits);
-</script>
-
-<template>
-  <ToastRoot
-    v-bind="forwarded"
-    :class="cn(toastVariants({ variant }), props.class)"
-    @update:open="onOpenChange"
-  >
-    <slot></slot>
-  </ToastRoot>
-</template>

+ 0 - 29
packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastAction.vue

@@ -1,29 +0,0 @@
-<script setup lang="ts">
-import { computed } from 'vue';
-
-import { cn } from '@vben-core/shared/utils';
-
-import { ToastAction, type ToastActionProps } from 'radix-vue';
-
-const props = defineProps<{ class?: any } & ToastActionProps>();
-
-const delegatedProps = computed(() => {
-  const { class: _, ...delegated } = props;
-
-  return delegated;
-});
-</script>
-
-<template>
-  <ToastAction
-    v-bind="delegatedProps"
-    :class="
-      cn(
-        'hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive border-border inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:outline-none focus:ring-1 disabled:pointer-events-none disabled:opacity-50',
-        props.class,
-      )
-    "
-  >
-    <slot></slot>
-  </ToastAction>
-</template>

+ 0 - 34
packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastClose.vue

@@ -1,34 +0,0 @@
-<script setup lang="ts">
-import { computed } from 'vue';
-
-import { cn } from '@vben-core/shared/utils';
-
-import { X } from 'lucide-vue-next';
-import { ToastClose, type ToastCloseProps } from 'radix-vue';
-
-const props = defineProps<
-  {
-    class?: any;
-  } & ToastCloseProps
->();
-
-const delegatedProps = computed(() => {
-  const { class: _, ...delegated } = props;
-
-  return delegated;
-});
-</script>
-
-<template>
-  <ToastClose
-    v-bind="delegatedProps"
-    :class="
-      cn(
-        'text-foreground/50 hover:text-foreground absolute right-1 top-1 rounded-md p-1 opacity-0 transition-opacity focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
-        props.class,
-      )
-    "
-  >
-    <X class="size-4" />
-  </ToastClose>
-</template>

+ 0 - 24
packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastDescription.vue

@@ -1,24 +0,0 @@
-<script setup lang="ts">
-import { computed } from 'vue';
-
-import { cn } from '@vben-core/shared/utils';
-
-import { ToastDescription, type ToastDescriptionProps } from 'radix-vue';
-
-const props = defineProps<{ class?: any } & ToastDescriptionProps>();
-
-const delegatedProps = computed(() => {
-  const { class: _, ...delegated } = props;
-
-  return delegated;
-});
-</script>
-
-<template>
-  <ToastDescription
-    :class="cn('text-sm opacity-90', props.class)"
-    v-bind="delegatedProps"
-  >
-    <slot></slot>
-  </ToastDescription>
-</template>

+ 0 - 11
packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastProvider.vue

@@ -1,11 +0,0 @@
-<script setup lang="ts">
-import { ToastProvider, type ToastProviderProps } from 'radix-vue';
-
-const props = defineProps<ToastProviderProps>();
-</script>
-
-<template>
-  <ToastProvider v-bind="props">
-    <slot></slot>
-  </ToastProvider>
-</template>

+ 0 - 24
packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastTitle.vue

@@ -1,24 +0,0 @@
-<script setup lang="ts">
-import { computed } from 'vue';
-
-import { cn } from '@vben-core/shared/utils';
-
-import { ToastTitle, type ToastTitleProps } from 'radix-vue';
-
-const props = defineProps<{ class?: any } & ToastTitleProps>();
-
-const delegatedProps = computed(() => {
-  const { class: _, ...delegated } = props;
-
-  return delegated;
-});
-</script>
-
-<template>
-  <ToastTitle
-    v-bind="delegatedProps"
-    :class="cn('text-sm font-semibold [&+div]:text-xs', props.class)"
-  >
-    <slot></slot>
-  </ToastTitle>
-</template>

+ 0 - 27
packages/@core/ui-kit/shadcn-ui/src/ui/toast/ToastViewport.vue

@@ -1,27 +0,0 @@
-<script setup lang="ts">
-import { computed } from 'vue';
-
-import { cn } from '@vben-core/shared/utils';
-
-import { ToastViewport, type ToastViewportProps } from 'radix-vue';
-
-const props = defineProps<{ class?: any } & ToastViewportProps>();
-
-const delegatedProps = computed(() => {
-  const { class: _, ...delegated } = props;
-
-  return delegated;
-});
-</script>
-
-<template>
-  <ToastViewport
-    v-bind="delegatedProps"
-    :class="
-      cn(
-        'fixed top-0 z-[1200] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
-        props.class,
-      )
-    "
-  />
-</template>

+ 0 - 36
packages/@core/ui-kit/shadcn-ui/src/ui/toast/Toaster.vue

@@ -1,36 +0,0 @@
-<script setup lang="ts">
-import { isVNode } from 'vue';
-
-import Toast from './Toast.vue';
-import ToastClose from './ToastClose.vue';
-import ToastDescription from './ToastDescription.vue';
-import ToastProvider from './ToastProvider.vue';
-import ToastTitle from './ToastTitle.vue';
-import ToastViewport from './ToastViewport.vue';
-import { useToast } from './use-toast';
-
-const { toasts } = useToast();
-</script>
-
-<template>
-  <ToastProvider swipe-direction="down">
-    <Toast v-for="toast in toasts" :key="toast.id" v-bind="toast">
-      <div class="grid gap-1">
-        <ToastTitle v-if="toast.title">
-          {{ toast.title }}
-        </ToastTitle>
-        <template v-if="toast.description">
-          <ToastDescription v-if="isVNode(toast.description)">
-            <component :is="toast.description" />
-          </ToastDescription>
-          <ToastDescription v-else>
-            {{ toast.description }}
-          </ToastDescription>
-        </template>
-        <ToastClose />
-      </div>
-      <component :is="toast.action" />
-    </Toast>
-    <ToastViewport />
-  </ToastProvider>
-</template>

+ 0 - 11
packages/@core/ui-kit/shadcn-ui/src/ui/toast/index.ts

@@ -1,11 +0,0 @@
-export * from './toast';
-export { default as Toast } from './Toast.vue';
-export { default as ToastAction } from './ToastAction.vue';
-export { default as ToastClose } from './ToastClose.vue';
-export { default as ToastDescription } from './ToastDescription.vue';
-export { default as Toaster } from './Toaster.vue';
-export { default as ToastProvider } from './ToastProvider.vue';
-export { default as ToastTitle } from './ToastTitle.vue';
-export { default as ToastViewport } from './ToastViewport.vue';
-
-export { toast, useToast } from './use-toast';

+ 0 - 27
packages/@core/ui-kit/shadcn-ui/src/ui/toast/toast.ts

@@ -1,27 +0,0 @@
-import type { ToastRootProps } from 'radix-vue';
-
-import { cva, type VariantProps } from 'class-variance-authority';
-
-export const toastVariants = cva(
-  'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
-  {
-    defaultVariants: {
-      variant: 'default',
-    },
-    variants: {
-      variant: {
-        default: 'border bg-background border-border text-foreground',
-        destructive:
-          'destructive group border-destructive bg-destructive text-destructive-foreground',
-      },
-    },
-  },
-);
-
-type ToastVariants = VariantProps<typeof toastVariants>;
-
-export interface ToastProps extends ToastRootProps {
-  class?: any;
-  onOpenChange?: ((value: boolean) => void) | undefined;
-  variant?: ToastVariants['variant'];
-}

+ 0 - 168
packages/@core/ui-kit/shadcn-ui/src/ui/toast/use-toast.ts

@@ -1,168 +0,0 @@
-import type { ToastProps } from './toast';
-
-import { computed, ref } from 'vue';
-import type { Component, VNode } from 'vue';
-
-const TOAST_LIMIT = 1;
-const TOAST_REMOVE_DELAY = 1_000_000;
-
-export type StringOrVNode = (() => VNode) | string | VNode;
-
-type ToasterToast = {
-  action?: Component;
-  description?: StringOrVNode;
-  id: string;
-  title?: string;
-} & ToastProps;
-
-const actionTypes = {
-  ADD_TOAST: 'ADD_TOAST',
-  DISMISS_TOAST: 'DISMISS_TOAST',
-  REMOVE_TOAST: 'REMOVE_TOAST',
-  UPDATE_TOAST: 'UPDATE_TOAST',
-} as const;
-
-let count = 0;
-
-function genId() {
-  count = (count + 1) % Number.MAX_VALUE;
-  return count.toString();
-}
-
-type ActionType = typeof actionTypes;
-
-type Action =
-  | {
-      toast: Partial<ToasterToast>;
-      type: ActionType['UPDATE_TOAST'];
-    }
-  | {
-      toast: ToasterToast;
-      type: ActionType['ADD_TOAST'];
-    }
-  | {
-      toastId?: ToasterToast['id'];
-      type: ActionType['DISMISS_TOAST'];
-    }
-  | {
-      toastId?: ToasterToast['id'];
-      type: ActionType['REMOVE_TOAST'];
-    };
-
-interface State {
-  toasts: ToasterToast[];
-}
-
-const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
-
-function addToRemoveQueue(toastId: string) {
-  if (toastTimeouts.has(toastId)) return;
-
-  const timeout = setTimeout(() => {
-    toastTimeouts.delete(toastId);
-    dispatch({
-      toastId,
-      type: actionTypes.REMOVE_TOAST,
-    });
-  }, TOAST_REMOVE_DELAY);
-
-  toastTimeouts.set(toastId, timeout);
-}
-
-const state = ref<State>({
-  toasts: [],
-});
-
-function dispatch(action: Action) {
-  switch (action.type) {
-    case actionTypes.ADD_TOAST: {
-      state.value.toasts = [action.toast, ...state.value.toasts].slice(
-        0,
-        TOAST_LIMIT,
-      );
-      break;
-    }
-
-    case actionTypes.UPDATE_TOAST: {
-      state.value.toasts = state.value.toasts.map((t) =>
-        t.id === action.toast.id ? { ...t, ...action.toast } : t,
-      );
-      break;
-    }
-
-    case actionTypes.DISMISS_TOAST: {
-      const { toastId } = action;
-
-      if (toastId) {
-        addToRemoveQueue(toastId);
-      } else {
-        state.value.toasts.forEach((toast) => {
-          addToRemoveQueue(toast.id);
-        });
-      }
-
-      state.value.toasts = state.value.toasts.map((t) =>
-        t.id === toastId || toastId === undefined
-          ? {
-              ...t,
-              open: false,
-            }
-          : t,
-      );
-      break;
-    }
-
-    case actionTypes.REMOVE_TOAST: {
-      state.value.toasts =
-        action.toastId === undefined
-          ? []
-          : state.value.toasts.filter((t) => t.id !== action.toastId);
-
-      break;
-    }
-  }
-}
-
-function useToast() {
-  return {
-    dismiss: (toastId?: string) =>
-      dispatch({ toastId, type: actionTypes.DISMISS_TOAST }),
-    toast,
-    toasts: computed(() => state.value.toasts),
-  };
-}
-
-type Toast = Omit<ToasterToast, 'id'>;
-
-function toast(props: Toast) {
-  const id = genId();
-
-  const update = (props: ToasterToast) =>
-    dispatch({
-      toast: { ...props, id },
-      type: actionTypes.UPDATE_TOAST,
-    });
-
-  const dismiss = () =>
-    dispatch({ toastId: id, type: actionTypes.DISMISS_TOAST });
-
-  dispatch({
-    toast: {
-      ...props,
-      id,
-      onOpenChange: (open: boolean) => {
-        if (!open) dismiss();
-      },
-      open: true,
-    },
-    type: actionTypes.ADD_TOAST,
-  });
-
-  return {
-    dismiss,
-    id,
-    update,
-  };
-}
-
-export { toast, useToast };

+ 2 - 0
packages/effects/common-ui/src/components/index.ts

@@ -13,3 +13,5 @@ export {
   VbenPinInput,
   VbenSpinner,
 } from '@vben-core/shadcn-ui';
+
+export { globalShareState } from '@vben-core/shared/global-state';

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

@@ -1,3 +1,2 @@
 export * from './components';
 export * from './ui';
-export { useToast } from '@vben-core/shadcn-ui';

+ 1 - 2
packages/effects/layouts/src/basic/layout.vue

@@ -13,7 +13,7 @@ import {
 import { useLockStore } from '@vben/stores';
 import { deepToRaw, mapTree } from '@vben/utils';
 import { VbenAdminLayout } from '@vben-core/layout-ui';
-import { Toaster, VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
+import { VbenBackTop, VbenLogo } from '@vben-core/shadcn-ui';
 
 import { Breadcrumb, CheckUpdates, Preferences } from '../widgets';
 import { LayoutContent, LayoutContentSpinner } from './content';
@@ -312,7 +312,6 @@ const headerSlots = computed(() => {
 
     <template #extra>
       <slot name="extra"></slot>
-      <Toaster />
       <CheckUpdates
         v-if="preferences.app.enableCheckUpdates"
         :check-updates-interval="preferences.app.checkUpdatesInterval"

+ 29 - 37
packages/effects/layouts/src/widgets/check-updates/check-updates.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import { h, onMounted, onUnmounted, ref } from 'vue';
+import { onMounted, onUnmounted, ref } from 'vue';
 
 import { $t } from '@vben/locales';
-import { ToastAction, useToast } from '@vben-core/shadcn-ui';
+import { useVbenModal } from '@vben-core/popup-ui';
 
 interface Props {
   // 轮训时间,分钟
@@ -18,10 +18,21 @@ const props = withDefaults(defineProps<Props>(), {
   checkUpdateUrl: import.meta.env.BASE_URL || '/',
 });
 
-const lastVersionTag = ref('');
 let isCheckingUpdates = false;
+const currentVersionTag = ref('');
+const lastVersionTag = ref('');
 const timer = ref<ReturnType<typeof setInterval>>();
-const { toast } = useToast();
+
+const [UpdateNoticeModal, modalApi] = useVbenModal({
+  closable: false,
+  closeOnPressEscape: false,
+  closeOnClickModal: false,
+  onConfirm() {
+    lastVersionTag.value = currentVersionTag.value;
+    window.location.reload();
+    // handleSubmitLogout();
+  },
+});
 
 async function getVersionTag() {
   try {
@@ -63,38 +74,8 @@ async function checkForUpdates() {
   }
 }
 function handleNotice(versionTag: string) {
-  const { dismiss } = toast({
-    action: h('div', { class: 'inline-flex items-center' }, [
-      h(
-        ToastAction,
-        {
-          altText: $t('common.cancel'),
-          onClick: () => dismiss(),
-        },
-        {
-          default: () => $t('common.cancel'),
-        },
-      ),
-      h(
-        ToastAction,
-        {
-          altText: $t('common.refresh'),
-          class:
-            'bg-primary text-primary-foreground hover:bg-primary-hover mx-1',
-          onClick: () => {
-            lastVersionTag.value = versionTag;
-            window.location.reload();
-          },
-        },
-        {
-          default: () => $t('common.refresh'),
-        },
-      ),
-    ]),
-    description: $t('widgets.checkUpdatesDescription'),
-    duration: 0,
-    title: $t('widgets.checkUpdatesTitle'),
-  });
+  currentVersionTag.value = versionTag;
+  modalApi.open();
 }
 
 function start() {
@@ -138,5 +119,16 @@ onUnmounted(() => {
 });
 </script>
 <template>
-  <slot></slot>
+  <UpdateNoticeModal
+    :cancel-text="$t('common.cancel')"
+    :confirm-text="$t('common.refresh')"
+    :fullscreen-button="false"
+    :title="$t('widgets.checkUpdatesTitle')"
+    centered
+    content-class="px-8 min-h-10"
+    footer-class="border-none mb-3 mr-3"
+    header-class="border-none"
+  >
+    {{ $t('widgets.checkUpdatesDescription') }}
+  </UpdateNoticeModal>
 </template>

+ 8 - 10
packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue

@@ -24,11 +24,11 @@ import {
 } from '@vben/preferences';
 import { useVbenDrawer } from '@vben-core/popup-ui';
 import {
-  useToast,
   VbenButton,
   VbenIconButton,
   VbenSegmented,
 } from '@vben-core/shadcn-ui';
+import { globalShareState } from '@vben-core/shared/global-state';
 
 import { useClipboard } from '@vueuse/core';
 
@@ -54,7 +54,9 @@ import {
 } from './blocks';
 
 const emit = defineEmits<{ clearPreferencesAndLogout: [] }>();
-const { toast } = useToast();
+
+const message = globalShareState.getMessage();
+
 const appLocale = defineModel<SupportedLanguagesType>('appLocale');
 const appDynamicTitle = defineModel<boolean>('appDynamicTitle');
 const appLayout = defineModel<LayoutType>('appLayout');
@@ -196,10 +198,10 @@ const showBreadcrumbConfig = computed(() => {
 async function handleCopy() {
   await copy(JSON.stringify(diffPreference.value, null, 2));
 
-  toast({
-    description: $t('preferences.copyPreferences'),
-    title: $t('preferences.copyPreferencesSuccess'),
-  });
+  message.copyPreferencesSuccess?.(
+    $t('preferences.copyPreferencesSuccessTitle'),
+    $t('preferences.copyPreferencesSuccess'),
+  );
 }
 
 async function handleClearCache() {
@@ -214,10 +216,6 @@ async function handleReset() {
   }
   resetPreferences();
   await loadLocaleMessages(preferences.app.locale);
-  toast({
-    description: $t('preferences.resetTitle'),
-    title: $t('preferences.resetSuccess'),
-  });
 }
 </script>
 

+ 1 - 1
packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue

@@ -149,7 +149,7 @@ if (enableShortcutKey.value) {
     :title="$t('common.prompt')"
     centered
     content-class="px-8 min-h-10"
-    footer-class="border-none mb-4 mr-4"
+    footer-class="border-none mb-3 mr-3"
     header-class="border-none"
   >
     {{ $t('widgets.logoutTip') }}

+ 1 - 1
packages/locales/src/i18n.ts

@@ -35,7 +35,7 @@ function loadLocalesMap(modules: Record<string, () => Promise<unknown>>) {
   const localesMap: Record<Locale, ImportLocaleFn> = {};
 
   for (const [path, loadLocale] of Object.entries(modules)) {
-    const key = path.match(/([\w-]*)\.(yaml|yml|json)/)?.[1];
+    const key = path.match(/([\w-]*)\.(json)/)?.[1];
     if (key) {
       localesMap[key] = loadLocale as ImportLocaleFn;
     }

+ 1 - 0
packages/locales/src/langs/en-US.json

@@ -178,6 +178,7 @@
     "plain": "Plain",
     "rounded": "Rounded",
     "copyPreferences": "Copy Preferences",
+    "copyPreferencesSuccessTitle": "Copy successful",
     "copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app",
     "clearAndLogout": "Clear Cache & Logout",
     "mode": "Mode",

+ 1 - 0
packages/locales/src/langs/zh-CN.json

@@ -178,6 +178,7 @@
     "plain": "朴素",
     "rounded": "圆润",
     "copyPreferences": "复制偏好设置",
+    "copyPreferencesSuccessTitle": "复制成功",
     "copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖",
     "clearAndLogout": "清空缓存 & 退出登录",
     "mode": "模式",

+ 2 - 1
packages/stores/src/modules/tabbar.ts

@@ -523,7 +523,8 @@ function isAffixTab(tab: TabDefinition) {
  * @param tab
  */
 function isTabShown(tab: TabDefinition) {
-  return !tab.meta.hideInTab;
+  const matched = tab?.matched ?? [];
+  return !tab.meta.hideInTab && matched.every((item) => !item.meta.hideInTab);
 }
 
 /**

+ 0 - 1
packages/stores/src/setup.ts

@@ -37,7 +37,6 @@ export function resetAllStores() {
     return;
   }
   const allStores = (pinia as any)._s;
-
   for (const [_key, store] of allStores) {
     store.$reset();
   }

+ 127 - 0
playground/src/adapter/component/index.ts

@@ -0,0 +1,127 @@
+/**
+ * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
+ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
+ */
+
+import type { BaseFormComponentType } from '@vben/common-ui';
+
+import type { Component, SetupContext } from 'vue';
+import { h } from 'vue';
+
+import { globalShareState } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import {
+  AutoComplete,
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  DatePicker,
+  Divider,
+  Input,
+  InputNumber,
+  InputPassword,
+  Mentions,
+  notification,
+  Radio,
+  RadioGroup,
+  RangePicker,
+  Rate,
+  Select,
+  Space,
+  Switch,
+  Textarea,
+  TimePicker,
+  TreeSelect,
+  Upload,
+} from 'ant-design-vue';
+
+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);
+  };
+};
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type ComponentType =
+  | 'AutoComplete'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'DefaultButton'
+  | 'Divider'
+  | 'Input'
+  | 'InputNumber'
+  | 'InputPassword'
+  | 'Mentions'
+  | 'PrimaryButton'
+  | 'Radio'
+  | 'RadioGroup'
+  | 'RangePicker'
+  | 'Rate'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'Textarea'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+async function initComponentAdapter() {
+  const components: Partial<Record<ComponentType, Component>> = {
+    // 如果你的组件体积比较大,可以使用异步加载
+    // Button: () =>
+    // import('xxx').then((res) => res.Button),
+
+    AutoComplete,
+    Checkbox,
+    CheckboxGroup,
+    DatePicker,
+    // 自定义默认按钮
+    DefaultButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'default' }, slots);
+    },
+    Divider,
+    Input: withDefaultPlaceholder(Input, 'input'),
+    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
+    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
+    Mentions: withDefaultPlaceholder(Mentions, 'input'),
+    // 自定义主要按钮
+    PrimaryButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Radio,
+    RadioGroup,
+    RangePicker,
+    Rate,
+    Select: withDefaultPlaceholder(Select, 'select'),
+    Space,
+    Switch,
+    Textarea: withDefaultPlaceholder(Textarea, 'input'),
+    TimePicker,
+    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
+    Upload,
+  };
+
+  // 将组件注册到全局共享状态中
+  globalShareState.setComponents(components);
+
+  // 定义全局共享状态中的消息提示
+  globalShareState.defineMessage({
+    // 复制成功消息提示
+    copyPreferencesSuccess: (title, content) => {
+      notification.success({
+        description: content,
+        message: title,
+        placement: 'bottomRight',
+      });
+    },
+  });
+}
+
+export { initComponentAdapter };

+ 4 - 97
playground/src/adapter/form.ts

@@ -1,109 +1,17 @@
 import type {
-  BaseFormComponentType,
   VbenFormSchema as FormSchema,
   VbenFormProps,
 } from '@vben/common-ui';
 
-import type { Component, SetupContext } from 'vue';
-import { h } from 'vue';
+import type { ComponentType } from './component';
 
 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;
-
-const withDefaultPlaceholder = <T extends Component>(
-  component: T,
-  type: 'input' | 'select',
-) => {
-  return (props: any, { attrs, slots }: Omit<SetupContext, 'expose'>) => {
-    const placeholder = props?.placeholder || $t(`placeholder.${type}`);
-    return h(component, { ...props, ...attrs, placeholder }, slots);
-  };
-};
-
-// 初始化表单组件,并注册到form组件内部
-setupVbenForm<FormComponentType>({
-  components: {
-    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: withDefaultPlaceholder(Input, 'input'),
-    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
-    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
-    Mentions: withDefaultPlaceholder(Mentions, 'input'),
-    Radio,
-    RadioGroup,
-    RangePicker,
-    Rate,
-    Select: withDefaultPlaceholder(Select, 'select'),
-    Space,
-    Switch,
-    Textarea: withDefaultPlaceholder(Textarea, 'input'),
-    TimePicker,
-    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
-    Upload,
-  },
+setupVbenForm<ComponentType>({
   config: {
     // ant design vue组件库默认都是 v-model:value
     baseModelPropName: 'value',
-
     // 一些组件是 v-model:checked 或者 v-model:fileList
     modelPropNameMap: {
       Checkbox: 'checked',
@@ -130,9 +38,8 @@ setupVbenForm<FormComponentType>({
   },
 });
 
-const useVbenForm = useForm<FormComponentType>;
+const useVbenForm = useForm<ComponentType>;
 
 export { useVbenForm, z };
-
-export type VbenFormSchema = FormSchema<FormComponentType>;
+export type VbenFormSchema = FormSchema<ComponentType>;
 export type { VbenFormProps };

+ 4 - 0
playground/src/bootstrap.ts

@@ -9,10 +9,14 @@ import { VueQueryPlugin } from '@tanstack/vue-query';
 
 import { setupI18n } from '#/locales';
 
+import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
 import { router } from './router';
 
 async function bootstrap(namespace: string) {
+  // 初始化组件适配器
+  await initComponentAdapter();
+
   const app = createApp(App);
 
   // 国际化 i18n 配置

+ 1 - 0
playground/src/router/routes/core.ts

@@ -32,6 +32,7 @@ const coreRoutes: RouteRecordRaw[] = [
   {
     component: AuthPageLayout,
     meta: {
+      hideInTab: true,
       title: 'Authentication',
     },
     name: 'Authentication',

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 200 - 271
pnpm-lock.yaml


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio