Kaynağa Gözat

Merge branch 'fork/ffgenius/antd-vue-next'

Jin Mao 4 ay önce
ebeveyn
işleme
cb1d7565a3
94 değiştirilmiş dosya ile 4534 ekleme ve 12 silme
  1. 1 0
      apps/web-antd/src/locales/langs/en-US/demos.json
  2. 1 0
      apps/web-antd/src/locales/langs/zh-CN/demos.json
  3. 13 1
      apps/web-antd/src/router/routes/modules/vben.ts
  4. 8 0
      apps/web-antdv-next/.env
  5. 7 0
      apps/web-antdv-next/.env.analyze
  6. 16 0
      apps/web-antdv-next/.env.development
  7. 19 0
      apps/web-antdv-next/.env.production
  8. 35 0
      apps/web-antdv-next/index.html
  9. 50 0
      apps/web-antdv-next/package.json
  10. 1 0
      apps/web-antdv-next/postcss.config.mjs
  11. BIN
      apps/web-antdv-next/public/favicon.ico
  12. 603 0
      apps/web-antdv-next/src/adapter/component/index.ts
  13. 49 0
      apps/web-antdv-next/src/adapter/form.ts
  14. 70 0
      apps/web-antdv-next/src/adapter/vxe-table.ts
  15. 51 0
      apps/web-antdv-next/src/api/core/auth.ts
  16. 3 0
      apps/web-antdv-next/src/api/core/index.ts
  17. 10 0
      apps/web-antdv-next/src/api/core/menu.ts
  18. 10 0
      apps/web-antdv-next/src/api/core/user.ts
  19. 1 0
      apps/web-antdv-next/src/api/index.ts
  20. 113 0
      apps/web-antdv-next/src/api/request.ts
  21. 39 0
      apps/web-antdv-next/src/app.vue
  22. 76 0
      apps/web-antdv-next/src/bootstrap.ts
  23. 25 0
      apps/web-antdv-next/src/layouts/auth.vue
  24. 206 0
      apps/web-antdv-next/src/layouts/basic.vue
  25. 6 0
      apps/web-antdv-next/src/layouts/index.ts
  26. 3 0
      apps/web-antdv-next/src/locales/README.md
  27. 102 0
      apps/web-antdv-next/src/locales/index.ts
  28. 14 0
      apps/web-antdv-next/src/locales/langs/en-US/demos.json
  29. 15 0
      apps/web-antdv-next/src/locales/langs/en-US/page.json
  30. 14 0
      apps/web-antdv-next/src/locales/langs/zh-CN/demos.json
  31. 15 0
      apps/web-antdv-next/src/locales/langs/zh-CN/page.json
  32. 31 0
      apps/web-antdv-next/src/main.ts
  33. 13 0
      apps/web-antdv-next/src/preferences.ts
  34. 42 0
      apps/web-antdv-next/src/router/access.ts
  35. 133 0
      apps/web-antdv-next/src/router/guard.ts
  36. 37 0
      apps/web-antdv-next/src/router/index.ts
  37. 97 0
      apps/web-antdv-next/src/router/routes/core.ts
  38. 37 0
      apps/web-antdv-next/src/router/routes/index.ts
  39. 38 0
      apps/web-antdv-next/src/router/routes/modules/dashboard.ts
  40. 28 0
      apps/web-antdv-next/src/router/routes/modules/demos.ts
  41. 116 0
      apps/web-antdv-next/src/router/routes/modules/vben.ts
  42. 118 0
      apps/web-antdv-next/src/store/auth.ts
  43. 1 0
      apps/web-antdv-next/src/store/index.ts
  44. 3 0
      apps/web-antdv-next/src/views/_core/README.md
  45. 9 0
      apps/web-antdv-next/src/views/_core/about/index.vue
  46. 69 0
      apps/web-antdv-next/src/views/_core/authentication/code-login.vue
  47. 43 0
      apps/web-antdv-next/src/views/_core/authentication/forget-password.vue
  48. 98 0
      apps/web-antdv-next/src/views/_core/authentication/login.vue
  49. 10 0
      apps/web-antdv-next/src/views/_core/authentication/qrcode-login.vue
  50. 96 0
      apps/web-antdv-next/src/views/_core/authentication/register.vue
  51. 7 0
      apps/web-antdv-next/src/views/_core/fallback/coming-soon.vue
  52. 9 0
      apps/web-antdv-next/src/views/_core/fallback/forbidden.vue
  53. 9 0
      apps/web-antdv-next/src/views/_core/fallback/internal-error.vue
  54. 9 0
      apps/web-antdv-next/src/views/_core/fallback/not-found.vue
  55. 9 0
      apps/web-antdv-next/src/views/_core/fallback/offline.vue
  56. 65 0
      apps/web-antdv-next/src/views/_core/profile/base-setting.vue
  57. 49 0
      apps/web-antdv-next/src/views/_core/profile/index.vue
  58. 31 0
      apps/web-antdv-next/src/views/_core/profile/notification-setting.vue
  59. 63 0
      apps/web-antdv-next/src/views/_core/profile/password-setting.vue
  60. 43 0
      apps/web-antdv-next/src/views/_core/profile/security-setting.vue
  61. 98 0
      apps/web-antdv-next/src/views/dashboard/analytics/analytics-trends.vue
  62. 82 0
      apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-data.vue
  63. 46 0
      apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-sales.vue
  64. 65 0
      apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-source.vue
  65. 55 0
      apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits.vue
  66. 90 0
      apps/web-antdv-next/src/views/dashboard/analytics/index.vue
  67. 266 0
      apps/web-antdv-next/src/views/dashboard/workspace/index.vue
  68. 66 0
      apps/web-antdv-next/src/views/demos/antd/index.vue
  69. 1 0
      apps/web-antdv-next/tailwind.config.mjs
  70. 12 0
      apps/web-antdv-next/tsconfig.json
  71. 10 0
      apps/web-antdv-next/tsconfig.node.json
  72. 20 0
      apps/web-antdv-next/vite.config.mts
  73. 1 0
      apps/web-ele/src/locales/langs/en-US/demos.json
  74. 1 0
      apps/web-ele/src/locales/langs/zh-CN/demos.json
  75. 17 1
      apps/web-ele/src/router/routes/modules/vben.ts
  76. 1 0
      apps/web-naive/src/locales/langs/en-US/demos.json
  77. 1 0
      apps/web-naive/src/locales/langs/zh-CN/demos.json
  78. 17 1
      apps/web-naive/src/router/routes/modules/vben.ts
  79. 1 0
      apps/web-tdesign/src/locales/langs/en-US/demos.json
  80. 1 0
      apps/web-tdesign/src/locales/langs/zh-CN/demos.json
  81. 13 1
      apps/web-tdesign/src/router/routes/modules/vben.ts
  82. 1 0
      docs/src/en/guide/introduction/quick-start.md
  83. 2 1
      docs/src/guide/introduction/quick-start.md
  84. 1 0
      package.json
  85. 2 0
      packages/@core/base/shared/src/constants/vben.ts
  86. 0 0
      packages/icons/src/svg/icons/antdv-next-logo.svg
  87. 2 0
      packages/icons/src/svg/index.ts
  88. 3 0
      packages/styles/package.json
  89. 77 0
      packages/styles/src/antdv-next/index.css
  90. 1 0
      playground/src/locales/langs/en-US/demos.json
  91. 1 0
      playground/src/locales/langs/zh-CN/demos.json
  92. 686 7
      pnpm-lock.yaml
  93. 1 0
      pnpm-workspace.yaml
  94. 4 0
      vben-admin.code-workspace

+ 1 - 0
apps/web-antd/src/locales/langs/en-US/demos.json

@@ -6,6 +6,7 @@
     "about": "About",
     "document": "Document",
     "antdv": "Ant Design Vue Version",
+    "antdv-next": "Antdv Next Version",
     "naive-ui": "Naive UI Version",
     "element-plus": "Element Plus Version",
     "tdesign": "TDesign Vue Version"

+ 1 - 0
apps/web-antd/src/locales/langs/zh-CN/demos.json

@@ -6,6 +6,7 @@
     "about": "关于",
     "document": "文档",
     "antdv": "Ant Design Vue 版本",
+    "antdv-next": "Antdv Next 版本",
     "naive-ui": "Naive UI 版本",
     "element-plus": "Element Plus 版本",
     "tdesign": "TDesign Vue 版本"

+ 13 - 1
apps/web-antd/src/router/routes/modules/vben.ts

@@ -1,6 +1,7 @@
 import type { RouteRecordRaw } from 'vue-router';
 
 import {
+  VBEN_ANTDV_NEXT_PREVIEW_URL,
   VBEN_DOC_URL,
   VBEN_ELE_PREVIEW_URL,
   VBEN_GITHUB_URL,
@@ -8,7 +9,7 @@ import {
   VBEN_NAIVE_PREVIEW_URL,
   VBEN_TD_PREVIEW_URL,
 } from '@vben/constants';
-import { SvgTDesignIcon } from '@vben/icons';
+import { SvgAntdvNextLogoIcon, SvgTDesignIcon } from '@vben/icons';
 
 import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
@@ -44,6 +45,17 @@ const routes: RouteRecordRaw[] = [
           title: 'Github',
         },
       },
+      {
+        name: 'VbenAntdVNext',
+        path: '/vben-admin/antdv-next',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: SvgAntdvNextLogoIcon,
+          link: VBEN_ANTDV_NEXT_PREVIEW_URL,
+          title: $t('demos.vben.antdv-next'),
+        },
+      },
       {
         name: 'VbenNaive',
         path: '/vben-admin/naive',

+ 8 - 0
apps/web-antdv-next/.env

@@ -0,0 +1,8 @@
+# 应用标题
+VITE_APP_TITLE=Vben Admin Antdv Next
+
+# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
+VITE_APP_NAMESPACE=vben-web-antdv-next
+
+# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
+VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key

+ 7 - 0
apps/web-antdv-next/.env.analyze

@@ -0,0 +1,7 @@
+# public path
+VITE_BASE=/
+
+# Basic interface address SPA
+VITE_GLOB_API_URL=/api
+
+VITE_VISUALIZER=true

+ 16 - 0
apps/web-antdv-next/.env.development

@@ -0,0 +1,16 @@
+# 端口号
+VITE_PORT=5999
+
+VITE_BASE=/
+
+# 接口地址
+VITE_GLOB_API_URL=/api
+
+# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
+VITE_NITRO_MOCK=true
+
+# 是否打开 devtools,true 为打开,false 为关闭
+VITE_DEVTOOLS=false
+
+# 是否注入全局loading
+VITE_INJECT_APP_LOADING=true

+ 19 - 0
apps/web-antdv-next/.env.production

@@ -0,0 +1,19 @@
+VITE_BASE=/
+
+# 接口地址
+VITE_GLOB_API_URL=https://mock-napi.vben.pro/api
+
+# 是否开启压缩,可以设置为 none, brotli, gzip
+VITE_COMPRESS=none
+
+# 是否开启 PWA
+VITE_PWA=false
+
+# vue-router 的模式
+VITE_ROUTER_HISTORY=hash
+
+# 是否注入全局loading
+VITE_INJECT_APP_LOADING=true
+
+# 打包后是否生成dist.zip
+VITE_ARCHIVER=true

+ 35 - 0
apps/web-antdv-next/index.html

@@ -0,0 +1,35 @@
+<!doctype html>
+<html lang="zh">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta name="description" content="A Modern Back-end Management System" />
+    <meta name="keywords" content="Vben Admin Vue3 Vite" />
+    <meta name="author" content="Vben" />
+    <meta
+      name="viewport"
+      content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
+    />
+    <!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
+    <title><%= VITE_APP_TITLE %></title>
+    <link rel="icon" href="/favicon.ico" />
+    <script>
+      // 生产环境下注入百度统计
+      if (window._VBEN_ADMIN_PRO_APP_CONF_) {
+        var _hmt = _hmt || [];
+        (function () {
+          var hm = document.createElement('script');
+          hm.src =
+            'https://hm.baidu.com/hm.js?b38e689f40558f20a9a686d7f6f33edf';
+          var s = document.getElementsByTagName('script')[0];
+          s.parentNode.insertBefore(hm, s);
+        })();
+      }
+    </script>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 50 - 0
apps/web-antdv-next/package.json

@@ -0,0 +1,50 @@
+{
+  "name": "@vben/web-antdv-next",
+  "version": "5.5.9",
+  "homepage": "https://vben.pro",
+  "bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/vbenjs/vue-vben-admin.git",
+    "directory": "apps/web-antdv-next"
+  },
+  "license": "MIT",
+  "author": {
+    "name": "vben",
+    "email": "ann.vben@gmail.com",
+    "url": "https://github.com/anncwb"
+  },
+  "type": "module",
+  "scripts": {
+    "build": "pnpm vite build --mode production",
+    "build:analyze": "pnpm vite build --mode analyze",
+    "dev": "pnpm vite --mode development",
+    "preview": "vite preview",
+    "typecheck": "vue-tsc --noEmit --skipLibCheck"
+  },
+  "imports": {
+    "#/*": "./src/*"
+  },
+  "dependencies": {
+    "@vben/access": "workspace:*",
+    "@vben/common-ui": "workspace:*",
+    "@vben/constants": "workspace:*",
+    "@vben/hooks": "workspace:*",
+    "@vben/icons": "workspace:*",
+    "@vben/layouts": "workspace:*",
+    "@vben/locales": "workspace:*",
+    "@vben/plugins": "workspace:*",
+    "@vben/preferences": "workspace:*",
+    "@vben/request": "workspace:*",
+    "@vben/stores": "workspace:*",
+    "@vben/styles": "workspace:*",
+    "@vben/types": "workspace:*",
+    "@vben/utils": "workspace:*",
+    "@vueuse/core": "catalog:",
+    "antdv-next": "catalog:",
+    "dayjs": "catalog:",
+    "pinia": "catalog:",
+    "vue": "catalog:",
+    "vue-router": "catalog:"
+  }
+}

+ 1 - 0
apps/web-antdv-next/postcss.config.mjs

@@ -0,0 +1 @@
+export { default } from '@vben/tailwind-config/postcss';

BIN
apps/web-antdv-next/public/favicon.ico


+ 603 - 0
apps/web-antdv-next/src/adapter/component/index.ts

