Просмотр исходного кода

Merge remote-tracking branch 'origin/main'

lmx 2 месяцев назад
Родитель
Сommit
da3580cbd7
96 измененных файлов с 2155 добавлено и 1719 удалено
  1. 2 1
      .vscode/settings.json
  2. 333 254
      apps/web-antd/src/adapter/component/index.ts
  3. 1 0
      cspell.json
  4. 1 0
      docs/package.json
  5. 3 3
      docs/src/en/guide/project/tailwindcss.md
  6. 3 3
      docs/src/guide/project/tailwindcss.md
  7. 0 7
      internal/lint-configs/eslint-config/build.config.ts
  8. 2 4
      internal/lint-configs/eslint-config/package.json
  9. 2 1
      internal/lint-configs/eslint-config/src/configs/node.ts
  10. 16 0
      internal/lint-configs/eslint-config/tsdown.config.ts
  11. 0 7
      internal/lint-configs/oxfmt-config/build.config.ts
  12. 1 1
      internal/lint-configs/oxfmt-config/package.json
  13. 2 2
      internal/lint-configs/oxfmt-config/src/index.ts
  14. 11 0
      internal/lint-configs/oxfmt-config/tsdown.config.ts
  15. 0 7
      internal/lint-configs/oxlint-config/build.config.ts
  16. 1 1
      internal/lint-configs/oxlint-config/package.json
  17. 1 1
      internal/lint-configs/oxlint-config/src/configs/tailwindcss.ts
  18. 11 0
      internal/lint-configs/oxlint-config/tsdown.config.ts
  19. 0 7
      internal/node-utils/build.config.ts
  20. 2 2
      internal/node-utils/package.json
  21. 40 0
      internal/node-utils/scripts/build.mjs
  22. 2 2
      internal/node-utils/src/date.ts
  23. 22 12
      internal/node-utils/src/monorepo.ts
  24. 8 0
      internal/node-utils/tsconfig.build.json
  25. 10 0
      internal/node-utils/tsdown.config.ts
  26. 38 0
      internal/tailwind-config/package.json
  27. 1 0
      internal/tailwind-config/src/index.ts
  28. 569 0
      internal/tailwind-config/src/theme.css
  29. 0 7
      internal/vite-config/build.config.ts
  30. 3 4
      internal/vite-config/package.json
  31. 3 2
      internal/vite-config/src/plugins/index.ts
  32. 1 1
      internal/vite-config/src/plugins/tailwind-reference.ts
  33. 1 1
      internal/vite-config/src/typing.ts
  34. 41 0
      internal/vite-config/tsdown.config.ts
  35. 5 2
      package.json
  36. 3 4
      packages/@core/base/design/package.json
  37. 1 569
      packages/@core/base/design/src/css/global.css
  38. 1 1
      packages/@core/base/design/src/css/nprogress.css
  39. 0 7
      packages/@core/base/icons/build.config.ts
  40. 2 1
      packages/@core/base/icons/package.json
  41. 11 0
      packages/@core/base/icons/tsdown.config.ts
  42. 0 14
      packages/@core/base/shared/build.config.ts
  43. 8 2
      packages/@core/base/shared/package.json
  44. 2 2
      packages/@core/base/shared/src/utils/__tests__/date.test.ts
  45. 2 2
      packages/@core/base/shared/src/utils/date.ts
  46. 18 0
      packages/@core/base/shared/tsdown.config.ts
  47. 0 7
      packages/@core/base/typings/build.config.ts
  48. 4 2
      packages/@core/base/typings/package.json
  49. 11 0
      packages/@core/base/typings/tsdown.config.ts
  50. 1 2
      packages/@core/base/typings/vue-router.d.ts
  51. 0 7
      packages/@core/composables/build.config.ts
  52. 2 1
      packages/@core/composables/package.json
  53. 11 0
      packages/@core/composables/tsdown.config.ts
  54. 0 7
      packages/@core/preferences/build.config.ts
  55. 14 3
      packages/@core/preferences/package.json
  56. 11 0
      packages/@core/preferences/tsdown.config.ts
  57. 0 21
      packages/@core/ui-kit/form-ui/build.config.ts
  58. 4 1
      packages/@core/ui-kit/form-ui/package.json
  59. 21 0
      packages/@core/ui-kit/form-ui/tsdown.config.ts
  60. 0 21
      packages/@core/ui-kit/layout-ui/build.config.ts
  61. 4 1
      packages/@core/ui-kit/layout-ui/package.json
  62. 21 0
      packages/@core/ui-kit/layout-ui/tsdown.config.ts
  63. 0 26
      packages/@core/ui-kit/menu-ui/build.config.ts
  64. 4 1
      packages/@core/ui-kit/menu-ui/package.json
  65. 1 1
      packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue
  66. 21 0
      packages/@core/ui-kit/menu-ui/tsdown.config.ts
  67. 0 21
      packages/@core/ui-kit/popup-ui/build.config.ts
  68. 4 1
      packages/@core/ui-kit/popup-ui/package.json
  69. 21 0
      packages/@core/ui-kit/popup-ui/tsdown.config.ts
  70. 0 27
      packages/@core/ui-kit/shadcn-ui/build.config.ts
  71. 6 9
      packages/@core/ui-kit/shadcn-ui/package.json
  72. 1 0
      packages/@core/ui-kit/shadcn-ui/src/assets/index.css
  73. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue
  74. 3 2
      packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue
  75. 0 21
      packages/@core/ui-kit/tabs-ui/build.config.ts
  76. 4 1
      packages/@core/ui-kit/tabs-ui/package.json
  77. 1 1
      packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue
  78. 21 0
      packages/@core/ui-kit/tabs-ui/tsdown.config.ts
  79. 2 2
      packages/effects/common-ui/src/components/cropper/cropper.vue
  80. 1 1
      packages/effects/layouts/src/widgets/theme-toggle/theme-button.vue
  81. 12 2
      packages/effects/plugins/src/vxe-table/init.ts
  82. 1 1
      packages/effects/plugins/src/vxe-table/style.css
  83. 333 254
      playground/src/adapter/component/index.ts
  84. 1 1
      playground/src/views/examples/drawer/index.vue
  85. 5 1
      playground/src/views/examples/form/basic.vue
  86. 312 287
      pnpm-lock.yaml
  87. 25 24
      pnpm-workspace.yaml
  88. 0 7
      scripts/turbo-run/build.config.ts
  89. 1 1
      scripts/turbo-run/package.json
  90. 11 0
      scripts/turbo-run/tsdown.config.ts
  91. 0 7
      scripts/vsh/build.config.ts
  92. 1 1
      scripts/vsh/package.json
  93. 57 10
      scripts/vsh/src/check-circular/index.ts
  94. 2 1
      scripts/vsh/src/check-dep/index.ts
  95. 11 0
      scripts/vsh/tsdown.config.ts
  96. 4 0
      vben-admin.code-workspace

+ 2 - 1
.vscode/settings.json

