瀏覽代碼

feat: 合并

DESKTOP-USV654P\pc 1 年之前
父節點
當前提交
9ea40232c6
共有 100 個文件被更改,包括 2024 次插入387 次删除
  1. 1 0
      .vscode/settings.json
  2. 1 1
      apps/web-antd/package.json
  3. 2 2
      apps/web-antd/src/adapter/vxe-table.ts
  4. 15 2
      apps/web-antd/src/bootstrap.ts
  5. 1 0
      apps/web-antd/src/preferences.ts
  6. 0 10
      apps/web-antd/src/router/guard.ts
  7. 1 1
      apps/web-baicai/package.json
  8. 3 3
      apps/web-baicai/src/adapter/vxe-table.ts
  9. 1 1
      apps/web-baicai/src/api/core/auth.ts
  10. 20 3
      apps/web-baicai/src/bootstrap.ts
  11. 2 2
      apps/web-baicai/src/layouts/basic.vue
  12. 1 0
      apps/web-baicai/src/preferences.ts
  13. 0 10
      apps/web-baicai/src/router/guard.ts
  14. 11 6
      apps/web-baicai/src/store/auth.ts
  15. 1 1
      apps/web-ele/package.json
  16. 2 2
      apps/web-ele/src/adapter/vxe-table.ts
  17. 15 2
      apps/web-ele/src/bootstrap.ts
  18. 1 0
      apps/web-ele/src/preferences.ts
  19. 0 10
      apps/web-ele/src/router/guard.ts
  20. 1 1
      apps/web-naive/package.json
  21. 2 2
      apps/web-naive/src/adapter/vxe-table.ts
  22. 15 2
      apps/web-naive/src/bootstrap.ts
  23. 1 0
      apps/web-naive/src/preferences.ts
  24. 0 10
      apps/web-naive/src/router/guard.ts
  25. 6 1
      docs/.vitepress/config/shared.mts
  26. 2 1
      docs/.vitepress/theme/index.ts
  27. 6 2
      docs/package.json
  28. 70 0
      docs/src/_env/adapter/vxe-table.ts
  29. 4 0
      docs/src/_env/node/adapter/form.ts
  30. 3 0
      docs/src/_env/node/adapter/vxe-table.ts
  31. 232 1
      docs/src/components/common-ui/vben-vxe-table.md
  32. 85 0
      docs/src/demos/vben-vxe-table/basic/index.vue
  33. 105 0
      docs/src/demos/vben-vxe-table/custom-cell/index.vue
  34. 55 0
      docs/src/demos/vben-vxe-table/edit-cell/index.vue
  35. 92 0
      docs/src/demos/vben-vxe-table/edit-row/index.vue
  36. 67 0
      docs/src/demos/vben-vxe-table/fixed/index.vue
  37. 120 0
      docs/src/demos/vben-vxe-table/form/index.vue
  38. 36 0
      docs/src/demos/vben-vxe-table/mock-api.ts
  39. 112 0
      docs/src/demos/vben-vxe-table/remote/index.vue
  40. 384 0
      docs/src/demos/vben-vxe-table/table-data.ts
  41. 80 0
      docs/src/demos/vben-vxe-table/tree/index.vue
  42. 64 0
      docs/src/demos/vben-vxe-table/virtual/index.vue
  43. 6 0
      docs/src/en/guide/essentials/development.md
  44. 1 1
      docs/src/en/guide/essentials/styles.md
  45. 6 0
      docs/src/guide/essentials/development.md
  46. 2 2
      docs/src/guide/essentials/settings.md
  47. 1 1
      docs/src/guide/essentials/styles.md
  48. 3 1
      docs/src/guide/introduction/quick-start.md
  49. 1 1
      internal/lint-configs/commitlint-config/package.json
  50. 1 1
      internal/lint-configs/stylelint-config/package.json
  51. 1 1
      internal/node-utils/package.json
  52. 1 1
      internal/tailwind-config/package.json
  53. 1 1
      internal/tsconfig/package.json
  54. 1 1
      internal/vite-config/package.json
  55. 1 1
      internal/vite-config/src/config/application.ts
  56. 1 0
      internal/vite-config/src/plugins/index.ts
  57. 29 0
      internal/vite-config/src/plugins/vxe-table.ts
  58. 2 2
      package.json
  59. 1 1
      packages/@core/base/design/package.json
  60. 1 1
      packages/@core/base/icons/package.json
  61. 3 1
      packages/@core/base/shared/package.json
  62. 1 1
      packages/@core/base/typings/package.json
  63. 1 1
      packages/@core/composables/package.json
  64. 1 1
      packages/@core/preferences/package.json
  65. 1 0
      packages/@core/ui-kit/form-ui/src/form-api.ts
  66. 1 1
      packages/@core/ui-kit/layout-ui/package.json
  67. 1 1
      packages/@core/ui-kit/menu-ui/package.json
  68. 1 1
      packages/@core/ui-kit/shadcn-ui/package.json
  69. 1 1
      packages/@core/ui-kit/tabs-ui/package.json
  70. 1 1
      packages/constants/package.json
  71. 1 1
      packages/effects/access/package.json
  72. 1 1
      packages/effects/common-ui/package.json
  73. 7 3
      packages/effects/common-ui/src/ui/authentication/code-login.vue
  74. 7 3
      packages/effects/common-ui/src/ui/authentication/forget-password.vue
  75. 8 4
      packages/effects/common-ui/src/ui/authentication/login.vue
  76. 7 3
      packages/effects/common-ui/src/ui/authentication/register.vue
  77. 1 1
      packages/effects/hooks/package.json
  78. 1 1
      packages/effects/layouts/package.json
  79. 1 1
      packages/effects/plugins/package.json
  80. 1 0
      packages/effects/plugins/src/vxe-table/index.ts
  81. 8 10
      packages/effects/plugins/src/vxe-table/theme.css
  82. 2 0
      packages/effects/plugins/src/vxe-table/use-vxe-grid.ts
  83. 17 12
      packages/effects/plugins/src/vxe-table/use-vxe-grid.vue
  84. 1 1
      packages/effects/request/package.json
  85. 1 1
      packages/icons/package.json
  86. 1 1
      packages/locales/package.json
  87. 1 1
      packages/preferences/package.json
  88. 1 1
      packages/stores/package.json
  89. 1 1
      packages/styles/package.json
  90. 1 1
      packages/styles/src/global/index.scss
  91. 1 1
      packages/types/package.json
  92. 1 1
      packages/utils/package.json
  93. 1 1
      playground/package.json
  94. 2 2
      playground/src/adapter/vxe-table.ts
  95. 15 3
      playground/src/bootstrap.ts
  96. 1 0
      playground/src/preferences.ts
  97. 0 10
      playground/src/router/guard.ts
  98. 221 202
      pnpm-lock.yaml
  99. 20 18
      pnpm-workspace.yaml
  100. 1 1
      scripts/turbo-run/package.json

+ 1 - 0
.vscode/settings.json

@@ -160,6 +160,7 @@
   "stylelint.enable": true,
   "stylelint.packageManager": "pnpm",
   "stylelint.validate": ["css", "less", "postcss", "scss", "vue"],
+  "stylelint.customSyntax": "postcss-html",
   "stylelint.snippet": ["css", "less", "postcss", "scss", "vue"],
 
   "typescript.inlayHints.enumMemberValues.enabled": true,

+ 1 - 1
apps/web-antd/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/web-antd",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://vben.pro",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 2 - 2
apps/web-antd/src/adapter/vxe-table.ts