@@ -0,0 +1,603 @@
+/**
+ * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
+ * 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
+ */
+
+/* eslint-disable vue/one-component-per-file */
+
+import type { UploadChangeParam, UploadFile, UploadProps } from 'antdv-next';
+
+import type { Component, Ref } from 'vue';
+
+import type { BaseFormComponentType } from '@vben/common-ui';
+import type { Recordable } from '@vben/types';
+
+import {
+  computed,
+  defineAsyncComponent,
+  defineComponent,
+  h,
+  ref,
+  render,
+  unref,
+  watch,
+} from 'vue';
+
+import {
+  ApiComponent,
+  globalShareState,
+  IconPicker,
+  VCropper,
+} from '@vben/common-ui';
+import { IconifyIcon } from '@vben/icons';
+import { $t } from '@vben/locales';
+import { isEmpty } from '@vben/utils';
+
+import { message, Modal, notification } from 'antdv-next';
+
+const AutoComplete = defineAsyncComponent(
+  () => import('antdv-next/dist/auto-complete/index'),
+);
+const Button = defineAsyncComponent(
+  () => import('antdv-next/dist/button/index'),
+);
+const Checkbox = defineAsyncComponent(
+  () => import('antdv-next/dist/checkbox/index'),
+);
+const CheckboxGroup = defineAsyncComponent(
+  () => import('antdv-next/dist/checkbox/Group'),
+);
+const DatePicker = defineAsyncComponent(
+  () => import('antdv-next/dist/date-picker/index'),
+);
+const Divider = defineAsyncComponent(
+  () => import('antdv-next/dist/divider/index'),
+);
+const Input = defineAsyncComponent(() => import('antdv-next/dist/input/index'));
+const InputNumber = defineAsyncComponent(
+  () => import('antdv-next/dist/input-number/index'),
+);
+const InputPassword = defineAsyncComponent(() =>
+  import('antdv-next/dist/input/index').then((res) => res.InputPassword),
+);
+const Mentions = defineAsyncComponent(
+  () => import('antdv-next/dist/mentions/index'),
+);
+const Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index'));
+const RadioGroup = defineAsyncComponent(() =>
+  import('antdv-next/dist/radio/index').then((res) => res.RadioGroup),
+);
+const RangePicker = defineAsyncComponent(() =>
+  import('antdv-next/dist/date-picker/index').then(
+    (res) => res.DateRangePicker,
+  ),
+);
+const Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index'));
+const Select = defineAsyncComponent(
+  () => import('antdv-next/dist/select/index'),
+);
+const Space = defineAsyncComponent(() => import('antdv-next/dist/space/index'));
+const Switch = defineAsyncComponent(
+  () => import('antdv-next/dist/switch/index'),
+);
+const Textarea = defineAsyncComponent(
+  () => import('antdv-next/dist/input/TextArea'),
+);
+const TimePicker = defineAsyncComponent(
+  () => import('antdv-next/dist/time-picker/index'),
+);
+const TreeSelect = defineAsyncComponent(
+  () => import('antdv-next/dist/tree-select/index'),
+);
+const Cascader = defineAsyncComponent(
+  () => import('antdv-next/dist/cascader/index'),
+);
+const Upload = defineAsyncComponent(
+  () => import('antdv-next/dist/upload/index'),
+);
+const Image = defineAsyncComponent(() => import('antdv-next/dist/image/index'));
+const PreviewGroup = defineAsyncComponent(() =>
+  import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup),
+);
+
+const withDefaultPlaceholder = <T extends Component>(
+  component: T,
+  type: 'input' | 'select',
+  componentProps: Recordable<any> = {},
+) => {
+  return defineComponent({
+    name: component.name,
+    inheritAttrs: false,
+    setup: (props: any, { attrs, expose, slots }) => {
+      const placeholder =
+        props?.placeholder ||
+        attrs?.placeholder ||
+        $t(`ui.placeholder.${type}`);
+      // 透传组件暴露的方法
+      const innerRef = ref();
+      expose(
+        new Proxy(
+          {},
+          {
+            get: (_target, key) => innerRef.value?.[key],
+            has: (_target, key) => key in (innerRef.value || {}),
+          },
+        ),
+      );
+      return () =>
+        h(
+          component,
+          { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
+          slots,
+        );
+    },
+  });
+};
+
+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;
+    }
+    return file.type.startsWith('image/');
+  };
+  // 创建默认的上传按钮插槽
+  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',
+                }),
+              },
+              () => placeholder,
+            ),
+        };
+      }
+    }
+  };
+  // 构建预览图片组
+  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');
+    document.body.append(container);
+
+    // 用于追踪组件是否已卸载
+    let isUnmounted = false;
+
+    const PreviewWrapper = {
+      setup() {
+        return () => {
+          if (isUnmounted) return null;
+          return h(
+            PreviewGroupComponent,
+            {
+              class: 'hidden',
+              preview: {
+                open: visible.value,
+                // 设置初始显示的图片索引
+                current: imageFiles.findIndex((f) => f.uid === file.uid),
+                onOpenChange: (value: boolean) => {
+                  visible.value = value;
+                  if (!value) {
+                    // 延迟清理,确保动画完成
+                    setTimeout(() => {
+                      if (!isUnmounted && container) {
+                        isUnmounted = true;
+                        render(null, container);
+                        container.remove();
+                      }
+                    }, 300);
+                  }
+                },
+              },
+            },
+            () =>
+              // 渲染所有图片文件
+              imageFiles.map((imgFile) =>
+                h(ImageComponent, {
+                  key: imgFile.uid,
+                  src: imgFile.url || imgFile.preview,
+                }),
+              ),
+          );
+        };
+      },
+    };
+
+    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'),
+                destroyOnHidden: 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);
+    });
+  };
+
+  return defineComponent({
+    name: 'AUpload',
+    emits: ['update:modelValue'],
+    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 listType = attrs?.listType || attrs?.['list-type'] || 'text';
+
+      const fileList = ref<UploadProps['fileList']>(
+        attrs?.fileList || attrs?.['file-list'] || [],
+      );
+
+      const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);
+      const aspectRatio = computed(
+        () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
+      );
+
+      const handleBeforeUpload = async (
+        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 &&
+          originFileList[0] &&
+          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);
+          });
+        }
+
+        return attrs.beforeUpload?.(file) ?? true;
+      };
+
+      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(
+          (file) => file.status !== 'removed',
+        );
+        emit(
+          'update:modelValue',
+          event.fileList?.length ? fileList.value : undefined,
+        );
+      };
+
+      const handlePreview = async (file: UploadFile) => {
+        previewVisible.value = true;
+        await previewImage(file, previewVisible, fileList);
+      };
+
+      const renderUploadButton = (): any => {
+        const isDisabled = attrs.disabled;
+
+        // 如果禁用,不渲染上传按钮
+        if (isDisabled) {
+          return null;
+        }
+
+        // 否则渲染默认上传按钮
+        return isEmpty(slots)
+          ? createDefaultSlotsWithUpload(listType, placeholder)
+          : slots;
+      };
+
+      // 可以监听到表单API设置的值
+      watch(
+        () => attrs.modelValue,
+        (res) => {
+          fileList.value = res;
+        },
+      );
+
+      return () =>
+        h(
+          Upload,
+          {
+            ...props,
+            ...attrs,
+            fileList: fileList.value,
+            beforeUpload: handleBeforeUpload,
+            onChange: handleChange,
+            onPreview: handlePreview,
+          },
+          renderUploadButton(),
+        );
+    },
+  });
+};
+
+// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
+export type ComponentType =
+  | 'ApiCascader'
+  | 'ApiSelect'
+  | 'ApiTreeSelect'
+  | 'AutoComplete'
+  | 'Cascader'
+  | 'Checkbox'
+  | 'CheckboxGroup'
+  | 'DatePicker'
+  | 'DefaultButton'
+  | 'Divider'
+  | 'IconPicker'
+  | 'Input'
+  | 'InputNumber'
+  | 'InputPassword'
+  | 'Mentions'
+  | 'PrimaryButton'
+  | 'Radio'
+  | 'RadioGroup'
+  | 'RangePicker'
+  | 'Rate'
+  | 'Select'
+  | 'Space'
+  | 'Switch'
+  | 'Textarea'
+  | 'TimePicker'
+  | 'TreeSelect'
+  | 'Upload'
+  | BaseFormComponentType;
+
+async function initComponentAdapter() {
+  const components: Partial<Record<ComponentType, Component>> = {
+    // 如果你的组件体积比较大,可以使用异步加载
+    // Button: () =>
+    // import('xxx').then((res) => res.Button),
+
+    ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', {
+      component: Cascader,
+      fieldNames: { label: 'label', value: 'value', children: 'children' },
+      loadingSlot: 'suffixIcon',
+      modelPropName: 'value',
+      visibleEvent: 'onVisibleChange',
+    }),
+    ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
+      component: Select,
+      loadingSlot: 'suffixIcon',
+      modelPropName: 'value',
+      visibleEvent: 'onVisibleChange',
+    }),
+    ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
+      component: TreeSelect,
+      fieldNames: { label: 'label', value: 'value', children: 'children' },
+      loadingSlot: 'suffixIcon',
+      modelPropName: 'value',
+      optionsPropName: 'treeData',
+      visibleEvent: 'onVisibleChange',
+    }),
+    AutoComplete,
+    Cascader,
+    Checkbox,
+    CheckboxGroup,
+    DatePicker,
+    // 自定义默认按钮
+    DefaultButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'default' }, slots);
+    },
+    Divider,
+    IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
+      iconSlot: 'addonAfter',
+      inputComponent: Input,
+      modelValueProp: 'value',
+    }),
+    Input: withDefaultPlaceholder(Input, 'input'),
+    InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
+    InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
+    Mentions: withDefaultPlaceholder(Mentions, 'input'),
+    // 自定义主要按钮
+    PrimaryButton: (props, { attrs, slots }) => {
+      return h(Button, { ...props, attrs, type: 'primary' }, slots);
+    },
+    Radio,
+    RadioGroup,
+    RangePicker,
+    Rate,
+    Select: withDefaultPlaceholder(Select, 'select'),
+    Space,
+    Switch,
+    Textarea: withDefaultPlaceholder(Textarea, 'input'),
+    TimePicker,
+    TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
+    Upload: withPreviewUpload(),
+  };
+
+  // 将组件注册到全局共享状态中
+  globalShareState.setComponents(components);
+
+  // 定义全局共享状态中的消息提示
+  globalShareState.defineMessage({
+    // 复制成功消息提示
+    copyPreferencesSuccess: (title, content) => {
+      notification.success({
+        description: content,
+        title,
+        placement: 'bottomRight',
+      });
+    },
+  });
+}
+
+export { initComponentAdapter };

+ 49 - 0
apps/web-antdv-next/src/adapter/form.ts

@@ -0,0 +1,49 @@
+import type {
+  VbenFormSchema as FormSchema,
+  VbenFormProps,
+} from '@vben/common-ui';
+
+import type { ComponentType } from './component';
+
+import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+async function initSetupVbenForm() {
+  setupVbenForm<ComponentType>({
+    config: {
+      // ant design vue组件库默认都是 v-model:value
+      baseModelPropName: 'value',
+
+      // 一些组件是 v-model:checked 或者 v-model:fileList
+      modelPropNameMap: {
+        Checkbox: 'checked',
+        Radio: 'checked',
+        Switch: 'checked',
+        Upload: 'fileList',
+      },
+    },
+    defineRules: {
+      // 输入项目必填国际化适配
+      required: (value, _params, ctx) => {
+        if (value === undefined || value === null || value.length === 0) {
+          return $t('ui.formRules.required', [ctx.label]);
+        }
+        return true;
+      },
+      // 选择项目必填国际化适配
+      selectRequired: (value, _params, ctx) => {
+        if (value === undefined || value === null) {
+          return $t('ui.formRules.selectRequired', [ctx.label]);
+        }
+        return true;
+      },
+    },
+  });
+}
+
+const useVbenForm = useForm<ComponentType>;
+
+export { initSetupVbenForm, useVbenForm, z };
+
+export type VbenFormSchema = FormSchema<ComponentType>;
+export type { VbenFormProps };

+ 70 - 0
apps/web-antdv-next/src/adapter/vxe-table.ts

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

+ 51 - 0
apps/web-antdv-next/src/api/core/auth.ts

@@ -0,0 +1,51 @@
+import { baseRequestClient, requestClient } from '#/api/request';
+
+export namespace AuthApi {
+  /** 登录接口参数 */
+  export interface LoginParams {
+    password?: string;
+    username?: string;
+  }
+
+  /** 登录接口返回值 */
+  export interface LoginResult {
+    accessToken: string;
+  }
+
+  export interface RefreshTokenResult {
+    data: string;
+    status: number;
+  }
+}
+
+/**
+ * 登录
+ */
+export async function loginApi(data: AuthApi.LoginParams) {
+  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
+}
+
+/**
+ * 刷新accessToken
+ */
+export async function refreshTokenApi() {
+  return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
+    withCredentials: true,
+  });
+}
+
+/**
+ * 退出登录
+ */
+export async function logoutApi() {
+  return baseRequestClient.post('/auth/logout', {
+    withCredentials: true,
+  });
+}
+
+/**
+ * 获取用户权限码
+ */
+export async function getAccessCodesApi() {
+  return requestClient.get<string[]>('/auth/codes');
+}

+ 3 - 0
apps/web-antdv-next/src/api/core/index.ts

@@ -0,0 +1,3 @@
+export * from './auth';
+export * from './menu';
+export * from './user';

+ 10 - 0
apps/web-antdv-next/src/api/core/menu.ts

@@ -0,0 +1,10 @@
+import type { RouteRecordStringComponent } from '@vben/types';
+
+import { requestClient } from '#/api/request';
+
+/**
+ * 获取用户所有菜单
+ */
+export async function getAllMenusApi() {
+  return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
+}

+ 10 - 0
apps/web-antdv-next/src/api/core/user.ts

@@ -0,0 +1,10 @@
+import type { UserInfo } from '@vben/types';
+
+import { requestClient } from '#/api/request';
+
+/**
+ * 获取用户信息
+ */
+export async function getUserInfoApi() {
+  return requestClient.get<UserInfo>('/user/info');
+}

+ 1 - 0
apps/web-antdv-next/src/api/index.ts

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

+ 113 - 0
apps/web-antdv-next/src/api/request.ts

@@ -0,0 +1,113 @@
+/**
+ * 该文件可自行根据业务逻辑进行调整
+ */
+import type { RequestClientOptions } from '@vben/request';
+
+import { useAppConfig } from '@vben/hooks';
+import { preferences } from '@vben/preferences';
+import {
+  authenticateResponseInterceptor,
+  defaultResponseInterceptor,
+  errorMessageResponseInterceptor,
+  RequestClient,
+} from '@vben/request';
+import { useAccessStore } from '@vben/stores';
+
+import { message } from 'antdv-next';
+
+import { useAuthStore } from '#/store';
+
+import { refreshTokenApi } from './core';
+
+const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
+
+function createRequestClient(baseURL: string, options?: RequestClientOptions) {
+  const client = new RequestClient({
+    ...options,
+    baseURL,
+  });
+
+  /**
+   * 重新认证逻辑
+   */
+  async function doReAuthenticate() {
+    console.warn('Access token or refresh token is invalid or expired. ');
+    const accessStore = useAccessStore();
+    const authStore = useAuthStore();
+    accessStore.setAccessToken(null);
+    if (
+      preferences.app.loginExpiredMode === 'modal' &&
+      accessStore.isAccessChecked
+    ) {
+      accessStore.setLoginExpired(true);
+    } else {
+      await authStore.logout();
+    }
+  }
+
+  /**
+   * 刷新token逻辑
+   */
+  async function doRefreshToken() {
+    const accessStore = useAccessStore();
+    const resp = await refreshTokenApi();
+    const newToken = resp.data;
+    accessStore.setAccessToken(newToken);
+    return newToken;
+  }
+
+  function formatToken(token: null | string) {
+    return token ? `Bearer ${token}` : null;
+  }
+
+  // 请求头处理
+  client.addRequestInterceptor({
+    fulfilled: async (config) => {
+      const accessStore = useAccessStore();
+
+      config.headers.Authorization = formatToken(accessStore.accessToken);
+      config.headers['Accept-Language'] = preferences.app.locale;
+      return config;
+    },
+  });
+
+  // 处理返回的响应数据格式
+  client.addResponseInterceptor(
+    defaultResponseInterceptor({
+      codeField: 'code',
+      dataField: 'data',
+      successCode: 0,
+    }),
+  );
+
+  // token过期的处理
+  client.addResponseInterceptor(
+    authenticateResponseInterceptor({
+      client,
+      doReAuthenticate,
+      doRefreshToken,
+      enableRefreshToken: preferences.app.enableRefreshToken,
+      formatToken,
+    }),
+  );
+
+  // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
+  client.addResponseInterceptor(
+    errorMessageResponseInterceptor((msg: string, error) => {
+      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
+      // 当前mock接口返回的错误字段是 error 或者 message
+      const responseData = error?.response?.data ?? {};
+      const errorMessage = responseData?.error ?? responseData?.message ?? '';
+      // 如果没有错误信息,则会根据状态码进行提示
+      message.error(errorMessage || msg);
+    }),
+  );
+
+  return client;
+}
+
+export const requestClient = createRequestClient(apiURL, {
+  responseReturn: 'data',
+});
+
+export const baseRequestClient = new RequestClient({ baseURL: apiURL });

+ 39 - 0
apps/web-antdv-next/src/app.vue

@@ -0,0 +1,39 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+
+import { useAntdDesignTokens } from '@vben/hooks';
+import { preferences, usePreferences } from '@vben/preferences';
+
+import { App, ConfigProvider, theme } from 'antdv-next';
+
+import { antdLocale } from '#/locales';
+
+defineOptions({ name: 'App' });
+
+const { isDark } = usePreferences();
+const { tokens } = useAntdDesignTokens();
+
+const tokenTheme = computed(() => {
+  const algorithm = isDark.value
+    ? [theme.darkAlgorithm]
+    : [theme.defaultAlgorithm];
+
+  // antd 紧凑模式算法
+  if (preferences.app.compact) {
+    algorithm.push(theme.compactAlgorithm);
+  }
+
+  return {
+    algorithm,
+    token: tokens,
+  };
+});
+</script>
+
+<template>
+  <ConfigProvider :locale="antdLocale" :theme="tokenTheme">
+    <App>
+      <RouterView />
+    </App>
+  </ConfigProvider>
+</template>

+ 76 - 0
apps/web-antdv-next/src/bootstrap.ts