@@ -1,5 +1,5 @@
 {
-  "tailwindCSS.experimental.configFile": "packages/@core/base/design/src/css/global.css",
+  "tailwindCSS.experimental.configFile": "internal/tailwind-config/src/theme.css",
   "tailwindCSS.lint.suggestCanonicalClasses": "ignore",
   // workbench
   "workbench.list.smoothScrolling": true,
@@ -43,6 +43,7 @@
   "oxc.fmt.configPath": "oxfmt.config.ts",
   "eslint.useFlatConfig": true,
   "editor.codeActionsOnSave": {
+    "source.fixAll.eslint": "explicit",
     "source.fixAll.oxc": "explicit",
     "source.fixAll.stylelint": "explicit",
     "source.organizeImports": "never"

+ 333 - 254
apps/web-antd/src/adapter/component/index.ts

@@ -14,6 +14,7 @@ import type {
 import type { Component, Ref } from 'vue';
 
 import type { BaseFormComponentType } from '@vben/common-ui';
+import type { Sortable } from '@vben/hooks';
 import type { Recordable } from '@vben/types';
 
 import {
@@ -21,6 +22,9 @@ import {
   defineAsyncComponent,
   defineComponent,
   h,
+  nextTick,
+  onMounted,
+  onUnmounted,
   ref,
   render,
   unref,
@@ -33,6 +37,7 @@ import {
   IconPicker,
   VCropper,
 } from '@vben/common-ui';
+import { useSortable } from '@vben/hooks';
 import { IconifyIcon } from '@vben/icons';
 import { $t } from '@vben/locales';
 import { isEmpty } from '@vben/utils';
@@ -126,260 +131,261 @@ const withDefaultPlaceholder = <T extends Component>(
   });
 };
 
-const withPreviewUpload = () => {
-  // 检查是否为图片文件的辅助函数
-  const isImageFile = (file: UploadFile): boolean => {
-    const imageExtensions = new Set([
-      'bmp',
-      'gif',
-      'jpeg',
-      'jpg',
-      'png',
-      'svg',
-      'webp',
-    ]);
-    if (file.url) {
-      try {
-        const pathname = new URL(file.url, 'http://localhost').pathname;
-        const ext = pathname.split('.').pop()?.toLowerCase();
-        return ext ? imageExtensions.has(ext) : false;
-      } catch {
-        const ext = file.url?.split('.').pop()?.toLowerCase();
-        return ext ? imageExtensions.has(ext) : false;
-      }
-    }
-    if (!file.type) {
-      const ext = file.name?.split('.').pop()?.toLowerCase();
-      return ext ? imageExtensions.has(ext) : false;
+const IMAGE_EXTENSIONS = new Set([
+  'bmp',
+  'gif',
+  'jpeg',
+  'jpg',
+  'png',
+  'svg',
+  'webp',
+]);
+
+/**
+ * 检查是否为图片文件
+ */
+function isImageFile(file: UploadFile): boolean {
+  if (file.url) {
+    try {
+      const pathname = new URL(file.url, 'http://localhost').pathname;
+      const ext = pathname.split('.').pop()?.toLowerCase();
+      return ext ? IMAGE_EXTENSIONS.has(ext) : false;
+    } catch {
+      const ext = file.url?.split('.').pop()?.toLowerCase();
+      return ext ? IMAGE_EXTENSIONS.has(ext) : false;
     }
-    return file.type.startsWith('image/');
+  }
+  if (!file.type) {
+    const ext = file.name?.split('.').pop()?.toLowerCase();
+    return ext ? IMAGE_EXTENSIONS.has(ext) : false;
+  }
+  return file.type.startsWith('image/');
+}
+
+/**
+ * 创建默认的上传按钮插槽
+ */
+function createDefaultUploadSlots(listType: string, placeholder: string) {
+  if (listType === 'picture-card') {
+    return { default: () => placeholder };
+  }
+  return {
+    default: () =>
+      h(
+        Button,
+        {
+          icon: h(IconifyIcon, {
+            icon: 'ant-design:upload-outlined',
+            class: 'mb-1 size-4',
+          }),
+        },
+        () => placeholder,
+      ),
   };
-  // 创建默认的上传按钮插槽
-  const createDefaultSlotsWithUpload = (
-    listType: string,
-    placeholder: string,
-  ) => {
-    switch (listType) {
-      case 'picture-card': {
-        return {
-          default: () => placeholder,
-        };
-      }
-      default: {
-        return {
-          default: () =>
-            h(
-              Button,
-              {
-                icon: h(IconifyIcon, {
-                  icon: 'ant-design:upload-outlined',
-                  class: 'mb-1 size-4',
-                }),
+}
+
+/**
+ * 获取文件的 Base64
+ */
+function getBase64(file: File): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.addEventListener('load', () => resolve(reader.result as string));
+    reader.addEventListener('error', reject);
+  });
+}
+
+/**
+ * 预览图片
+ */
+async function previewImage(
+  file: UploadFile,
+  visible: Ref<boolean>,
+  fileList: Ref<UploadProps['fileList']>,
+) {
+  // 非图片文件直接打开链接
+  if (!isImageFile(file)) {
+    const url = file.url || file.preview;
+    if (url) {
+      window.open(url, '_blank');
+    } else {
+      message.error($t('ui.formRules.previewWarning'));
+    }
+    return;
+  }
+
+  const [ImageComponent, PreviewGroupComponent] = await Promise.all([
+    Image,
+    PreviewGroup,
+  ]);
+
+  // 过滤图片文件并生成预览
+  const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f));
+
+  for (const imgFile of imageFiles) {
+    if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
+      imgFile.preview = await getBase64(imgFile.originFileObj);
+    }
+  }
+
+  const container = document.createElement('div');
+  document.body.append(container);
+  let isUnmounted = false;
+
+  const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid);
+
+  const PreviewWrapper = {
+    setup() {
+      return () => {
+        if (isUnmounted) return null;
+        return h(
+          PreviewGroupComponent,
+          {
+            class: 'hidden',
+            preview: {
+              visible: visible.value,
+              current: currentIndex,
+              onVisibleChange: (value: boolean) => {
+                visible.value = value;
+                if (!value) {
+                  setTimeout(() => {
+                    if (!isUnmounted && container) {
+                      isUnmounted = true;
+                      render(null, container);
+                      container.remove();
+                    }
+                  }, 300);
+                }
               },
-              () => placeholder,
+            },
+          },
+          () =>
+            imageFiles.map((imgFile) =>
+              h(ImageComponent, {
+                key: imgFile.uid,
+                src: imgFile.url || imgFile.preview,
+              }),
             ),
-        };
-      }
-    }
+        );
+      };
+    },
   };
-  // 构建预览图片组
-  const previewImage = async (
-    file: UploadFile,
-    visible: Ref<boolean>,
-    fileList: Ref<UploadProps['fileList']>,
-  ) => {
-    // 如果当前文件不是图片,直接打开
-    if (!isImageFile(file)) {
-      if (file.url) {
-        window.open(file.url, '_blank');
-      } else if (file.preview) {
-        window.open(file.preview, '_blank');
-      } else {
-        message.error($t('ui.formRules.previewWarning'));
-      }
-      return;
-    }
 
-    // 对于图片文件,继续使用预览组
-    const [ImageComponent, PreviewGroupComponent] = await Promise.all([
-      Image,
-      PreviewGroup,
-    ]);
-
-    const getBase64 = (file: File) => {
-      return new Promise((resolve, reject) => {
-        const reader = new FileReader();
-        reader.readAsDataURL(file);
-        reader.addEventListener('load', () => resolve(reader.result));
-        reader.addEventListener('error', (error) => reject(error));
-      });
-    };
-    // 从fileList中过滤出所有图片文件
-    const imageFiles = (unref(fileList) || []).filter((element) =>
-      isImageFile(element),
-    );
-
-    // 为所有没有预览地址的图片生成预览
-    for (const imgFile of imageFiles) {
-      if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
-        imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
-      }
-    }
-    const container: HTMLElement | null = document.createElement('div');
+  render(h(PreviewWrapper), container);
+}
+
+/**
+ * 图片裁剪操作
+ */
+function cropImage(file: File, aspectRatio: string | undefined) {
+  return new Promise<Blob | string | undefined>((resolve, reject) => {
+    const container = document.createElement('div');
     document.body.append(container);
 
-    // 用于追踪组件是否已卸载
     let isUnmounted = false;
+    let objectUrl: null | string = null;
+
+    const open = ref<boolean>(true);
+    const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
+
+    const closeModal = () => {
+      open.value = false;
+      setTimeout(() => {
+        if (!isUnmounted && container) {
+          if (objectUrl) {
+            URL.revokeObjectURL(objectUrl);
+          }
+          isUnmounted = true;
+          render(null, container);
+          container.remove();
+        }
+      }, 300);
+    };
 
-    const PreviewWrapper = {
+    const CropperWrapper = {
       setup() {
         return () => {
           if (isUnmounted) return null;
+          if (!objectUrl) {
+            objectUrl = URL.createObjectURL(file);
+          }
           return h(
-            PreviewGroupComponent,
+            Modal,
             {
-              class: 'hidden',
-              preview: {
-                visible: visible.value,
-                // 设置初始显示的图片索引
-                current: imageFiles.findIndex((f) => f.uid === file.uid),
-                onVisibleChange: (value: boolean) => {
-                  visible.value = value;
-                  if (!value) {
-                    // 延迟清理,确保动画完成
-                    setTimeout(() => {
-                      if (!isUnmounted && container) {
-                        isUnmounted = true;
-                        render(null, container);
-                        container.remove();
-                      }
-                    }, 300);
+              open: open.value,
+              title: h('div', {}, [
+                $t('ui.crop.title'),
+                h(
+                  'span',
+                  {
+                    class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
+                  },
+                  $t('ui.crop.titleTip', [aspectRatio]),
+                ),
+              ]),
+              centered: true,
+              width: 548,
+              keyboard: false,
+              maskClosable: false,
+              closable: false,
+              cancelText: $t('common.cancel'),
+              okText: $t('ui.crop.confirm'),
+              destroyOnClose: true,
+              onOk: async () => {
+                const cropper = cropperRef.value;
+                if (!cropper) {
+                  reject(new Error('Cropper not found'));
+                  closeModal();
+                  return;
+                }
+                try {
+                  const dataUrl = await cropper.getCropImage();
+                  if (dataUrl) {
+                    resolve(dataUrl);
+                  } else {
+                    reject(new Error($t('ui.crop.errorTip')));
                   }
-                },
+                } catch {
+                  reject(new Error($t('ui.crop.errorTip')));
+                } finally {
+                  closeModal();
+                }
+              },
+              onCancel() {
+                resolve('');
+                closeModal();
               },
             },
             () =>
-              // 渲染所有图片文件
-              imageFiles.map((imgFile) =>
-                h(ImageComponent, {
-                  key: imgFile.uid,
-                  src: imgFile.url || imgFile.preview,
-                }),
-              ),
+              h(VCropper, {
+                ref: (ref: any) => (cropperRef.value = ref),
+                img: objectUrl as string,
+                aspectRatio,
+              }),
           );
         };
       },
     };
 
-    render(h(PreviewWrapper), container);
-  };
-
-  // 图片裁剪操作
-  const cropImage = (file: File, aspectRatio: string | undefined) => {
-    return new Promise((resolve, reject) => {
-      const container: HTMLElement | null = document.createElement('div');
-      document.body.append(container);
-
-      // 用于追踪组件是否已卸载
-      let isUnmounted = false;
-      let objectUrl: null | string = null;
-
-      const open = ref<boolean>(true);
-      const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
-
-      const closeModal = () => {
-        open.value = false;
-        // 延迟清理,确保动画完成
-        setTimeout(() => {
-          if (!isUnmounted && container) {
-            if (objectUrl) {
-              URL.revokeObjectURL(objectUrl);
-            }
-            isUnmounted = true;
-            render(null, container);
-            container.remove();
-          }
-        }, 300);
-      };
-
-      const CropperWrapper = {
-        setup() {
-          return () => {
-            if (isUnmounted) return null;
-            if (!objectUrl) {
-              objectUrl = URL.createObjectURL(file);
-            }
-            return h(
-              Modal,
-              {
-                open: open.value,
-                title: h('div', {}, [
-                  $t('ui.crop.title'),
-                  h(
-                    'span',
-                    {
-                      class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
-                    },
-                    $t('ui.crop.titleTip', [aspectRatio]),
-                  ),
-                ]),
-                centered: true,
-                width: 548,
-                keyboard: false,
-                maskClosable: false,
-                closable: false,
-                cancelText: $t('common.cancel'),
-                okText: $t('ui.crop.confirm'),
-                destroyOnClose: true,
-                onOk: async () => {
-                  const cropper = cropperRef.value;
-                  if (!cropper) {
-                    reject(new Error('Cropper not found'));
-                    closeModal();
-                    return;
-                  }
-                  try {
-                    const dataUrl = await cropper.getCropImage();
-                    resolve(dataUrl);
-                  } catch {
-                    reject(new Error($t('ui.crop.errorTip')));
-                  } finally {
-                    closeModal();
-                  }
-                },
-                onCancel() {
-                  resolve('');
-                  closeModal();
-                },
-              },
-              () =>
-                h(VCropper, {
-                  ref: (ref: any) => (cropperRef.value = ref),
-                  img: objectUrl as string,
-                  aspectRatio,
-                }),
-            );
-          };
-        },
-      };
-
-      render(h(CropperWrapper), container);
-    });
-  };
+    render(h(CropperWrapper), container);
+  });
+}
 
+/**
+ * 带预览功能的上传组件
+ */
+const withPreviewUpload = () => {
   return defineComponent({
     name: Upload.name,
     emits: ['update:modelValue'],
-    setup: (
+    setup(
       props: any,
       { attrs, slots, emit }: { attrs: any; emit: any; slots: any },
-    ) => {
+    ) {
       const previewVisible = ref<boolean>(false);
-
-      const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`);
-
+      const placeholder = attrs?.placeholder || $t('ui.placeholder.upload');
       const listType = attrs?.listType || attrs?.['list-type'] || 'text';
-
       const fileList = ref<UploadProps['fileList']>(
         attrs?.fileList || attrs?.['file-list'] || [],
       );
@@ -393,12 +399,14 @@ const withPreviewUpload = () => {
         file: UploadFile,
         originFileList: Array<File>,
       ) => {
+        // 文件大小限制
         if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
           message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
           file.status = 'removed';
           return false;
         }
-        // 多选或者非图片不唤起裁剪框
+
+        // 图片裁剪处理
         if (
           attrs.crop &&
           !attrs.multiple &&
@@ -406,14 +414,11 @@ const withPreviewUpload = () => {
           isImageFile(file)
         ) {
           file.status = 'removed';
-          // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取
           const blob = await cropImage(originFileList[0], aspectRatio.value);
-          return new Promise((resolve, reject) => {
-            if (!blob) {
-              return reject(new Error($t('ui.crop.errorTip')));
-            }
-            resolve(blob);
-          });
+          if (!blob) {
+            throw new Error($t('ui.crop.errorTip'));
+          }
+          return blob;
         }
 
         return attrs.beforeUpload?.(file) ?? true;
@@ -421,12 +426,9 @@ const withPreviewUpload = () => {
 
       const handleChange = (event: UploadChangeParam) => {
         try {
-          // 行内写法 handleChange: (event) => {}
           attrs.handleChange?.(event);
-          // template写法 @handle-change="(event) => {}"
           attrs.onHandleChange?.(event);
         } catch (error) {
-          // Avoid breaking internal v-model sync on user handler errors
           console.error(error);
         }
         fileList.value = event.fileList.filter(
@@ -443,21 +445,88 @@ const withPreviewUpload = () => {
         await previewImage(file, previewVisible, fileList);
       };
 
-      const renderUploadButton = (): any => {
-        const isDisabled = attrs.disabled;
-
-        // 如果禁用,不渲染上传按钮
-        if (isDisabled) {
-          return null;
-        }
-
-        // 否则渲染默认上传按钮
+      const renderUploadButton = () => {
+        if (attrs.disabled) return null;
         return isEmpty(slots)
-          ? createDefaultSlotsWithUpload(listType, placeholder)
+          ? createDefaultUploadSlots(listType, placeholder)
           : slots;
       };
 
-      // 可以监听到表单API设置的值
+      // 拖拽排序
+      const draggable = computed(
+        () => (attrs.draggable ?? false) && !attrs.disabled,
+      );
+      const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
+      const sortableInstance = ref<null | Sortable>(null);
+
+      const styleId = `upload-drag-style-${uploadId}`;
+
+      function injectDragStyle() {
+        if (!document.querySelector(`[id="${styleId}"]`)) {
+          const style = document.createElement('style');
+          style.id = styleId;
+          style.textContent = `
+            [data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; }
+            [data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
+          `;
+          document.head.append(style);
+        }
+      }
+
+      function removeDragStyle() {
+        document.querySelector(`[id="${styleId}"]`)?.remove();
+      }
+
+      async function initSortable(retryCount = 0) {
+        if (!draggable.value) return;
+
+        injectDragStyle();
+        await nextTick();
+        await new Promise((resolve) => setTimeout(resolve, 100));
+
+        const container = document.querySelector(
+          `[data-upload-id="${uploadId}"] .ant-upload-list`,
+        ) as HTMLElement;
+
+        if (!container) {
+          if (retryCount < 5) {
+            setTimeout(() => initSortable(retryCount + 1), 200);
+          }
+          return;
+        }
+
+        const { initializeSortable } = useSortable(container, {
+          animation: 300,
+          delay: 400,
+          delayOnTouchOnly: true,
+          filter:
+            '.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',
+          onEnd: (evt) => {
+            const { oldIndex, newIndex } = evt;
+            if (
+              oldIndex === undefined ||
+              newIndex === undefined ||
+              oldIndex === newIndex
+            ) {
+              return;
+            }
+
+            const list = [...(fileList.value || [])];
+            const [movedItem] = list.splice(oldIndex, 1);
+            if (movedItem) {
+              list.splice(newIndex, 0, movedItem);
+              fileList.value = list;
+            }
+
+            attrs.onDragSort?.(oldIndex, newIndex);
+            emit('update:modelValue', fileList.value);
+          },
+        });
+
+        sortableInstance.value = await initializeSortable();
+      }
+
+      // 监听表单值变化
       watch(
         () => attrs.modelValue,
         (res) => {
@@ -465,18 +534,28 @@ const withPreviewUpload = () => {
         },
       );
 
+      onMounted(initSortable);
+      onUnmounted(() => {
+        sortableInstance.value?.destroy();
+        removeDragStyle();
+      });
+
       return () =>
         h(
-          Upload,
-          {
-            ...props,
-            ...attrs,
-            fileList: fileList.value,
-            beforeUpload: handleBeforeUpload,
-            onChange: handleChange,
-            onPreview: handlePreview,
-          },
-          renderUploadButton(),
+          'div',
+          { 'data-upload-id': uploadId, class: 'w-full' },
+          h(
+            Upload,
+            {
+              ...props,
+              ...attrs,
+              fileList: fileList.value,
+              beforeUpload: handleBeforeUpload,
+              onChange: handleChange,
+              onPreview: handlePreview,
+            },
+            renderUploadButton() as any,
+          ),
         );
     },
   });

+ 1 - 0
cspell.json

@@ -60,6 +60,7 @@
     "tabler",
     "taze",
     "tdesign",
+    "tsdown",
     "tsgolint",
     "turborepo",
     "ui-kit",

+ 1 - 0
docs/package.json

@@ -28,6 +28,7 @@
   "devDependencies": {
     "@nolebase/vitepress-plugin-git-changelog": "catalog:",
     "@tailwindcss/vite": "catalog:",
+    "@vben/tailwind-config": "workspace:*",
     "@vben/vite-config": "workspace:*",
     "@vite-pwa/vitepress": "catalog:",
     "vitepress": "catalog:",

+ 3 - 3
docs/src/en/guide/project/tailwindcss.md

@@ -6,7 +6,7 @@
 
 The project no longer maintains Tailwind through `tailwind.config.*` files. Theme definitions and scan scope are now managed through CSS and the shared Vite configuration.
 
-- Theme entry: `packages/@core/base/design/src/css/global.css`
+- Theme entry: `internal/tailwind-config/src/theme.css`
 - Vite integration: `internal/vite-config`
 
 In `global.css`, you will see the Tailwind CSS v4 directives currently used by the project, such as:
@@ -21,7 +21,7 @@ In `global.css`, you will see the Tailwind CSS v4 directives currently used by t
 
 The project does not decide whether Tailwind CSS is enabled based on whether a package contains `tailwind.config.mjs`.
 
-Apps and packages share `@vben/vite-config`, which integrates `@tailwindcss/vite`. The Tailwind scan scope is managed centrally in `packages/@core/base/design/src/css/global.css`.
+Apps and packages share `@vben/vite-config`, which integrates `@tailwindcss/vite`. The Tailwind scan scope is managed centrally in `@vben/tailwind-config`, backed by `internal/tailwind-config/src/theme.css`.
 
 ::: tip Notes on using Tailwind CSS in packages
 
@@ -35,4 +35,4 @@ The project applies a shared handling layer for `@apply` inside Vue single-file
 
 - `internal/vite-config/src/plugins/tailwind-reference.ts`
 
-When component styles use `@apply`, the required `@reference` is injected automatically in most cases.
+When component styles use `@apply`, `@reference "@vben/tailwind-config/theme"` is injected automatically in most cases.

+ 3 - 3
docs/src/guide/project/tailwindcss.md

@@ -6,7 +6,7 @@
 
 项目当前不再通过 `tailwind.config.*` 文件维护 Tailwind 配置,主题与扫描范围都统一放在 CSS 与共享 Vite 配置中。
 
-- 主题入口:`packages/@core/base/design/src/css/global.css`
+- 主题入口:`internal/tailwind-config/src/theme.css`
 - Vite 集成:`internal/vite-config`
 
 在 `global.css` 中你会看到当前项目使用的 Tailwind CSS v4 指令,例如:
@@ -21,7 +21,7 @@
 
 当前项目不会根据某个包下是否存在 `tailwind.config.mjs` 来决定是否启用 Tailwind CSS。
 
-应用和包统一复用 `@vben/vite-config`,并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `packages/@core/base/design/src/css/global.css` 中维护。
+应用和包统一复用 `@vben/vite-config`,并由该配置接入 `@tailwindcss/vite`。Tailwind 的扫描范围则统一在 `@vben/tailwind-config` 对应的 `internal/tailwind-config/src/theme.css` 中维护。
 
 ::: tip 包使用 Tailwind CSS 的说明
 
@@ -35,4 +35,4 @@
 
 - `internal/vite-config/src/plugins/tailwind-reference.ts`
 
-当组件样式中使用 `@apply` 时,会自动注入对应的 `@reference`,一般不需要手动补充。
+当组件样式中使用 `@apply` 时,会自动注入 `@reference "@vben/tailwind-config/theme"`,一般不需要手动补充。

+ 0 - 7
internal/lint-configs/eslint-config/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

+ 2 - 4
internal/lint-configs/eslint-config/package.json

@@ -12,7 +12,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "stub": "pnpm unbuild --stub"
+    "stub": "pnpm exec tsdown"
   },
   "files": [
     "dist"
@@ -27,12 +27,10 @@
     }
   },
   "dependencies": {
-    "@vben/oxlint-config": "workspace:*"
-  },
-  "devDependencies": {
     "@eslint/js": "catalog:",
     "@typescript-eslint/eslint-plugin": "catalog:",
     "@typescript-eslint/parser": "catalog:",
+    "@vben/oxlint-config": "workspace:*",
     "eslint": "catalog:",
     "eslint-plugin-jsonc": "catalog:",
     "eslint-plugin-n": "catalog:",

+ 2 - 1
internal/lint-configs/eslint-config/src/configs/node.ts

@@ -17,7 +17,8 @@ export async function node(): Promise<Linter.Config[]> {
           'error',
           {
             allowModules: [
-              'unbuild',
+              'tsdown',
+              'unplugin-vue',
               '@vben/vite-config',
               'vitest',
               'vite',

+ 16 - 0
internal/lint-configs/eslint-config/tsdown.config.ts

@@ -0,0 +1,16 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  deps: {
+    skipNodeModulesBundle: true,
+  },
+  dts: {
+    resolver: 'tsc',
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 0 - 7
internal/lint-configs/oxfmt-config/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

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

@@ -12,7 +12,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "stub": "pnpm unbuild --stub"
+    "stub": "pnpm exec tsdown"
   },
   "files": [
     "dist"

+ 2 - 2
internal/lint-configs/oxfmt-config/src/index.ts

@@ -2,7 +2,7 @@ import { defineConfig as defineOxfmtConfig } from 'oxfmt';
 
 type OxfmtConfig = Parameters<typeof defineOxfmtConfig>[0];
 
-const oxfmtConfig = defineOxfmtConfig({
+const oxfmtConfig: OxfmtConfig = defineOxfmtConfig({
   printWidth: 80,
   proseWrap: 'never',
   semi: true,
@@ -28,7 +28,7 @@ const oxfmtConfig = defineOxfmtConfig({
   ],
 });
 
-function defineConfig(config: OxfmtConfig = {}) {
+function defineConfig(config: OxfmtConfig = {}): OxfmtConfig {
   return defineOxfmtConfig({
     ...oxfmtConfig,
     ...config,

+ 11 - 0
internal/lint-configs/oxfmt-config/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 0 - 7
internal/lint-configs/oxlint-config/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

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

@@ -12,7 +12,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "stub": "pnpm unbuild --stub"
+    "stub": "pnpm exec tsdown"
   },
   "files": [
     "dist"

+ 1 - 1
internal/lint-configs/oxlint-config/src/configs/tailwindcss.ts

@@ -14,7 +14,7 @@ const selectors = [
 ];
 
 const settings = {
-  entryPoint: 'packages/@core/base/design/src/css/global.css',
+  entryPoint: 'internal/tailwind-config/src/theme.css',
   selectors,
 };
 

+ 11 - 0
internal/lint-configs/oxlint-config/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 0 - 7
internal/node-utils/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

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

@@ -12,7 +12,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "stub": "pnpm unbuild --stub"
+    "stub": "node ./scripts/build.mjs"
   },
   "files": [
     "dist"
@@ -22,7 +22,7 @@
   "types": "./dist/index.d.ts",
   "exports": {
     ".": {
-      "types": "./src/index.ts",
+      "types": "./dist/index.d.ts",
       "import": "./dist/index.mjs",
       "default": "./dist/index.mjs"
     }

+ 40 - 0
internal/node-utils/scripts/build.mjs

@@ -0,0 +1,40 @@
+import { spawnSync } from 'node:child_process';
+
+const pnpmCommand =
+  process.env.npm_execpath && process.env.npm_execpath.endsWith('.cjs')
+    ? [process.execPath, process.env.npm_execpath]
+    : ['pnpm'];
+
+const steps = [
+  ['exec', 'tsdown', '--no-dts'],
+  [
+    'exec',
+    'tsc',
+    '-p',
+    'tsconfig.build.json',
+    '--emitDeclarationOnly',
+    '--declaration',
+    '--outDir',
+    'dist',
+  ],
+];
+
+for (const args of steps) {
+  const [command, ...commandArgs] = pnpmCommand;
+  let cmd = command;
+  if (cmd.includes(' ')) {
+    cmd = `"${command}"`;
+  }
+  const result = spawnSync(cmd, [...commandArgs, ...args], {
+    shell: true,
+    stdio: 'inherit',
+  });
+
+  if (result.error) {
+    throw result.error;
+  }
+
+  if (result.status !== 0) {
+    process.exit(result.status ?? 1);
+  }
+}

+ 2 - 2
internal/node-utils/src/date.ts

@@ -1,6 +1,6 @@
 import dayjs from 'dayjs';
-import timezone from 'dayjs/plugin/timezone';
-import utc from 'dayjs/plugin/utc';
+import timezone from 'dayjs/plugin/timezone.js';
+import utc from 'dayjs/plugin/utc.js';
 
 dayjs.extend(utc);
 dayjs.extend(timezone);

+ 22 - 12
internal/node-utils/src/monorepo.ts

@@ -1,21 +1,31 @@
-import { dirname } from 'node:path';
+import type { Package } from '@manypkg/get-packages';
 
-import {
-  getPackages as getPackagesFunc,
-  getPackagesSync as getPackagesSyncFunc,
-} from '@manypkg/get-packages';
-import { findUpSync } from 'find-up';
+import { existsSync } from 'node:fs';
+import { dirname, join, resolve } from 'node:path';
+
+import * as manypkg from '@manypkg/get-packages';
+const { getPackages: getPackagesFunc, getPackagesSync: getPackagesSyncFunc } =
+  manypkg;
 
 /**
  * 查找大仓的根目录
  * @param cwd
  */
 function findMonorepoRoot(cwd: string = process.cwd()) {
-  const lockFile = findUpSync('pnpm-lock.yaml', {
-    cwd,
-    type: 'file',
-  });
-  return dirname(lockFile || '');
+  let currentDir = resolve(cwd);
+
+  while (true) {
+    if (existsSync(join(currentDir, 'pnpm-lock.yaml'))) {
+      return currentDir;
+    }
+
+    const parentDir = dirname(currentDir);
+    if (parentDir === currentDir) {
+      return '';
+    }
+
+    currentDir = parentDir;
+  }
 }
 
 /**
@@ -40,7 +50,7 @@ async function getPackages() {
  */
 async function getPackage(pkgName: string) {
   const { packages } = await getPackages();
-  return packages.find((pkg) => pkg.packageJson.name === pkgName);
+  return packages.find((pkg: Package) => pkg.packageJson.name === pkgName);
 }
 
 export { findMonorepoRoot, getPackage, getPackages, getPackagesSync };

+ 8 - 0
internal/node-utils/tsconfig.build.json

@@ -0,0 +1,8 @@
+{
+  "$schema": "https://json.schemastore.org/tsconfig",
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "noEmit": false
+  },
+  "exclude": ["node_modules", "src/__tests__"]
+}

+ 10 - 0
internal/node-utils/tsdown.config.ts

@@ -0,0 +1,10 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: false,
+  deps: {
+    skipNodeModulesBundle: true,
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+});

+ 38 - 0
internal/tailwind-config/package.json

@@ -0,0 +1,38 @@
+{
+  "name": "@vben/tailwind-config",
+  "version": "5.7.0",
+  "private": true,
+  "homepage": "https://github.com/vbenjs/vue-vben-admin",
+  "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/vbenjs/vue-vben-admin.git",
+    "directory": "internal/tailwind-config"
+  },
+  "license": "MIT",
+  "type": "module",
+  "files": [
+    "src"
+  ],
+  "sideEffects": [
+    "**/*.css"
+  ],
+  "main": "./src/index.ts",
+  "module": "./src/index.ts",
+  "types": "./src/index.ts",
+  "exports": {
+    ".": {
+      "types": "./src/index.ts",
+      "default": "./src/index.ts"
+    },
+    "./theme": {
+      "default": "./src/theme.css"
+    }
+  },
+  "dependencies": {
+    "@iconify/tailwind4": "catalog:",
+    "@tailwindcss/typography": "catalog:",
+    "tailwindcss": "catalog:",
+    "tw-animate-css": "catalog:"
+  }
+}

+ 1 - 0
internal/tailwind-config/src/index.ts

@@ -0,0 +1 @@
+import './theme.css';

+ 569 - 0
internal/tailwind-config/src/theme.css

@@ -0,0 +1,569 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@plugin '@tailwindcss/typography';
+@plugin '@iconify/tailwind4';
+
+/* Monorepo source detection: scan all packages and apps for utility classes */
+@source '../../../packages/';
+@source '../../../apps/';
+@source '../../../docs/';
+@source '../../../playground/';
+
+/* Dark mode uses .dark class selector, not prefers-color-scheme */
+@custom-variant dark (&:is(.dark *));
+
+/* Explicitly pin Tailwind v4 dynamic spacing for classes like w-150/h-55. */
+@theme {
+  --spacing: 0.25rem;
+}
+
+@theme inline {
+  /* Font */
+  --font-sans: var(--font-family);
+
+  /* Border Radius */
+  --radius-sm: calc(var(--radius) - 4px);
+  --radius-md: calc(var(--radius) - 2px);
+  --radius-lg: var(--radius);
+  --radius-xl: calc(var(--radius) + 4px);
+
+  /* Box Shadow */
+  --shadow-float:
+    0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%),
+    0 9px 28px 8px rgb(0 0 0 / 5%);
+
+  /* Animations */
+  --animate-accordion-down: accordion-down 0.2s ease-out;
+  --animate-accordion-up: accordion-up 0.2s ease-out;
+  --animate-collapsible-down: collapsible-down 0.2s ease-in-out;
+  --animate-collapsible-up: collapsible-up 0.2s ease-in-out;
+  --animate-float: float 5s linear 0ms infinite;
+
+  /* ===== Semantic Colors (shadcn-ui) ===== */
+
+  --color-background: hsl(var(--background));
+  --color-background-deep: hsl(var(--background-deep));
+  --color-foreground: hsl(var(--foreground));
+  --color-card: hsl(var(--card));
+  --color-card-foreground: hsl(var(--card-foreground));
+  --color-popover: hsl(var(--popover));
+  --color-popover-foreground: hsl(var(--popover-foreground));
+  --color-muted: hsl(var(--muted));
+  --color-muted-foreground: hsl(var(--muted-foreground));
+  --color-accent: hsl(var(--accent));
+  --color-accent-foreground: hsl(var(--accent-foreground));
+  --color-accent-hover: hsl(var(--accent-hover));
+  --color-accent-lighter: hsl(var(--accent-lighter));
+  --color-border: hsl(var(--border));
+  --color-input: hsl(var(--input));
+  --color-input-background: hsl(var(--input-background));
+  --color-ring: hsl(var(--ring));
+  --color-secondary: hsl(var(--secondary));
+  --color-secondary-desc: hsl(var(--secondary-desc));
+  --color-secondary-foreground: hsl(var(--secondary-foreground));
+
+  /* ===== Custom Semantic Colors ===== */
+
+  --color-header: hsl(var(--header));
+  --color-heavy: hsl(var(--heavy));
+  --color-heavy-foreground: hsl(var(--heavy-foreground));
+  --color-main: hsl(var(--main));
+  --color-overlay: hsl(var(--overlay));
+  --color-overlay-content: hsl(var(--overlay-content));
+  --color-sidebar: hsl(var(--sidebar));
+  --color-sidebar-deep: hsl(var(--sidebar-deep));
+
+  /* ===== Primary Palette ===== */
+
+  --color-primary: hsl(var(--primary));
+  --color-primary-foreground: hsl(var(--primary-foreground));
+  --color-primary-50: hsl(var(--primary-50));
+  --color-primary-100: hsl(var(--primary-100));
+  --color-primary-200: hsl(var(--primary-200));
+  --color-primary-300: hsl(var(--primary-300));
+  --color-primary-400: hsl(var(--primary-400));
+  --color-primary-500: hsl(var(--primary-500));
+  --color-primary-600: hsl(var(--primary-600));
+  --color-primary-700: hsl(var(--primary-700));
+  --color-primary-active: hsl(var(--primary-700));
+  --color-primary-background-light: hsl(var(--primary-200));
+  --color-primary-background-lighter: hsl(var(--primary-100));
+  --color-primary-background-lightest: hsl(var(--primary-50));
+  --color-primary-border: hsl(var(--primary-400));
+  --color-primary-border-light: hsl(var(--primary-300));
+  --color-primary-hover: hsl(var(--primary-600));
+  --color-primary-text: hsl(var(--primary-500));
+  --color-primary-text-active: hsl(var(--primary-700));
+  --color-primary-text-hover: hsl(var(--primary-600));
+
+  /* ===== Destructive Palette ===== */
+
+  --color-destructive: hsl(var(--destructive));
+  --color-destructive-foreground: hsl(var(--destructive-foreground));
+  --color-destructive-50: hsl(var(--destructive-50));
+  --color-destructive-100: hsl(var(--destructive-100));
+  --color-destructive-200: hsl(var(--destructive-200));
+  --color-destructive-300: hsl(var(--destructive-300));
+  --color-destructive-400: hsl(var(--destructive-400));
+  --color-destructive-500: hsl(var(--destructive-500));
+  --color-destructive-600: hsl(var(--destructive-600));
+  --color-destructive-700: hsl(var(--destructive-700));
+  --color-destructive-active: hsl(var(--destructive-700));
+  --color-destructive-background-light: hsl(var(--destructive-200));
+  --color-destructive-background-lighter: hsl(var(--destructive-100));
+  --color-destructive-background-lightest: hsl(var(--destructive-50));
+  --color-destructive-border: hsl(var(--destructive-400));
+  --color-destructive-border-light: hsl(var(--destructive-300));
+  --color-destructive-hover: hsl(var(--destructive-600));
+  --color-destructive-text: hsl(var(--destructive-500));
+  --color-destructive-text-active: hsl(var(--destructive-700));
+  --color-destructive-text-hover: hsl(var(--destructive-600));
+
+  /* ===== Success Palette ===== */
+
+  --color-success: hsl(var(--success));
+  --color-success-foreground: hsl(var(--success-foreground));
+  --color-success-50: hsl(var(--success-50));
+  --color-success-100: hsl(var(--success-100));
+  --color-success-200: hsl(var(--success-200));
+  --color-success-300: hsl(var(--success-300));
+  --color-success-400: hsl(var(--success-400));
+  --color-success-500: hsl(var(--success-500));
+  --color-success-600: hsl(var(--success-600));
+  --color-success-700: hsl(var(--success-700));
+  --color-success-active: hsl(var(--success-700));
+  --color-success-background-light: hsl(var(--success-200));
+  --color-success-background-lighter: hsl(var(--success-100));
+  --color-success-background-lightest: hsl(var(--success-50));
+  --color-success-border: hsl(var(--success-400));
+  --color-success-border-light: hsl(var(--success-300));
+  --color-success-hover: hsl(var(--success-600));
+  --color-success-text: hsl(var(--success-500));
+  --color-success-text-active: hsl(var(--success-700));
+  --color-success-text-hover: hsl(var(--success-600));
+
+  /* ===== Warning Palette ===== */
+
+  --color-warning: hsl(var(--warning));
+  --color-warning-foreground: hsl(var(--warning-foreground));
+  --color-warning-50: hsl(var(--warning-50));
+  --color-warning-100: hsl(var(--warning-100));
+  --color-warning-200: hsl(var(--warning-200));
+  --color-warning-300: hsl(var(--warning-300));
+  --color-warning-400: hsl(var(--warning-400));
+  --color-warning-500: hsl(var(--warning-500));
+  --color-warning-600: hsl(var(--warning-600));
+  --color-warning-700: hsl(var(--warning-700));
+  --color-warning-active: hsl(var(--warning-700));
+  --color-warning-background-light: hsl(var(--warning-200));
+  --color-warning-background-lighter: hsl(var(--warning-100));
+  --color-warning-background-lightest: hsl(var(--warning-50));
+  --color-warning-border: hsl(var(--warning-400));
+  --color-warning-border-light: hsl(var(--warning-300));
+  --color-warning-hover: hsl(var(--warning-600));
+  --color-warning-text: hsl(var(--warning-500));
+  --color-warning-text-active: hsl(var(--warning-700));
+  --color-warning-text-hover: hsl(var(--warning-600));
+
+  /* ===== Green Palette (alias for success shades) ===== */
+
+  --color-green-50: hsl(var(--green-50));
+  --color-green-100: hsl(var(--green-100));
+  --color-green-200: hsl(var(--green-200));
+  --color-green-300: hsl(var(--green-300));
+  --color-green-400: hsl(var(--green-400));
+  --color-green-500: hsl(var(--green-500));
+  --color-green-600: hsl(var(--green-600));
+  --color-green-700: hsl(var(--green-700));
+  --color-green-active: hsl(var(--green-700));
+  --color-green-background-light: hsl(var(--green-200));
+  --color-green-background-lighter: hsl(var(--green-100));
+  --color-green-background-lightest: hsl(var(--green-50));
+  --color-green-border: hsl(var(--green-400));
+  --color-green-border-light: hsl(var(--green-300));
+  --color-green-foreground: hsl(var(--success-foreground));
+  --color-green-hover: hsl(var(--green-600));
+  --color-green-text: hsl(var(--green-500));
+  --color-green-text-active: hsl(var(--green-700));
+  --color-green-text-hover: hsl(var(--green-600));
+
+  /* ===== Red Palette (alias for destructive shades) ===== */
+
+  --color-red-50: hsl(var(--red-50));
+  --color-red-100: hsl(var(--red-100));
+  --color-red-200: hsl(var(--red-200));
+  --color-red-300: hsl(var(--red-300));
+  --color-red-400: hsl(var(--red-400));
+  --color-red-500: hsl(var(--red-500));
+  --color-red-600: hsl(var(--red-600));
+  --color-red-700: hsl(var(--red-700));
+  --color-red-active: hsl(var(--red-700));
+  --color-red-background-light: hsl(var(--red-200));
+  --color-red-background-lighter: hsl(var(--red-100));
+  --color-red-background-lightest: hsl(var(--red-50));
+  --color-red-border: hsl(var(--red-400));
+  --color-red-border-light: hsl(var(--red-300));
+  --color-red-foreground: hsl(var(--destructive-foreground));
+  --color-red-hover: hsl(var(--red-600));
+  --color-red-text: hsl(var(--red-500));
+  --color-red-text-active: hsl(var(--red-700));
+  --color-red-text-hover: hsl(var(--red-600));
+
+  /* ===== Yellow Palette (alias for warning shades) ===== */
+
+  --color-yellow-50: hsl(var(--yellow-50));
+  --color-yellow-100: hsl(var(--yellow-100));
+  --color-yellow-200: hsl(var(--yellow-200));
+  --color-yellow-300: hsl(var(--yellow-300));
+  --color-yellow-400: hsl(var(--yellow-400));
+  --color-yellow-500: hsl(var(--yellow-500));
+  --color-yellow-600: hsl(var(--yellow-600));
+  --color-yellow-700: hsl(var(--yellow-700));
+  --color-yellow-active: hsl(var(--yellow-700));
+  --color-yellow-background-light: hsl(var(--yellow-200));
+  --color-yellow-background-lighter: hsl(var(--yellow-100));
+  --color-yellow-background-lightest: hsl(var(--yellow-50));
+  --color-yellow-border: hsl(var(--yellow-400));
+  --color-yellow-border-light: hsl(var(--yellow-300));
+  --color-yellow-foreground: hsl(var(--warning-foreground));
+  --color-yellow-hover: hsl(var(--yellow-600));
+  --color-yellow-text: hsl(var(--yellow-500));
+  --color-yellow-text-active: hsl(var(--yellow-700));
+  --color-yellow-text-hover: hsl(var(--yellow-600));
+}
+
+/* Keyframes */
+@keyframes accordion-down {
+  from {
+    height: 0;
+  }
+
+  to {
+    height: var(--reka-accordion-content-height);
+  }
+}
+
+@keyframes accordion-up {
+  from {
+    height: var(--reka-accordion-content-height);
+  }
+
+  to {
+    height: 0;
+  }
+}
+
+@keyframes collapsible-down {
+  from {
+    height: 0;
+  }
+
+  to {
+    height: var(--reka-collapsible-content-height);
+  }
+}
+
+@keyframes collapsible-up {
+  from {
+    height: var(--reka-collapsible-content-height);
+  }
+
+  to {
+    height: 0;
+  }
+}
+
+@keyframes float {
+  0% {
+    transform: translateY(0);
+  }
+
+  50% {
+    transform: translateY(-20px);
+  }
+
+  100% {
+    transform: translateY(0);
+  }
+}
+
+/* Base styles */
+@layer base {
+  *,
+  ::after,
+  ::before {
+    @apply border-border outline-ring/50;
+
+    box-sizing: border-box;
+    border-style: solid;
+    border-width: 0;
+  }
+
+  html {
+    @apply text-foreground bg-background font-sans;
+
+    scroll-behavior: smooth;
+    font-size: var(--font-size-base, 16px);
+    font-variation-settings: normal;
+    font-synthesis-weight: none;
+    line-height: 1.15;
+    text-rendering: optimizelegibility;
+    text-size-adjust: 100%;
+    -webkit-tap-highlight-color: transparent;
+  }
+
+  #app,
+  body,
+  html {
+    @apply size-full;
+  }
+
+  body {
+    min-height: 100vh;
+  }
+
+  a,
+  a:active,
+  a:hover,
+  a:link,
+  a:visited {
+    @apply no-underline;
+  }
+
+  ::view-transition-new(root),
+  ::view-transition-old(root) {
+    @apply animate-none mix-blend-normal;
+  }
+
+  ::view-transition-old(root) {
+    @apply z-1;
+  }
+
+  ::view-transition-new(root) {
+    @apply z-2147483646;
+  }
+
+  html.dark::view-transition-old(root) {
+    @apply z-2147483646;
+  }
+
+  html.dark::view-transition-new(root) {
+    @apply z-1;
+  }
+
+  input::placeholder,
+  textarea::placeholder {
+    @apply opacity-100;
+  }
+
+  input[type='number']::-webkit-inner-spin-button,
+  input[type='number']::-webkit-outer-spin-button {
+    @apply m-0 appearance-none;
+  }
+
+  /* Only adjust scrollbar for non-macOS */
+  html:not([data-platform='macOs']) {
+    ::-webkit-scrollbar {
+      @apply h-2.5 w-2.5;
+    }
+
+    ::-webkit-scrollbar-thumb {
+      @apply bg-border rounded-sm border-none;
+    }
+
+    ::-webkit-scrollbar-track {
+      @apply rounded-sm border-none bg-transparent shadow-none;
+    }
+
+    ::-webkit-scrollbar-button {
+      @apply hidden;
+    }
+  }
+}
+
+/* Custom utilities (v4 @utility syntax) */
+@utility flex-center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+@utility flex-col-center {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+/* Component styles (complex selectors, not convertible to @utility) */
+.outline-box {
+  @apply outline-border relative cursor-pointer rounded-md p-1 outline-1;
+}
+
+.outline-box::after {
+  @apply absolute top-1/2 left-1/2 z-20 h-0 w-px rounded-sm opacity-0 outline-2 outline-transparent transition-all duration-300 content-[""];
+}
+
+.outline-box.outline-box-active {
+  @apply outline-primary outline-2;
+}
+
+.outline-box.outline-box-active::after {
+  display: none;
+}
+
+.outline-box:not(.outline-box-active):hover::after {
+  @apply outline-primary top-0 left-0 h-full w-full p-1 opacity-100;
+}
+
+.vben-link {
+  @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer;
+}
+
+.card-box {
+  @apply bg-card text-card-foreground border-border rounded-xl border;
+}
+
+/* Enter animations (converted from enterAnimationPlugin) */
+@keyframes enter-x-animation {
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+@keyframes enter-y-animation {
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.enter-x:nth-child(1) {
+  opacity: 0;
+  transform: translateX(50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
+}
+
+.enter-x:nth-child(2) {
+  opacity: 0;
+  transform: translateX(50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
+}
+
+.enter-x:nth-child(3) {
+  opacity: 0;
+  transform: translateX(50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
+}
+
+.enter-x:nth-child(4) {
+  opacity: 0;
+  transform: translateX(50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
+}
+
+.enter-x:nth-child(5) {
+  opacity: 0;
+  transform: translateX(50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
+}
+
+.enter-y:nth-child(1) {
+  opacity: 0;
+  transform: translateY(50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
+}
+
+.enter-y:nth-child(2) {
+  opacity: 0;
+  transform: translateY(50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
+}
+
+.enter-y:nth-child(3) {
+  opacity: 0;
+  transform: translateY(50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
+}
+
+.enter-y:nth-child(4) {
+  opacity: 0;
+  transform: translateY(50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
+}
+
+.enter-y:nth-child(5) {
+  opacity: 0;
+  transform: translateY(50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
+}
+
+.-enter-x:nth-child(1) {
+  opacity: 0;
+  transform: translateX(-50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
+}
+
+.-enter-x:nth-child(2) {
+  opacity: 0;
+  transform: translateX(-50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
+}
+
+.-enter-x:nth-child(3) {
+  opacity: 0;
+  transform: translateX(-50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
+}
+
+.-enter-x:nth-child(4) {
+  opacity: 0;
+  transform: translateX(-50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
+}
+
+.-enter-x:nth-child(5) {
+  opacity: 0;
+  transform: translateX(-50px);
+  animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
+}
+
+.-enter-y:nth-child(1) {
+  opacity: 0;
+  transform: translateY(-50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
+}
+
+.-enter-y:nth-child(2) {
+  opacity: 0;
+  transform: translateY(-50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
+}
+
+.-enter-y:nth-child(3) {
+  opacity: 0;
+  transform: translateY(-50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
+}
+
+.-enter-y:nth-child(4) {
+  opacity: 0;
+  transform: translateY(-50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
+}
+
+.-enter-y:nth-child(5) {
+  opacity: 0;
+  transform: translateY(-50px);
+  animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
+}
+
+html.invert-mode {
+  @apply invert;
+}
+
+html.grayscale-mode {
+  @apply grayscale;
+}

+ 0 - 7
internal/vite-config/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

+ 3 - 4
internal/vite-config/package.json

@@ -12,7 +12,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "stub": "pnpm unbuild --stub"
+    "stub": "pnpm exec tsdown"
   },
   "files": [
     "dist"
@@ -30,6 +30,7 @@
     "@intlify/unplugin-vue-i18n": "catalog:",
     "@jspm/generator": "catalog:",
     "@tailwindcss/vite": "catalog:",
+    "@vben/node-utils": "workspace:*",
     "archiver": "catalog:",
     "cheerio": "catalog:",
     "get-port": "catalog:",
@@ -43,18 +44,16 @@
     "@pnpm/workspace.read-manifest": "catalog:",
     "@types/archiver": "catalog:",
     "@types/html-minifier-terser": "catalog:",
-    "@vben/node-utils": "workspace:*",
     "@vitejs/plugin-vue": "catalog:",
     "@vitejs/plugin-vue-jsx": "catalog:",
     "dayjs": "catalog:",
     "dotenv": "catalog:",
-    "rollup": "catalog:",
     "rollup-plugin-visualizer": "catalog:",
     "sass": "catalog:",
     "sass-embedded": "catalog:",
+    "unplugin-dts": "catalog:",
     "vite": "catalog:",
     "vite-plugin-compression": "catalog:",
-    "vite-plugin-dts": "catalog:",
     "vite-plugin-html": "catalog:",
     "vite-plugin-lazy-import": "catalog:"
   }

+ 3 - 2
internal/vite-config/src/plugins/index.ts

@@ -12,8 +12,8 @@ import tailwindcss from '@tailwindcss/vite';
 import viteVue from '@vitejs/plugin-vue';
 import viteVueJsx from '@vitejs/plugin-vue-jsx';
 import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer';
+import viteDtsPlugin from 'unplugin-dts/vite';
 import viteCompressPlugin from 'vite-plugin-compression';
-import viteDtsPlugin from 'vite-plugin-dts';
 import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html';
 import { VitePWA } from 'vite-plugin-pwa';
 import viteVueDevTools from 'vite-plugin-vue-devtools';
@@ -231,12 +231,13 @@ async function loadLibraryPlugins(
   // 单独取,否则commonOptions拿不到
   const isBuild = options.isBuild;
   const { dts, ...commonOptions } = options;
+  const dtsOptions = typeof dts === 'object' ? dts : undefined;
   const commonPlugins = await loadCommonPlugins(commonOptions);
   return await loadConditionPlugins([
     ...commonPlugins,
     {
       condition: isBuild && !!dts,
-      plugins: () => [viteDtsPlugin({ logLevel: 'error' })],
+      plugins: () => [viteDtsPlugin(dtsOptions)],
     },
   ]);
 }

+ 1 - 1
internal/vite-config/src/plugins/tailwind-reference.ts

@@ -1,6 +1,6 @@
 import type { Plugin } from 'vite';
 
-const REFERENCE_LINE = '@reference "@vben-core/design/theme";\n';
+const REFERENCE_LINE = '@reference "@vben/tailwind-config/theme";\n';
 
 /**
  * Auto-inject @reference into Vue SFC <style> blocks that use @apply.

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

@@ -1,11 +1,11 @@
 import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
+import type { PluginOptions } from 'unplugin-dts';
 import type {
   ConfigEnv,
   PluginOption,
   UserConfig,
   UserConfigFnPromise,
 } from 'vite';
-import type { PluginOptions } from 'vite-plugin-dts';
 import type { Options as PwaPluginOptions } from 'vite-plugin-pwa';
 
 /**

+ 41 - 0
internal/vite-config/tsdown.config.ts

@@ -0,0 +1,41 @@
+import { cp, mkdir } from 'node:fs/promises';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { defineConfig } from 'tsdown';
+
+const rootDir = dirname(fileURLToPath(import.meta.url));
+const loadingAssets = ['default-loading-antd.html', 'default-loading.html'];
+
+export default defineConfig({
+  clean: true,
+  deps: {
+    neverBundle: ['@vben/node-utils'],
+    skipNodeModulesBundle: true,
+  },
+  dts: {
+    resolver: 'tsc',
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  hooks: {
+    'build:done': async (context) => {
+      const outDir = context.options.outDir;
+      if (!outDir) {
+        return;
+      }
+
+      await mkdir(outDir, { recursive: true });
+
+      for (const file of loadingAssets) {
+        await cp(
+          join(rootDir, 'src/plugins/inject-app-loading', file),
+          join(outDir, file),
+        );
+      }
+    },
+  },
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 5 - 2
package.json

@@ -68,12 +68,14 @@
     "@changesets/changelog-github": "catalog:",
     "@changesets/cli": "catalog:",
     "@playwright/test": "catalog:",
+    "@tsdown/css": "catalog:",
     "@types/node": "catalog:",
     "@vben/commitlint-config": "workspace:*",
     "@vben/eslint-config": "workspace:*",
     "@vben/oxfmt-config": "workspace:*",
     "@vben/oxlint-config": "workspace:*",
     "@vben/stylelint-config": "workspace:*",
+    "@vben/tailwind-config": "workspace:*",
     "@vben/tsconfig": "workspace:*",
     "@vben/turbo-run": "workspace:*",
     "@vben/vite-config": "workspace:*",
@@ -92,16 +94,17 @@
     "playwright": "catalog:",
     "rimraf": "catalog:",
     "tailwindcss": "catalog:",
+    "tsdown": "catalog:",
     "turbo": "catalog:",
     "typescript": "catalog:",
-    "unbuild": "catalog:",
+    "unplugin-vue": "catalog:",
     "vite": "catalog:",
     "vitest": "catalog:",
     "vue": "catalog:",
     "vue-tsc": "catalog:"
   },
   "engines": {
-    "node": "^20.19.0 || ^22.13.0 || ^24.0.0",
+    "node": "^20.19.0 || ^22.18.0 || ^24.0.0",
     "pnpm": ">=10.0.0"
   },
   "packageManager": "pnpm@10.32.1"

+ 3 - 4
packages/@core/base/design/package.json

@@ -23,6 +23,7 @@
   "exports": {
     "./bem": {
       "development": "./src/scss-bem/bem.scss",
+      "production": "./src/scss-bem/bem.scss",
       "default": "./dist/bem.scss"
     },
     "./theme": {
@@ -31,6 +32,7 @@
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/design.css"
     }
   },
@@ -42,9 +44,6 @@
     }
   },
   "dependencies": {
-    "@iconify/json": "catalog:",
-    "@iconify/tailwind4": "catalog:",
-    "@tailwindcss/typography": "catalog:",
-    "tw-animate-css": "catalog:"
+    "@iconify/json": "catalog:"
   }
 }

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

@@ -1,569 +1 @@
-@import 'tailwindcss';
-@import 'tw-animate-css';
-
-@plugin '@tailwindcss/typography';
-@plugin '@iconify/tailwind4';
-
-/* Monorepo source detection: scan all packages and apps for utility classes */
-@source '../../../../../../packages/';
-@source '../../../../../../apps/';
-@source '../../../../../../docs/';
-@source '../../../../../../playground/';
-
-/* Dark mode uses .dark class selector, not prefers-color-scheme */
-@custom-variant dark (&:is(.dark *));
-
-/* Explicitly pin Tailwind v4 dynamic spacing for classes like w-150/h-55. */
-@theme {
-  --spacing: 0.25rem;
-}
-
-@theme inline {
-  /* Font */
-  --font-sans: var(--font-family);
-
-  /* Border Radius */
-  --radius-sm: calc(var(--radius) - 4px);
-  --radius-md: calc(var(--radius) - 2px);
-  --radius-lg: var(--radius);
-  --radius-xl: calc(var(--radius) + 4px);
-
-  /* Box Shadow */
-  --shadow-float:
-    0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%),
-    0 9px 28px 8px rgb(0 0 0 / 5%);
-
-  /* Animations */
-  --animate-accordion-down: accordion-down 0.2s ease-out;
-  --animate-accordion-up: accordion-up 0.2s ease-out;
-  --animate-collapsible-down: collapsible-down 0.2s ease-in-out;
-  --animate-collapsible-up: collapsible-up 0.2s ease-in-out;
-  --animate-float: float 5s linear 0ms infinite;
-
-  /* ===== Semantic Colors (shadcn-ui) ===== */
-
-  --color-background: hsl(var(--background));
-  --color-background-deep: hsl(var(--background-deep));
-  --color-foreground: hsl(var(--foreground));
-  --color-card: hsl(var(--card));
-  --color-card-foreground: hsl(var(--card-foreground));
-  --color-popover: hsl(var(--popover));
-  --color-popover-foreground: hsl(var(--popover-foreground));
-  --color-muted: hsl(var(--muted));
-  --color-muted-foreground: hsl(var(--muted-foreground));
-  --color-accent: hsl(var(--accent));
-  --color-accent-foreground: hsl(var(--accent-foreground));
-  --color-accent-hover: hsl(var(--accent-hover));
-  --color-accent-lighter: hsl(var(--accent-lighter));
-  --color-border: hsl(var(--border));
-  --color-input: hsl(var(--input));
-  --color-input-background: hsl(var(--input-background));
-  --color-ring: hsl(var(--ring));
-  --color-secondary: hsl(var(--secondary));
-  --color-secondary-desc: hsl(var(--secondary-desc));
-  --color-secondary-foreground: hsl(var(--secondary-foreground));
-
-  /* ===== Custom Semantic Colors ===== */
-
-  --color-header: hsl(var(--header));
-  --color-heavy: hsl(var(--heavy));
-  --color-heavy-foreground: hsl(var(--heavy-foreground));
-  --color-main: hsl(var(--main));
-  --color-overlay: hsl(var(--overlay));
-  --color-overlay-content: hsl(var(--overlay-content));
-  --color-sidebar: hsl(var(--sidebar));
-  --color-sidebar-deep: hsl(var(--sidebar-deep));
-
-  /* ===== Primary Palette ===== */
-
-  --color-primary: hsl(var(--primary));
-  --color-primary-foreground: hsl(var(--primary-foreground));
-  --color-primary-50: hsl(var(--primary-50));
-  --color-primary-100: hsl(var(--primary-100));
-  --color-primary-200: hsl(var(--primary-200));
-  --color-primary-300: hsl(var(--primary-300));
-  --color-primary-400: hsl(var(--primary-400));
-  --color-primary-500: hsl(var(--primary-500));
-  --color-primary-600: hsl(var(--primary-600));
-  --color-primary-700: hsl(var(--primary-700));
-  --color-primary-active: hsl(var(--primary-700));
-  --color-primary-background-light: hsl(var(--primary-200));
-  --color-primary-background-lighter: hsl(var(--primary-100));
-  --color-primary-background-lightest: hsl(var(--primary-50));
-  --color-primary-border: hsl(var(--primary-400));
-  --color-primary-border-light: hsl(var(--primary-300));
-  --color-primary-hover: hsl(var(--primary-600));
-  --color-primary-text: hsl(var(--primary-500));
-  --color-primary-text-active: hsl(var(--primary-700));
-  --color-primary-text-hover: hsl(var(--primary-600));
-
-  /* ===== Destructive Palette ===== */
-
-  --color-destructive: hsl(var(--destructive));
-  --color-destructive-foreground: hsl(var(--destructive-foreground));
-  --color-destructive-50: hsl(var(--destructive-50));
-  --color-destructive-100: hsl(var(--destructive-100));
-  --color-destructive-200: hsl(var(--destructive-200));
-  --color-destructive-300: hsl(var(--destructive-300));
-  --color-destructive-400: hsl(var(--destructive-400));
-  --color-destructive-500: hsl(var(--destructive-500));
-  --color-destructive-600: hsl(var(--destructive-600));
-  --color-destructive-700: hsl(var(--destructive-700));
-  --color-destructive-active: hsl(var(--destructive-700));
-  --color-destructive-background-light: hsl(var(--destructive-200));
-  --color-destructive-background-lighter: hsl(var(--destructive-100));
-  --color-destructive-background-lightest: hsl(var(--destructive-50));
-  --color-destructive-border: hsl(var(--destructive-400));
-  --color-destructive-border-light: hsl(var(--destructive-300));
-  --color-destructive-hover: hsl(var(--destructive-600));
-  --color-destructive-text: hsl(var(--destructive-500));
-  --color-destructive-text-active: hsl(var(--destructive-700));
-  --color-destructive-text-hover: hsl(var(--destructive-600));
-
-  /* ===== Success Palette ===== */
-
-  --color-success: hsl(var(--success));
-  --color-success-foreground: hsl(var(--success-foreground));
-  --color-success-50: hsl(var(--success-50));
-  --color-success-100: hsl(var(--success-100));
-  --color-success-200: hsl(var(--success-200));
-  --color-success-300: hsl(var(--success-300));
-  --color-success-400: hsl(var(--success-400));
-  --color-success-500: hsl(var(--success-500));
-  --color-success-600: hsl(var(--success-600));
-  --color-success-700: hsl(var(--success-700));
-  --color-success-active: hsl(var(--success-700));
-  --color-success-background-light: hsl(var(--success-200));
-  --color-success-background-lighter: hsl(var(--success-100));
-  --color-success-background-lightest: hsl(var(--success-50));
-  --color-success-border: hsl(var(--success-400));
-  --color-success-border-light: hsl(var(--success-300));
-  --color-success-hover: hsl(var(--success-600));
-  --color-success-text: hsl(var(--success-500));
-  --color-success-text-active: hsl(var(--success-700));
-  --color-success-text-hover: hsl(var(--success-600));
-
-  /* ===== Warning Palette ===== */
-
-  --color-warning: hsl(var(--warning));
-  --color-warning-foreground: hsl(var(--warning-foreground));
-  --color-warning-50: hsl(var(--warning-50));
-  --color-warning-100: hsl(var(--warning-100));
-  --color-warning-200: hsl(var(--warning-200));
-  --color-warning-300: hsl(var(--warning-300));
-  --color-warning-400: hsl(var(--warning-400));
-  --color-warning-500: hsl(var(--warning-500));
-  --color-warning-600: hsl(var(--warning-600));
-  --color-warning-700: hsl(var(--warning-700));
-  --color-warning-active: hsl(var(--warning-700));
-  --color-warning-background-light: hsl(var(--warning-200));
-  --color-warning-background-lighter: hsl(var(--warning-100));
-  --color-warning-background-lightest: hsl(var(--warning-50));
-  --color-warning-border: hsl(var(--warning-400));
-  --color-warning-border-light: hsl(var(--warning-300));
-  --color-warning-hover: hsl(var(--warning-600));
-  --color-warning-text: hsl(var(--warning-500));
-  --color-warning-text-active: hsl(var(--warning-700));
-  --color-warning-text-hover: hsl(var(--warning-600));
-
-  /* ===== Green Palette (alias for success shades) ===== */
-
-  --color-green-50: hsl(var(--green-50));
-  --color-green-100: hsl(var(--green-100));
-  --color-green-200: hsl(var(--green-200));
-  --color-green-300: hsl(var(--green-300));
-  --color-green-400: hsl(var(--green-400));
-  --color-green-500: hsl(var(--green-500));
-  --color-green-600: hsl(var(--green-600));
-  --color-green-700: hsl(var(--green-700));
-  --color-green-active: hsl(var(--green-700));
-  --color-green-background-light: hsl(var(--green-200));
-  --color-green-background-lighter: hsl(var(--green-100));
-  --color-green-background-lightest: hsl(var(--green-50));
-  --color-green-border: hsl(var(--green-400));
-  --color-green-border-light: hsl(var(--green-300));
-  --color-green-foreground: hsl(var(--success-foreground));
-  --color-green-hover: hsl(var(--green-600));
-  --color-green-text: hsl(var(--green-500));
-  --color-green-text-active: hsl(var(--green-700));
-  --color-green-text-hover: hsl(var(--green-600));
-
-  /* ===== Red Palette (alias for destructive shades) ===== */
-
-  --color-red-50: hsl(var(--red-50));
-  --color-red-100: hsl(var(--red-100));
-  --color-red-200: hsl(var(--red-200));
-  --color-red-300: hsl(var(--red-300));
-  --color-red-400: hsl(var(--red-400));
-  --color-red-500: hsl(var(--red-500));
-  --color-red-600: hsl(var(--red-600));
-  --color-red-700: hsl(var(--red-700));
-  --color-red-active: hsl(var(--red-700));
-  --color-red-background-light: hsl(var(--red-200));
-  --color-red-background-lighter: hsl(var(--red-100));
-  --color-red-background-lightest: hsl(var(--red-50));
-  --color-red-border: hsl(var(--red-400));
-  --color-red-border-light: hsl(var(--red-300));
-  --color-red-foreground: hsl(var(--destructive-foreground));
-  --color-red-hover: hsl(var(--red-600));
-  --color-red-text: hsl(var(--red-500));
-  --color-red-text-active: hsl(var(--red-700));
-  --color-red-text-hover: hsl(var(--red-600));
-
-  /* ===== Yellow Palette (alias for warning shades) ===== */
-
-  --color-yellow-50: hsl(var(--yellow-50));
-  --color-yellow-100: hsl(var(--yellow-100));
-  --color-yellow-200: hsl(var(--yellow-200));
-  --color-yellow-300: hsl(var(--yellow-300));
-  --color-yellow-400: hsl(var(--yellow-400));
-  --color-yellow-500: hsl(var(--yellow-500));
-  --color-yellow-600: hsl(var(--yellow-600));
-  --color-yellow-700: hsl(var(--yellow-700));
-  --color-yellow-active: hsl(var(--yellow-700));
-  --color-yellow-background-light: hsl(var(--yellow-200));
-  --color-yellow-background-lighter: hsl(var(--yellow-100));
-  --color-yellow-background-lightest: hsl(var(--yellow-50));
-  --color-yellow-border: hsl(var(--yellow-400));
-  --color-yellow-border-light: hsl(var(--yellow-300));
-  --color-yellow-foreground: hsl(var(--warning-foreground));
-  --color-yellow-hover: hsl(var(--yellow-600));
-  --color-yellow-text: hsl(var(--yellow-500));
-  --color-yellow-text-active: hsl(var(--yellow-700));
-  --color-yellow-text-hover: hsl(var(--yellow-600));
-}
-
-/* Keyframes */
-@keyframes accordion-down {
-  from {
-    height: 0;
-  }
-
-  to {
-    height: var(--reka-accordion-content-height);
-  }
-}
-
-@keyframes accordion-up {
-  from {
-    height: var(--reka-accordion-content-height);
-  }
-
-  to {
-    height: 0;
-  }
-}
-
-@keyframes collapsible-down {
-  from {
-    height: 0;
-  }
-
-  to {
-    height: var(--reka-collapsible-content-height);
-  }
-}
-
-@keyframes collapsible-up {
-  from {
-    height: var(--reka-collapsible-content-height);
-  }
-
-  to {
-    height: 0;
-  }
-}
-
-@keyframes float {
-  0% {
-    transform: translateY(0);
-  }
-
-  50% {
-    transform: translateY(-20px);
-  }
-
-  100% {
-    transform: translateY(0);
-  }
-}
-
-/* Base styles */
-@layer base {
-  *,
-  ::after,
-  ::before {
-    @apply border-border outline-ring/50;
-
-    box-sizing: border-box;
-    border-style: solid;
-    border-width: 0;
-  }
-
-  html {
-    @apply text-foreground bg-background font-sans;
-
-    scroll-behavior: smooth;
-    font-size: var(--font-size-base, 16px);
-    font-variation-settings: normal;
-    font-synthesis-weight: none;
-    line-height: 1.15;
-    text-rendering: optimizelegibility;
-    text-size-adjust: 100%;
-    -webkit-tap-highlight-color: transparent;
-  }
-
-  #app,
-  body,
-  html {
-    @apply size-full;
-  }
-
-  body {
-    min-height: 100vh;
-  }
-
-  a,
-  a:active,
-  a:hover,
-  a:link,
-  a:visited {
-    @apply no-underline;
-  }
-
-  ::view-transition-new(root),
-  ::view-transition-old(root) {
-    @apply animate-none mix-blend-normal;
-  }
-
-  ::view-transition-old(root) {
-    @apply z-1;
-  }
-
-  ::view-transition-new(root) {
-    @apply z-2147483646;
-  }
-
-  html.dark::view-transition-old(root) {
-    @apply z-2147483646;
-  }
-
-  html.dark::view-transition-new(root) {
-    @apply z-1;
-  }
-
-  input::placeholder,
-  textarea::placeholder {
-    @apply opacity-100;
-  }
-
-  input[type='number']::-webkit-inner-spin-button,
-  input[type='number']::-webkit-outer-spin-button {
-    @apply m-0 appearance-none;
-  }
-
-  /* Only adjust scrollbar for non-macOS */
-  html:not([data-platform='macOs']) {
-    ::-webkit-scrollbar {
-      @apply h-2.5 w-2.5;
-    }
-
-    ::-webkit-scrollbar-thumb {
-      @apply bg-border rounded-sm border-none;
-    }
-
-    ::-webkit-scrollbar-track {
-      @apply rounded-sm border-none bg-transparent shadow-none;
-    }
-
-    ::-webkit-scrollbar-button {
-      @apply hidden;
-    }
-  }
-}
-
-/* Custom utilities (v4 @utility syntax) */
-@utility flex-center {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-@utility flex-col-center {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-}
-
-/* Component styles (complex selectors, not convertible to @utility) */
-.outline-box {
-  @apply outline-border relative cursor-pointer rounded-md p-1 outline-1;
-}
-
-.outline-box::after {
-  @apply absolute top-1/2 left-1/2 z-20 h-0 w-px rounded-sm opacity-0 outline-2 outline-transparent transition-all duration-300 content-[""];
-}
-
-.outline-box.outline-box-active {
-  @apply outline-primary outline-2;
-}
-
-.outline-box.outline-box-active::after {
-  display: none;
-}
-
-.outline-box:not(.outline-box-active):hover::after {
-  @apply outline-primary top-0 left-0 h-full w-full p-1 opacity-100;
-}
-
-.vben-link {
-  @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer;
-}
-
-.card-box {
-  @apply bg-card text-card-foreground border-border rounded-xl border;
-}
-
-/* Enter animations (converted from enterAnimationPlugin) */
-@keyframes enter-x-animation {
-  to {
-    opacity: 1;
-    transform: translateX(0);
-  }
-}
-
-@keyframes enter-y-animation {
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
-}
-
-.enter-x:nth-child(1) {
-  opacity: 0;
-  transform: translateX(50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
-}
-
-.enter-x:nth-child(2) {
-  opacity: 0;
-  transform: translateX(50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
-}
-
-.enter-x:nth-child(3) {
-  opacity: 0;
-  transform: translateX(50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
-}
-
-.enter-x:nth-child(4) {
-  opacity: 0;
-  transform: translateX(50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
-}
-
-.enter-x:nth-child(5) {
-  opacity: 0;
-  transform: translateX(50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
-}
-
-.enter-y:nth-child(1) {
-  opacity: 0;
-  transform: translateY(50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
-}
-
-.enter-y:nth-child(2) {
-  opacity: 0;
-  transform: translateY(50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
-}
-
-.enter-y:nth-child(3) {
-  opacity: 0;
-  transform: translateY(50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
-}
-
-.enter-y:nth-child(4) {
-  opacity: 0;
-  transform: translateY(50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
-}
-
-.enter-y:nth-child(5) {
-  opacity: 0;
-  transform: translateY(50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
-}
-
-.-enter-x:nth-child(1) {
-  opacity: 0;
-  transform: translateX(-50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.1s forwards;
-}
-
-.-enter-x:nth-child(2) {
-  opacity: 0;
-  transform: translateX(-50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.2s forwards;
-}
-
-.-enter-x:nth-child(3) {
-  opacity: 0;
-  transform: translateX(-50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.3s forwards;
-}
-
-.-enter-x:nth-child(4) {
-  opacity: 0;
-  transform: translateX(-50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.4s forwards;
-}
-
-.-enter-x:nth-child(5) {
-  opacity: 0;
-  transform: translateX(-50px);
-  animation: enter-x-animation 0.3s ease-in-out 0.5s forwards;
-}
-
-.-enter-y:nth-child(1) {
-  opacity: 0;
-  transform: translateY(-50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.1s forwards;
-}
-
-.-enter-y:nth-child(2) {
-  opacity: 0;
-  transform: translateY(-50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.2s forwards;
-}
-
-.-enter-y:nth-child(3) {
-  opacity: 0;
-  transform: translateY(-50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.3s forwards;
-}
-
-.-enter-y:nth-child(4) {
-  opacity: 0;
-  transform: translateY(-50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.4s forwards;
-}
-
-.-enter-y:nth-child(5) {
-  opacity: 0;
-  transform: translateY(-50px);
-  animation: enter-y-animation 0.3s ease-in-out 0.5s forwards;
-}
-
-html.invert-mode {
-  @apply invert;
-}
-
-html.grayscale-mode {
-  @apply grayscale;
-}
+@import '@vben/tailwind-config/theme';

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

@@ -1,4 +1,4 @@
-@reference "./global.css";
+@reference "@vben/tailwind-config/theme";
 
 /* Make clicks pass-through */
 #nprogress {

+ 0 - 7
packages/@core/base/icons/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

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

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild"
+    "build": "pnpm exec tsdown"
   },
   "files": [
     "dist"
@@ -22,6 +22,7 @@
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     }
   },

+ 11 - 0
packages/@core/base/icons/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

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

@@ -1,14 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: [
-    'src/store',
-    'src/constants/index',
-    'src/utils/index',
-    'src/color/index',
-    'src/cache/index',
-    'src/global-state',
-  ],
-});

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

@@ -11,8 +11,8 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild",
-    "stub": "pnpm unbuild --stub"
+    "build": "pnpm exec tsdown",
+    "stub": "pnpm run build"
   },
   "files": [
     "dist"
@@ -22,31 +22,37 @@
     "./constants": {
       "types": "./src/constants/index.ts",
       "development": "./src/constants/index.ts",
+      "production": "./src/constants/index.ts",
       "default": "./dist/constants/index.mjs"
     },
     "./utils": {
       "types": "./src/utils/index.ts",
       "development": "./src/utils/index.ts",
+      "production": "./src/utils/index.ts",
       "default": "./dist/utils/index.mjs"
     },
     "./color": {
       "types": "./src/color/index.ts",
       "development": "./src/color/index.ts",
+      "production": "./src/color/index.ts",
       "default": "./dist/color/index.mjs"
     },
     "./cache": {
       "types": "./src/cache/index.ts",
       "development": "./src/cache/index.ts",
+      "production": "./src/cache/index.ts",
       "default": "./dist/cache/index.mjs"
     },
     "./store": {
       "types": "./src/store.ts",
       "development": "./src/store.ts",
+      "production": "./src/store.ts",
       "default": "./dist/store.mjs"
     },
     "./global-state": {
       "types": "./src/global-state.ts",
       "development": "./src/global-state.ts",
+      "production": "./src/global-state.ts",
       "default": "./dist/global-state.mjs"
     }
   },

+ 2 - 2
packages/@core/base/shared/src/utils/__tests__/date.test.ts

@@ -1,6 +1,6 @@
 import dayjs from 'dayjs';
-import timezone from 'dayjs/plugin/timezone';
-import utc from 'dayjs/plugin/utc';
+import timezone from 'dayjs/plugin/timezone.js';
+import utc from 'dayjs/plugin/utc.js';
 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 
 import {

+ 2 - 2
packages/@core/base/shared/src/utils/date.ts

@@ -1,6 +1,6 @@
 import dayjs from 'dayjs';
-import timezone from 'dayjs/plugin/timezone';
-import utc from 'dayjs/plugin/utc';
+import timezone from 'dayjs/plugin/timezone.js';
+import utc from 'dayjs/plugin/utc.js';
 
 dayjs.extend(utc);
 dayjs.extend(timezone);

+ 18 - 0
packages/@core/base/shared/tsdown.config.ts

@@ -0,0 +1,18 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: {
+    'cache/index': 'src/cache/index.ts',
+    'color/index': 'src/color/index.ts',
+    'constants/index': 'src/constants/index.ts',
+    'global-state': 'src/global-state.ts',
+    store: 'src/store.ts',
+    'utils/index': 'src/utils/index.ts',
+  },
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 0 - 7
packages/@core/base/typings/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

+ 4 - 2
packages/@core/base/typings/package.json

@@ -11,10 +11,11 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild"
+    "build": "pnpm exec tsdown"
   },
   "files": [
-    "dist"
+    "dist",
+    "vue-router.d.ts"
   ],
   "main": "./dist/index.mjs",
   "module": "./dist/index.mjs",
@@ -23,6 +24,7 @@
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     },
     "./vue-router": {

+ 11 - 0
packages/@core/base/typings/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 1 - 2
packages/@core/base/typings/vue-router.d.ts

@@ -1,5 +1,4 @@
-/* eslint-disable no-restricted-imports */
-import type { RouteMeta as IRouteMeta } from '@vben-core/typings';
+import type { RouteMeta as IRouteMeta } from './dist/index.d.mts';
 
 import 'vue-router';
 

+ 0 - 7
packages/@core/composables/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

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

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild"
+    "build": "pnpm exec tsdown"
   },
   "files": [
     "dist"
@@ -23,6 +23,7 @@
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     }
   },

+ 11 - 0
packages/@core/composables/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 0 - 7
packages/@core/preferences/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

+ 14 - 3
packages/@core/preferences/package.json

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "#build": "pnpm unbuild"
+    "#build": "pnpm exec tsdown"
   },
   "files": [
     "dist",
@@ -20,12 +20,23 @@
   "sideEffects": [
     "**/*.css"
   ],
+  "main": "./dist/index.mjs",
+  "module": "./dist/index.mjs",
+  "types": "./dist/index.d.ts",
   "exports": {
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
-      "default": "./src/index.ts",
-      "#default": "./dist/index.mjs"
+      "production": "./src/index.ts",
+      "default": "./dist/index.mjs"
+    }
+  },
+  "publishConfig": {
+    "exports": {
+      ".": {
+        "types": "./dist/index.d.ts",
+        "default": "./dist/index.mjs"
+      }
     }
   },
   "dependencies": {

+ 11 - 0
packages/@core/preferences/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 0 - 21
packages/@core/ui-kit/form-ui/build.config.ts

@@ -1,21 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: [
-    {
-      builder: 'mkdist',
-      input: './src',
-      loaders: ['vue'],
-      pattern: ['**/*.vue'],
-    },
-    {
-      builder: 'mkdist',
-      format: 'esm',
-      input: './src',
-      loaders: ['js'],
-      pattern: ['**/*.ts'],
-    },
-  ],
-});

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

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild",
+    "build": "pnpm exec tsdown",
     "prepublishOnly": "npm run build"
   },
   "files": [
@@ -22,16 +22,19 @@
   ],
   "main": "./dist/index.mjs",
   "module": "./dist/index.mjs",
+  "types": "./dist/index.d.ts",
   "exports": {
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     }
   },
   "publishConfig": {
     "exports": {
       ".": {
+        "types": "./dist/index.d.ts",
         "default": "./dist/index.mjs"
       }
     }

+ 21 - 0
packages/@core/ui-kit/form-ui/tsdown.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'tsdown';
+import Vue from 'unplugin-vue/rolldown';
+
+export default defineConfig({
+  clean: true,
+  deps: {
+    skipNodeModulesBundle: true,
+  },
+  dts: {
+    vue: true,
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+    js: '.mjs',
+  }),
+  platform: 'neutral',
+  plugins: [Vue({ isProduction: true })],
+  unbundle: true,
+});

+ 0 - 21
packages/@core/ui-kit/layout-ui/build.config.ts

@@ -1,21 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: [
-    {
-      builder: 'mkdist',
-      input: './src',
-      loaders: ['vue'],
-      pattern: ['**/*.vue'],
-    },
-    {
-      builder: 'mkdist',
-      format: 'esm',
-      input: './src',
-      loaders: ['js'],
-      pattern: ['**/*.ts'],
-    },
-  ],
-});

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

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild",
+    "build": "pnpm exec tsdown",
     "prepublishOnly": "npm run build"
   },
   "files": [
@@ -22,16 +22,19 @@
   ],
   "main": "./dist/index.mjs",
   "module": "./dist/index.mjs",
+  "types": "./dist/index.d.ts",
   "exports": {
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     }
   },
   "publishConfig": {
     "exports": {
       ".": {
+        "types": "./dist/index.d.ts",
         "default": "./dist/index.mjs"
       }
     }

+ 21 - 0
packages/@core/ui-kit/layout-ui/tsdown.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'tsdown';
+import Vue from 'unplugin-vue/rolldown';
+
+export default defineConfig({
+  clean: true,
+  deps: {
+    skipNodeModulesBundle: true,
+  },
+  dts: {
+    vue: true,
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+    js: '.mjs',
+  }),
+  platform: 'neutral',
+  plugins: [Vue({ isProduction: true })],
+  unbundle: true,
+});

+ 0 - 26
packages/@core/ui-kit/menu-ui/build.config.ts

@@ -1,26 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: [
-    {
-      builder: 'mkdist',
-      input: './src',
-      pattern: ['**/*'],
-    },
-    {
-      builder: 'mkdist',
-      input: './src',
-      loaders: ['vue'],
-      pattern: ['**/*.vue'],
-    },
-    {
-      builder: 'mkdist',
-      format: 'esm',
-      input: './src',
-      loaders: ['js'],
-      pattern: ['**/*.ts'],
-    },
-  ],
-});

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

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild",
+    "build": "pnpm exec tsdown",
     "prepublishOnly": "npm run build"
   },
   "files": [
@@ -22,16 +22,19 @@
   ],
   "main": "./dist/index.mjs",
   "module": "./dist/index.mjs",
+  "types": "./dist/index.d.ts",
   "exports": {
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     }
   },
   "publishConfig": {
     "exports": {
       ".": {
+        "types": "./dist/index.d.ts",
         "default": "./dist/index.mjs"
       }
     }

+ 1 - 1
packages/@core/ui-kit/menu-ui/src/components/normal-menu/normal-menu.vue

@@ -58,7 +58,7 @@ function menuIcon(menu: MenuRecordRaw) {
   </ul>
 </template>
 <style scoped>
-@reference "@vben-core/design/theme";
+@reference "@vben/tailwind-config/theme";
 
 .vben-normal-menu {
   --menu-item-margin-y: 4px;

+ 21 - 0
packages/@core/ui-kit/menu-ui/tsdown.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'tsdown';
+import Vue from 'unplugin-vue/rolldown';
+
+export default defineConfig({
+  clean: true,
+  deps: {
+    skipNodeModulesBundle: true,
+  },
+  dts: {
+    vue: true,
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+    js: '.mjs',
+  }),
+  platform: 'neutral',
+  plugins: [Vue({ isProduction: true })],
+  unbundle: true,
+});

+ 0 - 21
packages/@core/ui-kit/popup-ui/build.config.ts

@@ -1,21 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: [
-    {
-      builder: 'mkdist',
-      input: './src',
-      loaders: ['vue'],
-      pattern: ['**/*.vue'],
-    },
-    {
-      builder: 'mkdist',
-      format: 'esm',
-      input: './src',
-      loaders: ['js'],
-      pattern: ['**/*.ts'],
-    },
-  ],
-});

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

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild",
+    "build": "pnpm exec tsdown",
     "prepublishOnly": "npm run build"
   },
   "files": [
@@ -22,16 +22,19 @@
   ],
   "main": "./dist/index.mjs",
   "module": "./dist/index.mjs",
+  "types": "./dist/index.d.ts",
   "exports": {
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     }
   },
   "publishConfig": {
     "exports": {
       ".": {
+        "types": "./dist/index.d.ts",
         "default": "./dist/index.mjs"
       }
     }

+ 21 - 0
packages/@core/ui-kit/popup-ui/tsdown.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'tsdown';
+import Vue from 'unplugin-vue/rolldown';
+
+export default defineConfig({
+  clean: true,
+  deps: {
+    skipNodeModulesBundle: true,
+  },
+  dts: {
+    vue: true,
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+    js: '.mjs',
+  }),
+  platform: 'neutral',
+  plugins: [Vue({ isProduction: true })],
+  unbundle: true,
+});

+ 0 - 27
packages/@core/ui-kit/shadcn-ui/build.config.ts

@@ -1,27 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: [
-    {
-      builder: 'mkdist',
-      input: './src',
-
-      pattern: ['**/*'],
-    },
-    {
-      builder: 'mkdist',
-      input: './src',
-      loaders: ['vue'],
-      pattern: ['**/*.vue'],
-    },
-    {
-      builder: 'mkdist',
-      format: 'esm',
-      input: './src',
-      loaders: ['js'],
-      pattern: ['**/*.ts'],
-    },
-  ],
-});

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

@@ -1,8 +1,6 @@
 {
   "name": "@vben-core/shadcn-ui",
   "version": "5.7.0",
-  "#main": "./dist/index.mjs",
-  "#module": "./dist/index.mjs",
   "homepage": "https://github.com/vbenjs/vue-vben-admin",
   "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
   "repository": {
@@ -12,29 +10,28 @@
   },
   "license": "MIT",
   "type": "module",
-  "scripts": {
-    "#build": "pnpm unbuild",
-    "#prepublishOnly": "npm run build"
-  },
   "files": [
-    "dist"
+    "components.json",
+    "src"
   ],
   "sideEffects": [
     "**/*.css"
   ],
   "main": "./src/index.ts",
   "module": "./src/index.ts",
+  "types": "./src/index.ts",
   "exports": {
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
-      "default": "./src/index.ts",
-      "//default": "./dist/index.mjs"
+      "default": "./src/index.ts"
     }
   },
   "publishConfig": {
     "exports": {
       ".": {
+        "types": "./src/index.ts",
+        "development": "./src/index.ts",
         "default": "./src/index.ts"
       }
     }

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

@@ -0,0 +1 @@
+@reference "@vben/tailwind-config/theme";

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/breadcrumb/breadcrumb-background.vue

@@ -50,7 +50,7 @@ function handleClick(index: number, path?: string) {
   </ul>
 </template>
 <style scoped>
-@reference "@vben-core/design/theme";
+@reference "@vben/tailwind-config/theme";
 
 li {
   @apply h-7;

+ 3 - 2
packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue

@@ -448,8 +448,9 @@ defineExpose({
 }
 
 .item {
-  @apply h-7.5 w-full box-border;
-
+  box-sizing: border-box;
+  width: 100%;
+  height: 30px;
   background-color: #f3f3f3;
   border: 1px solid #666;
 }

+ 0 - 21
packages/@core/ui-kit/tabs-ui/build.config.ts

@@ -1,21 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: [
-    {
-      builder: 'mkdist',
-      input: './src',
-      loaders: ['vue'],
-      pattern: ['**/*.vue'],
-    },
-    {
-      builder: 'mkdist',
-      format: 'esm',
-      input: './src',
-      loaders: ['js'],
-      pattern: ['**/*.ts'],
-    },
-  ],
-});

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

@@ -11,7 +11,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "build": "pnpm unbuild",
+    "build": "pnpm exec tsdown",
     "prepublishOnly": "npm run build"
   },
   "files": [
@@ -22,16 +22,19 @@
   ],
   "main": "./dist/index.mjs",
   "module": "./dist/index.mjs",
+  "types": "./dist/index.d.ts",
   "exports": {
     ".": {
       "types": "./src/index.ts",
       "development": "./src/index.ts",
+      "production": "./src/index.ts",
       "default": "./dist/index.mjs"
     }
   },
   "publishConfig": {
     "exports": {
       ".": {
+        "types": "./dist/index.d.ts",
         "default": "./dist/index.mjs"
       }
     }

+ 1 - 1
packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue

@@ -173,7 +173,7 @@ function onMouseDown(e: MouseEvent, tab: TabConfig) {
 </template>
 
 <style scoped>
-@reference "@vben-core/design/theme";
+@reference "@vben/tailwind-config/theme";
 
 .tabs-chrome__item:not(.dragging) {
   @apply cursor-pointer;

+ 21 - 0
packages/@core/ui-kit/tabs-ui/tsdown.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'tsdown';
+import Vue from 'unplugin-vue/rolldown';
+
+export default defineConfig({
+  clean: true,
+  deps: {
+    skipNodeModulesBundle: true,
+  },
+  dts: {
+    vue: true,
+  },
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+    js: '.mjs',
+  }),
+  platform: 'neutral',
+  plugins: [Vue({ isProduction: true })],
+  unbundle: true,
+});

+ 2 - 2
packages/effects/common-ui/src/components/cropper/cropper.vue

@@ -528,7 +528,7 @@ const handleImageLoad = () => {
  * @param {number} targetHeight - 目标高度(可选,不传则为原始裁剪高度)
  */
 const getCropImage = async (
-  format: 'image/jpeg' | 'image/png' = 'image/jpeg',
+  format: 'image/jpeg' | 'image/png' = 'image/png',
   quality: number = 0.92,
   outputType: 'base64' | 'blob' = 'blob',
   targetWidth?: number,
@@ -851,7 +851,7 @@ defineExpose({ getCropImage });
 </template>
 
 <style scoped>
-@reference "@vben-core/design/theme";
+@reference "@vben/tailwind-config/theme";
 
 .cropper-action-wrapper {
   @apply box-border flex items-center justify-center;

+ 1 - 1
packages/effects/layouts/src/widgets/theme-toggle/theme-button.vue

@@ -119,7 +119,7 @@ function toggleTheme(event: MouseEvent) {
 </template>
 
 <style scoped>
-@reference "@vben-core/design/theme";
+@reference "@vben/tailwind-config/theme";
 
 .theme-toggle__moon > circle {
   transition: transform 0.5s cubic-bezier(0, 0, 0.3, 1);

+ 12 - 2
packages/effects/plugins/src/vxe-table/init.ts

@@ -52,6 +52,16 @@ let isInit = false;
 
 let tableFormFactory: typeof useVbenForm | undefined;
 
+function normalizeVxeLocale<T extends Record<string, any>>(localeModule: T) {
+  return (
+    localeModule &&
+    typeof localeModule === 'object' &&
+    'default' in localeModule
+      ? localeModule.default
+      : localeModule
+  ) as T;
+}
+
 export const useTableForm: typeof useVbenForm = ((...args) => {
   if (!tableFormFactory) {
     throw new Error('useTableForm is not initialized');
@@ -116,8 +126,8 @@ export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
   const { isDark, locale } = usePreferences();
 
   const localMap = {
-    'zh-CN': zhCN,
-    'en-US': enUS,
+    'zh-CN': normalizeVxeLocale(zhCN),
+    'en-US': normalizeVxeLocale(enUS),
   };
 
   watch(

+ 1 - 1
packages/effects/plugins/src/vxe-table/style.css

@@ -1,4 +1,4 @@
-@reference "@vben-core/design/theme";
+@reference "@vben/tailwind-config/theme";
 
 :root .vxe-grid {
   --vxe-ui-font-color: hsl(var(--foreground));

+ 333 - 254
playground/src/adapter/component/index.ts

@@ -14,6 +14,7 @@ import type {
 import type { Component, Ref } from 'vue';
 
 import type { BaseFormComponentType } from '@vben/common-ui';
+import type { Sortable } from '@vben/hooks';
 import type { Recordable } from '@vben/types';
 
 import {
@@ -21,6 +22,9 @@ import {
   defineAsyncComponent,
   defineComponent,
   h,
+  nextTick,
+  onMounted,
+  onUnmounted,
   ref,
   render,
   unref,
@@ -33,6 +37,7 @@ import {
   IconPicker,
   VCropper,
 } from '@vben/common-ui';
+import { useSortable } from '@vben/hooks';
 import { IconifyIcon } from '@vben/icons';
 import { $t } from '@vben/locales';
 import { isEmpty } from '@vben/utils';
@@ -126,260 +131,261 @@ const withDefaultPlaceholder = <T extends Component>(
   });
 };
 
-const withPreviewUpload = () => {
-  // 检查是否为图片文件的辅助函数
-  const isImageFile = (file: UploadFile): boolean => {
-    const imageExtensions = new Set([
-      'bmp',
-      'gif',
-      'jpeg',
-      'jpg',
-      'png',
-      'svg',
-      'webp',
-    ]);
-    if (file.url) {
-      try {
-        const pathname = new URL(file.url, 'http://localhost').pathname;
-        const ext = pathname.split('.').pop()?.toLowerCase();
-        return ext ? imageExtensions.has(ext) : false;
-      } catch {
-        const ext = file.url?.split('.').pop()?.toLowerCase();
-        return ext ? imageExtensions.has(ext) : false;
-      }
-    }
-    if (!file.type) {
-      const ext = file.name?.split('.').pop()?.toLowerCase();
-      return ext ? imageExtensions.has(ext) : false;
+const IMAGE_EXTENSIONS = new Set([
+  'bmp',
+  'gif',
+  'jpeg',
+  'jpg',
+  'png',
+  'svg',
+  'webp',
+]);
+
+/**
+ * 检查是否为图片文件
+ */
+function isImageFile(file: UploadFile): boolean {
+  if (file.url) {
+    try {
+      const pathname = new URL(file.url, 'http://localhost').pathname;
+      const ext = pathname.split('.').pop()?.toLowerCase();
+      return ext ? IMAGE_EXTENSIONS.has(ext) : false;
+    } catch {
+      const ext = file.url?.split('.').pop()?.toLowerCase();
+      return ext ? IMAGE_EXTENSIONS.has(ext) : false;
     }
-    return file.type.startsWith('image/');
+  }
+  if (!file.type) {
+    const ext = file.name?.split('.').pop()?.toLowerCase();
+    return ext ? IMAGE_EXTENSIONS.has(ext) : false;
+  }
+  return file.type.startsWith('image/');
+}
+
+/**
+ * 创建默认的上传按钮插槽
+ */
+function createDefaultUploadSlots(listType: string, placeholder: string) {
+  if (listType === 'picture-card') {
+    return { default: () => placeholder };
+  }
+  return {
+    default: () =>
+      h(
+        Button,
+        {
+          icon: h(IconifyIcon, {
+            icon: 'ant-design:upload-outlined',
+            class: 'mb-1 size-4',
+          }),
+        },
+        () => placeholder,
+      ),
   };
-  // 创建默认的上传按钮插槽
-  const createDefaultSlotsWithUpload = (
-    listType: string,
-    placeholder: string,
-  ) => {
-    switch (listType) {
-      case 'picture-card': {
-        return {
-          default: () => placeholder,
-        };
-      }
-      default: {
-        return {
-          default: () =>
-            h(
-              Button,
-              {
-                icon: h(IconifyIcon, {
-                  icon: 'ant-design:upload-outlined',
-                  class: 'mb-1 size-4',
-                }),
+}
+
+/**
+ * 获取文件的 Base64
+ */
+function getBase64(file: File): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.readAsDataURL(file);
+    reader.addEventListener('load', () => resolve(reader.result as string));
+    reader.addEventListener('error', reject);
+  });
+}
+
+/**
+ * 预览图片
+ */
+async function previewImage(
+  file: UploadFile,
+  visible: Ref<boolean>,
+  fileList: Ref<UploadProps['fileList']>,
+) {
+  // 非图片文件直接打开链接
+  if (!isImageFile(file)) {
+    const url = file.url || file.preview;
+    if (url) {
+      window.open(url, '_blank');
+    } else {
+      message.error($t('ui.formRules.previewWarning'));
+    }
+    return;
+  }
+
+  const [ImageComponent, PreviewGroupComponent] = await Promise.all([
+    Image,
+    PreviewGroup,
+  ]);
+
+  // 过滤图片文件并生成预览
+  const imageFiles = (unref(fileList) || []).filter((f) => isImageFile(f));
+
+  for (const imgFile of imageFiles) {
+    if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
+      imgFile.preview = await getBase64(imgFile.originFileObj);
+    }
+  }
+
+  const container = document.createElement('div');
+  document.body.append(container);
+  let isUnmounted = false;
+
+  const currentIndex = imageFiles.findIndex((f) => f.uid === file.uid);
+
+  const PreviewWrapper = {
+    setup() {
+      return () => {
+        if (isUnmounted) return null;
+        return h(
+          PreviewGroupComponent,
+          {
+            class: 'hidden',
+            preview: {
+              visible: visible.value,
+              current: currentIndex,
+              onVisibleChange: (value: boolean) => {
+                visible.value = value;
+                if (!value) {
+                  setTimeout(() => {
+                    if (!isUnmounted && container) {
+                      isUnmounted = true;
+                      render(null, container);
+                      container.remove();
+                    }
+                  }, 300);
+                }
               },
-              () => placeholder,
+            },
+          },
+          () =>
+            imageFiles.map((imgFile) =>
+              h(ImageComponent, {
+                key: imgFile.uid,
+                src: imgFile.url || imgFile.preview,
+              }),
             ),
-        };
-      }
-    }
+        );
+      };
+    },
   };
-  // 构建预览图片组
-  const previewImage = async (
-    file: UploadFile,
-    visible: Ref<boolean>,
-    fileList: Ref<UploadProps['fileList']>,
-  ) => {
-    // 如果当前文件不是图片,直接打开
-    if (!isImageFile(file)) {
-      if (file.url) {
-        window.open(file.url, '_blank');
-      } else if (file.preview) {
-        window.open(file.preview, '_blank');
-      } else {
-        message.error($t('ui.formRules.previewWarning'));
-      }
-      return;
-    }
 
-    // 对于图片文件,继续使用预览组
-    const [ImageComponent, PreviewGroupComponent] = await Promise.all([
-      Image,
-      PreviewGroup,
-    ]);
-
-    const getBase64 = (file: File) => {
-      return new Promise((resolve, reject) => {
-        const reader = new FileReader();
-        reader.readAsDataURL(file);
-        reader.addEventListener('load', () => resolve(reader.result));
-        reader.addEventListener('error', (error) => reject(error));
-      });
-    };
-    // 从fileList中过滤出所有图片文件
-    const imageFiles = (unref(fileList) || []).filter((element) =>
-      isImageFile(element),
-    );
-
-    // 为所有没有预览地址的图片生成预览
-    for (const imgFile of imageFiles) {
-      if (!imgFile.url && !imgFile.preview && imgFile.originFileObj) {
-        imgFile.preview = (await getBase64(imgFile.originFileObj)) as string;
-      }
-    }
-    const container: HTMLElement | null = document.createElement('div');
+  render(h(PreviewWrapper), container);
+}
+
+/**
+ * 图片裁剪操作
+ */
+function cropImage(file: File, aspectRatio: string | undefined) {
+  return new Promise<Blob | string | undefined>((resolve, reject) => {
+    const container = document.createElement('div');
     document.body.append(container);
 
-    // 用于追踪组件是否已卸载
     let isUnmounted = false;
+    let objectUrl: null | string = null;
+
+    const open = ref<boolean>(true);
+    const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
+
+    const closeModal = () => {
+      open.value = false;
+      setTimeout(() => {
+        if (!isUnmounted && container) {
+          if (objectUrl) {
+            URL.revokeObjectURL(objectUrl);
+          }
+          isUnmounted = true;
+          render(null, container);
+          container.remove();
+        }
+      }, 300);
+    };
 
-    const PreviewWrapper = {
+    const CropperWrapper = {
       setup() {
         return () => {
           if (isUnmounted) return null;
+          if (!objectUrl) {
+            objectUrl = URL.createObjectURL(file);
+          }
           return h(
-            PreviewGroupComponent,
+            Modal,
             {
-              class: 'hidden',
-              preview: {
-                visible: visible.value,
-                // 设置初始显示的图片索引
-                current: imageFiles.findIndex((f) => f.uid === file.uid),
-                onVisibleChange: (value: boolean) => {
-                  visible.value = value;
-                  if (!value) {
-                    // 延迟清理,确保动画完成
-                    setTimeout(() => {
-                      if (!isUnmounted && container) {
-                        isUnmounted = true;
-                        render(null, container);
-                        container.remove();
-                      }
-                    }, 300);
+              open: open.value,
+              title: h('div', {}, [
+                $t('ui.crop.title'),
+                h(
+                  'span',
+                  {
+                    class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
+                  },
+                  $t('ui.crop.titleTip', [aspectRatio]),
+                ),
+              ]),
+              centered: true,
+              width: 548,
+              keyboard: false,
+              maskClosable: false,
+              closable: false,
+              cancelText: $t('common.cancel'),
+              okText: $t('ui.crop.confirm'),
+              destroyOnClose: true,
+              onOk: async () => {
+                const cropper = cropperRef.value;
+                if (!cropper) {
+                  reject(new Error('Cropper not found'));
+                  closeModal();
+                  return;
+                }
+                try {
+                  const dataUrl = await cropper.getCropImage();
+                  if (dataUrl) {
+                    resolve(dataUrl);
+                  } else {
+                    reject(new Error($t('ui.crop.errorTip')));
                   }
-                },
+                } catch {
+                  reject(new Error($t('ui.crop.errorTip')));
+                } finally {
+                  closeModal();
+                }
+              },
+              onCancel() {
+                resolve('');
+                closeModal();
               },
             },
             () =>
-              // 渲染所有图片文件
-              imageFiles.map((imgFile) =>
-                h(ImageComponent, {
-                  key: imgFile.uid,
-                  src: imgFile.url || imgFile.preview,
-                }),
-              ),
+              h(VCropper, {
+                ref: (ref: any) => (cropperRef.value = ref),
+                img: objectUrl as string,
+                aspectRatio,
+              }),
           );
         };
       },
     };
 
-    render(h(PreviewWrapper), container);
-  };
-
-  // 图片裁剪操作
-  const cropImage = (file: File, aspectRatio: string | undefined) => {
-    return new Promise((resolve, reject) => {
-      const container: HTMLElement | null = document.createElement('div');
-      document.body.append(container);
-
-      // 用于追踪组件是否已卸载
-      let isUnmounted = false;
-      let objectUrl: null | string = null;
-
-      const open = ref<boolean>(true);
-      const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
-
-      const closeModal = () => {
-        open.value = false;
-        // 延迟清理,确保动画完成
-        setTimeout(() => {
-          if (!isUnmounted && container) {
-            if (objectUrl) {
-              URL.revokeObjectURL(objectUrl);
-            }
-            isUnmounted = true;
-            render(null, container);
-            container.remove();
-          }
-        }, 300);
-      };
-
-      const CropperWrapper = {
-        setup() {
-          return () => {
-            if (isUnmounted) return null;
-            if (!objectUrl) {
-              objectUrl = URL.createObjectURL(file);
-            }
-            return h(
-              Modal,
-              {
-                open: open.value,
-                title: h('div', {}, [
-                  $t('ui.crop.title'),
-                  h(
-                    'span',
-                    {
-                      class: `${aspectRatio ? '' : 'hidden'} ml-2 text-sm text-gray-400 font-normal`,
-                    },
-                    $t('ui.crop.titleTip', [aspectRatio]),
-                  ),
-                ]),
-                centered: true,
-                width: 548,
-                keyboard: false,
-                maskClosable: false,
-                closable: false,
-                cancelText: $t('common.cancel'),
-                okText: $t('ui.crop.confirm'),
-                destroyOnClose: true,
-                onOk: async () => {
-                  const cropper = cropperRef.value;
-                  if (!cropper) {
-                    reject(new Error('Cropper not found'));
-                    closeModal();
-                    return;
-                  }
-                  try {
-                    const dataUrl = await cropper.getCropImage();
-                    resolve(dataUrl);
-                  } catch {
-                    reject(new Error($t('ui.crop.errorTip')));
-                  } finally {
-                    closeModal();
-                  }
-                },
-                onCancel() {
-                  resolve('');
-                  closeModal();
-                },
-              },
-              () =>
-                h(VCropper, {
-                  ref: (ref: any) => (cropperRef.value = ref),
-                  img: objectUrl as string,
-                  aspectRatio,
-                }),
-            );
-          };
-        },
-      };
-
-      render(h(CropperWrapper), container);
-    });
-  };
+    render(h(CropperWrapper), container);
+  });
+}
 
+/**
+ * 带预览功能的上传组件
+ */
+const withPreviewUpload = () => {
   return defineComponent({
     name: Upload.name,
     emits: ['update:modelValue'],
-    setup: (
+    setup(
       props: any,
       { attrs, slots, emit }: { attrs: any; emit: any; slots: any },
-    ) => {
+    ) {
       const previewVisible = ref<boolean>(false);
-
-      const placeholder = attrs?.placeholder || $t(`ui.placeholder.upload`);
-
+      const placeholder = attrs?.placeholder || $t('ui.placeholder.upload');
       const listType = attrs?.listType || attrs?.['list-type'] || 'text';
-
       const fileList = ref<UploadProps['fileList']>(
         attrs?.fileList || attrs?.['file-list'] || [],
       );
@@ -393,12 +399,14 @@ const withPreviewUpload = () => {
         file: UploadFile,
         originFileList: Array<File>,
       ) => {
+        // 文件大小限制
         if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
           message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
           file.status = 'removed';
           return false;
         }
-        // 多选或者非图片不唤起裁剪框
+
+        // 图片裁剪处理
         if (
           attrs.crop &&
           !attrs.multiple &&
@@ -406,14 +414,11 @@ const withPreviewUpload = () => {
           isImageFile(file)
         ) {
           file.status = 'removed';
-          // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取
           const blob = await cropImage(originFileList[0], aspectRatio.value);
-          return new Promise((resolve, reject) => {
-            if (!blob) {
-              return reject(new Error($t('ui.crop.errorTip')));
-            }
-            resolve(blob);
-          });
+          if (!blob) {
+            throw new Error($t('ui.crop.errorTip'));
+          }
+          return blob;
         }
 
         return attrs.beforeUpload?.(file) ?? true;
@@ -421,12 +426,9 @@ const withPreviewUpload = () => {
 
       const handleChange = (event: UploadChangeParam) => {
         try {
-          // 行内写法 handleChange: (event) => {}
           attrs.handleChange?.(event);
-          // template写法 @handle-change="(event) => {}"
           attrs.onHandleChange?.(event);
         } catch (error) {
-          // Avoid breaking internal v-model sync on user handler errors
           console.error(error);
         }
         fileList.value = event.fileList.filter(
@@ -443,21 +445,88 @@ const withPreviewUpload = () => {
         await previewImage(file, previewVisible, fileList);
       };
 
-      const renderUploadButton = (): any => {
-        const isDisabled = attrs.disabled;
-
-        // 如果禁用,不渲染上传按钮
-        if (isDisabled) {
-          return null;
-        }
-
-        // 否则渲染默认上传按钮
+      const renderUploadButton = () => {
+        if (attrs.disabled) return null;
         return isEmpty(slots)
-          ? createDefaultSlotsWithUpload(listType, placeholder)
+          ? createDefaultUploadSlots(listType, placeholder)
           : slots;
       };
 
-      // 可以监听到表单API设置的值
+      // 拖拽排序
+      const draggable = computed(
+        () => (attrs.draggable ?? false) && !attrs.disabled,
+      );
+      const uploadId = `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
+      const sortableInstance = ref<null | Sortable>(null);
+
+      const styleId = `upload-drag-style-${uploadId}`;
+
+      function injectDragStyle() {
+        if (!document.querySelector(`[id="${styleId}"]`)) {
+          const style = document.createElement('style');
+          style.id = styleId;
+          style.textContent = `
+            [data-upload-id="${uploadId}"] .ant-upload-list-item { cursor: move; }
+            [data-upload-id="${uploadId}"] .ant-upload-list-item:hover { box-shadow: 0 2px 8px rgba(0,0,0,0.15); }
+          `;
+          document.head.append(style);
+        }
+      }
+
+      function removeDragStyle() {
+        document.querySelector(`[id="${styleId}"]`)?.remove();
+      }
+
+      async function initSortable(retryCount = 0) {
+        if (!draggable.value) return;
+
+        injectDragStyle();
+        await nextTick();
+        await new Promise((resolve) => setTimeout(resolve, 100));
+
+        const container = document.querySelector(
+          `[data-upload-id="${uploadId}"] .ant-upload-list`,
+        ) as HTMLElement;
+
+        if (!container) {
+          if (retryCount < 5) {
+            setTimeout(() => initSortable(retryCount + 1), 200);
+          }
+          return;
+        }
+
+        const { initializeSortable } = useSortable(container, {
+          animation: 300,
+          delay: 400,
+          delayOnTouchOnly: true,
+          filter:
+            '.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading',
+          onEnd: (evt) => {
+            const { oldIndex, newIndex } = evt;
+            if (
+              oldIndex === undefined ||
+              newIndex === undefined ||
+              oldIndex === newIndex
+            ) {
+              return;
+            }
+
+            const list = [...(fileList.value || [])];
+            const [movedItem] = list.splice(oldIndex, 1);
+            if (movedItem) {
+              list.splice(newIndex, 0, movedItem);
+              fileList.value = list;
+            }
+
+            attrs.onDragSort?.(oldIndex, newIndex);
+            emit('update:modelValue', fileList.value);
+          },
+        });
+
+        sortableInstance.value = await initializeSortable();
+      }
+
+      // 监听表单值变化
       watch(
         () => attrs.modelValue,
         (res) => {
@@ -465,18 +534,28 @@ const withPreviewUpload = () => {
         },
       );
 
+      onMounted(initSortable);
+      onUnmounted(() => {
+        sortableInstance.value?.destroy();
+        removeDragStyle();
+      });
+
       return () =>
         h(
-          Upload,
-          {
-            ...props,
-            ...attrs,
-            fileList: fileList.value,
-            beforeUpload: handleBeforeUpload,
-            onChange: handleChange,
-            onPreview: handlePreview,
-          },
-          renderUploadButton(),
+          'div',
+          { 'data-upload-id': uploadId, class: 'w-full' },
+          h(
+            Upload,
+            {
+              ...props,
+              ...attrs,
+              fileList: fileList.value,
+              beforeUpload: handleBeforeUpload,
+              onChange: handleChange,
+              onPreview: handlePreview,
+            },
+            renderUploadButton() as any,
+          ),
         );
     },
   });

+ 1 - 1
playground/src/views/examples/drawer/index.vue

@@ -60,7 +60,7 @@ function openInContentDrawer(placement: DrawerPlacement = 'right') {
 }
 
 function openMaxContentDrawer() {
-  // 这里只是用来演示方便。实际上自己使用的时候可以直接将这些配置卸载Drawer的属性里
+  // 这里只是用来演示方便。实际上自己使用的时候可以直接将这些配置写在Drawer的属性里
   inContentDrawerApi.setState({ class: 'w-full', placement: 'right' }).open();
 }
 

+ 5 - 1
playground/src/views/examples/form/basic.vue

@@ -348,13 +348,14 @@ const [BaseForm, baseFormApi] = useVbenForm({
         // 自动携带认证信息
         customRequest: upload_file,
         disabled: false,
-        maxCount: 1,
+        maxCount: 3,
         // 单位:MB
         maxSize: 2,
         multiple: false,
         showUploadList: true,
         // 上传列表的内建样式,支持四种基本样式 text, picture, picture-card 和 picture-circle
         listType: 'picture-card',
+        draggable: true, // 启用拖拽排序
         // onChange事件已被重写,如需自定义请在此基础上扩展
         handleChange: ({ file }: { file: UploadFile }) => {
           const { name, status } = file;
@@ -364,6 +365,9 @@ const [BaseForm, baseFormApi] = useVbenForm({
             message.error(`${name} ${$t('examples.form.upload-fail')}`);
           }
         },
+        onDragSort: (oldIndex: number, newIndex: number) => {
+          console.warn(`图片从 ${oldIndex} 移动到 ${newIndex}`);
+        },
       },
       fieldName: 'files',
       label: $t('examples.form.file'),

Разница между файлами не показана из-за своего большого размера
+ 312 - 287
pnpm-lock.yaml


+ 25 - 24
pnpm-workspace.yaml

@@ -20,32 +20,33 @@ overrides:
   pinia: 'catalog:'
   vue: 'catalog:'
 catalog:
-  '@ast-grep/napi': ^0.41.1
+  '@ast-grep/napi': ^0.42.0
   '@changesets/changelog-github': ^0.6.0
   '@changesets/cli': ^2.30.0
   '@changesets/git': ^3.0.4
   '@clack/prompts': ^1.1.0
-  '@commitlint/cli': ^20.4.4
-  '@commitlint/config-conventional': ^20.4.4
+  '@commitlint/cli': ^20.5.0
+  '@commitlint/config-conventional': ^20.5.0
   '@ctrl/tinycolor': ^4.2.0
   '@eslint-community/eslint-plugin-eslint-comments': ^4.7.1
   '@eslint/js': ^10.0.1
   '@faker-js/faker': ^10.3.0
-  '@iconify/json': ^2.2.449
+  '@iconify/json': ^2.2.451
   '@iconify/tailwind4': ^1.2.3
   '@iconify/vue': ^5.0.0
   '@intlify/core-base': ^11.3.0
   '@intlify/unplugin-vue-i18n': ^11.0.7
-  '@jspm/generator': ^2.11.0
+  '@jspm/generator': ^2.12.0
   '@manypkg/get-packages': ^3.1.0
   '@nolebase/vitepress-plugin-git-changelog': ^2.18.2
   '@playwright/test': ^1.58.2
   '@pnpm/workspace.read-manifest': ^1000.3.0
   '@stylistic/stylelint-plugin': ^5.0.1
   '@tailwindcss/typography': ^0.5.19
-  '@tailwindcss/vite': ^4.2.1
-  '@tanstack/vue-query': ^5.92.9
+  '@tailwindcss/vite': ^4.2.2
+  '@tanstack/vue-query': ^5.92.10
   '@tanstack/vue-store': ^0.9.2
+  '@tsdown/css': ^0.21.4
   '@types/archiver': ^7.0.0
   '@types/html-minifier-terser': ^7.0.2
   '@types/json-bigint': ^1.0.4
@@ -56,8 +57,8 @@ catalog:
   '@types/qrcode': ^1.5.6
   '@types/qs': ^6.15.0
   '@types/sortablejs': ^1.15.9
-  '@typescript-eslint/eslint-plugin': ^8.57.0
-  '@typescript-eslint/parser': ^8.57.0
+  '@typescript-eslint/eslint-plugin': ^8.57.1
+  '@typescript-eslint/parser': ^8.57.1
   '@vee-validate/zod': ^4.15.1
   '@vite-pwa/vitepress': ^1.1.0
   '@vitejs/plugin-vue': ^6.0.5
@@ -68,7 +69,7 @@ catalog:
   '@vueuse/integrations': ^14.2.1
   '@vueuse/motion': ^3.0.3
   ant-design-vue: ^4.2.6
-  antdv-next: ^1.1.4
+  antdv-next: ^1.1.5
   archiver: ^7.0.1
   axios: ^1.13.6
   axios-mock-adapter: ^2.1.0
@@ -96,7 +97,7 @@ catalog:
   eslint-plugin-command: ^3.5.2
   eslint-plugin-jsonc: ^3.1.2
   eslint-plugin-n: ^17.24.0
-  eslint-plugin-perfectionist: ^5.6.0
+  eslint-plugin-perfectionist: ^5.7.0
   eslint-plugin-pnpm: ^1.6.0
   eslint-plugin-unicorn: ^63.0.0
   eslint-plugin-unused-imports: ^4.4.1
@@ -106,7 +107,7 @@ catalog:
   find-up: ^8.0.0
   get-port: ^7.1.0
   globals: ^17.4.0
-  h3: ^1.15.6
+  h3: ^1.15.8
   happy-dom: ^20.8.4
   html-minifier-terser: ^7.2.0
   is-ci: ^4.1.0
@@ -120,9 +121,9 @@ catalog:
   nitropack: ^2.13.1
   nprogress: ^0.2.0
   ora: ^9.3.0
-  oxfmt: ^0.40.0
-  oxlint: ^1.55.0
-  oxlint-tsgolint: ^0.16.0
+  oxfmt: ^0.41.0
+  oxlint: ^1.56.0
+  oxlint-tsgolint: ^0.17.0
   pinia: ^3.0.4
   pinia-plugin-persistedstate: ^4.7.1
   pkg-types: ^2.3.0
@@ -136,14 +137,13 @@ catalog:
   reka-ui: ^2.9.2
   resolve.exports: ^2.0.3
   rimraf: ^6.1.3
-  rollup: ^4.59.0
   rollup-plugin-visualizer: ^7.0.1
   sass: ^1.98.0
   sass-embedded: ^1.98.0
   secure-ls: ^2.0.0
   sortablejs: ^1.15.7
   stylelint: ^17.4.0
-  stylelint-config-recess-order: ^7.6.1
+  stylelint-config-recess-order: ^7.7.0
   stylelint-config-recommended: ^18.0.0
   stylelint-config-recommended-scss: ^17.0.0
   stylelint-config-recommended-vue: ^1.6.1
@@ -151,19 +151,20 @@ catalog:
   stylelint-order: ^8.1.1
   stylelint-scss: ^7.0.0
   tailwind-merge: ^3.5.0
-  tailwindcss: ^4.2.1
+  tailwindcss: ^4.2.2
   tdesign-vue-next: ^1.18.5
   theme-colors: ^0.1.0
   tippy.js: ^6.3.7
-  turbo: ^2.8.17
+  tsdown: ^0.21.4
+  turbo: ^2.8.19
   tw-animate-css: ^1.4.0
   typescript: ^5.9.3
-  unbuild: ^3.6.1
+  unplugin-dts: ^1.0.0-beta.6
   unplugin-element-plus: ^0.11.2
+  unplugin-vue: ^7.1.1
   vee-validate: ^4.15.1
   vite: ^8.0.0
   vite-plugin-compression: ^0.5.1
-  vite-plugin-dts: ^4.5.4
   vite-plugin-html: ^3.2.2
   vite-plugin-lazy-import: ^1.0.7
   vite-plugin-pwa: ^1.2.0
@@ -177,9 +178,9 @@ catalog:
   vue-json-viewer: ^3.0.4
   vue-router: ^5.0.3
   vue-tippy: ^6.7.1
-  vue-tsc: ^3.2.5
-  vxe-pc-ui: ^4.13.6
-  vxe-table: ^4.18.2
+  vue-tsc: ^3.2.6
+  vxe-pc-ui: ^4.13.10
+  vxe-table: ^4.18.5
   watermark-js-plus: ^1.6.3
   yaml-eslint-parser: ^2.0.0
   zod: ^3.25.76

+ 0 - 7
scripts/turbo-run/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

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

@@ -5,7 +5,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "stub": "pnpm unbuild --stub"
+    "stub": "pnpm exec tsdown"
   },
   "files": [
     "dist"

+ 11 - 0
scripts/turbo-run/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 0 - 7
scripts/vsh/build.config.ts

@@ -1,7 +0,0 @@
-import { defineBuildConfig } from 'unbuild';
-
-export default defineBuildConfig({
-  clean: true,
-  declaration: true,
-  entries: ['src/index'],
-});

+ 1 - 1
scripts/vsh/package.json

@@ -5,7 +5,7 @@
   "license": "MIT",
   "type": "module",
   "scripts": {
-    "stub": "pnpm unbuild --stub"
+    "stub": "pnpm exec tsdown"
   },
   "files": [
     "dist"

+ 57 - 10
scripts/vsh/src/check-circular/index.ts

@@ -1,10 +1,15 @@
 import type { CAC } from 'cac';
 
-import { extname } from 'node:path';
+import { access, mkdtemp, readFile, rm } from 'node:fs/promises';
+import { createRequire } from 'node:module';
+import { tmpdir } from 'node:os';
+import { extname, join } from 'node:path';
 
-import { getStagedFiles } from '@vben/node-utils';
+import { execa, getStagedFiles } from '@vben/node-utils';
 
-import { circularDepsDetect } from 'circular-dependency-scanner';
+const require = createRequire(import.meta.url);
+const circularScannerCli =
+  require.resolve('circular-dependency-scanner/dist/cli.js');
 
 // 默认配置
 const DEFAULT_CONFIG = {
@@ -41,6 +46,44 @@ interface CommandOptions {
 // 缓存机制
 const cache = new Map<string, CircularDependencyResult[]>();
 
+async function detectCircularDependencies({
+  cwd,
+  ignorePattern,
+  staged,
+}: {
+  cwd: string;
+  ignorePattern: string;
+  staged: boolean;
+}): Promise<CircularDependencyResult[]> {
+  const tempDir = await mkdtemp(join(tmpdir(), 'vsh-check-circular-'));
+  const outputFile = join(tempDir, 'circles.json');
+
+  try {
+    const args = [circularScannerCli, cwd, '--output', outputFile];
+
+    if (staged) {
+      args.push('--absolute');
+    }
+
+    args.push('--ignore', ignorePattern);
+
+    await execa(process.execPath, args, {
+      cwd,
+    });
+
+    await access(outputFile);
+    const output = await readFile(outputFile, 'utf8');
+    return JSON.parse(output) as CircularDependencyResult[];
+  } catch (error) {
+    if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
+      return [];
+    }
+    throw error;
+  } finally {
+    await rm(tempDir, { force: true, recursive: true });
+  }
+}
+
 /**
  * 格式化循环依赖的输出
  * @param circles - 循环依赖结果
@@ -85,17 +128,17 @@ async function checkCircular({
     const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`;
     if (cache.has(cacheKey)) {
       const cachedResults = cache.get(cacheKey);
-      if (cachedResults) {
-        verbose && formatCircles(cachedResults);
+      if (cachedResults && verbose) {
+        formatCircles(cachedResults);
       }
       return;
     }
 
     // 检测循环依赖
-    const results = await circularDepsDetect({
-      absolute: staged,
+    const results = await detectCircularDependencies({
       cwd: process.cwd(),
-      ignore: [ignorePattern],
+      ignorePattern,
+      staged,
     });
 
     if (staged) {
@@ -118,11 +161,15 @@ async function checkCircular({
 
       // 更新缓存
       cache.set(cacheKey, circularFiles);
-      verbose && formatCircles(circularFiles);
+      if (verbose) {
+        formatCircles(circularFiles);
+      }
     } else {
       // 更新缓存
       cache.set(cacheKey, results);
-      verbose && formatCircles(results);
+      if (verbose) {
+        formatCircles(results);
+      }
     }
 
     // 如果发现循环依赖,只输出警告信息

+ 2 - 1
scripts/vsh/src/check-dep/index.ts

@@ -10,7 +10,8 @@ const DEFAULT_CONFIG = {
   ignoreMatches: [
     'vite',
     'vitest',
-    'unbuild',
+    'tsdown',
+    '@vben/tailwind-config',
     '@vben/tsconfig',
     '@vben/vite-config',
     '@types/*',

+ 11 - 0
scripts/vsh/tsdown.config.ts

@@ -0,0 +1,11 @@
+import { defineConfig } from 'tsdown';
+
+export default defineConfig({
+  clean: true,
+  dts: true,
+  entry: ['src/index.ts'],
+  format: ['esm'],
+  outExtensions: () => ({
+    dts: '.d.ts',
+  }),
+});

+ 4 - 0
vben-admin.code-workspace

@@ -52,6 +52,10 @@
       "name": "@vben/node-utils",
       "path": "internal/node-utils"
     },
+    {
+      "name": "@vben/tailwind-config",
+      "path": "internal/tailwind-config"
+    },
     {
       "name": "@vben/tsconfig",
       "path": "internal/tsconfig"

Некоторые файлы не были показаны из-за большого количества измененных файлов