@@ -38,7 +38,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellImage' },
     vxeUI.renderer.add('CellImage', {
-      renderDefault(_renderOpts, params) {
+      renderTableDefault(_renderOpts, params) {
         const { column, row } = params;
         return h(Image, { src: row[column.field] });
       },
@@ -46,7 +46,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellLink' },
     vxeUI.renderer.add('CellLink', {
-      renderDefault(renderOpts) {
+      renderTableDefault(renderOpts) {
         const { props } = renderOpts;
         return h(
           Button,

+ 15 - 2
apps/web-antd/src/bootstrap.ts

@@ -1,11 +1,14 @@
-import { createApp } from 'vue';
+import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
+import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
 import '@vben/styles/antd';
 
-import { setupI18n } from '#/locales';
+import { useTitle } from '@vueuse/core';
+
+import { $t, setupI18n } from '#/locales';
 
 import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
@@ -29,6 +32,16 @@ async function bootstrap(namespace: string) {
   // 配置路由及路由守卫
   app.use(router);
 
+  // 动态更新标题
+  watchEffect(() => {
+    if (preferences.app.dynamicTitle) {
+      const routeTitle = router.currentRoute.value.meta?.title;
+      const pageTitle =
+        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
+      useTitle(pageTitle);
+    }
+  });
+
   app.mount('#app');
 }
 

+ 1 - 0
apps/web-antd/src/preferences.ts

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
 /**
  * @description 项目配置文件
  * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
+ * !!! 更改配置后请清空缓存,否则可能不生效
  */
 export const overridesPreferences = defineOverridesPreferences({
   // overrides

+ 0 - 10
apps/web-antd/src/router/guard.ts

@@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
 import { useAccessStore, useUserStore } from '@vben/stores';
 import { startProgress, stopProgress } from '@vben/utils';
 
-import { useTitle } from '@vueuse/core';
-
-import { $t } from '#/locales';
 import { accessRoutes, coreRouteNames } from '#/router/routes';
 import { useAuthStore } from '#/store';
 
@@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
     if (preferences.transition.progress) {
       stopProgress();
     }
-
-    // 动态修改标题
-    if (preferences.app.dynamicTitle) {
-      const { title } = to.meta;
-      // useTitle(`${$t(title)} - ${preferences.app.name}`);
-      useTitle(`${$t(title)} - ${preferences.app.name}`);
-    }
   });
 }
 

+ 1 - 1
apps/web-baicai/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/baicai",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://vben.pro",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 3 - 3
apps/web-baicai/src/adapter/vxe-table.ts

@@ -11,7 +11,7 @@ setupVbenVxeTable({
     vxeUI.setConfig({
       grid: {
         align: 'center',
-        border: true,
+        border: false,
         columnConfig: {
           resizable: true,
         },
@@ -39,7 +39,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellImage' },
     vxeUI.renderer.add('CellImage', {
-      renderDefault(_renderOpts, params) {
+      renderTableDefault(_renderOpts, params) {
         const { column, row } = params;
         return h(Image, { src: row[column.field] });
       },
@@ -47,7 +47,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellLink' },
     vxeUI.renderer.add('CellLink', {
-      renderDefault(renderOpts) {
+      renderTableDefault(renderOpts) {
         const { props } = renderOpts;
         return h(
           Button,

+ 1 - 1
apps/web-baicai/src/api/core/auth.ts

@@ -5,7 +5,7 @@ export namespace AuthApi {
   /** 登录接口参数 */
   export interface LoginParams {
     password: string;
-    username: string;
+    username?: string;
   }
 
   /** 登录接口返回值 */

+ 20 - 3
apps/web-baicai/src/bootstrap.ts

@@ -1,15 +1,19 @@
-import { createApp } from 'vue';
+import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
+import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
 import '@vben/styles/antd';
 
-import { setupI18n } from '#/locales';
+import { VueQueryPlugin } from '@tanstack/vue-query';
+import { useTitle } from '@vueuse/core';
+
+import { $t, setupI18n } from '#/locales';
+import { router } from '#/router';
 
 import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
-import { router } from './router';
 
 async function bootstrap(namespace: string) {
   // 初始化组件适配器
@@ -29,6 +33,19 @@ async function bootstrap(namespace: string) {
   // 配置路由及路由守卫
   app.use(router);
 
+  // 配置@tanstack/vue-query
+  app.use(VueQueryPlugin);
+
+  // 动态更新标题
+  watchEffect(() => {
+    if (preferences.app.dynamicTitle) {
+      const routeTitle = router.currentRoute.value.meta?.title;
+      const pageTitle =
+        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
+      useTitle(pageTitle);
+    }
+  });
+
   app.mount('#app');
 }
 

+ 2 - 2
apps/web-baicai/src/layouts/basic.vue

@@ -68,7 +68,7 @@ const menus = computed(() => [
       });
     },
     icon: BookOpenText,
-    text: $t('widgets.document'),
+    text: $t('ui.widgets.document'),
   },
   {
     handler: () => {
@@ -86,7 +86,7 @@ const menus = computed(() => [
       });
     },
     icon: CircleHelp,
-    text: $t('widgets.qa'),
+    text: $t('ui.widgets.qa'),
   },
 ]);
 

+ 1 - 0
apps/web-baicai/src/preferences.ts

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
 /**
  * @description 项目配置文件
  * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
+ * !!! 更改配置后请清空缓存,否则可能不生效
  */
 export const overridesPreferences = defineOverridesPreferences({
   // overrides

+ 0 - 10
apps/web-baicai/src/router/guard.ts

@@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
 import { useAccessStore, useUserStore } from '@vben/stores';
 import { startProgress, stopProgress } from '@vben/utils';
 
-import { useTitle } from '@vueuse/core';
-
-import { $t } from '#/locales';
 import { accessRoutes, coreRouteNames } from '#/router/routes';
 import { useAuthStore } from '#/store';
 
@@ -39,13 +36,6 @@ function setupCommonGuard(router: Router) {
     if (preferences.transition.progress) {
       stopProgress();
     }
-
-    // 动态修改标题
-    if (preferences.app.dynamicTitle) {
-      const { title } = to.meta;
-      // useTitle(`${$t(title)} - ${preferences.app.name}`);
-      useTitle(`${$t(title)} - ${preferences.app.name}`);
-    }
   });
 }
 

+ 11 - 6
apps/web-baicai/src/store/auth.ts

@@ -1,5 +1,4 @@
-import type { LoginAndRegisterParams } from '@vben/common-ui';
-import type { UserInfo } from '@vben/types';
+import type { Recordable, UserInfo } from '@vben/types';
 
 import { ref } from 'vue';
 import { useRouter } from 'vue-router';
@@ -10,7 +9,13 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
 import { notification } from 'ant-design-vue';
 import { defineStore } from 'pinia';
 
-import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
+import {
+  type AuthApi,
+  getAccessCodesApi,
+  getUserInfoApi,
+  loginApi,
+  logoutApi,
+} from '#/api';
 import { $t } from '#/locales';
 
 export const useAuthStore = defineStore('auth', () => {
@@ -27,14 +32,14 @@ export const useAuthStore = defineStore('auth', () => {
    * @param onSuccess 成功之后的回调函数
    */
   async function authLogin(
-    params: LoginAndRegisterParams,
+    params: Recordable<any>,
     onSuccess?: () => Promise<void> | void,
   ) {
     // 异步处理用户登录操作并获取 accessToken
     let userInfo: null | UserInfo = null;
     try {
       loginLoading.value = true;
-      const { accessToken } = await loginApi(params);
+      const { accessToken } = await loginApi(params as AuthApi.LoginParams);
 
       // 如果成功获取到 accessToken
       if (accessToken) {
@@ -86,7 +91,7 @@ export const useAuthStore = defineStore('auth', () => {
     resetAllStores();
     accessStore.setLoginExpired(false);
 
-    // 回登页带上当前路由地址
+    // 回登页带上当前路由地址
     await router.replace({
       path: LOGIN_PATH,
       query: redirect

+ 1 - 1
apps/web-ele/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/web-ele",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://vben.pro",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 2 - 2
apps/web-ele/src/adapter/vxe-table.ts

@@ -38,7 +38,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellImage' },
     vxeUI.renderer.add('CellImage', {
-      renderDefault(_renderOpts, params) {
+      renderTableDefault(_renderOpts, params) {
         const { column, row } = params;
         const src = row[column.field];
         return h(ElImage, { src, previewSrcList: [src] });
@@ -47,7 +47,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellLink' },
     vxeUI.renderer.add('CellLink', {
-      renderDefault(renderOpts) {
+      renderTableDefault(renderOpts) {
         const { props } = renderOpts;
         return h(
           ElButton,

+ 15 - 2
apps/web-ele/src/bootstrap.ts

@@ -1,11 +1,14 @@
-import { createApp } from 'vue';
+import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
+import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
 import '@vben/styles/ele';
 
-import { setupI18n } from '#/locales';
+import { useTitle } from '@vueuse/core';
+
+import { $t, setupI18n } from '#/locales';
 
 import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
@@ -28,6 +31,16 @@ async function bootstrap(namespace: string) {
   // 配置路由及路由守卫
   app.use(router);
 
+  // 动态更新标题
+  watchEffect(() => {
+    if (preferences.app.dynamicTitle) {
+      const routeTitle = router.currentRoute.value.meta?.title;
+      const pageTitle =
+        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
+      useTitle(pageTitle);
+    }
+  });
+
   app.mount('#app');
 }
 

+ 1 - 0
apps/web-ele/src/preferences.ts

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
 /**
  * @description 项目配置文件
  * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
+ * !!! 更改配置后请清空缓存,否则可能不生效
  */
 export const overridesPreferences = defineOverridesPreferences({
   // overrides

+ 0 - 10
apps/web-ele/src/router/guard.ts

@@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
 import { useAccessStore, useUserStore } from '@vben/stores';
 import { startProgress, stopProgress } from '@vben/utils';
 
-import { useTitle } from '@vueuse/core';
-
-import { $t } from '#/locales';
 import { accessRoutes, coreRouteNames } from '#/router/routes';
 import { useAuthStore } from '#/store';
 
@@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
     if (preferences.transition.progress) {
       stopProgress();
     }
-
-    // 动态修改标题
-    if (preferences.app.dynamicTitle) {
-      const { title } = to.meta;
-      // useTitle(`${$t(title)} - ${preferences.app.name}`);
-      useTitle(`${$t(title)} - ${preferences.app.name}`);
-    }
   });
 }
 

+ 1 - 1
apps/web-naive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/web-naive",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://vben.pro",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 2 - 2
apps/web-naive/src/adapter/vxe-table.ts

@@ -38,7 +38,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellImage' },
     vxeUI.renderer.add('CellImage', {
-      renderDefault(_renderOpts, params) {
+      renderTableDefault(_renderOpts, params) {
         const { column, row } = params;
         return h(NImage, { src: row[column.field] });
       },
@@ -46,7 +46,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellLink' },
     vxeUI.renderer.add('CellLink', {
-      renderDefault(renderOpts) {
+      renderTableDefault(renderOpts) {
         const { props } = renderOpts;
         return h(
           NButton,

+ 15 - 2
apps/web-naive/src/bootstrap.ts

@@ -1,10 +1,13 @@
-import { createApp } from 'vue';
+import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
+import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
 
-import { setupI18n } from '#/locales';
+import { useTitle } from '@vueuse/core';
+
+import { $t, setupI18n } from '#/locales';
 
 import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
@@ -27,6 +30,16 @@ async function bootstrap(namespace: string) {
   // 配置路由及路由守卫
   app.use(router);
 
+  // 动态更新标题
+  watchEffect(() => {
+    if (preferences.app.dynamicTitle) {
+      const routeTitle = router.currentRoute.value.meta?.title;
+      const pageTitle =
+        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
+      useTitle(pageTitle);
+    }
+  });
+
   app.mount('#app');
 }
 

+ 1 - 0
apps/web-naive/src/preferences.ts

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
 /**
  * @description 项目配置文件
  * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
+ * !!! 更改配置后请清空缓存,否则可能不生效
  */
 export const overridesPreferences = defineOverridesPreferences({
   // overrides

+ 0 - 10
apps/web-naive/src/router/guard.ts

@@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
 import { useAccessStore, useUserStore } from '@vben/stores';
 import { startProgress, stopProgress } from '@vben/utils';
 
-import { useTitle } from '@vueuse/core';
-
-import { $t } from '#/locales';
 import { accessRoutes, coreRouteNames } from '#/router/routes';
 import { useAuthStore } from '#/store';
 
@@ -40,13 +37,6 @@ function setupCommonGuard(router: Router) {
     if (preferences.transition.progress) {
       stopProgress();
     }
-
-    // 动态修改标题
-    if (preferences.app.dynamicTitle) {
-      const { title } = to.meta;
-      // useTitle(`${$t(title)} - ${preferences.app.name}`);
-      useTitle(`${$t(title)} - ${preferences.app.name}`);
-    }
   });
 }
 

+ 6 - 1
docs/.vitepress/config/shared.mts

@@ -3,7 +3,10 @@ import type { HeadConfig } from 'vitepress';
 
 import { resolve } from 'node:path';
 
-import { viteArchiverPlugin } from '@vben/vite-config';
+import {
+  viteArchiverPlugin,
+  viteVxeTableImportsPlugin,
+} from '@vben/vite-config';
 
 import {
   GitChangelog,
@@ -85,6 +88,7 @@ export const shared = defineConfig({
       GitChangelogMarkdownSection(),
       viteArchiverPlugin({ outputDir: '.vitepress' }),
       groupIconVitePlugin(),
+      await viteVxeTableImportsPlugin(),
     ],
     server: {
       fs: {
@@ -156,6 +160,7 @@ function pwa(): PwaOptions {
     registerType: 'autoUpdate',
     workbox: {
       globPatterns: ['**/*.{css,js,html,svg,png,ico,txt,woff2}'],
+      maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
     },
   };
 }

+ 2 - 1
docs/.vitepress/theme/index.ts

@@ -15,11 +15,12 @@ import 'virtual:group-icons.css';
 import '@nolebase/vitepress-plugin-git-changelog/client/style.css';
 
 export default {
-  enhanceApp(ctx: EnhanceAppContext) {
+  async enhanceApp(ctx: EnhanceAppContext) {
     const { app } = ctx;
     app.component('VbenContributors', VbenContributors);
     app.component('DemoPreview', DemoPreview);
     app.use(NolebaseGitChangelogPlugin);
+
     // 百度统计
     initHmPlugin();
   },

+ 6 - 2
docs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/docs",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "scripts": {
     "build": "vitepress build",
@@ -8,12 +8,16 @@
     "docs:preview": "vitepress preview"
   },
   "imports": {
-    "#/*": "./src/_env/*"
+    "#/*": {
+      "node": "./src/_env/node/*",
+      "default": "./src/_env/*"
+    }
   },
   "dependencies": {
     "@vben-core/shadcn-ui": "workspace:*",
     "@vben/common-ui": "workspace:*",
     "@vben/locales": "workspace:*",
+    "@vben/plugins": "workspace:*",
     "@vben/styles": "workspace:*",
     "ant-design-vue": "catalog:",
     "lucide-vue-next": "catalog:",

+ 70 - 0
docs/src/_env/adapter/vxe-table.ts

@@ -0,0 +1,70 @@
+import { h } from 'vue';
+
+import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
+
+import { Button, Image } from 'ant-design-vue';
+
+import { useVbenForm } from './form';
+
+if (!import.meta.env.SSR) {
+  setupVbenVxeTable({
+    configVxeTable: (vxeUI) => {
+      vxeUI.setConfig({
+        grid: {
+          align: 'center',
+          border: false,
+          columnConfig: {
+            resizable: true,
+          },
+
+          formConfig: {
+            // 全局禁用vxe-table的表单配置,使用formOptions
+            enabled: false,
+          },
+          minHeight: 180,
+          proxyConfig: {
+            autoLoad: true,
+            response: {
+              result: 'items',
+              total: 'total',
+              list: 'items',
+            },
+            showActiveMsg: true,
+            showResponseMsg: false,
+          },
+          round: true,
+          showOverflow: true,
+          size: 'small',
+        },
+      });
+
+      // 表格配置项可以用 cellRender: { name: 'CellImage' },
+      vxeUI.renderer.add('CellImage', {
+        renderTableDefault(_renderOpts, params) {
+          const { column, row } = params;
+          return h(Image, { src: row[column.field] });
+        },
+      });
+
+      // 表格配置项可以用 cellRender: { name: 'CellLink' },
+      vxeUI.renderer.add('CellLink', {
+        renderTableDefault(renderOpts) {
+          const { props } = renderOpts;
+          return h(
+            Button,
+            { size: 'small', type: 'link' },
+            { default: () => props?.text },
+          );
+        },
+      });
+
+      // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
+      // vxeUI.formats.add
+    },
+    useVbenForm,
+  });
+}
+
+export { useVbenVxeGrid };
+
+export type * from '@vben/plugins/vxe-table';

+ 4 - 0
docs/src/_env/node/adapter/form.ts

@@ -0,0 +1,4 @@
+export const useVbenForm = () => {};
+export const z = {};
+export type VbenFormSchema = any;
+export type VbenFormProps = any;

+ 3 - 0
docs/src/_env/node/adapter/vxe-table.ts

@@ -0,0 +1,3 @@
+export type * from '@vben/plugins/vxe-table';
+
+export const useVbenVxeGrid = () => {};

+ 232 - 1
docs/src/components/common-ui/vben-vxe-table.md

@@ -4,4 +4,235 @@ outline: deep
 
 # Vben Vxe Table 表格
 
-文档待补充,如果需要使用,可先行查看 vxe-table 文档和 示例代码,内部有部分注释。
+框架提供的Table 列表组件基于 [vxe-table](https://vxetable.cn/v4/#/grid/api?apiKey=grid),结合`Vben Form 表单`进行了二次封装。
+
+其中,表头的 **表单搜索** 部分采用了`Vben Form表单`,表格主体部分使用了`vxe-grid`组件,支持表格的分页、排序、筛选等功能。
+
+> 如果文档内没有参数说明,可以尝试在在线示例或者在 [vxe-grid 官方API 文档](https://vxetable.cn/v4/#/grid/api?apiKey=grid) 内寻找
+
+::: info 写在前面
+
+如果你觉得现有组件的封装不够理想,或者不完全符合你的需求,大可以直接使用原生组件,亦或亲手封装一个适合的组件。框架提供的组件并非束缚,使用与否,完全取决于你的需求与自由。
+
+:::
+
+## 适配器
+
+表格底层使用 [vxe-table](https://vxetable.cn/#/start/install) 进行实现,所以你可以使用 `vxe-table` 的所有功能。对于不同的 UI 框架,我们提供了适配器,以便更好的适配不同的 UI 框架。
+
+### 适配器说明
+
+每个应用都可以自己配置`vxe-table`的适配器,你可以根据自己的需求。下面是一个简单的配置示例:
+
+::: details vxe-table 表格适配器
+
+```ts
+import { h } from 'vue';
+
+import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
+
+import { Button, Image } from 'ant-design-vue';
+
+import { useVbenForm } from './form';
+
+setupVbenVxeTable({
+  configVxeTable: (vxeUI) => {
+    vxeUI.setConfig({
+      grid: {
+        align: 'center',
+        border: false,
+        columnConfig: {
+          resizable: true,
+        },
+        minHeight: 180,
+        formConfig: {
+          // 全局禁用vxe-table的表单配置,使用formOptions
+          enabled: false,
+        },
+        proxyConfig: {
+          autoLoad: true,
+          response: {
+            result: 'items',
+            total: 'total',
+            list: 'items',
+          },
+          showActiveMsg: true,
+          showResponseMsg: false,
+        },
+        round: true,
+        showOverflow: true,
+        size: 'small',
+      },
+    });
+
+    // 表格配置项可以用 cellRender: { name: 'CellImage' },
+    vxeUI.renderer.add('CellImage', {
+      renderTableDefault(_renderOpts, params) {
+        const { column, row } = params;
+        return h(Image, { src: row[column.field] });
+      },
+    });
+
+    // 表格配置项可以用 cellRender: { name: 'CellLink' },
+    vxeUI.renderer.add('CellLink', {
+      renderTableDefault(renderOpts) {
+        const { props } = renderOpts;
+        return h(
+          Button,
+          { size: 'small', type: 'link' },
+          { default: () => props?.text },
+        );
+      },
+    });
+
+    // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
+    // vxeUI.formats.add
+  },
+  useVbenForm,
+});
+
+export { useVbenVxeGrid };
+
+export type * from '@vben/plugins/vxe-table';
+```
+
+:::
+
+## 基础表格
+
+使用 `useVbenVxeGrid` 创建最基础的表格。
+
+<DemoPreview dir="demos/vben-vxe-table/basic" />
+
+## 远程加载
+
+通过指定 `proxyConfig.ajax` 的 `query` 方法,可以实现远程加载数据。
+
+<DemoPreview dir="demos/vben-vxe-table/remote" />
+
+## 树形表格
+
+树形表格的数据源为扁平结构,可以指定`treeConfig`配置项,实现树形表格。
+
+```typescript
+treeConfig: {
+  transform: true, // 指定表格为树形表格
+  parentField: 'parentId', // 父节点字段名
+  rowField: 'id', // 行数据字段名
+},
+```
+
+<DemoPreview dir="demos/vben-vxe-table/tree" />
+
+## 固定表头/列
+
+列固定可选参数: `'left' | 'right' | '' | null`
+
+<DemoPreview dir="demos/vben-vxe-table/fixed" />
+
+## 自定义单元格
+
+自定义单元格有两种实现方式
+
+- 通过 `slots` 插槽
+- 通过 `customCell` 自定义单元格,但是要先添加渲染器
+
+```typescript
+// 表格配置项可以用 cellRender: { name: 'CellImage' },
+vxeUI.renderer.add('CellImage', {
+  renderDefault(_renderOpts, params) {
+    const { column, row } = params;
+    return h(Image, { src: row[column.field] } as any); // 注意此处的Image 组件,来源于Antd,需要自行引入,否则会使用js的Image类
+  },
+});
+
+// 表格配置项可以用 cellRender: { name: 'CellLink' },
+vxeUI.renderer.add('CellLink', {
+  renderDefault(renderOpts) {
+    const { props } = renderOpts;
+    return h(
+      Button,
+      { size: 'small', type: 'link' },
+      { default: () => props?.text },
+    );
+  },
+});
+```
+
+<DemoPreview dir="demos/vben-vxe-table/custom-cell" />
+
+## 搜索表单
+
+**表单搜索** 部分采用了`Vben Form 表单`,参考 [Vben Form 表单文档](/components/common-ui/vben-form)。
+
+<DemoPreview dir="demos/vben-vxe-table/form" />
+
+## 单元格编辑
+
+通过指定`editConfig.mode`为`cell`,可以实现单元格编辑。
+
+<DemoPreview dir="demos/vben-vxe-table/edit-cell" />
+
+## 行编辑
+
+通过指定`editConfig.mode`为`row`,可以实现行编辑。
+
+<DemoPreview dir="demos/vben-vxe-table/edit-row" />
+
+## 虚拟滚动
+
+通过 scroll-y.enabled 与 scroll-y.gt 组合开启,其中 enabled 为总开关,gt 是指当总行数大于指定行数时自动开启。
+
+> 参考 [vxe-table 官方文档 - 虚拟滚动](https://vxetable.cn/v4/#/component/grid/scroll/vertical)。
+
+<DemoPreview dir="demos/vben-vxe-table/virtual" />
+
+## API
+
+`useVbenVxeGrid` 返回一个数组,第一个元素是表格组件,第二个元素是表格的方法。
+
+```vue
+<script setup lang="ts">
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+// Grid 为表格组件
+// gridApi 为表格的方法
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {},
+  formOptions: {},
+  gridEvents: {},
+  // 属性
+  // 事件
+});
+</script>
+
+<template>
+  <Grid />
+</template>
+```
+
+### GridApi
+
+useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表单的方法。
+
+| 方法名 | 描述 | 类型 |
+| --- | --- | --- |
+| setLoading | 设置loading状态 | `(loading)=>void` |
+| setGridOptions | 设置vxe-table grid组件参数 | `(options: Partial<VxeGridProps['gridOptions'])=>void` |
+| reload | 重载表格,会进行初始化 | `(params:any)=>void` |
+| query | 重载表格,会保留当前分页 | `(params:any)=>void` |
+| grid | vxe-table grid实例 | `VxeGridInstance` |
+| formApi | vbenForm api实例 | `FormApi` |
+
+## Props
+
+所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
+
+| 属性名         | 描述               | 类型                |
+| -------------- | ------------------ | ------------------- |
+| tableTitle     | 表格标题           | `string`            |
+| tableTitleHelp | 表格标题帮助信息   | `string`            |
+| gridClass      | grid组件的class    | `string`            |
+| gridOptions    | grid组件的参数     | `VxeTableGridProps` |
+| gridEvents     | grid组件的触发的⌚️ | `VxeGridListeners`  |
+| formOptions    | 表单参数           | `VbenFormProps`     |

+ 85 - 0
docs/src/demos/vben-vxe-table/basic/index.vue

@@ -0,0 +1,85 @@
+<script lang="ts" setup>
+import type { VxeGridListeners, VxeGridProps } from '#/adapter/vxe-table';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { MOCK_TABLE_DATA } from '../table-data';
+
+interface RowType {
+  address: string;
+  age: number;
+  id: number;
+  name: string;
+  nickname: string;
+  role: string;
+}
+
+const gridOptions: VxeGridProps<RowType> = {
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { field: 'name', title: 'Name' },
+    { field: 'age', sortable: true, title: 'Age' },
+    { field: 'nickname', title: 'Nickname' },
+    { field: 'role', title: 'Role' },
+    { field: 'address', showOverflow: true, title: 'Address' },
+  ],
+  data: MOCK_TABLE_DATA,
+  pagerConfig: {
+    enabled: false,
+  },
+  sortConfig: {
+    multiple: true,
+  },
+};
+
+const gridEvents: VxeGridListeners<RowType> = {
+  cellClick: ({ row }) => {
+    message.info(`cell-click: ${row.name}`);
+  },
+};
+
+const [Grid, gridApi] = useVbenVxeGrid({ gridEvents, gridOptions });
+
+const showBorder = gridApi.useStore((state) => state.gridOptions?.border);
+const showStripe = gridApi.useStore((state) => state.gridOptions?.stripe);
+
+function changeBorder() {
+  gridApi.setGridOptions({
+    border: !showBorder.value,
+  });
+}
+
+function changeStripe() {
+  gridApi.setGridOptions({
+    stripe: !showStripe.value,
+  });
+}
+
+function changeLoading() {
+  gridApi.setLoading(true);
+  setTimeout(() => {
+    gridApi.setLoading(false);
+  }, 2000);
+}
+</script>
+
+<template>
+  <!-- 此处的`vp-raw` 是为了适配文档的展示效果,实际使用时不需要 -->
+  <div class="vp-raw w-full">
+    <Grid>
+      <template #toolbar-tools>
+        <Button class="mr-2" type="primary" @click="changeBorder">
+          {{ showBorder ? '隐藏' : '显示' }}边框
+        </Button>
+        <Button class="mr-2" type="primary" @click="changeLoading">
+          显示loading
+        </Button>
+        <Button class="mr-2" type="primary" @click="changeStripe">
+          {{ showStripe ? '隐藏' : '显示' }}斑马纹
+        </Button>
+      </template>
+    </Grid>
+  </div>
+</template>

+ 105 - 0
docs/src/demos/vben-vxe-table/custom-cell/index.vue

@@ -0,0 +1,105 @@
+<script lang="ts" setup>
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { Button, Image, Switch, Tag } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { getExampleTableApi } from '../mock-api';
+
+interface RowType {
+  category: string;
+  color: string;
+  id: string;
+  imageUrl: string;
+  open: boolean;
+  price: string;
+  productName: string;
+  releaseDate: string;
+  status: 'error' | 'success' | 'warning';
+}
+
+const gridOptions: VxeGridProps<RowType> = {
+  checkboxConfig: {
+    highlight: true,
+    labelField: 'name',
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { field: 'category', title: 'Category', width: 100 },
+    {
+      field: 'imageUrl',
+      slots: { default: 'image-url' },
+      title: 'Image',
+      width: 100,
+    },
+    {
+      cellRender: { name: 'CellImage' },
+      field: 'imageUrl2',
+      title: 'Render Image',
+      width: 130,
+    },
+    {
+      field: 'open',
+      slots: { default: 'open' },
+      title: 'Open',
+      width: 100,
+    },
+    {
+      field: 'status',
+      slots: { default: 'status' },
+      title: 'Status',
+      width: 100,
+    },
+    { field: 'color', title: 'Color', width: 100 },
+    { field: 'productName', title: 'Product Name', width: 200 },
+    { field: 'price', title: 'Price', width: 100 },
+    {
+      field: 'releaseDate',
+      formatter: 'formatDateTime',
+      title: 'Date',
+      width: 200,
+    },
+    {
+      cellRender: { name: 'CellLink', props: { text: '编辑' } },
+      field: 'action',
+      fixed: 'right',
+      title: '操作',
+      width: 120,
+    },
+  ],
+  keepSource: true,
+  pagerConfig: {},
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }) => {
+        return await getExampleTableApi({
+          page: page.currentPage,
+          pageSize: page.pageSize,
+        });
+      },
+    },
+  },
+};
+
+const [Grid] = useVbenVxeGrid({ gridOptions });
+</script>
+
+<template>
+  <div class="vp-raw w-full">
+    <Grid>
+      <template #image-url="{ row }">
+        <Image :src="row.imageUrl" height="30" width="30" />
+      </template>
+      <template #open="{ row }">
+        <Switch v-model:checked="row.open" />
+      </template>
+      <template #status="{ row }">
+        <Tag :color="row.color">{{ row.status }}</Tag>
+      </template>
+      <template #action>
+        <Button type="link">编辑</Button>
+      </template>
+    </Grid>
+  </div>
+</template>

+ 55 - 0
docs/src/demos/vben-vxe-table/edit-cell/index.vue

@@ -0,0 +1,55 @@
+<script lang="ts" setup>
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { getExampleTableApi } from '../mock-api';
+
+interface RowType {
+  category: string;
+  color: string;
+  id: string;
+  price: string;
+  productName: string;
+  releaseDate: string;
+}
+
+const gridOptions: VxeGridProps<RowType> = {
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { editRender: { name: 'input' }, field: 'category', title: 'Category' },
+    { editRender: { name: 'input' }, field: 'color', title: 'Color' },
+    {
+      editRender: { name: 'input' },
+      field: 'productName',
+      title: 'Product Name',
+    },
+    { field: 'price', title: 'Price' },
+    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
+  ],
+  editConfig: {
+    mode: 'cell',
+    trigger: 'click',
+  },
+  pagerConfig: {},
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }) => {
+        return await getExampleTableApi({
+          page: page.currentPage,
+          pageSize: page.pageSize,
+        });
+      },
+    },
+  },
+  showOverflow: true,
+};
+
+const [Grid] = useVbenVxeGrid({ gridOptions });
+</script>
+
+<template>
+  <div class="vp-raw w-full">
+    <Grid />
+  </div>
+</template>

+ 92 - 0
docs/src/demos/vben-vxe-table/edit-row/index.vue

@@ -0,0 +1,92 @@
+<script lang="ts" setup>
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { getExampleTableApi } from '../mock-api';
+
+interface RowType {
+  category: string;
+  color: string;
+  id: string;
+  price: string;
+  productName: string;
+  releaseDate: string;
+}
+
+const gridOptions: VxeGridProps<RowType> = {
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { editRender: { name: 'input' }, field: 'category', title: 'Category' },
+    { editRender: { name: 'input' }, field: 'color', title: 'Color' },
+    {
+      editRender: { name: 'input' },
+      field: 'productName',
+      title: 'Product Name',
+    },
+    { field: 'price', title: 'Price' },
+    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
+    { slots: { default: 'action' }, title: '操作' },
+  ],
+  editConfig: {
+    mode: 'row',
+    trigger: 'click',
+  },
+  pagerConfig: {},
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }) => {
+        return await getExampleTableApi({
+          page: page.currentPage,
+          pageSize: page.pageSize,
+        });
+      },
+    },
+  },
+  showOverflow: true,
+};
+
+const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
+
+function hasEditStatus(row: RowType) {
+  return gridApi.grid?.isEditByRow(row);
+}
+
+function editRowEvent(row: RowType) {
+  gridApi.grid?.setEditRow(row);
+}
+
+async function saveRowEvent(row: RowType) {
+  await gridApi.grid?.clearEdit();
+
+  gridApi.setLoading(true);
+  setTimeout(() => {
+    gridApi.setLoading(false);
+    message.success({
+      content: `保存成功!category=${row.category}`,
+    });
+  }, 600);
+}
+
+const cancelRowEvent = (_row: RowType) => {
+  gridApi.grid?.clearEdit();
+};
+</script>
+
+<template>
+  <div class="vp-raw w-full">
+    <Grid>
+      <template #action="{ row }">
+        <template v-if="hasEditStatus(row)">
+          <Button type="link" @click="saveRowEvent(row)">保存</Button>
+          <Button type="link" @click="cancelRowEvent(row)">取消</Button>
+        </template>
+        <template v-else>
+          <Button type="link" @click="editRowEvent(row)">编辑</Button>
+        </template>
+      </template>
+    </Grid>
+  </div>
+</template>

+ 67 - 0
docs/src/demos/vben-vxe-table/fixed/index.vue

@@ -0,0 +1,67 @@
+<script lang="ts" setup>
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { Button } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { getExampleTableApi } from '../mock-api';
+
+interface RowType {
+  category: string;
+  color: string;
+  id: string;
+  price: string;
+  productName: string;
+  releaseDate: string;
+}
+
+const gridOptions: VxeGridProps<RowType> = {
+  columns: [
+    { fixed: 'left', title: '序号', type: 'seq', width: 50 },
+    { field: 'category', title: 'Category', width: 300 },
+    { field: 'color', title: 'Color', width: 300 },
+    { field: 'productName', title: 'Product Name', width: 300 },
+    { field: 'price', title: 'Price', width: 300 },
+    {
+      field: 'releaseDate',
+      formatter: 'formatDateTime',
+      title: 'DateTime',
+      width: 500,
+    },
+    {
+      field: 'action',
+      fixed: 'right',
+      slots: { default: 'action' },
+      title: '操作',
+      width: 120,
+    },
+  ],
+  pagerConfig: {},
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }) => {
+        return await getExampleTableApi({
+          page: page.currentPage,
+          pageSize: page.pageSize,
+        });
+      },
+    },
+  },
+  rowConfig: {
+    isHover: true,
+  },
+};
+
+const [Grid] = useVbenVxeGrid({ gridOptions });
+</script>
+
+<template>
+  <div class="vp-raw w-full">
+    <Grid>
+      <template #action>
+        <Button type="link">编辑</Button>
+      </template>
+    </Grid>
+  </div>
+</template>

+ 120 - 0
docs/src/demos/vben-vxe-table/form/index.vue

@@ -0,0 +1,120 @@
+<script lang="ts" setup>
+import type { VbenFormProps } from '#/adapter/form';
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { getExampleTableApi } from '../mock-api';
+
+interface RowType {
+  category: string;
+  color: string;
+  id: string;
+  price: string;
+  productName: string;
+  releaseDate: string;
+}
+
+const formOptions: VbenFormProps = {
+  // 默认展开
+  collapsed: false,
+  schema: [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: 'Please enter category',
+      },
+      defaultValue: '1',
+      fieldName: 'category',
+      label: 'Category',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: 'Please enter productName',
+      },
+      fieldName: 'productName',
+      label: 'ProductName',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: 'Please enter price',
+      },
+      fieldName: 'price',
+      label: 'Price',
+    },
+    {
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: [
+          {
+            label: 'Color1',
+            value: '1',
+          },
+          {
+            label: 'Color2',
+            value: '2',
+          },
+        ],
+        placeholder: '请选择',
+      },
+      fieldName: 'color',
+      label: 'Color',
+    },
+    {
+      component: 'DatePicker',
+      fieldName: 'datePicker',
+      label: 'Date',
+    },
+  ],
+  // 控制表单是否显示折叠按钮
+  showCollapseButton: true,
+  submitButtonOptions: {
+    content: '查询',
+  },
+  // 按下回车时是否提交表单
+  submitOnEnter: false,
+};
+
+const gridOptions: VxeGridProps<RowType> = {
+  checkboxConfig: {
+    highlight: true,
+    labelField: 'name',
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { align: 'left', title: 'Name', type: 'checkbox', width: 100 },
+    { field: 'category', title: 'Category' },
+    { field: 'color', title: 'Color' },
+    { field: 'productName', title: 'Product Name' },
+    { field: 'price', title: 'Price' },
+    { field: 'releaseDate', formatter: 'formatDateTime', title: 'Date' },
+  ],
+  keepSource: true,
+  pagerConfig: {},
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }, formValues) => {
+        message.success(`Query params: ${JSON.stringify(formValues)}`);
+        return await getExampleTableApi({
+          page: page.currentPage,
+          pageSize: page.pageSize,
+          ...formValues,
+        });
+      },
+    },
+  },
+};
+
+const [Grid] = useVbenVxeGrid({ formOptions, gridOptions });
+</script>
+
+<template>
+  <div class="vp-raw w-full">
+    <Grid />
+  </div>
+</template>

+ 36 - 0
docs/src/demos/vben-vxe-table/mock-api.ts

@@ -0,0 +1,36 @@
+import { MOCK_API_DATA } from './table-data';
+
+export namespace DemoTableApi {
+  export interface PageFetchParams {
+    [key: string]: any;
+    page: number;
+    pageSize: number;
+  }
+}
+
+export function sleep(time = 1000) {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(true);
+    }, time);
+  });
+}
+
+/**
+ * 获取示例表格数据
+ */
+async function getExampleTableApi(params: DemoTableApi.PageFetchParams) {
+  return new Promise<{ items: any; total: number }>((resolve) => {
+    const { page, pageSize } = params;
+    const items = MOCK_API_DATA.slice((page - 1) * pageSize, page * pageSize);
+
+    sleep(1000).then(() => {
+      resolve({
+        total: items.length,
+        items,
+      });
+    });
+  });
+}
+
+export { getExampleTableApi };

+ 112 - 0
docs/src/demos/vben-vxe-table/remote/index.vue

@@ -0,0 +1,112 @@
+<script lang="ts" setup>
+import type { DemoTableApi } from '../mock-api';
+
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { Button } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { MOCK_API_DATA } from '../table-data';
+
+interface RowType {
+  category: string;
+  color: string;
+  id: string;
+  price: string;
+  productName: string;
+  releaseDate: string;
+}
+
+// 数据实例
+// const MOCK_TREE_TABLE_DATA = [
+//   {
+//     date: '2020-08-01',
+//     id: 10_000,
+//     name: 'Test1',
+//     parentId: null,
+//     size: 1024,
+//     type: 'mp3',
+//   },
+// ]
+
+const sleep = (time = 1000) => {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(true);
+    }, time);
+  });
+};
+
+/**
+ * 获取示例表格数据
+ */
+async function getExampleTableApi(params: DemoTableApi.PageFetchParams) {
+  return new Promise<{ items: any; total: number }>((resolve) => {
+    const { page, pageSize } = params;
+    const items = MOCK_API_DATA.slice((page - 1) * pageSize, page * pageSize);
+
+    sleep(1000).then(() => {
+      resolve({
+        total: items.length,
+        items,
+      });
+    });
+  });
+}
+
+const gridOptions: VxeGridProps<RowType> = {
+  checkboxConfig: {
+    highlight: true,
+    labelField: 'name',
+  },
+  columns: [
+    { title: '序号', type: 'seq', width: 50 },
+    { align: 'left', title: 'Name', type: 'checkbox', width: 100 },
+    { field: 'category', title: 'Category' },
+    { field: 'color', title: 'Color' },
+    { field: 'productName', title: 'Product Name' },
+    { field: 'price', title: 'Price' },
+    { field: 'releaseDate', formatter: 'formatDateTime', title: 'DateTime' },
+  ],
+  exportConfig: {},
+  // height: 'auto', // 如果设置为 auto,则必须确保存在父节点且不允许存在相邻元素,否则会出现高度闪动问题
+  keepSource: true,
+  proxyConfig: {
+    ajax: {
+      query: async ({ page }) => {
+        return await getExampleTableApi({
+          page: page.currentPage,
+          pageSize: page.pageSize,
+        });
+      },
+    },
+  },
+  toolbarConfig: {
+    custom: true,
+    export: true,
+    // import: true,
+    refresh: true,
+    zoom: true,
+  },
+};
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions,
+});
+</script>
+
+<template>
+  <div class="vp-raw w-full">
+    <Grid>
+      <template #toolbar-tools>
+        <Button class="mr-2" type="primary" @click="() => gridApi.query()">
+          刷新当前页面
+        </Button>
+        <Button type="primary" @click="() => gridApi.reload()">
+          刷新并返回第一页
+        </Button>
+      </template>
+    </Grid>
+  </div>
+</template>

+ 384 - 0
docs/src/demos/vben-vxe-table/table-data.ts

@@ -0,0 +1,384 @@
+interface TableRowData {
+  address: string;
+  age: number;
+  id: number;
+  name: string;
+  nickname: string;
+  role: string;
+}
+
+const roles = ['User', 'Admin', 'Manager', 'Guest'];
+
+export const MOCK_TABLE_DATA: TableRowData[] = (() => {
+  const data: TableRowData[] = [];
+  for (let i = 0; i < 10; i++) {
+    data.push({
+      address: `New York${i}`,
+      age: i + 1,
+      id: i,
+      name: `Test${i}`,
+      nickname: `Test${i}`,
+      role: roles[Math.floor(Math.random() * roles.length)] as string,
+    });
+  }
+  return data;
+})();
+
+export const MOCK_TREE_TABLE_DATA = [
+  {
+    date: '2020-08-01',
+    id: 10_000,
+    name: 'Test1',
+    parentId: null,
+    size: 1024,
+    type: 'mp3',
+  },
+  {
+    date: '2021-04-01',
+    id: 10_050,
+    name: 'Test2',
+    parentId: null,
+    size: 0,
+    type: 'mp4',
+  },
+  {
+    date: '2020-03-01',
+    id: 24_300,
+    name: 'Test3',
+    parentId: 10_050,
+    size: 1024,
+    type: 'avi',
+  },
+  {
+    date: '2021-04-01',
+    id: 20_045,
+    name: 'Test4',
+    parentId: 24_300,
+    size: 600,
+    type: 'html',
+  },
+  {
+    date: '2021-04-01',
+    id: 10_053,
+    name: 'Test5',
+    parentId: 24_300,
+    size: 0,
+    type: 'avi',
+  },
+  {
+    date: '2021-10-01',
+    id: 24_330,
+    name: 'Test6',
+    parentId: 10_053,
+    size: 25,
+    type: 'txt',
+  },
+  {
+    date: '2020-01-01',
+    id: 21_011,
+    name: 'Test7',
+    parentId: 10_053,
+    size: 512,
+    type: 'pdf',
+  },
+  {
+    date: '2021-06-01',
+    id: 22_200,
+    name: 'Test8',
+    parentId: 10_053,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2020-11-01',
+    id: 23_666,
+    name: 'Test9',
+    parentId: null,
+    size: 2048,
+    type: 'xlsx',
+  },
+  {
+    date: '2021-06-01',
+    id: 23_677,
+    name: 'Test10',
+    parentId: 23_666,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2021-06-01',
+    id: 23_671,
+    name: 'Test11',
+    parentId: 23_677,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2021-06-01',
+    id: 23_672,
+    name: 'Test12',
+    parentId: 23_677,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2021-06-01',
+    id: 23_688,
+    name: 'Test13',
+    parentId: 23_666,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2021-06-01',
+    id: 23_681,
+    name: 'Test14',
+    parentId: 23_688,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2021-06-01',
+    id: 23_682,
+    name: 'Test15',
+    parentId: 23_688,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2020-10-01',
+    id: 24_555,
+    name: 'Test16',
+    parentId: null,
+    size: 224,
+    type: 'avi',
+  },
+  {
+    date: '2021-06-01',
+    id: 24_566,
+    name: 'Test17',
+    parentId: 24_555,
+    size: 1024,
+    type: 'js',
+  },
+  {
+    date: '2021-06-01',
+    id: 24_577,
+    name: 'Test18',
+    parentId: 24_555,
+    size: 1024,
+    type: 'js',
+  },
+];
+
+export const MOCK_API_DATA = [
+  {
+    available: true,
+    category: 'Computers',
+    color: 'purple',
+    currency: 'NAD',
+    description:
+      'Ergonomic executive chair upholstered in bonded black leather and PVC padded seat and back for all-day comfort and support',
+    id: '45a613df-227a-4907-a89f-4a7f1252ca0c',
+    imageUrl: 'https://avatars.githubusercontent.com/u/62715097',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/75395683',
+    inProduction: false,
+    open: true,
+    price: '48.89',
+    productName: 'Handcrafted Steel Salad',
+    quantity: 70,
+    rating: 3.780_582_329_574_367,
+    releaseDate: '2024-09-09T04:06:57.793Z',
+    status: 'error',
+    tags: ['Bespoke', 'Handmade', 'Luxurious'],
+    weight: 1.031_015_671_912_002_5,
+  },
+  {
+    available: true,
+    category: 'Toys',
+    color: 'green',
+    currency: 'CZK',
+    description:
+      'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J',
+    id: 'd02e5ee9-bc98-4de2-98fa-25a6567ecc19',
+    imageUrl: 'https://avatars.githubusercontent.com/u/51512330',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/58698113',
+    inProduction: false,
+    open: false,
+    price: '68.15',
+    productName: 'Generic Cotton Gloves',
+    quantity: 3,
+    rating: 1.681_749_367_682_703_3,
+    releaseDate: '2024-06-16T09:00:36.806Z',
+    status: 'warning',
+    tags: ['Rustic', 'Handcrafted', 'Recycled'],
+    weight: 9.601_076_149_300_575,
+  },
+  {
+    available: true,
+    category: 'Beauty',
+    color: 'teal',
+    currency: 'OMR',
+    description:
+      'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design',
+    id: '2b72521c-225c-4e64-8030-611b76b10b37',
+    imageUrl: 'https://avatars.githubusercontent.com/u/50300075',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/36541691',
+    inProduction: true,
+    open: true,
+    price: '696.94',
+    productName: 'Gorgeous Soft Ball',
+    quantity: 50,
+    rating: 2.361_581_777_372_057_5,
+    releaseDate: '2024-06-03T13:24:19.809Z',
+    status: 'warning',
+    tags: ['Gorgeous', 'Ergonomic', 'Licensed'],
+    weight: 8.882_340_049_286_19,
+  },
+  {
+    available: true,
+    category: 'Games',
+    color: 'silver',
+    currency: 'SOS',
+    description:
+      'Carbonite web goalkeeper gloves are ergonomically designed to give easy fit',
+    id: 'bafab694-3801-452c-b102-9eb519bd1143',
+    imageUrl: 'https://avatars.githubusercontent.com/u/89827115',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/55952747',
+    inProduction: false,
+    open: false,
+    price: '553.84',
+    productName: 'Bespoke Soft Computer',
+    quantity: 29,
+    rating: 2.176_412_873_760_271_7,
+    releaseDate: '2024-09-17T12:16:27.034Z',
+    status: 'error',
+    tags: ['Elegant', 'Rustic', 'Recycled'],
+    weight: 9.653_285_869_978_038,
+  },
+  {
+    available: true,
+    category: 'Toys',
+    color: 'indigo',
+    currency: 'BIF',
+    description:
+      'Andy shoes are designed to keeping in mind durability as well as trends, the most stylish range of shoes & sandals',
+    id: 'bf6dea6b-2a55-441d-8773-937e03d99389',
+    imageUrl: 'https://avatars.githubusercontent.com/u/21431092',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/3771350',
+    inProduction: true,
+    open: true,
+    price: '237.39',
+    productName: 'Handcrafted Cotton Mouse',
+    quantity: 54,
+    rating: 4.363_265_388_265_461,
+    releaseDate: '2023-10-23T13:42:34.947Z',
+    status: 'error',
+    tags: ['Unbranded', 'Handmade', 'Generic'],
+    weight: 9.513_203_612_535_571,
+  },
+  {
+    available: false,
+    category: 'Tools',
+    color: 'violet',
+    currency: 'TZS',
+    description:
+      'New ABC 13 9370, 13.3, 5th Gen CoreA5-8250U, 8GB RAM, 256GB SSD, power UHD Graphics, OS 10 Home, OS Office A & J 2016',
+    id: '135ba6ab-32ee-4989-8189-5cfa658ef970',
+    imageUrl: 'https://avatars.githubusercontent.com/u/29946092',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/23842994',
+    inProduction: false,
+    open: false,
+    price: '825.25',
+    productName: 'Awesome Bronze Ball',
+    quantity: 94,
+    rating: 4.251_159_804_726_753,
+    releaseDate: '2023-12-30T07:31:43.464Z',
+    status: 'warning',
+    tags: ['Handmade', 'Elegant', 'Unbranded'],
+    weight: 2.247_473_385_732_636_8,
+  },
+  {
+    available: true,
+    category: 'Automotive',
+    color: 'teal',
+    currency: 'BOB',
+    description: 'The Football Is Good For Training And Recreational Purposes',
+    id: '652ef256-7d4e-48b7-976c-7afaa781ea92',
+    imageUrl: 'https://avatars.githubusercontent.com/u/2531904',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/15215990',
+    inProduction: false,
+    open: false,
+    price: '780.49',
+    productName: 'Oriental Rubber Pants',
+    quantity: 70,
+    rating: 2.636_323_417_377_916,
+    releaseDate: '2024-02-23T23:30:49.628Z',
+    status: 'success',
+    tags: ['Unbranded', 'Elegant', 'Unbranded'],
+    weight: 4.812_965_858_018_838,
+  },
+  {
+    available: false,
+    category: 'Garden',
+    color: 'plum',
+    currency: 'LRD',
+    description:
+      'The slim & simple Maple Gaming Keyboard from Dev Byte comes with a sleek body and 7- Color RGB LED Back-lighting for smart functionality',
+    id: '3ea24798-6589-40cc-85f0-ab78752244a0',
+    imageUrl: 'https://avatars.githubusercontent.com/u/23165285',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/14595665',
+    inProduction: false,
+    open: true,
+    price: '583.85',
+    productName: 'Handcrafted Concrete Hat',
+    quantity: 15,
+    rating: 1.371_600_527_752_802_7,
+    releaseDate: '2024-03-02T19:40:50.255Z',
+    status: 'error',
+    tags: ['Rustic', 'Sleek', 'Ergonomic'],
+    weight: 4.926_949_366_405_728_4,
+  },
+  {
+    available: false,
+    category: 'Industrial',
+    color: 'salmon',
+    currency: 'AUD',
+    description:
+      'The Apollotech B340 is an affordable wireless mouse with reliable connectivity, 12 months battery life and modern design',
+    id: '997113dd-f6e4-4acc-9790-ef554c7498d1',
+    imageUrl: 'https://avatars.githubusercontent.com/u/49021914',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/4690621',
+    inProduction: true,
+    open: false,
+    price: '67.99',
+    productName: 'Generic Rubber Bacon',
+    quantity: 68,
+    rating: 4.129_840_682_128_08,
+    releaseDate: '2023-12-17T01:40:25.415Z',
+    status: 'error',
+    tags: ['Oriental', 'Small', 'Handcrafted'],
+    weight: 1.080_114_331_801_906_4,
+  },
+  {
+    available: false,
+    category: 'Tools',
+    color: 'sky blue',
+    currency: 'NOK',
+    description:
+      'The Nagasaki Lander is the trademarked name of several series of Nagasaki sport bikes, that started with the 1984 ABC800J',
+    id: 'f697a250-6cb2-46c8-b0f7-871ab1f2fa8d',
+    imageUrl: 'https://avatars.githubusercontent.com/u/95928385',
+    imageUrl2: 'https://avatars.githubusercontent.com/u/47588244',
+    inProduction: false,
+    open: false,
+    price: '613.89',
+    productName: 'Gorgeous Frozen Ball',
+    quantity: 55,
+    rating: 1.646_947_205_998_534_6,
+    releaseDate: '2024-10-13T12:31:04.929Z',
+    status: 'warning',
+    tags: ['Handmade', 'Unbranded', 'Unbranded'],
+    weight: 9.430_690_557_758_114,
+  },
+];

+ 80 - 0
docs/src/demos/vben-vxe-table/tree/index.vue

@@ -0,0 +1,80 @@
+<script lang="ts" setup>
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { Button } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+import { MOCK_TREE_TABLE_DATA } from '../table-data';
+
+interface RowType {
+  date: string;
+  id: number;
+  name: string;
+  parentId: null | number;
+  size: number;
+  type: string;
+}
+
+// 数据实例
+// const MOCK_TREE_TABLE_DATA = [
+//   {
+//     date: '2020-08-01',
+//     id: 10_000,
+//     name: 'Test1',
+//     parentId: null,
+//     size: 1024,
+//     type: 'mp3',
+//   },
+//   {
+//     date: '2021-04-01',
+//     id: 10_050,
+//     name: 'Test2',
+//     parentId: 10_000,
+//     size: 0,
+//     type: 'mp4',
+//   },
+// ];
+
+const gridOptions: VxeGridProps<RowType> = {
+  columns: [
+    { type: 'seq', width: 70 },
+    { field: 'name', minWidth: 300, title: 'Name', treeNode: true },
+    { field: 'size', title: 'Size' },
+    { field: 'type', title: 'Type' },
+    { field: 'date', title: 'Date' },
+  ],
+  data: MOCK_TREE_TABLE_DATA,
+  pagerConfig: {
+    enabled: false,
+  },
+  treeConfig: {
+    parentField: 'parentId',
+    rowField: 'id',
+    transform: true,
+  },
+};
+
+const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
+
+const expandAll = () => {
+  gridApi.grid?.setAllTreeExpand(true);
+};
+
+const collapseAll = () => {
+  gridApi.grid?.setAllTreeExpand(false);
+};
+</script>
+
+<template>
+  <div class="vp-raw h-[300px] w-full">
+    <Grid>
+      <template #toolbar-tools>
+        <Button class="mr-2" type="primary" @click="expandAll">
+          展开全部
+        </Button>
+        <Button type="primary" @click="collapseAll"> 折叠全部 </Button>
+      </template>
+    </Grid>
+  </div>
+</template>

+ 64 - 0
docs/src/demos/vben-vxe-table/virtual/index.vue

@@ -0,0 +1,64 @@
+<script lang="ts" setup>
+import type { VxeGridProps } from '#/adapter/vxe-table';
+
+import { onMounted } from 'vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+
+interface RowType {
+  id: number;
+  name: string;
+  role: string;
+  sex: string;
+}
+
+const gridOptions: VxeGridProps<RowType> = {
+  columns: [
+    { type: 'seq', width: 70 },
+    { field: 'name', title: 'Name' },
+    { field: 'role', title: 'Role' },
+    { field: 'sex', title: 'Sex' },
+  ],
+  data: [],
+  height: 'auto',
+  pagerConfig: {
+    enabled: false,
+  },
+  scrollY: {
+    enabled: true,
+    gt: 0,
+  },
+  showOverflow: true,
+};
+
+const [Grid, gridApi] = useVbenVxeGrid({ gridOptions });
+
+// 模拟行数据
+const loadList = (size = 200) => {
+  try {
+    const dataList: RowType[] = [];
+    for (let i = 0; i < size; i++) {
+      dataList.push({
+        id: 10_000 + i,
+        name: `Test${i}`,
+        role: 'Developer',
+        sex: '男',
+      });
+    }
+    gridApi.setGridOptions({ data: dataList });
+  } catch (error) {
+    console.error('Failed to load data:', error);
+    // Implement user-friendly error handling
+  }
+};
+
+onMounted(() => {
+  loadList(1000);
+});
+</script>
+
+<template>
+  <div class="vp-raw h-[500px] w-full">
+    <Grid />
+  </div>
+</template>

+ 6 - 0
docs/src/en/guide/essentials/development.md

@@ -150,6 +150,12 @@ To run the `docs` application:
 pnpm dev:docs
 ```
 
+## Public Static Resources
+
+If you need to use public static resources in the project, such as images, static HTML, etc., and you want to directly import them in the development process through `src="/xxx.png"`.
+
+You need to put the resource in the corresponding project's `public/static` directory. The import path for the resource should be `src="/static/xxx.png"`.
+
 ## DevTools
 
 The project has a built-in [Vue DevTools](https://github.com/vuejs/devtools-next) plugin, which can be used during development. It is disabled by default, but can be enabled in the `.env.development` file. After enabling it, restart the project:

+ 1 - 1
docs/src/en/guide/essentials/styles.md

@@ -72,7 +72,7 @@ const { b, e, is } = useNamespace('menu');
 </template>
 <style lang="scss" scoped>
 // If you use it within the application, this line of code can be omitted as it has already been globally introduced in all applications
-@import '@vben/styles/global';
+@use '@vben/styles/global' as *;
 @include b('menu') {
   color: black;
 

+ 6 - 0
docs/src/guide/essentials/development.md

@@ -150,6 +150,12 @@ pnpm dev:ele
 pnpm dev:docs
 ```
 
+## 公共静态资源
+
+项目中需要使用到的公共静态资源,如:图片、静态HTML等,需要在开发中通过 `src="/xxx.png"` 直接引入的。
+
+需要将资源放在对应项目的 `public/static` 目录下。引入的路径为:`src="/static/xxx.png"`。
+
 ## DevTools
 
 项目内置了 [Vue DevTools](https://github.com/vuejs/devtools-next) 插件,可以在开发过程中使用。默认关闭,可在`.env.development` 内开启,并重新运行项目即可:

+ 2 - 2
docs/src/guide/essentials/settings.md

@@ -164,6 +164,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
 /**
  * @description 项目配置文件
  * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
+ * !!! 更改配置后请清空缓存,否则可能不生效
  */
 export const overridesPreferences = defineOverridesPreferences({
   // overrides
@@ -536,5 +537,4 @@ interface Preferences {
 
 - `overridesPreferences`方法只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置。
 - 任何配置项都可以覆盖,只需要在`overridesPreferences`方法内覆盖即可,不要修改默认配置文件。
-
-:::
+- 更改配置后请清空缓存,否则可能不生效。:::

+ 1 - 1
docs/src/guide/essentials/styles.md

@@ -72,7 +72,7 @@ const { b, e, is } = useNamespace('menu');
 </template>
 <style lang="scss" scoped>
 // 如果你在应用内使用,这行代码可以省略,已经在所有的应用内全局引入了
-@import '@vben/styles/global';
+@use '@vben/styles/global' as *;
 @include b('menu') {
   color: black;
 

+ 3 - 1
docs/src/guide/introduction/quick-start.md

@@ -66,7 +66,9 @@ pnpm install
 
 ::: tip 注意
 
-项目只支持使用 `pnpm` 进行依赖安装,默认会使用 `corepack` 来安装指定版本的 `pnpm`。:
+- 项目只支持使用 `pnpm` 进行依赖安装,默认会使用 `corepack` 来安装指定版本的 `pnpm`。:
+- 如果你的网络环境无法访问npm源,你可以设置系统的环境变量`COREPACK_REGISTRY=https://registry.npmmirror.com`,然后再执行`pnpm install`。
+- 如果你不想使用`corepack`,你需要禁用`corepack`,然后使用你自己的`pnpm`进行安装。
 
 :::
 

+ 1 - 1
internal/lint-configs/commitlint-config/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/commitlint-config",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

+ 1 - 1
internal/lint-configs/stylelint-config/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/stylelint-config",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

+ 1 - 1
internal/node-utils/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/node-utils",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

+ 1 - 1
internal/tailwind-config/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/tailwind-config",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

+ 1 - 1
internal/tsconfig/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/tsconfig",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

+ 1 - 1
internal/vite-config/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/vite-config",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",

+ 1 - 1
internal/vite-config/src/config/application.ts

@@ -109,7 +109,7 @@ function createCssOptions(injectGlobalScss = true) {
               const relativePath = relative(root, filepath);
               // apps下的包注入全局样式
               if (relativePath.startsWith(`apps${path.sep}`)) {
-                return `@import "@vben/styles/global";\n${content}`;
+                return `@use "@vben/styles/global" as *;\n${content}`;
               }
               return content;
             },

+ 1 - 0
internal/vite-config/src/plugins/index.ts

@@ -243,4 +243,5 @@ export {
   viteDtsPlugin,
   viteHtmlPlugin,
   viteVisualizerPlugin,
+  viteVxeTableImportsPlugin,
 };

+ 29 - 0
internal/vite-config/src/plugins/vxe-table.ts

@@ -4,6 +4,35 @@ import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import';
 
 async function viteVxeTableImportsPlugin(): Promise<PluginOption> {
   return [
+    // {
+    //   config() {
+    //     return {
+    //       optimizeDeps: {
+    //         include: [
+    //           'vxe-pc-ui/es/vxe-button/index.js',
+    //           'vxe-pc-ui/es/vxe-checkbox/index.js',
+    //           'vxe-pc-ui/es/vxe-icon/index.js',
+    //           'vxe-pc-ui/es/vxe-input/index.js',
+    //           'vxe-pc-ui/es/vxe-loading/index.js',
+    //           'vxe-pc-ui/es/vxe-modal/index.js',
+    //           'vxe-pc-ui/es/vxe-pager/index.js',
+    //           'vxe-pc-ui/es/vxe-radio-group/index.js',
+    //           'vxe-pc-ui/es/vxe-select/index.js',
+    //           'vxe-pc-ui/es/vxe-tooltip/index.js',
+    //           'vxe-pc-ui/es/vxe-ui/index.js',
+    //           'vxe-pc-ui/es/vxe-upload/index.js',
+    //           'vxe-table/es/vxe-colgroup/index.js',
+    //           'vxe-table/es/vxe-column/index.js',
+    //           'vxe-table/es/vxe-grid/index.js',
+    //           'vxe-table/es/vxe-table/index.js',
+    //           'vxe-table/es/vxe-toolbar/index.js',
+    //           'vxe-table/es/vxe-ui/index.js',
+    //         ],
+    //       },
+    //     };
+    //   },
+    //   name: 'vxe-table-adapter',
+    // },
     lazyImport({
       resolvers: [
         VxeResolver({

+ 2 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "vben-admin-monorepo",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "keywords": [
     "monorepo",
@@ -97,7 +97,7 @@
   },
   "engines": {
     "node": ">=20.10.0",
-    "pnpm": ">=9.5.0"
+    "pnpm": ">=9.12.0"
   },
   "packageManager": "pnpm@9.12.3",
   "pnpm": {

+ 1 - 1
packages/@core/base/design/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/design",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/@core/base/icons/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/icons",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 3 - 1
packages/@core/base/shared/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/shared",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {
@@ -81,10 +81,12 @@
   "dependencies": {
     "@ctrl/tinycolor": "catalog:",
     "@tanstack/vue-store": "catalog:",
+    "@types/lodash.get": "catalog:",
     "@vue/shared": "catalog:",
     "clsx": "catalog:",
     "defu": "catalog:",
     "lodash.clonedeep": "catalog:",
+    "lodash.get": "catalog:",
     "nprogress": "catalog:",
     "tailwind-merge": "catalog:",
     "theme-colors": "catalog:"

+ 1 - 1
packages/@core/base/typings/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/typings",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/@core/composables/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/composables",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/@core/preferences/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/preferences",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

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

@@ -253,6 +253,7 @@ export class FormApi {
   }
 
   unmount() {
+    this.form?.resetForm?.();
     // this.state = null;
     this.isMounted = false;
     this.stateHandler.reset();

+ 1 - 1
packages/@core/ui-kit/layout-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/layout-ui",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/@core/ui-kit/menu-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/menu-ui",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/shadcn-ui",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "#main": "./dist/index.mjs",
   "#module": "./dist/index.mjs",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",

+ 1 - 1
packages/@core/ui-kit/tabs-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben-core/tabs-ui",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/constants/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/constants",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/effects/access/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/access",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/effects/common-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/common-ui",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 7 - 3
packages/effects/common-ui/src/ui/authentication/code-login.vue

@@ -53,7 +53,7 @@ const emit = defineEmits<{
 
 const router = useRouter();
 
-const [Form, { validate, getValues }] = useVbenForm(
+const [Form, formApi] = useVbenForm(
   reactive({
     commonConfig: {
       hideLabel: true,
@@ -65,8 +65,8 @@ const [Form, { validate, getValues }] = useVbenForm(
 );
 
 async function handleSubmit() {
-  const { valid } = await validate();
-  const values = await getValues();
+  const { valid } = await formApi.validate();
+  const values = await formApi.getValues();
   if (valid) {
     emit('submit', {
       code: values?.code,
@@ -78,6 +78,10 @@ async function handleSubmit() {
 function goToLogin() {
   router.push(props.loginPath);
 }
+
+defineExpose({
+  getFormApi: () => formApi,
+});
 </script>
 
 <template>

+ 7 - 3
packages/effects/common-ui/src/ui/authentication/forget-password.vue

@@ -50,7 +50,7 @@ const emit = defineEmits<{
   submit: [Record<string, any>];
 }>();
 
-const [Form, { validate, getValues }] = useVbenForm(
+const [Form, formApi] = useVbenForm(
   reactive({
     commonConfig: {
       hideLabel: true,
@@ -64,8 +64,8 @@ const [Form, { validate, getValues }] = useVbenForm(
 const router = useRouter();
 
 async function handleSubmit() {
-  const { valid } = await validate();
-  const values = await getValues();
+  const { valid } = await formApi.validate();
+  const values = await formApi.getValues();
   if (valid) {
     emit('submit', values);
   }
@@ -74,6 +74,10 @@ async function handleSubmit() {
 function goToLogin() {
   router.push(props.loginPath);
 }
+
+defineExpose({
+  getFormApi: () => formApi,
+});
 </script>
 
 <template>

+ 8 - 4
packages/effects/common-ui/src/ui/authentication/login.vue

@@ -44,7 +44,7 @@ const emit = defineEmits<{
   submit: [Recordable<any>];
 }>();
 
-const [Form, { setFieldValue, validate, getValues }] = useVbenForm(
+const [Form, formApi] = useVbenForm(
   reactive({
     commonConfig: {
       hideLabel: true,
@@ -63,8 +63,8 @@ const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || '';
 const rememberMe = ref(!!localUsername);
 
 async function handleSubmit() {
-  const { valid } = await validate();
-  const values = await getValues();
+  const { valid } = await formApi.validate();
+  const values = await formApi.getValues();
   if (valid) {
     localStorage.setItem(
       REMEMBER_ME_KEY,
@@ -80,9 +80,13 @@ function handleGo(path: string) {
 
 onMounted(() => {
   if (localUsername) {
-    setFieldValue('username', localUsername);
+    formApi.setFieldValue('username', localUsername);
   }
 });
+
+defineExpose({
+  getFormApi: () => formApi,
+});
 </script>
 
 <template>

+ 7 - 3
packages/effects/common-ui/src/ui/authentication/register.vue

@@ -52,7 +52,7 @@ const emit = defineEmits<{
   submit: [Recordable<any>];
 }>();
 
-const [Form, { validate, getValues }] = useVbenForm(
+const [Form, formApi] = useVbenForm(
   reactive({
     commonConfig: {
       hideLabel: true,
@@ -66,8 +66,8 @@ const [Form, { validate, getValues }] = useVbenForm(
 const router = useRouter();
 
 async function handleSubmit() {
-  const { valid } = await validate();
-  const values = await getValues();
+  const { valid } = await formApi.validate();
+  const values = await formApi.getValues();
   if (valid) {
     emit('submit', values as { password: string; username: string });
   }
@@ -76,6 +76,10 @@ async function handleSubmit() {
 function goToLogin() {
   router.push(props.loginPath);
 }
+
+defineExpose({
+  getFormApi: () => formApi,
+});
 </script>
 
 <template>

+ 1 - 1
packages/effects/hooks/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/hooks",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/effects/layouts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/layouts",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/effects/plugins/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/plugins",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 0
packages/effects/plugins/src/vxe-table/index.ts

@@ -1,4 +1,5 @@
 export { setupVbenVxeTable } from './init';
 export * from './use-vxe-grid';
 export { default as VbenVxeGrid } from './use-vxe-grid.vue';
+
 export type { VxeGridListeners, VxeGridProps } from 'vxe-table';

+ 8 - 10
packages/effects/plugins/src/vxe-table/theme.css

@@ -1,4 +1,4 @@
-:root {
+:root .vxe-grid {
   --vxe-ui-font-color: hsl(var(--foreground));
   --vxe-ui-font-primary-color: hsl(var(--primary));
 
@@ -14,7 +14,7 @@
 
   /* layout */
   --vxe-ui-layout-background-color: hsl(var(--background));
-  --vxe-ui-table-resizable-line-color: hsl(var(--border));
+  --vxe-ui-table-resizable-line-color: hsl(var(--heavy));
 
   /* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent));
   --vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */
@@ -66,15 +66,13 @@
     box-shadow: 0 0 0 1px hsl(var(--border));
   }
 
-  .vxe-pager {
-    &--wrapper {
-      display: flex;
-      align-items: center;
-    }
+  .vxe-pager--wrapper {
+    display: flex;
+    align-items: center;
+  }
 
-    &--sizes {
-      margin-right: auto;
-    }
+  .vxe-pager--sizes {
+    margin-right: auto;
   }
 }
 

+ 2 - 0
packages/effects/plugins/src/vxe-table/use-vxe-grid.ts

@@ -41,3 +41,5 @@ export function useVbenVxeGrid(options: VxeGridProps) {
 
   return [Grid, extendedApi] as const;
 }
+
+export type UseVbenVxeGrid = typeof useVbenVxeGrid;

+ 17 - 12
packages/effects/plugins/src/vxe-table/use-vxe-grid.vue

@@ -11,6 +11,7 @@ import {
   computed,
   nextTick,
   onMounted,
+  onUnmounted,
   toRaw,
   useSlots,
   useTemplateRef,
@@ -41,6 +42,9 @@ const props = withDefaults(defineProps<Props>(), {});
 
 const FORM_SLOT_PREFIX = 'form-';
 
+const TOOLBAR_ACTIONS = 'toolbar-actions';
+const TOOLBAR_TOOLS = 'toolbar-tools';
+
 const gridRef = useTemplateRef<VxeGridInstance>('gridRef');
 
 const state = props.api?.useStore?.();
@@ -87,15 +91,16 @@ const showTableTitle = computed(() => {
 
 const showToolbar = computed(() => {
   return (
-    !!slots['toolbar-actions']?.() ||
-    !!slots['toolbar-tools']?.() ||
+    !!slots[TOOLBAR_ACTIONS]?.() ||
+    !!slots[TOOLBAR_TOOLS]?.() ||
     showTableTitle.value
   );
 });
 
 const toolbarOptions = computed(() => {
-  const slotActions = slots['toolbar-actions']?.();
-  const slotTools = slots['toolbar-tools']?.();
+  const slotActions = slots[TOOLBAR_ACTIONS]?.();
+  const slotTools = slots[TOOLBAR_TOOLS]?.();
+
   if (!showToolbar.value) {
     return {};
   }
@@ -105,9 +110,9 @@ const toolbarOptions = computed(() => {
     toolbarConfig: {
       slots: {
         ...(slotActions || showTableTitle.value
-          ? { buttons: 'toolbar-actions' }
+          ? { buttons: TOOLBAR_ACTIONS }
           : {}),
-        ...(slotTools ? { tools: 'toolbar-tools' } : {}),
+        ...(slotTools ? { tools: TOOLBAR_TOOLS } : {}),
       },
     },
   };
@@ -122,11 +127,6 @@ const options = computed(() => {
       toolbarOptions.value,
       toRaw(gridOptions.value),
       globalGridConfig,
-      {
-        // toolbarConfig: {
-        //   tools: [],
-        // },
-      } as VxeTableGridProps,
     ),
   );
 
@@ -185,7 +185,7 @@ const delegatedSlots = computed(() => {
   const resultSlots: string[] = [];
 
   for (const key of Object.keys(slots)) {
-    if (!['empty', 'form', 'loading', 'toolbar-actions'].includes(key)) {
+    if (!['empty', 'form', 'loading', TOOLBAR_ACTIONS].includes(key)) {
       resultSlots.push(key);
     }
   }
@@ -257,6 +257,11 @@ onMounted(() => {
   props.api?.mount?.(gridRef.value, formApi);
   init();
 });
+
+onUnmounted(() => {
+  formApi?.unmount?.();
+  props.api?.unmount?.();
+});
 </script>
 
 <template>

+ 1 - 1
packages/effects/request/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/request",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/icons/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/icons",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/locales/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/locales",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/preferences/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/preferences",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/stores/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/stores",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/styles/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/styles",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/styles/src/global/index.scss

@@ -1 +1 @@
-@import '@vben-core/design/bem';
+@use '@vben-core/design/bem' as *;

+ 1 - 1
packages/types/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/types",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
packages/utils/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/utils",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 1 - 1
playground/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/playground",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "homepage": "https://vben.pro",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {

+ 2 - 2
playground/src/adapter/vxe-table.ts

@@ -39,7 +39,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellImage' },
     vxeUI.renderer.add('CellImage', {
-      renderDefault(_renderOpts, params) {
+      renderTableDefault(_renderOpts, params) {
         const { column, row } = params;
         return h(Image, { src: row[column.field] });
       },
@@ -47,7 +47,7 @@ setupVbenVxeTable({
 
     // 表格配置项可以用 cellRender: { name: 'CellLink' },
     vxeUI.renderer.add('CellLink', {
-      renderDefault(renderOpts) {
+      renderTableDefault(renderOpts) {
         const { props } = renderOpts;
         return h(
           Button,

+ 15 - 3
playground/src/bootstrap.ts

@@ -1,17 +1,19 @@
-import { createApp } from 'vue';
+import { createApp, watchEffect } from 'vue';
 
 import { registerAccessDirective } from '@vben/access';
+import { preferences } from '@vben/preferences';
 import { initStores } from '@vben/stores';
 import '@vben/styles';
 import '@vben/styles/antd';
 
 import { VueQueryPlugin } from '@tanstack/vue-query';
+import { useTitle } from '@vueuse/core';
 
-import { setupI18n } from '#/locales';
+import { $t, setupI18n } from '#/locales';
+import { router } from '#/router';
 
 import { initComponentAdapter } from './adapter/component';
 import App from './app.vue';
-import { router } from './router';
 
 async function bootstrap(namespace: string) {
   // 初始化组件适配器
@@ -34,6 +36,16 @@ async function bootstrap(namespace: string) {
   // 配置@tanstack/vue-query
   app.use(VueQueryPlugin);
 
+  // 动态更新标题
+  watchEffect(() => {
+    if (preferences.app.dynamicTitle) {
+      const routeTitle = router.currentRoute.value.meta?.title;
+      const pageTitle =
+        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
+      useTitle(pageTitle);
+    }
+  });
+
   app.mount('#app');
 }
 

+ 1 - 0
playground/src/preferences.ts

@@ -3,6 +3,7 @@ import { defineOverridesPreferences } from '@vben/preferences';
 /**
  * @description 项目配置文件
  * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
+ * !!! 更改配置后请清空缓存,否则可能不生效
  */
 export const overridesPreferences = defineOverridesPreferences({
   // overrides

+ 0 - 10
playground/src/router/guard.ts

@@ -5,9 +5,6 @@ import { preferences } from '@vben/preferences';
 import { useAccessStore, useUserStore } from '@vben/stores';
 import { startProgress, stopProgress } from '@vben/utils';
 
-import { useTitle } from '@vueuse/core';
-
-import { $t } from '#/locales';
 import { accessRoutes, coreRouteNames } from '#/router/routes';
 import { useAuthStore } from '#/store';
 
@@ -39,13 +36,6 @@ function setupCommonGuard(router: Router) {
     if (preferences.transition.progress) {
       stopProgress();
     }
-
-    // 动态修改标题
-    if (preferences.app.dynamicTitle) {
-      const { title } = to.meta;
-      // useTitle(`${$t(title)} - ${preferences.app.name}`);
-      useTitle(`${$t(title)} - ${preferences.app.name}`);
-    }
   });
 }
 

文件差異過大導致無法顯示
+ 221 - 202
pnpm-lock.yaml


+ 20 - 18
pnpm-workspace.yaml

@@ -22,8 +22,8 @@ catalog:
   '@commitlint/config-conventional': ^19.5.0
   '@ctrl/tinycolor': ^4.1.0
   '@eslint/js': ^9.14.0
-  '@faker-js/faker': ^9.1.0
-  '@iconify/json': ^2.2.266
+  '@faker-js/faker': ^9.2.0
+  '@iconify/json': ^2.2.269
   '@iconify/tailwind': ^1.1.3
   '@iconify/vue': ^4.1.2
   '@intlify/core-base': ^10.0.4
@@ -36,20 +36,21 @@ catalog:
   '@stylistic/stylelint-plugin': ^3.1.1
   '@tailwindcss/nesting': 0.0.0-insiders.565cd3e
   '@tailwindcss/typography': ^0.5.15
-  '@tanstack/vue-query': ^5.59.16
+  '@tanstack/vue-query': ^5.59.20
   '@tanstack/vue-store': ^0.5.6
   '@types/archiver': ^6.0.3
   '@types/eslint': ^9.6.1
   '@types/html-minifier-terser': ^7.0.2
   '@types/jsonwebtoken': ^9.0.7
   '@types/lodash.clonedeep': ^4.5.9
-  '@types/node': ^22.8.6
+  '@types/lodash.get': ^4.4.9
+  '@types/node': ^22.9.0
   '@types/nprogress': ^0.2.3
   '@types/postcss-import': ^14.0.3
   '@types/qrcode': ^1.5.5
   '@types/sortablejs': ^1.15.8
-  '@typescript-eslint/eslint-plugin': ^8.12.2
-  '@typescript-eslint/parser': ^8.12.2
+  '@typescript-eslint/eslint-plugin': ^8.13.0
+  '@typescript-eslint/parser': ^8.13.0
   '@vee-validate/zod': ^4.14.6
   '@vite-pwa/vitepress': ^0.5.3
   '@vitejs/plugin-vue': ^5.1.4
@@ -70,10 +71,10 @@ catalog:
   circular-dependency-scanner: ^2.3.0
   class-variance-authority: ^0.7.0
   clsx: ^2.1.1
-  commitlint-plugin-function-rules: ^4.0.0
+  commitlint-plugin-function-rules: ^4.0.1
   consola: ^3.2.3
   cross-env: ^7.0.3
-  cspell: ^8.15.5
+  cspell: ^8.16.0
   cssnano: ^7.0.6
   cz-git: ^1.10.1
   czg: ^1.10.1
@@ -90,7 +91,7 @@ catalog:
   eslint-plugin-import-x: ^4.4.0
   eslint-plugin-jsdoc: ^50.4.3
   eslint-plugin-jsonc: ^2.16.0
-  eslint-plugin-n: ^17.12.0
+  eslint-plugin-n: ^17.13.1
   eslint-plugin-no-only-tests: ^3.3.0
   eslint-plugin-perfectionist: ^3.9.1
   eslint-plugin-prettier: ^5.2.1
@@ -102,9 +103,9 @@ catalog:
   execa: ^9.5.1
   find-up: ^7.0.0
   get-port: ^7.1.0
-  globals: ^15.11.0
+  globals: ^15.12.0
   h3: ^1.13.0
-  happy-dom: ^15.8.0
+  happy-dom: ^15.11.0
   html-minifier-terser: ^7.2.0
   husky: ^9.1.6
   is-ci: ^3.0.1
@@ -112,10 +113,11 @@ catalog:
   jsonwebtoken: ^9.0.2
   lint-staged: ^15.2.10
   lodash.clonedeep: ^4.5.0
+  lodash.get: ^4.4.2
   lucide-vue-next: ^0.454.0
   medium-zoom: ^1.1.0
   naive-ui: ^2.40.1
-  nitropack: ^2.10.0
+  nitropack: ^2.10.3
   nprogress: ^0.2.0
   ora: ^8.1.1
   pinia: 2.2.2
@@ -135,9 +137,9 @@ catalog:
   radix-vue: ^1.9.8
   resolve.exports: ^2.0.2
   rimraf: ^6.0.1
-  rollup: ^4.24.3
+  rollup: ^4.24.4
   rollup-plugin-visualizer: ^5.12.0
-  sass: 1.79.5
+  sass: 1.80.6
   sortablejs: ^1.15.3
   stylelint: ^16.10.0
   stylelint-config-recess-order: ^5.1.1
@@ -163,8 +165,8 @@ catalog:
   vite-plugin-html: ^3.2.2
   vite-plugin-lazy-import: ^1.0.7
   vite-plugin-pwa: ^0.20.5
-  vite-plugin-vue-devtools: ^7.6.2
-  vitepress: ^1.4.3
+  vite-plugin-vue-devtools: ^7.6.3
+  vitepress: ^1.5.0
   vitepress-plugin-group-icons: ^1.3.0
   vitest: ^2.1.4
   vue: ^3.5.12
@@ -172,8 +174,8 @@ catalog:
   vue-i18n: ^10.0.4
   vue-router: ^4.4.5
   vue-tsc: ^2.1.10
-  vxe-pc-ui: ^4.2.37
-  vxe-table: ^4.7.97
+  vxe-pc-ui: ^4.2.45
+  vxe-table: ^4.8.2
   watermark-js-plus: ^1.5.7
   zod: ^3.23.8
   zod-defaults: ^0.1.3

+ 1 - 1
scripts/turbo-run/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vben/turbo-run",
-  "version": "5.4.3",
+  "version": "5.4.4",
   "private": true,
   "license": "MIT",
   "type": "module",

部分文件因文件數量過多而無法顯示