@@ -0,0 +1,76 @@
+import { createApp, watchEffect } from 'vue';
+
+import { registerAccessDirective } from '@vben/access';
+import { registerLoadingDirective } from '@vben/common-ui/es/loading';
+import { preferences } from '@vben/preferences';
+import { initStores } from '@vben/stores';
+import '@vben/styles';
+import '@vben/styles/antdv-next';
+
+import { useTitle } from '@vueuse/core';
+
+import { $t, setupI18n } from '#/locales';
+
+import { initComponentAdapter } from './adapter/component';
+import { initSetupVbenForm } from './adapter/form';
+import App from './app.vue';
+import { router } from './router';
+
+async function bootstrap(namespace: string) {
+  // 初始化组件适配器
+  await initComponentAdapter();
+
+  // 初始化表单组件
+  await initSetupVbenForm();
+
+  // // 设置弹窗的默认配置
+  // setDefaultModalProps({
+  //   fullscreenButton: false,
+  // });
+  // // 设置抽屉的默认配置
+  // setDefaultDrawerProps({
+  //   zIndex: 1020,
+  // });
+
+  const app = createApp(App);
+
+  // 注册v-loading指令
+  registerLoadingDirective(app, {
+    loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
+    spinning: 'spinning',
+  });
+
+  // 国际化 i18n 配置
+  await setupI18n(app);
+
+  // 配置 pinia-tore
+  await initStores(app, { namespace });
+
+  // 安装权限指令
+  registerAccessDirective(app);
+
+  // 初始化 tippy
+  const { initTippy } = await import('@vben/common-ui/es/tippy');
+  initTippy(app);
+
+  // 配置路由及路由守卫
+  app.use(router);
+
+  // 配置Motion插件
+  const { MotionPlugin } = await import('@vben/plugins/motion');
+  app.use(MotionPlugin);
+
+  // 动态更新标题
+  watchEffect(() => {
+    if (preferences.app.dynamicTitle) {
+      const routeTitle = router.currentRoute.value.meta?.title;
+      const pageTitle =
+        (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
+      useTitle(pageTitle);
+    }
+  });
+
+  app.mount('#app');
+}
+
+export { bootstrap };

+ 25 - 0
apps/web-antdv-next/src/layouts/auth.vue

@@ -0,0 +1,25 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+
+import { AuthPageLayout } from '@vben/layouts';
+import { preferences } from '@vben/preferences';
+
+import { $t } from '#/locales';
+
+const appName = computed(() => preferences.app.name);
+const logo = computed(() => preferences.logo.source);
+const logoDark = computed(() => preferences.logo.sourceDark);
+</script>
+
+<template>
+  <AuthPageLayout
+    :app-name="appName"
+    :logo="logo"
+    :logo-dark="logoDark"
+    :page-description="$t('authentication.pageDesc')"
+    :page-title="$t('authentication.pageTitle')"
+  >
+    <!-- 自定义工具栏 -->
+    <!-- <template #toolbar></template> -->
+  </AuthPageLayout>
+</template>

+ 206 - 0
apps/web-antdv-next/src/layouts/basic.vue

@@ -0,0 +1,206 @@
+<script lang="ts" setup>
+import type { NotificationItem } from '@vben/layouts';
+
+import { computed, ref, watch } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
+import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
+import { useWatermark } from '@vben/hooks';
+import { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons';
+import {
+  BasicLayout,
+  LockScreen,
+  Notification,
+  UserDropdown,
+} from '@vben/layouts';
+import { preferences } from '@vben/preferences';
+import { useAccessStore, useUserStore } from '@vben/stores';
+import { openWindow } from '@vben/utils';
+
+import { $t } from '#/locales';
+import { useAuthStore } from '#/store';
+import LoginForm from '#/views/_core/authentication/login.vue';
+
+const notifications = ref<NotificationItem[]>([
+  {
+    id: 1,
+    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
+    date: '3小时前',
+    isRead: true,
+    message: '描述信息描述信息描述信息',
+    title: '收到了 14 份新周报',
+  },
+  {
+    id: 2,
+    avatar: 'https://avatar.vercel.sh/1',
+    date: '刚刚',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '朱偏右 回复了你',
+  },
+  {
+    id: 3,
+    avatar: 'https://avatar.vercel.sh/1',
+    date: '2024-01-01',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '曲丽丽 评论了你',
+  },
+  {
+    id: 4,
+    avatar: 'https://avatar.vercel.sh/satori',
+    date: '1天前',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '代办提醒',
+  },
+  {
+    id: 5,
+    avatar: 'https://avatar.vercel.sh/satori',
+    date: '1天前',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '跳转Workspace示例',
+    link: '/workspace',
+  },
+  {
+    id: 6,
+    avatar: 'https://avatar.vercel.sh/satori',
+    date: '1天前',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '跳转外部链接示例',
+    link: 'https://doc.vben.pro',
+  },
+]);
+
+const router = useRouter();
+const userStore = useUserStore();
+const authStore = useAuthStore();
+const accessStore = useAccessStore();
+const { destroyWatermark, updateWatermark } = useWatermark();
+const showDot = computed(() =>
+  notifications.value.some((item) => !item.isRead),
+);
+
+const menus = computed(() => [
+  {
+    handler: () => {
+      router.push({ name: 'Profile' });
+    },
+    icon: 'lucide:user',
+    text: $t('page.auth.profile'),
+  },
+  {
+    handler: () => {
+      openWindow(VBEN_DOC_URL, {
+        target: '_blank',
+      });
+    },
+    icon: BookOpenText,
+    text: $t('ui.widgets.document'),
+  },
+  {
+    handler: () => {
+      openWindow(VBEN_GITHUB_URL, {
+        target: '_blank',
+      });
+    },
+    icon: SvgGithubIcon,
+    text: 'GitHub',
+  },
+  {
+    handler: () => {
+      openWindow(`${VBEN_GITHUB_URL}/issues`, {
+        target: '_blank',
+      });
+    },
+    icon: CircleHelp,
+    text: $t('ui.widgets.qa'),
+  },
+]);
+
+const avatar = computed(() => {
+  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
+});
+
+async function handleLogout() {
+  await authStore.logout(false);
+}
+
+function handleNoticeClear() {
+  notifications.value = [];
+}
+
+function markRead(id: number | string) {
+  const item = notifications.value.find((item) => item.id === id);
+  if (item) {
+    item.isRead = true;
+  }
+}
+
+function remove(id: number | string) {
+  notifications.value = notifications.value.filter((item) => item.id !== id);
+}
+
+function handleMakeAll() {
+  notifications.value.forEach((item) => (item.isRead = true));
+}
+watch(
+  () => ({
+    enable: preferences.app.watermark,
+    content: preferences.app.watermarkContent,
+  }),
+  async ({ enable, content }) => {
+    if (enable) {
+      await updateWatermark({
+        content:
+          content ||
+          `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
+      });
+    } else {
+      destroyWatermark();
+    }
+  },
+  {
+    immediate: true,
+  },
+);
+</script>
+
+<template>
+  <BasicLayout @clear-preferences-and-logout="handleLogout">
+    <template #user-dropdown>
+      <UserDropdown
+        :avatar
+        :menus
+        :text="userStore.userInfo?.realName"
+        description="ann.vben@gmail.com"
+        tag-text="Pro"
+        @logout="handleLogout"
+      />
+    </template>
+    <template #notification>
+      <Notification
+        :dot="showDot"
+        :notifications="notifications"
+        @clear="handleNoticeClear"
+        @read="(item) => item.id && markRead(item.id)"
+        @remove="(item) => item.id && remove(item.id)"
+        @make-all="handleMakeAll"
+      />
+    </template>
+    <template #extra>
+      <AuthenticationLoginExpiredModal
+        v-model:open="accessStore.loginExpired"
+        :avatar
+      >
+        <LoginForm />
+      </AuthenticationLoginExpiredModal>
+    </template>
+    <template #lock-screen>
+      <LockScreen :avatar @to-login="handleLogout" />
+    </template>
+  </BasicLayout>
+</template>

+ 6 - 0
apps/web-antdv-next/src/layouts/index.ts

@@ -0,0 +1,6 @@
+const BasicLayout = () => import('./basic.vue');
+const AuthPageLayout = () => import('./auth.vue');
+
+const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
+
+export { AuthPageLayout, BasicLayout, IFrameView };

+ 3 - 0
apps/web-antdv-next/src/locales/README.md

@@ -0,0 +1,3 @@
+# locale
+
+每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。

+ 102 - 0
apps/web-antdv-next/src/locales/index.ts

@@ -0,0 +1,102 @@
+import type { Locale } from 'antdv-next/dist/locale/index';
+
+import type { App } from 'vue';
+
+import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
+
+import { ref } from 'vue';
+
+import {
+  $t,
+  setupI18n as coreSetup,
+  loadLocalesMapFromDir,
+} from '@vben/locales';
+import { preferences } from '@vben/preferences';
+
+import antdEnLocale from 'antdv-next/dist/locale/en_US';
+import antdDefaultLocale from 'antdv-next/dist/locale/zh_CN';
+import dayjs from 'dayjs';
+
+const antdLocale = ref<Locale>(antdDefaultLocale);
+
+const modules = import.meta.glob('./langs/**/*.json');
+
+const localesMap = loadLocalesMapFromDir(
+  /\.\/langs\/([^/]+)\/(.*)\.json$/,
+  modules,
+);
+/**
+ * 加载应用特有的语言包
+ * 这里也可以改造为从服务端获取翻译数据
+ * @param lang
+ */
+async function loadMessages(lang: SupportedLanguagesType) {
+  const [appLocaleMessages] = await Promise.all([
+    localesMap[lang]?.(),
+    loadThirdPartyMessage(lang),
+  ]);
+  return appLocaleMessages?.default;
+}
+
+/**
+ * 加载第三方组件库的语言包
+ * @param lang
+ */
+async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
+  await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]);
+}
+
+/**
+ * 加载dayjs的语言包
+ * @param lang
+ */
+async function loadDayjsLocale(lang: SupportedLanguagesType) {
+  let locale;
+  switch (lang) {
+    case 'en-US': {
+      locale = await import('dayjs/locale/en');
+      break;
+    }
+    case 'zh-CN': {
+      locale = await import('dayjs/locale/zh-cn');
+      break;
+    }
+    // 默认使用英语
+    default: {
+      locale = await import('dayjs/locale/en');
+    }
+  }
+  if (locale) {
+    dayjs.locale(locale);
+  } else {
+    console.error(`Failed to load dayjs locale for ${lang}`);
+  }
+}
+
+/**
+ * 加载antd的语言包
+ * @param lang
+ */
+async function loadAntdLocale(lang: SupportedLanguagesType) {
+  switch (lang) {
+    case 'en-US': {
+      antdLocale.value = antdEnLocale;
+      break;
+    }
+    case 'zh-CN': {
+      antdLocale.value = antdDefaultLocale;
+      break;
+    }
+  }
+}
+
+async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
+  await coreSetup(app, {
+    defaultLocale: preferences.app.locale,
+    loadMessages,
+    missingWarn: !import.meta.env.PROD,
+    ...options,
+  });
+}
+
+export { $t, antdLocale, setupI18n };

+ 14 - 0
apps/web-antdv-next/src/locales/langs/en-US/demos.json

@@ -0,0 +1,14 @@
+{
+  "title": "Demos",
+  "antd": "Antdv Next",
+  "vben": {
+    "title": "Project",
+    "about": "About",
+    "document": "Document",
+    "antdv": "Ant Design Vue Version",
+    "antdv-next": "Antdv Next Version",
+    "naive-ui": "Naive UI Version",
+    "element-plus": "Element Plus Version",
+    "tdesign": "TDesign Vue Version"
+  }
+}

+ 15 - 0
apps/web-antdv-next/src/locales/langs/en-US/page.json

@@ -0,0 +1,15 @@
+{
+  "auth": {
+    "login": "Login",
+    "register": "Register",
+    "codeLogin": "Code Login",
+    "qrcodeLogin": "Qr Code Login",
+    "forgetPassword": "Forget Password",
+    "profile": "Profile"
+  },
+  "dashboard": {
+    "title": "Dashboard",
+    "analytics": "Analytics",
+    "workspace": "Workspace"
+  }
+}

+ 14 - 0
apps/web-antdv-next/src/locales/langs/zh-CN/demos.json

@@ -0,0 +1,14 @@
+{
+  "title": "演示",
+  "antd": "Antdv Next",
+  "vben": {
+    "title": "项目",
+    "about": "关于",
+    "document": "文档",
+    "antdv": "Ant Design Vue 版本",
+    "antdv-next": "Antdv Next 版本",
+    "naive-ui": "Naive UI 版本",
+    "element-plus": "Element Plus 版本",
+    "tdesign": "TDesign Vue 版本"
+  }
+}

+ 15 - 0
apps/web-antdv-next/src/locales/langs/zh-CN/page.json

@@ -0,0 +1,15 @@
+{
+  "auth": {
+    "login": "登录",
+    "register": "注册",
+    "codeLogin": "验证码登录",
+    "qrcodeLogin": "二维码登录",
+    "forgetPassword": "忘记密码",
+    "profile": "个人中心"
+  },
+  "dashboard": {
+    "title": "概览",
+    "analytics": "分析页",
+    "workspace": "工作台"
+  }
+}

+ 31 - 0
apps/web-antdv-next/src/main.ts

@@ -0,0 +1,31 @@
+import { initPreferences } from '@vben/preferences';
+import { unmountGlobalLoading } from '@vben/utils';
+
+import { overridesPreferences } from './preferences';
+
+/**
+ * 应用初始化完成之后再进行页面加载渲染
+ */
+async function initApplication() {
+  // name用于指定项目唯一标识
+  // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据
+  const env = import.meta.env.PROD ? 'prod' : 'dev';
+  const appVersion = import.meta.env.VITE_APP_VERSION;
+  const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;
+
+  // app偏好设置初始化
+  await initPreferences({
+    namespace,
+    overrides: overridesPreferences,
+  });
+
+  // 启动应用并挂载
+  // vue应用主要逻辑及视图
+  const { bootstrap } = await import('./bootstrap');
+  await bootstrap(namespace);
+
+  // 移除并销毁loading
+  unmountGlobalLoading();
+}
+
+initApplication();

+ 13 - 0
apps/web-antdv-next/src/preferences.ts

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

+ 42 - 0
apps/web-antdv-next/src/router/access.ts

@@ -0,0 +1,42 @@
+import type {
+  ComponentRecordType,
+  GenerateMenuAndRoutesOptions,
+} from '@vben/types';
+
+import { generateAccessible } from '@vben/access';
+import { preferences } from '@vben/preferences';
+
+import { message } from 'antdv-next';
+
+import { getAllMenusApi } from '#/api';
+import { BasicLayout, IFrameView } from '#/layouts';
+import { $t } from '#/locales';
+
+const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
+
+async function generateAccess(options: GenerateMenuAndRoutesOptions) {
+  const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
+
+  const layoutMap: ComponentRecordType = {
+    BasicLayout,
+    IFrameView,
+  };
+
+  return await generateAccessible(preferences.app.accessMode, {
+    ...options,
+    fetchMenuListAsync: async () => {
+      message.loading({
+        content: `${$t('common.loadingMenu')}...`,
+        duration: 1.5,
+      });
+      return await getAllMenusApi();
+    },
+    // 可以指定没有权限跳转403页面
+    forbiddenComponent,
+    // 如果 route.meta.menuVisibleWithForbidden = true
+    layoutMap,
+    pageMap,
+  });
+}
+
+export { generateAccess };

+ 133 - 0
apps/web-antdv-next/src/router/guard.ts

@@ -0,0 +1,133 @@
+import type { Router } from 'vue-router';
+
+import { LOGIN_PATH } from '@vben/constants';
+import { preferences } from '@vben/preferences';
+import { useAccessStore, useUserStore } from '@vben/stores';
+import { startProgress, stopProgress } from '@vben/utils';
+
+import { accessRoutes, coreRouteNames } from '#/router/routes';
+import { useAuthStore } from '#/store';
+
+import { generateAccess } from './access';
+
+/**
+ * 通用守卫配置
+ * @param router
+ */
+function setupCommonGuard(router: Router) {
+  // 记录已经加载的页面
+  const loadedPaths = new Set<string>();
+
+  router.beforeEach((to) => {
+    to.meta.loaded = loadedPaths.has(to.path);
+
+    // 页面加载进度条
+    if (!to.meta.loaded && preferences.transition.progress) {
+      startProgress();
+    }
+    return true;
+  });
+
+  router.afterEach((to) => {
+    // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
+
+    loadedPaths.add(to.path);
+
+    // 关闭页面加载进度条
+    if (preferences.transition.progress) {
+      stopProgress();
+    }
+  });
+}
+
+/**
+ * 权限访问守卫配置
+ * @param router
+ */
+function setupAccessGuard(router: Router) {
+  router.beforeEach(async (to, from) => {
+    const accessStore = useAccessStore();
+    const userStore = useUserStore();
+    const authStore = useAuthStore();
+
+    // 基本路由,这些路由不需要进入权限拦截
+    if (coreRouteNames.includes(to.name as string)) {
+      if (to.path === LOGIN_PATH && accessStore.accessToken) {
+        return decodeURIComponent(
+          (to.query?.redirect as string) ||
+            userStore.userInfo?.homePath ||
+            preferences.app.defaultHomePath,
+        );
+      }
+      return true;
+    }
+
+    // accessToken 检查
+    if (!accessStore.accessToken) {
+      // 明确声明忽略权限访问权限,则可以访问
+      if (to.meta.ignoreAccess) {
+        return true;
+      }
+
+      // 没有访问权限,跳转登录页面
+      if (to.fullPath !== LOGIN_PATH) {
+        return {
+          path: LOGIN_PATH,
+          // 如不需要,直接删除 query
+          query:
+            to.fullPath === preferences.app.defaultHomePath
+              ? {}
+              : { redirect: encodeURIComponent(to.fullPath) },
+          // 携带当前跳转的页面,登录后重新跳转该页面
+          replace: true,
+        };
+      }
+      return to;
+    }
+
+    // 是否已经生成过动态路由
+    if (accessStore.isAccessChecked) {
+      return true;
+    }
+
+    // 生成路由表
+    // 当前登录用户拥有的角色标识列表
+    const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
+    const userRoles = userInfo.roles ?? [];
+
+    // 生成菜单和路由
+    const { accessibleMenus, accessibleRoutes } = await generateAccess({
+      roles: userRoles,
+      router,
+      // 则会在菜单中显示,但是访问会被重定向到403
+      routes: accessRoutes,
+    });
+
+    // 保存菜单信息和路由信息
+    accessStore.setAccessMenus(accessibleMenus);
+    accessStore.setAccessRoutes(accessibleRoutes);
+    accessStore.setIsAccessChecked(true);
+    const redirectPath = (from.query.redirect ??
+      (to.path === preferences.app.defaultHomePath
+        ? userInfo.homePath || preferences.app.defaultHomePath
+        : to.fullPath)) as string;
+
+    return {
+      ...router.resolve(decodeURIComponent(redirectPath)),
+      replace: true,
+    };
+  });
+}
+
+/**
+ * 项目守卫配置
+ * @param router
+ */
+function createRouterGuard(router: Router) {
+  /** 通用 */
+  setupCommonGuard(router);
+  /** 权限访问 */
+  setupAccessGuard(router);
+}
+
+export { createRouterGuard };

+ 37 - 0
apps/web-antdv-next/src/router/index.ts

@@ -0,0 +1,37 @@
+import {
+  createRouter,
+  createWebHashHistory,
+  createWebHistory,
+} from 'vue-router';
+
+import { resetStaticRoutes } from '@vben/utils';
+
+import { createRouterGuard } from './guard';
+import { routes } from './routes';
+
+/**
+ *  @zh_CN 创建vue-router实例
+ */
+const router = createRouter({
+  history:
+    import.meta.env.VITE_ROUTER_HISTORY === 'hash'
+      ? createWebHashHistory(import.meta.env.VITE_BASE)
+      : createWebHistory(import.meta.env.VITE_BASE),
+  // 应该添加到路由的初始路由列表。
+  routes,
+  scrollBehavior: (to, _from, savedPosition) => {
+    if (savedPosition) {
+      return savedPosition;
+    }
+    return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
+  },
+  // 是否应该禁止尾部斜杠。
+  // strict: true,
+});
+
+const resetRoutes = () => resetStaticRoutes(router, routes);
+
+// 创建路由守卫
+createRouterGuard(router);
+
+export { resetRoutes, router };

+ 97 - 0
apps/web-antdv-next/src/router/routes/core.ts

@@ -0,0 +1,97 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { LOGIN_PATH } from '@vben/constants';
+import { preferences } from '@vben/preferences';
+
+import { $t } from '#/locales';
+
+const BasicLayout = () => import('#/layouts/basic.vue');
+const AuthPageLayout = () => import('#/layouts/auth.vue');
+/** 全局404页面 */
+const fallbackNotFoundRoute: RouteRecordRaw = {
+  component: () => import('#/views/_core/fallback/not-found.vue'),
+  meta: {
+    hideInBreadcrumb: true,
+    hideInMenu: true,
+    hideInTab: true,
+    title: '404',
+  },
+  name: 'FallbackNotFound',
+  path: '/:path(.*)*',
+};
+
+/** 基本路由,这些路由是必须存在的 */
+const coreRoutes: RouteRecordRaw[] = [
+  /**
+   * 根路由
+   * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
+   * 此路由必须存在,且不应修改
+   */
+  {
+    component: BasicLayout,
+    meta: {
+      hideInBreadcrumb: true,
+      title: 'Root',
+    },
+    name: 'Root',
+    path: '/',
+    redirect: preferences.app.defaultHomePath,
+    children: [],
+  },
+  {
+    component: AuthPageLayout,
+    meta: {
+      hideInTab: true,
+      title: 'Authentication',
+    },
+    name: 'Authentication',
+    path: '/auth',
+    redirect: LOGIN_PATH,
+    children: [
+      {
+        name: 'Login',
+        path: 'login',
+        component: () => import('#/views/_core/authentication/login.vue'),
+        meta: {
+          title: $t('page.auth.login'),
+        },
+      },
+      {
+        name: 'CodeLogin',
+        path: 'code-login',
+        component: () => import('#/views/_core/authentication/code-login.vue'),
+        meta: {
+          title: $t('page.auth.codeLogin'),
+        },
+      },
+      {
+        name: 'QrCodeLogin',
+        path: 'qrcode-login',
+        component: () =>
+          import('#/views/_core/authentication/qrcode-login.vue'),
+        meta: {
+          title: $t('page.auth.qrcodeLogin'),
+        },
+      },
+      {
+        name: 'ForgetPassword',
+        path: 'forget-password',
+        component: () =>
+          import('#/views/_core/authentication/forget-password.vue'),
+        meta: {
+          title: $t('page.auth.forgetPassword'),
+        },
+      },
+      {
+        name: 'Register',
+        path: 'register',
+        component: () => import('#/views/_core/authentication/register.vue'),
+        meta: {
+          title: $t('page.auth.register'),
+        },
+      },
+    ],
+  },
+];
+
+export { coreRoutes, fallbackNotFoundRoute };

+ 37 - 0
apps/web-antdv-next/src/router/routes/index.ts

@@ -0,0 +1,37 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
+
+import { coreRoutes, fallbackNotFoundRoute } from './core';
+
+const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
+  eager: true,
+});
+
+// 有需要可以自行打开注释,并创建文件夹
+// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
+// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
+
+/** 动态路由 */
+const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
+
+/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
+// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
+// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
+const staticRoutes: RouteRecordRaw[] = [];
+const externalRoutes: RouteRecordRaw[] = [];
+
+/** 路由列表,由基本路由、外部路由和404兜底路由组成
+ *  无需走权限验证(会一直显示在菜单中) */
+const routes: RouteRecordRaw[] = [
+  ...coreRoutes,
+  ...externalRoutes,
+  fallbackNotFoundRoute,
+];
+
+/** 基本路由列表,这些路由不需要进入权限拦截 */
+const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
+
+/** 有权限校验的路由列表,包含动态路由和静态路由 */
+const accessRoutes = [...dynamicRoutes, ...staticRoutes];
+export { accessRoutes, coreRouteNames, routes };

+ 38 - 0
apps/web-antdv-next/src/router/routes/modules/dashboard.ts

@@ -0,0 +1,38 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { $t } from '#/locales';
+
+const routes: RouteRecordRaw[] = [
+  {
+    meta: {
+      icon: 'lucide:layout-dashboard',
+      order: -1,
+      title: $t('page.dashboard.title'),
+    },
+    name: 'Dashboard',
+    path: '/dashboard',
+    children: [
+      {
+        name: 'Analytics',
+        path: '/analytics',
+        component: () => import('#/views/dashboard/analytics/index.vue'),
+        meta: {
+          affixTab: true,
+          icon: 'lucide:area-chart',
+          title: $t('page.dashboard.analytics'),
+        },
+      },
+      {
+        name: 'Workspace',
+        path: '/workspace',
+        component: () => import('#/views/dashboard/workspace/index.vue'),
+        meta: {
+          icon: 'carbon:workspace',
+          title: $t('page.dashboard.workspace'),
+        },
+      },
+    ],
+  },
+];
+
+export default routes;

+ 28 - 0
apps/web-antdv-next/src/router/routes/modules/demos.ts

@@ -0,0 +1,28 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { $t } from '#/locales';
+
+const routes: RouteRecordRaw[] = [
+  {
+    meta: {
+      icon: 'ic:baseline-view-in-ar',
+      keepAlive: true,
+      order: 1000,
+      title: $t('demos.title'),
+    },
+    name: 'Demos',
+    path: '/demos',
+    children: [
+      {
+        meta: {
+          title: $t('demos.antd'),
+        },
+        name: 'AntDesignDemos',
+        path: '/demos/ant-design-next',
+        component: () => import('#/views/demos/antd/index.vue'),
+      },
+    ],
+  },
+];
+
+export default routes;

+ 116 - 0
apps/web-antdv-next/src/router/routes/modules/vben.ts

@@ -0,0 +1,116 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import {
+  VBEN_ANT_PREVIEW_URL,
+  VBEN_DOC_URL,
+  VBEN_ELE_PREVIEW_URL,
+  VBEN_GITHUB_URL,
+  VBEN_LOGO_URL,
+  VBEN_NAIVE_PREVIEW_URL,
+  VBEN_TD_PREVIEW_URL,
+} from '@vben/constants';
+import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons';
+
+import { IFrameView } from '#/layouts';
+import { $t } from '#/locales';
+
+const routes: RouteRecordRaw[] = [
+  {
+    meta: {
+      badgeType: 'dot',
+      icon: VBEN_LOGO_URL,
+      order: 9998,
+      title: $t('demos.vben.title'),
+    },
+    name: 'VbenProject',
+    path: '/vben-admin',
+    children: [
+      {
+        name: 'VbenDocument',
+        path: '/vben-admin/document',
+        component: IFrameView,
+        meta: {
+          icon: 'lucide:book-open-text',
+          link: VBEN_DOC_URL,
+          title: $t('demos.vben.document'),
+        },
+      },
+      {
+        name: 'VbenGithub',
+        path: '/vben-admin/github',
+        component: IFrameView,
+        meta: {
+          icon: 'mdi:github',
+          link: VBEN_GITHUB_URL,
+          title: 'Github',
+        },
+      },
+      {
+        name: 'VbenAntd',
+        path: '/vben-admin/antd',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: SvgAntdvLogoIcon,
+          link: VBEN_ANT_PREVIEW_URL,
+          title: $t('demos.vben.antdv'),
+        },
+      },
+      {
+        name: 'VbenNaive',
+        path: '/vben-admin/naive',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: 'logos:naiveui',
+          link: VBEN_NAIVE_PREVIEW_URL,
+          title: $t('demos.vben.naive-ui'),
+        },
+      },
+      {
+        name: 'VbenTDesign',
+        path: '/vben-admin/tdesign',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: SvgTDesignIcon,
+          link: VBEN_TD_PREVIEW_URL,
+          title: $t('demos.vben.tdesign'),
+        },
+      },
+      {
+        name: 'VbenElementPlus',
+        path: '/vben-admin/ele',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: 'logos:element',
+          link: VBEN_ELE_PREVIEW_URL,
+          title: $t('demos.vben.element-plus'),
+        },
+      },
+    ],
+  },
+  {
+    name: 'VbenAbout',
+    path: '/vben-admin/about',
+    component: () => import('#/views/_core/about/index.vue'),
+    meta: {
+      icon: 'lucide:copyright',
+      title: $t('demos.vben.about'),
+      order: 9999,
+    },
+  },
+  {
+    name: 'Profile',
+    path: '/profile',
+    component: () => import('#/views/_core/profile/index.vue'),
+    meta: {
+      icon: 'lucide:user',
+      hideInMenu: true,
+      title: $t('page.auth.profile'),
+    },
+  },
+];
+
+export default routes;

+ 118 - 0
apps/web-antdv-next/src/store/auth.ts

@@ -0,0 +1,118 @@
+import type { Recordable, UserInfo } from '@vben/types';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { LOGIN_PATH } from '@vben/constants';
+import { preferences } from '@vben/preferences';
+import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
+
+import { notification } from 'antdv-next';
+import { defineStore } from 'pinia';
+
+import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
+import { $t } from '#/locales';
+
+export const useAuthStore = defineStore('auth', () => {
+  const accessStore = useAccessStore();
+  const userStore = useUserStore();
+  const router = useRouter();
+
+  const loginLoading = ref(false);
+
+  /**
+   * 异步处理登录操作
+   * Asynchronously handle the login process
+   * @param params 登录表单数据
+   */
+  async function authLogin(
+    params: Recordable<any>,
+    onSuccess?: () => Promise<void> | void,
+  ) {
+    // 异步处理用户登录操作并获取 accessToken
+    let userInfo: null | UserInfo = null;
+    try {
+      loginLoading.value = true;
+      const { accessToken } = await loginApi(params);
+
+      // 如果成功获取到 accessToken
+      if (accessToken) {
+        accessStore.setAccessToken(accessToken);
+
+        // 获取用户信息并存储到 accessStore 中
+        const [fetchUserInfoResult, accessCodes] = await Promise.all([
+          fetchUserInfo(),
+          getAccessCodesApi(),
+        ]);
+
+        userInfo = fetchUserInfoResult;
+
+        userStore.setUserInfo(userInfo);
+        accessStore.setAccessCodes(accessCodes);
+
+        if (accessStore.loginExpired) {
+          accessStore.setLoginExpired(false);
+        } else {
+          onSuccess
+            ? await onSuccess?.()
+            : await router.push(
+                userInfo.homePath || preferences.app.defaultHomePath,
+              );
+        }
+
+        if (userInfo?.realName) {
+          notification.success({
+            description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
+            duration: 3,
+            title: $t('authentication.loginSuccess'),
+          });
+        }
+      }
+    } finally {
+      loginLoading.value = false;
+    }
+
+    return {
+      userInfo,
+    };
+  }
+
+  async function logout(redirect: boolean = true) {
+    try {
+      await logoutApi();
+    } catch {
+      // 不做任何处理
+    }
+    resetAllStores();
+    accessStore.setLoginExpired(false);
+
+    // 回登录页带上当前路由地址
+    await router.replace({
+      path: LOGIN_PATH,
+      query: redirect
+        ? {
+            redirect: encodeURIComponent(router.currentRoute.value.fullPath),
+          }
+        : {},
+    });
+  }
+
+  async function fetchUserInfo() {
+    let userInfo: null | UserInfo = null;
+    userInfo = await getUserInfoApi();
+    userStore.setUserInfo(userInfo);
+    return userInfo;
+  }
+
+  function $reset() {
+    loginLoading.value = false;
+  }
+
+  return {
+    $reset,
+    authLogin,
+    fetchUserInfo,
+    loginLoading,
+    logout,
+  };
+});

+ 1 - 0
apps/web-antdv-next/src/store/index.ts

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

+ 3 - 0
apps/web-antdv-next/src/views/_core/README.md

@@ -0,0 +1,3 @@
+# \_core
+
+此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。

+ 9 - 0
apps/web-antdv-next/src/views/_core/about/index.vue

@@ -0,0 +1,9 @@
+<script lang="ts" setup>
+import { About } from '@vben/common-ui';
+
+defineOptions({ name: 'About' });
+</script>
+
+<template>
+  <About />
+</template>

+ 69 - 0
apps/web-antdv-next/src/views/_core/authentication/code-login.vue

@@ -0,0 +1,69 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '@vben/common-ui';
+import type { Recordable } from '@vben/types';
+
+import { computed, ref } from 'vue';
+
+import { AuthenticationCodeLogin, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+defineOptions({ name: 'CodeLogin' });
+
+const loading = ref(false);
+const CODE_LENGTH = 6;
+
+const formSchema = computed((): VbenFormSchema[] => {
+  return [
+    {
+      component: 'VbenInput',
+      componentProps: {
+        placeholder: $t('authentication.mobile'),
+      },
+      fieldName: 'phoneNumber',
+      label: $t('authentication.mobile'),
+      rules: z
+        .string()
+        .min(1, { message: $t('authentication.mobileTip') })
+        .refine((v) => /^\d{11}$/.test(v), {
+          message: $t('authentication.mobileErrortip'),
+        }),
+    },
+    {
+      component: 'VbenPinInput',
+      componentProps: {
+        codeLength: CODE_LENGTH,
+        createText: (countdown: number) => {
+          const text =
+            countdown > 0
+              ? $t('authentication.sendText', [countdown])
+              : $t('authentication.sendCode');
+          return text;
+        },
+        placeholder: $t('authentication.code'),
+      },
+      fieldName: 'code',
+      label: $t('authentication.code'),
+      rules: z.string().length(CODE_LENGTH, {
+        message: $t('authentication.codeTip', [CODE_LENGTH]),
+      }),
+    },
+  ];
+});
+/**
+ * 异步处理登录操作
+ * Asynchronously handle the login process
+ * @param values 登录表单数据
+ */
+async function handleLogin(values: Recordable<any>) {
+  // eslint-disable-next-line no-console
+  console.log(values);
+}
+</script>
+
+<template>
+  <AuthenticationCodeLogin
+    :form-schema="formSchema"
+    :loading="loading"
+    @submit="handleLogin"
+  />
+</template>

+ 43 - 0
apps/web-antdv-next/src/views/_core/authentication/forget-password.vue

@@ -0,0 +1,43 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '@vben/common-ui';
+import type { Recordable } from '@vben/types';
+
+import { computed, ref } from 'vue';
+
+import { AuthenticationForgetPassword, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+defineOptions({ name: 'ForgetPassword' });
+
+const loading = ref(false);
+
+const formSchema = computed((): VbenFormSchema[] => {
+  return [
+    {
+      component: 'VbenInput',
+      componentProps: {
+        placeholder: 'example@example.com',
+      },
+      fieldName: 'email',
+      label: $t('authentication.email'),
+      rules: z
+        .string()
+        .min(1, { message: $t('authentication.emailTip') })
+        .email($t('authentication.emailValidErrorTip')),
+    },
+  ];
+});
+
+function handleSubmit(value: Recordable<any>) {
+  // eslint-disable-next-line no-console
+  console.log('reset email:', value);
+}
+</script>
+
+<template>
+  <AuthenticationForgetPassword
+    :form-schema="formSchema"
+    :loading="loading"
+    @submit="handleSubmit"
+  />
+</template>

+ 98 - 0
apps/web-antdv-next/src/views/_core/authentication/login.vue

@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '@vben/common-ui';
+import type { BasicOption } from '@vben/types';
+
+import { computed, markRaw } from 'vue';
+
+import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+import { useAuthStore } from '#/store';
+
+defineOptions({ name: 'Login' });
+
+const authStore = useAuthStore();
+
+const MOCK_USER_OPTIONS: BasicOption[] = [
+  {
+    label: 'Super',
+    value: 'vben',
+  },
+  {
+    label: 'Admin',
+    value: 'admin',
+  },
+  {
+    label: 'User',
+    value: 'jack',
+  },
+];
+
+const formSchema = computed((): VbenFormSchema[] => {
+  return [
+    {
+      component: 'VbenSelect',
+      componentProps: {
+        options: MOCK_USER_OPTIONS,
+        placeholder: $t('authentication.selectAccount'),
+      },
+      fieldName: 'selectAccount',
+      label: $t('authentication.selectAccount'),
+      rules: z
+        .string()
+        .min(1, { message: $t('authentication.selectAccount') })
+        .optional()
+        .default('vben'),
+    },
+    {
+      component: 'VbenInput',
+      componentProps: {
+        placeholder: $t('authentication.usernameTip'),
+      },
+      dependencies: {
+        trigger(values, form) {
+          if (values.selectAccount) {
+            const findUser = MOCK_USER_OPTIONS.find(
+              (item) => item.value === values.selectAccount,
+            );
+            if (findUser) {
+              form.setValues({
+                password: '123456',
+                username: findUser.value,
+              });
+            }
+          }
+        },
+        triggerFields: ['selectAccount'],
+      },
+      fieldName: 'username',
+      label: $t('authentication.username'),
+      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
+    },
+    {
+      component: 'VbenInputPassword',
+      componentProps: {
+        placeholder: $t('authentication.password'),
+      },
+      fieldName: 'password',
+      label: $t('authentication.password'),
+      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
+    },
+    {
+      component: markRaw(SliderCaptcha),
+      fieldName: 'captcha',
+      rules: z.boolean().refine((value) => value, {
+        message: $t('authentication.verifyRequiredTip'),
+      }),
+    },
+  ];
+});
+</script>
+
+<template>
+  <AuthenticationLogin
+    :form-schema="formSchema"
+    :loading="authStore.loginLoading"
+    @submit="authStore.authLogin"
+  />
+</template>

+ 10 - 0
apps/web-antdv-next/src/views/_core/authentication/qrcode-login.vue

@@ -0,0 +1,10 @@
+<script lang="ts" setup>
+import { AuthenticationQrCodeLogin } from '@vben/common-ui';
+import { LOGIN_PATH } from '@vben/constants';
+
+defineOptions({ name: 'QrCodeLogin' });
+</script>
+
+<template>
+  <AuthenticationQrCodeLogin :login-path="LOGIN_PATH" />
+</template>

+ 96 - 0
apps/web-antdv-next/src/views/_core/authentication/register.vue

@@ -0,0 +1,96 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '@vben/common-ui';
+import type { Recordable } from '@vben/types';
+
+import { computed, h, ref } from 'vue';
+
+import { AuthenticationRegister, z } from '@vben/common-ui';
+import { $t } from '@vben/locales';
+
+defineOptions({ name: 'Register' });
+
+const loading = ref(false);
+
+const formSchema = computed((): VbenFormSchema[] => {
+  return [
+    {
+      component: 'VbenInput',
+      componentProps: {
+        placeholder: $t('authentication.usernameTip'),
+      },
+      fieldName: 'username',
+      label: $t('authentication.username'),
+      rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
+    },
+    {
+      component: 'VbenInputPassword',
+      componentProps: {
+        passwordStrength: true,
+        placeholder: $t('authentication.password'),
+      },
+      fieldName: 'password',
+      label: $t('authentication.password'),
+      renderComponentContent() {
+        return {
+          strengthText: () => $t('authentication.passwordStrength'),
+        };
+      },
+      rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
+    },
+    {
+      component: 'VbenInputPassword',
+      componentProps: {
+        placeholder: $t('authentication.confirmPassword'),
+      },
+      dependencies: {
+        rules(values) {
+          const { password } = values;
+          return z
+            .string({ required_error: $t('authentication.passwordTip') })
+            .min(1, { message: $t('authentication.passwordTip') })
+            .refine((value) => value === password, {
+              message: $t('authentication.confirmPasswordTip'),
+            });
+        },
+        triggerFields: ['password'],
+      },
+      fieldName: 'confirmPassword',
+      label: $t('authentication.confirmPassword'),
+    },
+    {
+      component: 'VbenCheckbox',
+      fieldName: 'agreePolicy',
+      renderComponentContent: () => ({
+        default: () =>
+          h('span', [
+            $t('authentication.agree'),
+            h(
+              'a',
+              {
+                class: 'vben-link ml-1 ',
+                href: '',
+              },
+              `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,
+            ),
+          ]),
+      }),
+      rules: z.boolean().refine((value) => !!value, {
+        message: $t('authentication.agreeTip'),
+      }),
+    },
+  ];
+});
+
+function handleSubmit(value: Recordable<any>) {
+  // eslint-disable-next-line no-console
+  console.log('register submit:', value);
+}
+</script>
+
+<template>
+  <AuthenticationRegister
+    :form-schema="formSchema"
+    :loading="loading"
+    @submit="handleSubmit"
+  />
+</template>

+ 7 - 0
apps/web-antdv-next/src/views/_core/fallback/coming-soon.vue

@@ -0,0 +1,7 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/common-ui';
+</script>
+
+<template>
+  <Fallback status="coming-soon" />
+</template>

+ 9 - 0
apps/web-antdv-next/src/views/_core/fallback/forbidden.vue

@@ -0,0 +1,9 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/common-ui';
+
+defineOptions({ name: 'Fallback403Demo' });
+</script>
+
+<template>
+  <Fallback status="403" />
+</template>

+ 9 - 0
apps/web-antdv-next/src/views/_core/fallback/internal-error.vue

@@ -0,0 +1,9 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/common-ui';
+
+defineOptions({ name: 'Fallback500Demo' });
+</script>
+
+<template>
+  <Fallback status="500" />
+</template>

+ 9 - 0
apps/web-antdv-next/src/views/_core/fallback/not-found.vue

@@ -0,0 +1,9 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/common-ui';
+
+defineOptions({ name: 'Fallback404Demo' });
+</script>
+
+<template>
+  <Fallback status="404" />
+</template>

+ 9 - 0
apps/web-antdv-next/src/views/_core/fallback/offline.vue

@@ -0,0 +1,9 @@
+<script lang="ts" setup>
+import { Fallback } from '@vben/common-ui';
+
+defineOptions({ name: 'FallbackOfflineDemo' });
+</script>
+
+<template>
+  <Fallback status="offline" />
+</template>

+ 65 - 0
apps/web-antdv-next/src/views/_core/profile/base-setting.vue

@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import type { BasicOption } from '@vben/types';
+
+import type { VbenFormSchema } from '#/adapter/form';
+
+import { computed, onMounted, ref } from 'vue';
+
+import { ProfileBaseSetting } from '@vben/common-ui';
+
+import { getUserInfoApi } from '#/api';
+
+const profileBaseSettingRef = ref();
+
+const MOCK_ROLES_OPTIONS: BasicOption[] = [
+  {
+    label: '管理员',
+    value: 'super',
+  },
+  {
+    label: '用户',
+    value: 'user',
+  },
+  {
+    label: '测试',
+    value: 'test',
+  },
+];
+
+const formSchema = computed((): VbenFormSchema[] => {
+  return [
+    {
+      fieldName: 'realName',
+      component: 'Input',
+      label: '姓名',
+    },
+    {
+      fieldName: 'username',
+      component: 'Input',
+      label: '用户名',
+    },
+    {
+      fieldName: 'roles',
+      component: 'Select',
+      componentProps: {
+        mode: 'tags',
+        options: MOCK_ROLES_OPTIONS,
+      },
+      label: '角色',
+    },
+    {
+      fieldName: 'introduction',
+      component: 'Textarea',
+      label: '个人简介',
+    },
+  ];
+});
+
+onMounted(async () => {
+  const data = await getUserInfoApi();
+  profileBaseSettingRef.value.getFormApi().setValues(data);
+});
+</script>
+<template>
+  <ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
+</template>

+ 49 - 0
apps/web-antdv-next/src/views/_core/profile/index.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import { ref } from 'vue';
+
+import { Profile } from '@vben/common-ui';
+import { useUserStore } from '@vben/stores';
+
+import ProfileBase from './base-setting.vue';
+import ProfileNotificationSetting from './notification-setting.vue';
+import ProfilePasswordSetting from './password-setting.vue';
+import ProfileSecuritySetting from './security-setting.vue';
+
+const userStore = useUserStore();
+
+const tabsValue = ref<string>('basic');
+
+const tabs = ref([
+  {
+    label: '基本设置',
+    value: 'basic',
+  },
+  {
+    label: '安全设置',
+    value: 'security',
+  },
+  {
+    label: '修改密码',
+    value: 'password',
+  },
+  {
+    label: '新消息提醒',
+    value: 'notice',
+  },
+]);
+</script>
+<template>
+  <Profile
+    v-model:model-value="tabsValue"
+    title="个人中心"
+    :user-info="userStore.userInfo"
+    :tabs="tabs"
+  >
+    <template #content>
+      <ProfileBase v-if="tabsValue === 'basic'" />
+      <ProfileSecuritySetting v-if="tabsValue === 'security'" />
+      <ProfilePasswordSetting v-if="tabsValue === 'password'" />
+      <ProfileNotificationSetting v-if="tabsValue === 'notice'" />
+    </template>
+  </Profile>
+</template>

+ 31 - 0
apps/web-antdv-next/src/views/_core/profile/notification-setting.vue

@@ -0,0 +1,31 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+
+import { ProfileNotificationSetting } from '@vben/common-ui';
+
+const formSchema = computed(() => {
+  return [
+    {
+      value: true,
+      fieldName: 'accountPassword',
+      label: '账户密码',
+      description: '其他用户的消息将以站内信的形式通知',
+    },
+    {
+      value: true,
+      fieldName: 'systemMessage',
+      label: '系统消息',
+      description: '系统消息将以站内信的形式通知',
+    },
+    {
+      value: true,
+      fieldName: 'todoTask',
+      label: '待办任务',
+      description: '待办任务将以站内信的形式通知',
+    },
+  ];
+});
+</script>
+<template>
+  <ProfileNotificationSetting :form-schema="formSchema" />
+</template>

+ 63 - 0
apps/web-antdv-next/src/views/_core/profile/password-setting.vue

@@ -0,0 +1,63 @@
+<script setup lang="ts">
+import type { VbenFormSchema } from '#/adapter/form';
+
+import { computed } from 'vue';
+
+import { ProfilePasswordSetting, z } from '@vben/common-ui';
+
+import { message } from 'antdv-next';
+
+const formSchema = computed((): VbenFormSchema[] => {
+  return [
+    {
+      fieldName: 'oldPassword',
+      label: '旧密码',
+      component: 'VbenInputPassword',
+      componentProps: {
+        placeholder: '请输入旧密码',
+      },
+    },
+    {
+      fieldName: 'newPassword',
+      label: '新密码',
+      component: 'VbenInputPassword',
+      componentProps: {
+        passwordStrength: true,
+        placeholder: '请输入新密码',
+      },
+    },
+    {
+      fieldName: 'confirmPassword',
+      label: '确认密码',
+      component: 'VbenInputPassword',
+      componentProps: {
+        passwordStrength: true,
+        placeholder: '请再次输入新密码',
+      },
+      dependencies: {
+        rules(values) {
+          const { newPassword } = values;
+          return z
+            .string({ required_error: '请再次输入新密码' })
+            .min(1, { message: '请再次输入新密码' })
+            .refine((value) => value === newPassword, {
+              message: '两次输入的密码不一致',
+            });
+        },
+        triggerFields: ['newPassword'],
+      },
+    },
+  ];
+});
+
+function handleSubmit() {
+  message.success('密码修改成功');
+}
+</script>
+<template>
+  <ProfilePasswordSetting
+    class="w-1/3"
+    :form-schema="formSchema"
+    @submit="handleSubmit"
+  />
+</template>

+ 43 - 0
apps/web-antdv-next/src/views/_core/profile/security-setting.vue

@@ -0,0 +1,43 @@
+<script setup lang="ts">
+import { computed } from 'vue';
+
+import { ProfileSecuritySetting } from '@vben/common-ui';
+
+const formSchema = computed(() => {
+  return [
+    {
+      value: true,
+      fieldName: 'accountPassword',
+      label: '账户密码',
+      description: '当前密码强度:强',
+    },
+    {
+      value: true,
+      fieldName: 'securityPhone',
+      label: '密保手机',
+      description: '已绑定手机:138****8293',
+    },
+    {
+      value: true,
+      fieldName: 'securityQuestion',
+      label: '密保问题',
+      description: '未设置密保问题,密保问题可有效保护账户安全',
+    },
+    {
+      value: true,
+      fieldName: 'securityEmail',
+      label: '备用邮箱',
+      description: '已绑定邮箱:ant***sign.com',
+    },
+    {
+      value: false,
+      fieldName: 'securityMfa',
+      label: 'MFA 设备',
+      description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
+    },
+  ];
+});
+</script>
+<template>
+  <ProfileSecuritySetting :form-schema="formSchema" />
+</template>

+ 98 - 0
apps/web-antdv-next/src/views/dashboard/analytics/analytics-trends.vue

@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { EchartsUIType } from '@vben/plugins/echarts';
+
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
+
+const chartRef = ref<EchartsUIType>();
+const { renderEcharts } = useEcharts(chartRef);
+
+onMounted(() => {
+  renderEcharts({
+    grid: {
+      bottom: 0,
+      containLabel: true,
+      left: '1%',
+      right: '1%',
+      top: '2 %',
+    },
+    series: [
+      {
+        areaStyle: {},
+        data: [
+          111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000,
+          36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222,
+          111,
+        ],
+        itemStyle: {
+          color: '#5ab1ef',
+        },
+        smooth: true,
+        type: 'line',
+      },
+      {
+        areaStyle: {},
+        data: [
+          33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000,
+          11_000, 2221, 1201, 390, 198, 60, 30, 22, 11,
+        ],
+        itemStyle: {
+          color: '#019680',
+        },
+        smooth: true,
+        type: 'line',
+      },
+    ],
+    tooltip: {
+      axisPointer: {
+        lineStyle: {
+          color: '#019680',
+          width: 1,
+        },
+      },
+      trigger: 'axis',
+    },
+    // xAxis: {
+    //   axisTick: {
+    //     show: false,
+    //   },
+    //   boundaryGap: false,
+    //   data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
+    //   type: 'category',
+    // },
+    xAxis: {
+      axisTick: {
+        show: false,
+      },
+      boundaryGap: false,
+      data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`),
+      splitLine: {
+        lineStyle: {
+          type: 'solid',
+          width: 1,
+        },
+        show: true,
+      },
+      type: 'category',
+    },
+    yAxis: [
+      {
+        axisTick: {
+          show: false,
+        },
+        max: 80_000,
+        splitArea: {
+          show: true,
+        },
+        splitNumber: 4,
+        type: 'value',
+      },
+    ],
+  });
+});
+</script>
+
+<template>
+  <EchartsUI ref="chartRef" />
+</template>

+ 82 - 0
apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-data.vue

@@ -0,0 +1,82 @@
+<script lang="ts" setup>
+import type { EchartsUIType } from '@vben/plugins/echarts';
+
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
+
+const chartRef = ref<EchartsUIType>();
+const { renderEcharts } = useEcharts(chartRef);
+
+onMounted(() => {
+  renderEcharts({
+    legend: {
+      bottom: 0,
+      data: ['访问', '趋势'],
+    },
+    radar: {
+      indicator: [
+        {
+          name: '网页',
+        },
+        {
+          name: '移动端',
+        },
+        {
+          name: 'Ipad',
+        },
+        {
+          name: '客户端',
+        },
+        {
+          name: '第三方',
+        },
+        {
+          name: '其它',
+        },
+      ],
+      radius: '60%',
+      splitNumber: 8,
+    },
+    series: [
+      {
+        areaStyle: {
+          opacity: 1,
+          shadowBlur: 0,
+          shadowColor: 'rgba(0,0,0,.2)',
+          shadowOffsetX: 0,
+          shadowOffsetY: 10,
+        },
+        data: [
+          {
+            itemStyle: {
+              color: '#b6a2de',
+            },
+            name: '访问',
+            value: [90, 50, 86, 40, 50, 20],
+          },
+          {
+            itemStyle: {
+              color: '#5ab1ef',
+            },
+            name: '趋势',
+            value: [70, 75, 70, 76, 20, 85],
+          },
+        ],
+        itemStyle: {
+          // borderColor: '#fff',
+          borderRadius: 10,
+          borderWidth: 2,
+        },
+        symbolSize: 0,
+        type: 'radar',
+      },
+    ],
+    tooltip: {},
+  });
+});
+</script>
+
+<template>
+  <EchartsUI ref="chartRef" />
+</template>

+ 46 - 0
apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-sales.vue

@@ -0,0 +1,46 @@
+<script lang="ts" setup>
+import type { EchartsUIType } from '@vben/plugins/echarts';
+
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
+
+const chartRef = ref<EchartsUIType>();
+const { renderEcharts } = useEcharts(chartRef);
+
+onMounted(() => {
+  renderEcharts({
+    series: [
+      {
+        animationDelay() {
+          return Math.random() * 400;
+        },
+        animationEasing: 'exponentialInOut',
+        animationType: 'scale',
+        center: ['50%', '50%'],
+        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
+        data: [
+          { name: '外包', value: 500 },
+          { name: '定制', value: 310 },
+          { name: '技术支持', value: 274 },
+          { name: '远程', value: 400 },
+        ].toSorted((a, b) => {
+          return a.value - b.value;
+        }),
+        name: '商业占比',
+        radius: '80%',
+        roseType: 'radius',
+        type: 'pie',
+      },
+    ],
+
+    tooltip: {
+      trigger: 'item',
+    },
+  });
+});
+</script>
+
+<template>
+  <EchartsUI ref="chartRef" />
+</template>

+ 65 - 0
apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits-source.vue

@@ -0,0 +1,65 @@
+<script lang="ts" setup>
+import type { EchartsUIType } from '@vben/plugins/echarts';
+
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
+
+const chartRef = ref<EchartsUIType>();
+const { renderEcharts } = useEcharts(chartRef);
+
+onMounted(() => {
+  renderEcharts({
+    legend: {
+      bottom: '2%',
+      left: 'center',
+    },
+    series: [
+      {
+        animationDelay() {
+          return Math.random() * 100;
+        },
+        animationEasing: 'exponentialInOut',
+        animationType: 'scale',
+        avoidLabelOverlap: false,
+        color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
+        data: [
+          { name: '搜索引擎', value: 1048 },
+          { name: '直接访问', value: 735 },
+          { name: '邮件营销', value: 580 },
+          { name: '联盟广告', value: 484 },
+        ],
+        emphasis: {
+          label: {
+            fontSize: '12',
+            fontWeight: 'bold',
+            show: true,
+          },
+        },
+        itemStyle: {
+          // borderColor: '#fff',
+          borderRadius: 10,
+          borderWidth: 2,
+        },
+        label: {
+          position: 'center',
+          show: false,
+        },
+        labelLine: {
+          show: false,
+        },
+        name: '访问来源',
+        radius: ['40%', '65%'],
+        type: 'pie',
+      },
+    ],
+    tooltip: {
+      trigger: 'item',
+    },
+  });
+});
+</script>
+
+<template>
+  <EchartsUI ref="chartRef" />
+</template>

+ 55 - 0
apps/web-antdv-next/src/views/dashboard/analytics/analytics-visits.vue

@@ -0,0 +1,55 @@
+<script lang="ts" setup>
+import type { EchartsUIType } from '@vben/plugins/echarts';
+
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
+
+const chartRef = ref<EchartsUIType>();
+const { renderEcharts } = useEcharts(chartRef);
+
+onMounted(() => {
+  renderEcharts({
+    grid: {
+      bottom: 0,
+      containLabel: true,
+      left: '1%',
+      right: '1%',
+      top: '2 %',
+    },
+    series: [
+      {
+        barMaxWidth: 80,
+        // color: '#4f69fd',
+        data: [
+          3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000,
+          3200, 4800,
+        ],
+        type: 'bar',
+      },
+    ],
+    tooltip: {
+      axisPointer: {
+        lineStyle: {
+          // color: '#4f69fd',
+          width: 1,
+        },
+      },
+      trigger: 'axis',
+    },
+    xAxis: {
+      data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`),
+      type: 'category',
+    },
+    yAxis: {
+      max: 8000,
+      splitNumber: 4,
+      type: 'value',
+    },
+  });
+});
+</script>
+
+<template>
+  <EchartsUI ref="chartRef" />
+</template>

+ 90 - 0
apps/web-antdv-next/src/views/dashboard/analytics/index.vue

@@ -0,0 +1,90 @@
+<script lang="ts" setup>
+import type { AnalysisOverviewItem } from '@vben/common-ui';
+import type { TabOption } from '@vben/types';
+
+import {
+  AnalysisChartCard,
+  AnalysisChartsTabs,
+  AnalysisOverview,
+} from '@vben/common-ui';
+import {
+  SvgBellIcon,
+  SvgCakeIcon,
+  SvgCardIcon,
+  SvgDownloadIcon,
+} from '@vben/icons';
+
+import AnalyticsTrends from './analytics-trends.vue';
+import AnalyticsVisitsData from './analytics-visits-data.vue';
+import AnalyticsVisitsSales from './analytics-visits-sales.vue';
+import AnalyticsVisitsSource from './analytics-visits-source.vue';
+import AnalyticsVisits from './analytics-visits.vue';
+
+const overviewItems: AnalysisOverviewItem[] = [
+  {
+    icon: SvgCardIcon,
+    title: '用户量',
+    totalTitle: '总用户量',
+    totalValue: 120_000,
+    value: 2000,
+  },
+  {
+    icon: SvgCakeIcon,
+    title: '访问量',
+    totalTitle: '总访问量',
+    totalValue: 500_000,
+    value: 20_000,
+  },
+  {
+    icon: SvgDownloadIcon,
+    title: '下载量',
+    totalTitle: '总下载量',
+    totalValue: 120_000,
+    value: 8000,
+  },
+  {
+    icon: SvgBellIcon,
+    title: '使用量',
+    totalTitle: '总使用量',
+    totalValue: 50_000,
+    value: 5000,
+  },
+];
+
+const chartTabs: TabOption[] = [
+  {
+    label: '流量趋势',
+    value: 'trends',
+  },
+  {
+    label: '月访问量',
+    value: 'visits',
+  },
+];
+</script>
+
+<template>
+  <div class="p-5">
+    <AnalysisOverview :items="overviewItems" />
+    <AnalysisChartsTabs :tabs="chartTabs" class="mt-5">
+      <template #trends>
+        <AnalyticsTrends />
+      </template>
+      <template #visits>
+        <AnalyticsVisits />
+      </template>
+    </AnalysisChartsTabs>
+
+    <div class="mt-5 w-full md:flex">
+      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量">
+        <AnalyticsVisitsData />
+      </AnalysisChartCard>
+      <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源">
+        <AnalyticsVisitsSource />
+      </AnalysisChartCard>
+      <AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源">
+        <AnalyticsVisitsSales />
+      </AnalysisChartCard>
+    </div>
+  </div>
+</template>

+ 266 - 0
apps/web-antdv-next/src/views/dashboard/workspace/index.vue

@@ -0,0 +1,266 @@
+<script lang="ts" setup>
+import type {
+  WorkbenchProjectItem,
+  WorkbenchQuickNavItem,
+  WorkbenchTodoItem,
+  WorkbenchTrendItem,
+} from '@vben/common-ui';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import {
+  AnalysisChartCard,
+  WorkbenchHeader,
+  WorkbenchProject,
+  WorkbenchQuickNav,
+  WorkbenchTodo,
+  WorkbenchTrends,
+} from '@vben/common-ui';
+import { preferences } from '@vben/preferences';
+import { useUserStore } from '@vben/stores';
+import { openWindow } from '@vben/utils';
+
+import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
+
+const userStore = useUserStore();
+
+// 这是一个示例数据,实际项目中需要根据实际情况进行调整
+// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转
+// 例如:url: /dashboard/workspace
+const projectItems: WorkbenchProjectItem[] = [
+  {
+    color: '',
+    content: '不要等待机会,而要创造机会。',
+    date: '2021-04-01',
+    group: '开源组',
+    icon: 'carbon:logo-github',
+    title: 'Github',
+    url: 'https://github.com',
+  },
+  {
+    color: '#3fb27f',
+    content: '现在的你决定将来的你。',
+    date: '2021-04-01',
+    group: '算法组',
+    icon: 'ion:logo-vue',
+    title: 'Vue',
+    url: 'https://vuejs.org',
+  },
+  {
+    color: '#e18525',
+    content: '没有什么才能比努力更重要。',
+    date: '2021-04-01',
+    group: '上班摸鱼',
+    icon: 'ion:logo-html5',
+    title: 'Html5',
+    url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML',
+  },
+  {
+    color: '#bf0c2c',
+    content: '热情和欲望可以突破一切难关。',
+    date: '2021-04-01',
+    group: 'UI',
+    icon: 'ion:logo-angular',
+    title: 'Angular',
+    url: 'https://angular.io',
+  },
+  {
+    color: '#00d8ff',
+    content: '健康的身体是实现目标的基石。',
+    date: '2021-04-01',
+    group: '技术牛',
+    icon: 'bx:bxl-react',
+    title: 'React',
+    url: 'https://reactjs.org',
+  },
+  {
+    color: '#EBD94E',
+    content: '路是走出来的,而不是空想出来的。',
+    date: '2021-04-01',
+    group: '架构组',
+    icon: 'ion:logo-javascript',
+    title: 'Js',
+    url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript',
+  },
+];
+
+// 同样,这里的 url 也可以使用以 http 开头的外部链接
+const quickNavItems: WorkbenchQuickNavItem[] = [
+  {
+    color: '#1fdaca',
+    icon: 'ion:home-outline',
+    title: '首页',
+    url: '/',
+  },
+  {
+    color: '#bf0c2c',
+    icon: 'ion:grid-outline',
+    title: '仪表盘',
+    url: '/dashboard',
+  },
+  {
+    color: '#e18525',
+    icon: 'ion:layers-outline',
+    title: '组件',
+    url: '/demos/features/icons',
+  },
+  {
+    color: '#3fb27f',
+    icon: 'ion:settings-outline',
+    title: '系统管理',
+    url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整
+  },
+  {
+    color: '#4daf1bc9',
+    icon: 'ion:key-outline',
+    title: '权限管理',
+    url: '/demos/access/page-control',
+  },
+  {
+    color: '#00d8ff',
+    icon: 'ion:bar-chart-outline',
+    title: '图表',
+    url: '/analytics',
+  },
+];
+
+const todoItems = ref<WorkbenchTodoItem[]>([
+  {
+    completed: false,
+    content: `审查最近提交到Git仓库的前端代码,确保代码质量和规范。`,
+    date: '2024-07-30 11:00:00',
+    title: '审查前端代码提交',
+  },
+  {
+    completed: true,
+    content: `检查并优化系统性能,降低CPU使用率。`,
+    date: '2024-07-30 11:00:00',
+    title: '系统性能优化',
+  },
+  {
+    completed: false,
+    content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `,
+    date: '2024-07-30 11:00:00',
+    title: '安全检查',
+  },
+  {
+    completed: false,
+    content: `更新项目中的所有npm依赖包,确保使用最新版本。`,
+    date: '2024-07-30 11:00:00',
+    title: '更新项目依赖',
+  },
+  {
+    completed: false,
+    content: `修复用户报告的页面UI显示问题,确保在不同浏览器中显示一致。 `,
+    date: '2024-07-30 11:00:00',
+    title: '修复UI显示问题',
+  },
+]);
+const trendItems: WorkbenchTrendItem[] = [
+  {
+    avatar: 'svg:avatar-1',
+    content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
+    date: '刚刚',
+    title: '威廉',
+  },
+  {
+    avatar: 'svg:avatar-2',
+    content: `关注了 <a>威廉</a> `,
+    date: '1个小时前',
+    title: '艾文',
+  },
+  {
+    avatar: 'svg:avatar-3',
+    content: `发布了 <a>个人动态</a> `,
+    date: '1天前',
+    title: '克里斯',
+  },
+  {
+    avatar: 'svg:avatar-4',
+    content: `发表文章 <a>如何编写一个Vite插件</a> `,
+    date: '2天前',
+    title: 'Vben',
+  },
+  {
+    avatar: 'svg:avatar-1',
+    content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
+    date: '3天前',
+    title: '皮特',
+  },
+  {
+    avatar: 'svg:avatar-2',
+    content: `关闭了问题 <a>如何运行项目</a> `,
+    date: '1周前',
+    title: '杰克',
+  },
+  {
+    avatar: 'svg:avatar-3',
+    content: `发布了 <a>个人动态</a> `,
+    date: '1周前',
+    title: '威廉',
+  },
+  {
+    avatar: 'svg:avatar-4',
+    content: `推送了代码到 <a>Github</a>`,
+    date: '2021-04-01 20:00',
+    title: '威廉',
+  },
+  {
+    avatar: 'svg:avatar-4',
+    content: `发表文章 <a>如何编写使用 Admin Vben</a> `,
+    date: '2021-03-01 20:00',
+    title: 'Vben',
+  },
+];
+
+const router = useRouter();
+
+// 这是一个示例方法,实际项目中需要根据实际情况进行调整
+// This is a sample method, adjust according to the actual project requirements
+function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
+  if (nav.url?.startsWith('http')) {
+    openWindow(nav.url);
+    return;
+  }
+  if (nav.url?.startsWith('/')) {
+    router.push(nav.url).catch((error) => {
+      console.error('Navigation failed:', error);
+    });
+  } else {
+    console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`);
+  }
+}
+</script>
+
+<template>
+  <div class="p-5">
+    <WorkbenchHeader
+      :avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar"
+    >
+      <template #title>
+        早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧!
+      </template>
+      <template #description> 今日晴,20℃ - 32℃! </template>
+    </WorkbenchHeader>
+
+    <div class="mt-5 flex flex-col lg:flex-row">
+      <div class="mr-4 w-full lg:w-3/5">
+        <WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
+        <WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
+      </div>
+      <div class="w-full lg:w-2/5">
+        <WorkbenchQuickNav
+          :items="quickNavItems"
+          class="mt-5 lg:mt-0"
+          title="快捷导航"
+          @click="navTo"
+        />
+        <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
+        <AnalysisChartCard class="mt-5" title="访问来源">
+          <AnalyticsVisitsSource />
+        </AnalysisChartCard>
+      </div>
+    </div>
+  </div>
+</template>

+ 66 - 0
apps/web-antdv-next/src/views/demos/antd/index.vue

@@ -0,0 +1,66 @@
+<script lang="ts" setup>
+import { Page } from '@vben/common-ui';
+
+import { Button, Card, message, notification, Space } from 'antdv-next';
+
+type NotificationType = 'error' | 'info' | 'success' | 'warning';
+
+function info() {
+  message.info('How many roads must a man walk down');
+}
+
+function error() {
+  message.error({
+    content: 'Once upon a time you dressed so fine',
+    duration: 2.5,
+  });
+}
+
+function warning() {
+  message.warning('How many roads must a man walk down');
+}
+function success() {
+  message.success('Cause you walked hand in hand With another man in my place');
+}
+
+function notify(type: NotificationType) {
+  notification[type]({
+    duration: 2.5,
+    title: '说点啥呢',
+    type,
+  });
+}
+</script>
+
+<template>
+  <Page
+    description="支持多语言,主题功能集成切换等"
+    title="Ant Design Vue组件使用演示"
+  >
+    <Card class="mb-5" title="按钮">
+      <Space>
+        <Button>Default</Button>
+        <Button type="primary"> Primary </Button>
+        <Button> Info </Button>
+        <Button danger> Error </Button>
+      </Space>
+    </Card>
+    <Card class="mb-5" title="Message">
+      <Space>
+        <Button @click="info"> 信息 </Button>
+        <Button danger @click="error"> 错误 </Button>
+        <Button @click="warning"> 警告 </Button>
+        <Button @click="success"> 成功 </Button>
+      </Space>
+    </Card>
+
+    <Card class="mb-5" title="Notification">
+      <Space>
+        <Button @click="notify('info')"> 信息 </Button>
+        <Button danger @click="notify('error')"> 错误 </Button>
+        <Button @click="notify('warning')"> 警告 </Button>
+        <Button @click="notify('success')"> 成功 </Button>
+      </Space>
+    </Card>
+  </Page>
+</template>

+ 1 - 0
apps/web-antdv-next/tailwind.config.mjs

@@ -0,0 +1 @@
+export { default } from '@vben/tailwind-config';

+ 12 - 0
apps/web-antdv-next/tsconfig.json

@@ -0,0 +1,12 @@
+{
+  "$schema": "https://json.schemastore.org/tsconfig",
+  "extends": "@vben/tsconfig/web-app.json",
+  "compilerOptions": {
+    "baseUrl": ".",
+    "paths": {
+      "#/*": ["./src/*"]
+    }
+  },
+  "references": [{ "path": "./tsconfig.node.json" }],
+  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
+}

+ 10 - 0
apps/web-antdv-next/tsconfig.node.json

@@ -0,0 +1,10 @@
+{
+  "$schema": "https://json.schemastore.org/tsconfig",
+  "extends": "@vben/tsconfig/node.json",
+  "compilerOptions": {
+    "composite": true,
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+    "noEmit": false
+  },
+  "include": ["vite.config.mts"]
+}

+ 20 - 0
apps/web-antdv-next/vite.config.mts

@@ -0,0 +1,20 @@
+import { defineConfig } from '@vben/vite-config';
+
+export default defineConfig(async () => {
+  return {
+    application: {},
+    vite: {
+      server: {
+        proxy: {
+          '/api': {
+            changeOrigin: true,
+            rewrite: (path) => path.replace(/^\/api/, ''),
+            // mock代理目标地址
+            target: 'http://localhost:5320/api',
+            ws: true,
+          },
+        },
+      },
+    },
+  };
+});

+ 1 - 0
apps/web-ele/src/locales/langs/en-US/demos.json

@@ -7,6 +7,7 @@
     "about": "About",
     "document": "Document",
     "antdv": "Ant Design Vue Version",
+    "antdv-next": "Antdv Next Version",
     "naive-ui": "Naive UI Version",
     "element-plus": "Element Plus Version",
     "tdesign": "TDesign Vue Version"

+ 1 - 0
apps/web-ele/src/locales/langs/zh-CN/demos.json

@@ -7,6 +7,7 @@
     "about": "关于",
     "document": "文档",
     "antdv": "Ant Design Vue 版本",
+    "antdv-next": "Antdv Next 版本",
     "naive-ui": "Naive UI 版本",
     "element-plus": "Element Plus 版本",
     "tdesign": "TDesign Vue 版本"

+ 17 - 1
apps/web-ele/src/router/routes/modules/vben.ts

@@ -2,13 +2,18 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import {
   VBEN_ANT_PREVIEW_URL,
+  VBEN_ANTDV_NEXT_PREVIEW_URL,
   VBEN_DOC_URL,
   VBEN_GITHUB_URL,
   VBEN_LOGO_URL,
   VBEN_NAIVE_PREVIEW_URL,
   VBEN_TD_PREVIEW_URL,
 } from '@vben/constants';
-import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons';
+import {
+  SvgAntdvLogoIcon,
+  SvgAntdvNextLogoIcon,
+  SvgTDesignIcon,
+} from '@vben/icons';
 
 import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
@@ -66,6 +71,17 @@ const routes: RouteRecordRaw[] = [
           title: $t('demos.vben.antdv'),
         },
       },
+      {
+        name: 'VbenAntdVNext',
+        path: '/vben-admin/antdv-next',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: SvgAntdvNextLogoIcon,
+          link: VBEN_ANTDV_NEXT_PREVIEW_URL,
+          title: $t('demos.vben.antdv-next'),
+        },
+      },
       {
         name: 'VbenTDesign',
         path: '/vben-admin/tdesign',

+ 1 - 0
apps/web-naive/src/locales/langs/en-US/demos.json

@@ -8,6 +8,7 @@
     "about": "About",
     "document": "Document",
     "antdv": "Ant Design Vue Version",
+    "antdv-next": "Antdv Next Version",
     "naive-ui": "Naive UI Version",
     "element-plus": "Element Plus Version",
     "tdesign": "TDesign Vue Version"

+ 1 - 0
apps/web-naive/src/locales/langs/zh-CN/demos.json

@@ -8,6 +8,7 @@
     "about": "关于",
     "document": "文档",
     "antdv": "Ant Design Vue 版本",
+    "antdv-next": "Antdv Next 版本",
     "naive-ui": "Naive UI 版本",
     "element-plus": "Element Plus 版本",
     "tdesign": "TDesign Vue 版本"

+ 17 - 1
apps/web-naive/src/router/routes/modules/vben.ts

@@ -2,13 +2,18 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import {
   VBEN_ANT_PREVIEW_URL,
+  VBEN_ANTDV_NEXT_PREVIEW_URL,
   VBEN_DOC_URL,
   VBEN_ELE_PREVIEW_URL,
   VBEN_GITHUB_URL,
   VBEN_LOGO_URL,
   VBEN_TD_PREVIEW_URL,
 } from '@vben/constants';
-import { SvgAntdvLogoIcon, SvgTDesignIcon } from '@vben/icons';
+import {
+  SvgAntdvLogoIcon,
+  SvgAntdvNextLogoIcon,
+  SvgTDesignIcon,
+} from '@vben/icons';
 
 import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
@@ -55,6 +60,17 @@ const routes: RouteRecordRaw[] = [
           title: $t('demos.vben.antdv'),
         },
       },
+      {
+        name: 'VbenAntdVNext',
+        path: '/vben-admin/antdv-next',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: SvgAntdvNextLogoIcon,
+          link: VBEN_ANTDV_NEXT_PREVIEW_URL,
+          title: $t('demos.vben.antdv-next'),
+        },
+      },
       {
         name: 'VbenTDesign',
         path: '/vben-admin/tdesign',

+ 1 - 0
apps/web-tdesign/src/locales/langs/en-US/demos.json

@@ -6,6 +6,7 @@
     "about": "About",
     "document": "Document",
     "antdv": "Ant Design Vue Version",
+    "antdv-next": "Antdv Next Version",
     "naive-ui": "Naive UI Version",
     "element-plus": "Element Plus Version"
   }

+ 1 - 0
apps/web-tdesign/src/locales/langs/zh-CN/demos.json

@@ -6,6 +6,7 @@
     "about": "关于",
     "document": "文档",
     "antdv": "Ant Design Vue 版本",
+    "antdv-next": "Antdv Next 版本",
     "naive-ui": "Naive UI 版本",
     "element-plus": "Element Plus 版本"
   }

+ 13 - 1
apps/web-tdesign/src/router/routes/modules/vben.ts

@@ -2,13 +2,14 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import {
   VBEN_ANT_PREVIEW_URL,
+  VBEN_ANTDV_NEXT_PREVIEW_URL,
   VBEN_DOC_URL,
   VBEN_ELE_PREVIEW_URL,
   VBEN_GITHUB_URL,
   VBEN_LOGO_URL,
   VBEN_NAIVE_PREVIEW_URL,
 } from '@vben/constants';
-import { SvgAntdvLogoIcon } from '@vben/icons';
+import { SvgAntdvLogoIcon, SvgAntdvNextLogoIcon } from '@vben/icons';
 
 import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
@@ -66,6 +67,17 @@ const routes: RouteRecordRaw[] = [
           title: $t('demos.vben.antdv'),
         },
       },
+      {
+        name: 'VbenAntdVNext',
+        path: '/vben-admin/antdv-next',
+        component: IFrameView,
+        meta: {
+          badgeType: 'dot',
+          icon: SvgAntdvNextLogoIcon,
+          link: VBEN_ANTDV_NEXT_PREVIEW_URL,
+          title: $t('demos.vben.antdv-next'),
+        },
+      },
       {
         name: 'VbenElementPlus',
         path: '/vben-admin/ele',

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

@@ -85,6 +85,7 @@ You will see an output similar to the following, allowing you to select the proj
 ◆  Select the app you need to run [dev]:
 │  ● @vben/web-antd
+│  ○ @vben/web-antdv-next
 │  ○ @vben/web-ele
 │  ○ @vben/web-naive
 │  ○ @vben/docs

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

@@ -88,7 +88,8 @@ pnpm dev
 ```bash
 ◆  Select the app you need to run [dev]:
-│  ○ @vben/web-antd
+│  ● @vben/web-antd
+│  ○ @vben/web-antdv-next
 │  ○ @vben/web-ele
 │  ○ @vben/web-naive
 │  ○ @vben/docs

+ 1 - 0
package.json

@@ -44,6 +44,7 @@
     "commit": "czg",
     "dev": "turbo-run dev",
     "dev:antd": "pnpm -F @vben/web-antd run dev",
+    "dev:antdv-next": "pnpm -F @vben/web-antdv-next run dev",
     "dev:docs": "pnpm -F @vben/docs run dev",
     "dev:ele": "pnpm -F @vben/web-ele run dev",
     "dev:naive": "pnpm -F @vben/web-naive run dev",

+ 2 - 0
packages/@core/base/shared/src/constants/vben.ts

@@ -19,6 +19,8 @@ export const VBEN_LOGO_URL =
  */
 export const VBEN_PREVIEW_URL = 'https://www.vben.pro';
 
+export const VBEN_ANTDV_NEXT_PREVIEW_URL = 'https://antdv-next.vben.pro';
+
 export const VBEN_ELE_PREVIEW_URL = 'https://ele.vben.pro';
 
 export const VBEN_NAIVE_PREVIEW_URL = 'https://naive.vben.pro';

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
packages/icons/src/svg/icons/antdv-next-logo.svg


+ 2 - 0
packages/icons/src/svg/index.ts

@@ -17,9 +17,11 @@ const SvgQQChatIcon = createIconifyIcon('svg:qqchat');
 const SvgWeChatIcon = createIconifyIcon('svg:wechat');
 const SvgDingDingIcon = createIconifyIcon('svg:dingding');
 const SvgTDesignIcon = createIconifyIcon('svg:tdesign-logo');
+const SvgAntdvNextLogoIcon = createIconifyIcon('svg:antdv-next-logo');
 
 export {
   SvgAntdvLogoIcon,
+  SvgAntdvNextLogoIcon,
   SvgAvatar1Icon,
   SvgAvatar2Icon,
   SvgAvatar3Icon,

+ 3 - 0
packages/styles/package.json

@@ -18,6 +18,9 @@
     "./antd": {
       "default": "./src/antd/index.css"
     },
+    "./antdv-next": {
+      "default": "./src/antdv-next/index.css"
+    },
     "./ele": {
       "default": "./src/ele/index.css"
     },

+ 77 - 0
packages/styles/src/antdv-next/index.css

@@ -0,0 +1,77 @@
+/* antdv-next 组件库的一些样式重置 */
+
+.ant-app {
+  width: 100%;
+  height: 100%;
+  overscroll-behavior: none;
+  color: inherit;
+}
+
+.ant-btn {
+  .anticon {
+    display: inline-flex;
+  }
+
+  /* * 修复按钮添加图标时的位置问题 */
+  > .ant-btn-icon {
+    svg {
+      display: inline-block;
+    }
+
+    svg + span {
+      margin-inline-start: 6px;
+    }
+  }
+}
+
+.ant-tag {
+  > svg {
+    display: inline-block;
+  }
+
+  > svg + span {
+    margin-inline-start: 4px;
+  }
+}
+
+.ant-message-notice-content,
+.ant-notification-notice {
+  @apply dark:border-border/60 dark:border;
+}
+
+.form-valid-error {
+  /** select 选择器的样式 */
+
+  .ant-select:not(.valid-success) {
+    border-color: hsl(var(--destructive)) !important;
+  }
+
+  .ant-select-focused {
+    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%) !important;
+  }
+
+  /** 数字输入框样式 */
+  .ant-input-number-focused {
+    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);
+  }
+
+  /** 密码输入框样式 */
+  .ant-input-affix-wrapper:hover {
+    border-color: hsl(var(--destructive));
+    box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);
+  }
+
+  .ant-input:not(.valid-success) {
+    border-color: hsl(var(--destructive)) !important;
+  }
+}
+
+/** 区间选择器下面来回切换时的样式 */
+.ant-app .form-valid-error .ant-picker-active-bar {
+  background-color: hsl(var(--destructive));
+}
+
+/** 时间选择器的样式 */
+.ant-app .form-valid-error .ant-picker-focused {
+  box-shadow: 0 0 0 2px rgb(255 38 5 / 6%);
+}

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

@@ -64,6 +64,7 @@
     "about": "About",
     "document": "Document",
     "antdv": "Ant Design Vue Version",
+    "antdv-next": "Antdv Next Version",
     "naive-ui": "Naive UI Version",
     "element-plus": "Element Plus Version",
     "tdesign": "TDesign Vue Version"

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

@@ -65,6 +65,7 @@
     "about": "关于",
     "document": "文档",
     "antdv": "Ant Design Vue 版本",
+    "antdv-next": "Antdv Next 版本",
     "naive-ui": "Naive UI 版本",
     "element-plus": "Element Plus 版本",
     "tdesign": "TDesign Vue 版本"

+ 686 - 7
pnpm-lock.yaml

@@ -147,6 +147,9 @@ catalogs:
     ant-design-vue:
       specifier: ^4.2.6
       version: 4.2.6
+    antdv-next:
+      specifier: ^1.0.2
+      version: 1.0.2
     archiver:
       specifier: ^7.0.1
       version: 7.0.1
@@ -712,6 +715,69 @@ importers:
         specifier: 'catalog:'
         version: 4.6.4(vue@3.5.27(typescript@5.9.3))
 
+  apps/web-antdv-next:
+    dependencies:
+      '@vben/access':
+        specifier: workspace:*
+        version: link:../../packages/effects/access
+      '@vben/common-ui':
+        specifier: workspace:*
+        version: link:../../packages/effects/common-ui
+      '@vben/constants':
+        specifier: workspace:*
+        version: link:../../packages/constants
+      '@vben/hooks':
+        specifier: workspace:*
+        version: link:../../packages/effects/hooks
+      '@vben/icons':
+        specifier: workspace:*
+        version: link:../../packages/icons
+      '@vben/layouts':
+        specifier: workspace:*
+        version: link:../../packages/effects/layouts
+      '@vben/locales':
+        specifier: workspace:*
+        version: link:../../packages/locales
+      '@vben/plugins':
+        specifier: workspace:*
+        version: link:../../packages/effects/plugins
+      '@vben/preferences':
+        specifier: workspace:*
+        version: link:../../packages/preferences
+      '@vben/request':
+        specifier: workspace:*
+        version: link:../../packages/effects/request
+      '@vben/stores':
+        specifier: workspace:*
+        version: link:../../packages/stores
+      '@vben/styles':
+        specifier: workspace:*
+        version: link:../../packages/styles
+      '@vben/types':
+        specifier: workspace:*
+        version: link:../../packages/types
+      '@vben/utils':
+        specifier: workspace:*
+        version: link:../../packages/utils
+      '@vueuse/core':
+        specifier: 'catalog:'
+        version: 14.1.0(vue@3.5.27(typescript@5.9.3))
+      antdv-next:
+        specifier: 'catalog:'
+        version: 1.0.2(date-fns@4.1.0)(vue@3.5.27(typescript@5.9.3))
+      dayjs:
+        specifier: 'catalog:'
+        version: 1.11.19
+      pinia:
+        specifier: ^3.0.4
+        version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
+      vue:
+        specifier: ^3.5.27
+        version: 3.5.27(typescript@5.9.3)
+      vue-router:
+        specifier: 'catalog:'
+        version: 4.6.4(vue@3.5.27(typescript@5.9.3))
+
   apps/web-ele:
     dependencies:
       '@vben/access':
@@ -958,7 +1024,7 @@ importers:
     dependencies:
       '@commitlint/cli':
         specifier: 'catalog:'
-        version: 19.8.1(@types/node@24.10.9)(typescript@5.9.3)
+        version: 19.8.1(@types/node@25.0.10)(typescript@5.9.3)
       '@commitlint/config-conventional':
         specifier: 'catalog:'
         version: 19.8.1
@@ -2082,6 +2148,17 @@ packages:
   '@ant-design/colors@6.0.0':
     resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==}
 
+  '@ant-design/colors@7.2.1':
+    resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==}
+
+  '@ant-design/fast-color@2.0.6':
+    resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==}
+    engines: {node: '>=8.x'}
+
+  '@ant-design/fast-color@3.0.1':
+    resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==}
+    engines: {node: '>=8.x'}
+
   '@ant-design/icons-svg@4.4.2':
     resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==}
 
@@ -2090,6 +2167,16 @@ packages:
     peerDependencies:
       vue: ^3.5.27
 
+  '@antdv-next/cssinjs@1.0.1':
+    resolution: {integrity: sha512-9C7f2dZ7seDLuRU7dy23dmyxsWGZyHl9OriDQvamYHjqpKf6WdJhdUkQpTZ7q63t6h5JXrDgX4VYZH3JD6hX3g==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@antdv-next/icons@1.0.0':
+    resolution: {integrity: sha512-O3gxRGEYOYsNbyPuhEqZ+HIDTakl6v3ukBDQwZ3ZvbzOPBorgdhMhHQ1WFjlLBCo72bdx5/wKyjz6oFRhk0G+g==}
+    peerDependencies:
+      vue: ^3.5.27
+
   '@antfu/install-pkg@1.1.0':
     resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
 
@@ -3436,6 +3523,9 @@ packages:
   '@emotion/hash@0.9.2':
     resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
 
+  '@emotion/unitless@0.7.5':
+    resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
+
   '@emotion/unitless@0.8.1':
     resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
 
@@ -4789,6 +4879,220 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@v-c/async-validator@1.0.0':
+    resolution: {integrity: sha512-nwi4hW/V3L5M4qY8cwScEuUonGfm1KRmN6aPwGpG9zhy7UDTEXKA3Tv4pdfjY9ryXKQc5TYo78TLSX9EjAPLUA==}
+
+  '@v-c/cascader@1.0.0':
+    resolution: {integrity: sha512-GoDcocPUnIgJOVknk96nxzx7Jkf9kFKJ+sjcZPT45pBynZvaMOFJ9ZLGMB2ZMOYtRr0SfXeqZ09lLy9c8PtwZw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/checkbox@1.0.0':
+    resolution: {integrity: sha512-2abQmtpdYpQjb9+Xe9w1iPjdgSKDyQ1TpVq2QfTMdcj656S9npfDT2AH6HOgQV76oezV86dxgi1QG+lmXnDm0Q==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/collapse@1.0.0':
+    resolution: {integrity: sha512-y4NAl3j4mka193ZMDLHdISA8to61qoROG6/kTQ0myM2ZuEsonnEK1QWlqoEw3gveMsa6a4RdyoXLxdGdcJyp0Q==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/color-picker@1.0.4':
+    resolution: {integrity: sha512-VwNowJREYp25C6M39EHyTQ1nrxN2Usg96xGPupV2XiZs6wyKpXpXSjMbDXzD5/NKW1PKu/BTZOnrdLsrmY0mxg==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/dialog@1.0.1':
+    resolution: {integrity: sha512-8+HqvlDXGcgie4Z+sQB3HnMkkFN0N1zfIxdQwtpXTl+gWu9ue8tz+zI7pNXMq7XdM3DU6A+x+qeNRstJ2poCbw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/drawer@1.0.0':
+    resolution: {integrity: sha512-1FhalqOqDmdvEfXGmlEZtwbeZBMKeK3+LIeDfXfqUiLy38QMMDTxMARmnHCxUGhuj20zFxrZUl0sxNEaTjTkEA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/dropdown@1.0.2':
+    resolution: {integrity: sha512-D6TACf3jUiRWx4xW5h2+wVT9SMYxUasFlAHESYJr4ZMjLTLLM1Q8iBjkjhGF+vA0eYR5zqRTwlaacN0DNDZBPw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/image@1.0.2':
+    resolution: {integrity: sha512-uTAhX+SRWjQHIUPJ0DAYUSzOEVnVDAmZazJNP5OqCqLuncrS7hUN37CQ1XgNKaF2Dt8b1aPFZlpXYb/zXfQdXw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/input-number@1.0.2':
+    resolution: {integrity: sha512-9wbAmdC0Nt18XlLLgfVbmulY50K7tc5RZsNeZuWnmqluTK3JTgZRSbPdRkzl308x8vtGyCOufnepsMrFei1NoQ==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/input@1.0.2':
+    resolution: {integrity: sha512-NzBor6XbUYP42zRrcaBUgWtQI1aIaN3oylmMdvSZ5UMcWsAXRNC8XKsedkFF/LLOEJBJ2NaTbomHRwkIjmtQAA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/mentions@1.0.0':
+    resolution: {integrity: sha512-trkG1lvfiaIY7UnHn0gx6B01o3rFLEMin3KGp1q4oU6zOCRWde4ejZ+EHSvmXzOz2N+FlRMTE4EMJFi4w0oOlQ==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/menu@1.0.10':
+    resolution: {integrity: sha512-iKXuPpZteVjPlg3nQ+4YbAlYa3/I21N/5RswKLai61IRO/8IcfRLc+YBEoG3n5xV+I5b2Yby2iJjsA8lU1En/w==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/mini-decimal@1.0.1':
+    resolution: {integrity: sha512-76wZLdlkI017iDlaZMNOWZyDCv29YVabUJn5urQgIKtW4dnI5AkNXWtmLyhl/mu/OS7ZGisRi5ai/558QhLQxQ==}
+
+  '@v-c/mutate-observer@1.0.1':
+    resolution: {integrity: sha512-84+9KGORX8LY9u+K0DEGyRwRCJaky0sjRkXxBC7X/jahHJl8NQGQ0Gxve5IVwaxRTfZ9eftlRmHs90JD6Utfqg==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/notification@1.0.0':
+    resolution: {integrity: sha512-aU5g+ZiYxp0KVdKuho067wJRF38Mv7MrQS95dwSJLsbDmVFBpjO3Lo3ptakfPkwn+7uwRytHKIf39t9QVGk+sg==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/overflow@1.0.3':
+    resolution: {integrity: sha512-GVWxd2gk9T0t9kO7EH1fMy2DgYULle/D+GBXiEeB5j/V1b9Gj39pvFLA1EHuiiyJW56Lr/P/uJ/ZM38WFh755w==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/pagination@1.0.0':
+    resolution: {integrity: sha512-uYIMkvHKMtY+nwHTu5rXxiq6KPf0zGpZbtQTn1nDPng0tOyA1vLQ+R6OfE+1LOwuQqvFTEDnAq4vb90By+eBfw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/picker@1.0.2':
+    resolution: {integrity: sha512-Lf9qPZ/hODaIBEoXpkpGmHK5oXubquia8bYN8zVWigr312jbl/bI6txRF4p+zci0UTtJQsOX1zp9fqX8rg0vSA==}
+    peerDependencies:
+      date-fns: '>= 2.x'
+      dayjs: '>= 1.x'
+      luxon: '>= 3.x'
+      moment: '>= 2.x'
+      vue: ^3.5.27
+    peerDependenciesMeta:
+      date-fns:
+        optional: true
+      dayjs:
+        optional: true
+      luxon:
+        optional: true
+      moment:
+        optional: true
+
+  '@v-c/portal@1.0.7':
+    resolution: {integrity: sha512-iIcU+9C8GNas7BWMXlWkwz9RoB8E7B4f0D+qPNTPLUbdwDvKRNypATYImkIWK/BSGDHOkxu1kIBshxgolaODfA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/progress@1.0.0':
+    resolution: {integrity: sha512-kWDTU1uXnPDMmoezwyAECxuSH+WKn92OjSdk/GgDbQgZ0qNy9woOiRe5fOsrcy61agHdJxzf0MvsUy1b6bZVlA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/qrcode@1.0.0':
+    resolution: {integrity: sha512-OSMrYDhP/NQiUcO6J0X2X8BskHPRqX/E/F9npH3oayZgjCo5Aom+63Ja3J0u6SOmKP1JgLSgjrm5karc0671jw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/rate@1.0.0':
+    resolution: {integrity: sha512-H2cj/dS3guxq9s79HlTzu8uUzH/dQM8Ko5zlPosWrBI33YvySqoxcShY8cZS/tcq8I4xDE/SjeAmBe7rHd9VEA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/resize-observer@1.0.8':
+    resolution: {integrity: sha512-VH8WBsNfZA5KQ+CXVaQ1PK5B6FIHnuTdqOLrjRWiZTrIYDZi/MyREi9b21YDj55fbFWMRx4yapnO9tiZX1RNxA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/segmented@1.0.0':
+    resolution: {integrity: sha512-HWo8Ck6Lg0epTEvw5d2yhE+mU/SOxTN6/ngMXLz7iwGI2TwskKu8l+atOSNcJ7XqS/QgsqIHpL26GATPOS8qvQ==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/select@1.0.9':
+    resolution: {integrity: sha512-RpZMtjKi3BIWj2+SKJKFKhydBE0Q0vZYyJduJZ6GioBi7JF26JPQuwZOsthaHDVr6oaOoc9ejlSAQqP92tFIKA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/slick@1.0.0':
+    resolution: {integrity: sha512-Pzb4bahxbXvXTCmgOJ9OX6Ek1joUHx0EFUxmjDSAfrvaAkHWv2pSqGLDF0CJvm+/uG+rcdT2YQUNcxMtKInQ3A==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/slider@1.0.10':
+    resolution: {integrity: sha512-KMIVytBm8K8RQ+aPPraS28GmBptGHESF/gDRbGjOLD7xyivuQDJeEqVaUFY3EcCWsERjh4VP/L96gUbMTF0uag==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/steps@1.0.0':
+    resolution: {integrity: sha512-DPL0OOb8pDLlTPZB93b8+Saxiz6V5zEpGXKaCnsbXUuOhimkc7089AuEKfpMw+8x1SrVe+gapWf5RRHWXUm2pg==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/switch@1.0.0':
+    resolution: {integrity: sha512-VIem244KJkYfqDgofpgHjK00sGL9rJ/9OtmK4Gbs4hnPsrTtzHDBRltYxR4IT7HQleathZfj6NhcZ1bjdWKYUw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/table@1.0.0':
+    resolution: {integrity: sha512-1/TMEppX3BpLYYSXzAcwrD5Os6O1VX5OGZsvGyqIp9A8Iy8QeI4Vb54XizGyooHNMs378RIWzX4yDe9JxYMm/A==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/tabs@1.0.1':
+    resolution: {integrity: sha512-6G6cWKdxb8l3IuR802mZGV+l8GAvCEJCoVQcyG3BwiBYOeOii6eyET5D+yMp7mC7dsFRxc2y7q0krxyo97CosQ==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/textarea@1.0.3':
+    resolution: {integrity: sha512-oCpqdOyiNPFgLRUw913IjTdIMVtryy9maJEaSz+ledn/cVO4OJ44dEP8eCnhxlHTfduLKesKIlDoRIBhLu4qqQ==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/tooltip@1.0.2':
+    resolution: {integrity: sha512-EwKbftSPCBrp+D40qMXV0/IzOIshQ6Wm5a+yYBZKJxF03uR0sUkQP+/cj7V55Gr9rDJ5G1EaxQjCuQAezwhzig==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/tour@1.0.3':
+    resolution: {integrity: sha512-y4DVJPP7jvL+MWUMAKQWxLAMXSWJEfZXaKASPn3DKbSQ8drBhsjMXwcep3glAfrCjCKfj/QD3OrUMxqydi4qFw==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/tree-select@1.0.0':
+    resolution: {integrity: sha512-EOUt3zBFMm2wCCDgY5pYU2mjff7NQiGss0uM/J2dOw8JP3LgPjhCuT2/0ZgXt6HkqK+Fl5AdhzxYujGbgTw6Ow==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/tree@1.0.2':
+    resolution: {integrity: sha512-Z/x1DuazauoPtuKWaL6iaNdTq6t/c+6j8dbOxQN7kAJOW4RJ8BKKx8DA0fq49R7OCUH/YQLOWTrWI5gyQ0169A==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/trigger@1.0.10':
+    resolution: {integrity: sha512-wPX/RBzJ7JzSShqBZlX8IWFPpa9c5jHvxzGdGTukBRukuPDBuXLLKHgt9IIpkqukuJuN3HLU39m+uuUK1rZ4Lg==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/upload@1.0.0':
+    resolution: {integrity: sha512-W92PNCD61aM/B5w8oUzHQSDHur1T8484726Ls0IoNMO5nPiF/15eEE3RuuI/t7xXQVP/fA06hNSwzXwGWdDg1w==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/util@1.0.13':
+    resolution: {integrity: sha512-/CUsbqrgqMz2TGqdzUnq1jwNFwX6lqy+HsYzmGH8auTUly9buIQiDmoxLxnjsI2XO6IryCqhzOP2Ii1213zqtA==}
+    peerDependencies:
+      vue: ^3.5.27
+
+  '@v-c/virtual-list@1.0.5':
+    resolution: {integrity: sha512-hv+ZIXkT7Bprv4pRloa/EYfWsJjOL2hI6wRSWX/XOxMcra+eq9uFvTY5GSOzYURPyzA/ExGq4607+fl1vEhwZQ==}
+    peerDependencies:
+      vue: ^3.5.27
+
   '@vee-validate/zod@4.15.1':
     resolution: {integrity: sha512-329Z4TDBE5Vx0FdbA8S4eR9iGCFFUNGbxjpQ20ff5b5wGueScjocUIx9JHPa79LTG06RnlUR4XogQsjN4tecKA==}
     peerDependencies:
@@ -4984,6 +5288,11 @@ packages:
     peerDependencies:
       vue: ^3.5.27
 
+  '@vueuse/core@14.2.0':
+    resolution: {integrity: sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==}
+    peerDependencies:
+      vue: ^3.5.27
+
   '@vueuse/integrations@12.8.2':
     resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==}
     peerDependencies:
@@ -5079,6 +5388,9 @@ packages:
   '@vueuse/metadata@14.1.0':
     resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
 
+  '@vueuse/metadata@14.2.0':
+    resolution: {integrity: sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==}
+
   '@vueuse/motion@3.0.3':
     resolution: {integrity: sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==}
     peerDependencies:
@@ -5100,6 +5412,11 @@ packages:
     peerDependencies:
       vue: ^3.5.27
 
+  '@vueuse/shared@14.2.0':
+    resolution: {integrity: sha512-Z0bmluZTlAXgUcJ4uAFaML16JcD8V0QG00Db3quR642I99JXIDRa2MI2LGxiLVhcBjVnL1jOzIvT5TT2lqJlkA==}
+    peerDependencies:
+      vue: ^3.5.27
+
   '@vxe-ui/core@4.3.1':
     resolution: {integrity: sha512-sr2WdFDWM3IKID02HbSaDxxRDvj1LZ5ZkOnH2POvGkkCfCWItkx3avkizfRUk8RtjNU+wXozaPbYTNha5kjSdg==}
     peerDependencies:
@@ -5215,6 +5532,9 @@ packages:
     peerDependencies:
       vue: ^3.5.27
 
+  antdv-next@1.0.2:
+    resolution: {integrity: sha512-JkPXDLk7MfAtye2pS3z/heLiUeow1K3JJCAnmaM+vzZhCds+wXUCNNHJ03qUIQvKCNbFdko4OJ9p0dY/pDJncg==}
+
   any-promise@1.3.0:
     resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
 
@@ -5716,6 +6036,9 @@ packages:
   compute-scroll-into-view@1.0.20:
     resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==}
 
+  compute-scroll-into-view@3.1.1:
+    resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
+
   concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
 
@@ -6355,6 +6678,9 @@ packages:
     resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
     engines: {node: '>= 0.4'}
 
+  es-toolkit@1.43.0:
+    resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==}
+
   es-toolkit@1.44.0:
     resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==}
 
@@ -9362,6 +9688,9 @@ packages:
   scroll-into-view-if-needed@2.2.31:
     resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==}
 
+  scroll-into-view-if-needed@3.1.0:
+    resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
+
   scslre@0.3.0:
     resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==}
     engines: {node: ^14.0.0 || >=16.0.0}
@@ -10978,6 +11307,16 @@ snapshots:
     dependencies:
       '@ctrl/tinycolor': 4.2.0
 
+  '@ant-design/colors@7.2.1':
+    dependencies:
+      '@ant-design/fast-color': 2.0.6
+
+  '@ant-design/fast-color@2.0.6':
+    dependencies:
+      '@babel/runtime': 7.28.6
+
+  '@ant-design/fast-color@3.0.1': {}
+
   '@ant-design/icons-svg@4.4.2': {}
 
   '@ant-design/icons-vue@7.0.1(vue@3.5.27(typescript@5.9.3))':
@@ -10986,6 +11325,24 @@ snapshots:
       '@ant-design/icons-svg': 4.4.2
       vue: 3.5.27(typescript@5.9.3)
 
+  '@antdv-next/cssinjs@1.0.1(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@emotion/hash': 0.8.0
+      '@emotion/unitless': 0.7.5
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      csstype: 3.2.3
+      defu: 6.1.4
+      stylis: 4.3.6
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@antdv-next/icons@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@ant-design/colors': 7.2.1
+      '@ant-design/icons-svg': 4.4.2
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      es-toolkit: 1.43.0
+      vue: 3.5.27(typescript@5.9.3)
+
   '@antfu/install-pkg@1.1.0':
     dependencies:
       package-manager-detector: 1.6.0
@@ -11926,11 +12283,11 @@ snapshots:
 
   '@cloudflare/kv-asset-handler@0.4.2': {}
 
-  '@commitlint/cli@19.8.1(@types/node@24.10.9)(typescript@5.9.3)':
+  '@commitlint/cli@19.8.1(@types/node@25.0.10)(typescript@5.9.3)':
     dependencies:
       '@commitlint/format': 19.8.1
       '@commitlint/lint': 19.8.1
-      '@commitlint/load': 19.8.1(@types/node@24.10.9)(typescript@5.9.3)
+      '@commitlint/load': 19.8.1(@types/node@25.0.10)(typescript@5.9.3)
       '@commitlint/read': 19.8.1
       '@commitlint/types': 19.8.1
       tinyexec: 1.0.2
@@ -11977,7 +12334,7 @@ snapshots:
       '@commitlint/rules': 19.8.1
       '@commitlint/types': 19.8.1
 
-  '@commitlint/load@19.8.1(@types/node@24.10.9)(typescript@5.9.3)':
+  '@commitlint/load@19.8.1(@types/node@25.0.10)(typescript@5.9.3)':
     dependencies:
       '@commitlint/config-validator': 19.8.1
       '@commitlint/execute-rule': 19.8.1
@@ -11985,7 +12342,7 @@ snapshots:
       '@commitlint/types': 19.8.1
       chalk: 5.6.2
       cosmiconfig: 9.0.0(typescript@5.9.3)
-      cosmiconfig-typescript-loader: 6.2.0(@types/node@24.10.9)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3)
+      cosmiconfig-typescript-loader: 6.2.0(@types/node@25.0.10)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3)
       lodash.isplainobject: 4.0.6
       lodash.merge: 4.6.2
       lodash.uniq: 4.5.0
@@ -12627,6 +12984,8 @@ snapshots:
 
   '@emotion/hash@0.9.2': {}
 
+  '@emotion/unitless@0.7.5': {}
+
   '@emotion/unitless@0.8.1': {}
 
   '@epic-web/invariant@1.0.0': {}
@@ -13968,6 +14327,249 @@ snapshots:
   '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
     optional: true
 
+  '@v-c/async-validator@1.0.0': {}
+
+  '@v-c/cascader@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/select': 1.0.9(vue@3.5.27(typescript@5.9.3))
+      '@v-c/tree': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/checkbox@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/collapse@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/color-picker@1.0.4(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@ant-design/fast-color': 3.0.1
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/dialog@1.0.1(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/drawer@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/dropdown@1.0.2(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/image@1.0.2(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/input-number@1.0.2(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/mini-decimal': 1.0.1
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/input@1.0.2(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/mentions@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/menu': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/textarea': 1.0.3(vue@3.5.27(typescript@5.9.3))
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/menu@1.0.10(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3))
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/mini-decimal@1.0.1': {}
+
+  '@v-c/mutate-observer@1.0.1(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/notification@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/overflow@1.0.3(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/pagination@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/picker@1.0.2(date-fns@4.1.0)(dayjs@1.11.19)(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3))
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+    optionalDependencies:
+      date-fns: 4.1.0
+      dayjs: 1.11.19
+
+  '@v-c/portal@1.0.7(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/progress@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/qrcode@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/rate@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/resize-observer@1.0.8(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      resize-observer-polyfill: 1.5.1
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/segmented@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/select@1.0.9(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3))
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/slick@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      es-toolkit: 1.44.0
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/slider@1.0.10(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/steps@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/switch@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/table@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/tabs@1.0.1(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/dropdown': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/menu': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/overflow': 1.0.3(vue@3.5.27(typescript@5.9.3))
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/textarea@1.0.3(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/tooltip@1.0.2(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/tour@1.0.3(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3))
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/tree-select@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/select': 1.0.9(vue@3.5.27(typescript@5.9.3))
+      '@v-c/tree': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/tree@1.0.2(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/trigger@1.0.10(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/portal': 1.0.7(vue@3.5.27(typescript@5.9.3))
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/upload@1.0.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/util@1.0.13(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      vue: 3.5.27(typescript@5.9.3)
+
+  '@v-c/virtual-list@1.0.5(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
   '@vee-validate/zod@4.15.1(vue@3.5.27(typescript@5.9.3))(zod@3.25.76)':
     dependencies:
       type-fest: 4.41.0
@@ -14318,6 +14920,13 @@ snapshots:
       '@vueuse/shared': 14.1.0(vue@3.5.27(typescript@5.9.3))
       vue: 3.5.27(typescript@5.9.3)
 
+  '@vueuse/core@14.2.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      '@types/web-bluetooth': 0.0.21
+      '@vueuse/metadata': 14.2.0
+      '@vueuse/shared': 14.2.0(vue@3.5.27(typescript@5.9.3))
+      vue: 3.5.27(typescript@5.9.3)
+
   '@vueuse/integrations@12.8.2(async-validator@4.2.5)(axios@1.13.4)(change-case@5.4.4)(focus-trap@7.8.0)(nprogress@0.2.0)(qrcode@1.5.4)(sortablejs@1.15.6)(typescript@5.9.3)':
     dependencies:
       '@vueuse/core': 12.8.2(typescript@5.9.3)
@@ -14356,6 +14965,8 @@ snapshots:
 
   '@vueuse/metadata@14.1.0': {}
 
+  '@vueuse/metadata@14.2.0': {}
+
   '@vueuse/motion@3.0.3(magicast@0.5.1)(vue@3.5.27(typescript@5.9.3))':
     dependencies:
       '@vueuse/core': 13.9.0(vue@3.5.27(typescript@5.9.3))
@@ -14391,6 +15002,10 @@ snapshots:
     dependencies:
       vue: 3.5.27(typescript@5.9.3)
 
+  '@vueuse/shared@14.2.0(vue@3.5.27(typescript@5.9.3))':
+    dependencies:
+      vue: 3.5.27(typescript@5.9.3)
+
   '@vxe-ui/core@4.3.1(vue@3.5.27(typescript@5.9.3))':
     dependencies:
       dom-zindex: 1.0.6
@@ -14527,6 +15142,62 @@ snapshots:
       vue-types: 3.0.2(vue@3.5.27(typescript@5.9.3))
       warning: 4.0.3
 
+  antdv-next@1.0.2(date-fns@4.1.0)(vue@3.5.27(typescript@5.9.3)):
+    dependencies:
+      '@ant-design/colors': 7.2.1
+      '@ant-design/fast-color': 3.0.1
+      '@antdv-next/cssinjs': 1.0.1(vue@3.5.27(typescript@5.9.3))
+      '@antdv-next/icons': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/async-validator': 1.0.0
+      '@v-c/cascader': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/checkbox': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/collapse': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/color-picker': 1.0.4(vue@3.5.27(typescript@5.9.3))
+      '@v-c/dialog': 1.0.1(vue@3.5.27(typescript@5.9.3))
+      '@v-c/drawer': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/dropdown': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/image': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/input': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/input-number': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/mentions': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/menu': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/mutate-observer': 1.0.1(vue@3.5.27(typescript@5.9.3))
+      '@v-c/notification': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/pagination': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/picker': 1.0.2(date-fns@4.1.0)(dayjs@1.11.19)(vue@3.5.27(typescript@5.9.3))
+      '@v-c/progress': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/qrcode': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/rate': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/resize-observer': 1.0.8(vue@3.5.27(typescript@5.9.3))
+      '@v-c/segmented': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/select': 1.0.9(vue@3.5.27(typescript@5.9.3))
+      '@v-c/slick': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/slider': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/steps': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/switch': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/table': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/tabs': 1.0.1(vue@3.5.27(typescript@5.9.3))
+      '@v-c/textarea': 1.0.3(vue@3.5.27(typescript@5.9.3))
+      '@v-c/tooltip': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/tour': 1.0.3(vue@3.5.27(typescript@5.9.3))
+      '@v-c/tree': 1.0.2(vue@3.5.27(typescript@5.9.3))
+      '@v-c/tree-select': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/trigger': 1.0.10(vue@3.5.27(typescript@5.9.3))
+      '@v-c/upload': 1.0.0(vue@3.5.27(typescript@5.9.3))
+      '@v-c/util': 1.0.13(vue@3.5.27(typescript@5.9.3))
+      '@v-c/virtual-list': 1.0.5(vue@3.5.27(typescript@5.9.3))
+      '@vueuse/core': 14.2.0(vue@3.5.27(typescript@5.9.3))
+      dayjs: 1.11.19
+      defu: 6.1.4
+      es-toolkit: 1.43.0
+      scroll-into-view-if-needed: 3.1.0
+      throttle-debounce: 5.0.2
+    transitivePeerDependencies:
+      - date-fns
+      - luxon
+      - moment
+      - vue
+
   any-promise@1.3.0: {}
 
   anymatch@3.1.3:
@@ -15064,6 +15735,8 @@ snapshots:
 
   compute-scroll-into-view@1.0.20: {}
 
+  compute-scroll-into-view@3.1.1: {}
+
   concat-map@0.0.1: {}
 
   confbox@0.1.8: {}
@@ -15125,9 +15798,9 @@ snapshots:
 
   core-util-is@1.0.3: {}
 
-  cosmiconfig-typescript-loader@6.2.0(@types/node@24.10.9)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3):
+  cosmiconfig-typescript-loader@6.2.0(@types/node@25.0.10)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3):
     dependencies:
-      '@types/node': 24.10.9
+      '@types/node': 25.0.10
       cosmiconfig: 9.0.0(typescript@5.9.3)
       jiti: 2.6.1
       typescript: 5.9.3
@@ -15780,6 +16453,8 @@ snapshots:
       is-date-object: 1.1.0
       is-symbol: 1.1.1
 
+  es-toolkit@1.43.0: {}
+
   es-toolkit@1.44.0: {}
 
   esbuild@0.25.12:
@@ -18950,6 +19625,10 @@ snapshots:
     dependencies:
       compute-scroll-into-view: 1.0.20
 
+  scroll-into-view-if-needed@3.1.0:
+    dependencies:
+      compute-scroll-into-view: 3.1.1
+
   scslre@0.3.0:
     dependencies:
       '@eslint-community/regexpp': 4.12.2

+ 1 - 0
pnpm-workspace.yaml

@@ -72,6 +72,7 @@ catalog:
   '@vueuse/integrations': ^14.1.0
   '@vueuse/motion': ^3.0.3
   ant-design-vue: ^4.2.6
+  antdv-next: ^1.0.2
   archiver: ^7.0.1
   autoprefixer: ^10.4.23
   axios: ^1.13.4

+ 4 - 0
vben-admin.code-workspace

@@ -8,6 +8,10 @@
       "name": "@vben/web-antd",
       "path": "apps/web-antd",
     },
+    {
+      "name": "@vben/web-antdv-next",
+      "path": "apps/web-antdv-next",
+    },
     {
       "name": "@vben/web-ele",
       "path": "apps/web-ele",

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor