Browse Source

feat: add naive app

vben 9 months ago
parent
commit
fdee2d2239
68 changed files with 2540 additions and 33 deletions
  1. 3 3
      apps/web-antd/src/app.vue
  2. 5 0
      apps/web-naive/.env
  3. 7 0
      apps/web-naive/.env.analyze
  4. 16 0
      apps/web-naive/.env.development
  5. 16 0
      apps/web-naive/.env.production
  6. 35 0
      apps/web-naive/index.html
  7. 49 0
      apps/web-naive/package.json
  8. 1 0
      apps/web-naive/postcss.config.mjs
  9. BIN
      apps/web-naive/public/favicon.ico
  10. 33 0
      apps/web-naive/src/api/core/auth.ts
  11. 3 0
      apps/web-naive/src/api/core/index.ts
  12. 10 0
      apps/web-naive/src/api/core/menu.ts
  13. 10 0
      apps/web-naive/src/api/core/user.ts
  14. 1 0
      apps/web-naive/src/api/index.ts
  15. 66 0
      apps/web-naive/src/api/request.ts
  16. 52 0
      apps/web-naive/src/app.vue
  17. 35 0
      apps/web-naive/src/bootstrap.ts
  18. 153 0
      apps/web-naive/src/layouts/basic.vue
  19. 8 0
      apps/web-naive/src/layouts/index.ts
  20. 3 0
      apps/web-naive/src/locales/README.md
  21. 31 0
      apps/web-naive/src/locales/index.ts
  22. 8 0
      apps/web-naive/src/locales/langs/en-US.json
  23. 8 0
      apps/web-naive/src/locales/langs/zh-CN.json
  24. 31 0
      apps/web-naive/src/main.ts
  25. 25 0
      apps/web-naive/src/naive.ts
  26. 9 0
      apps/web-naive/src/preferences.ts
  27. 40 0
      apps/web-naive/src/router/access.ts
  28. 132 0
      apps/web-naive/src/router/guard.ts
  29. 32 0
      apps/web-naive/src/router/index.ts
  30. 86 0
      apps/web-naive/src/router/routes/core.ts
  31. 31 0
      apps/web-naive/src/router/routes/index.ts
  32. 39 0
      apps/web-naive/src/router/routes/modules/dashboard.ts
  33. 32 0
      apps/web-naive/src/router/routes/modules/demos.ts
  34. 57 0
      apps/web-naive/src/router/routes/modules/vben.ts
  35. 111 0
      apps/web-naive/src/store/auth.ts
  36. 1 0
      apps/web-naive/src/store/index.ts
  37. 3 0
      apps/web-naive/src/views/_core/README.md
  38. 30 0
      apps/web-naive/src/views/_core/authentication/code-login.vue
  39. 23 0
      apps/web-naive/src/views/_core/authentication/forget-password.vue
  40. 18 0
      apps/web-naive/src/views/_core/authentication/login.vue
  41. 10 0
      apps/web-naive/src/views/_core/authentication/qrcode-login.vue
  42. 25 0
      apps/web-naive/src/views/_core/authentication/register.vue
  43. 7 0
      apps/web-naive/src/views/_core/fallback/coming-soon.vue
  44. 9 0
      apps/web-naive/src/views/_core/fallback/forbidden.vue
  45. 9 0
      apps/web-naive/src/views/_core/fallback/internal-error.vue
  46. 9 0
      apps/web-naive/src/views/_core/fallback/not-found.vue
  47. 9 0
      apps/web-naive/src/views/_core/fallback/offline.vue
  48. 9 0
      apps/web-naive/src/views/_core/vben/about/index.vue
  49. 78 0
      apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue
  50. 80 0
      apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue
  51. 44 0
      apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue
  52. 63 0
      apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue
  53. 53 0
      apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue
  54. 90 0
      apps/web-naive/src/views/dashboard/analytics/index.vue
  55. 225 0
      apps/web-naive/src/views/dashboard/workspace/index.vue
  56. 93 0
      apps/web-naive/src/views/demos/naive/index.vue
  57. 1 0
      apps/web-naive/tailwind.config.mjs
  58. 12 0
      apps/web-naive/tsconfig.json
  59. 10 0
      apps/web-naive/tsconfig.node.json
  60. 20 0
      apps/web-naive/vite.config.mts
  61. 1 0
      internal/vite-config/package.json
  62. 7 0
      internal/vite-config/src/plugins/nitor-mock.ts
  63. 2 0
      package.json
  64. 1 1
      packages/@core/preferences/src/config.ts
  65. 9 1
      packages/@core/ui-kit/tabs-ui/src/tabs-view.vue
  66. 120 15
      packages/effects/hooks/src/use-design-tokens.ts
  67. 287 13
      pnpm-lock.yaml
  68. 4 0
      vben-admin.code-workspace

+ 3 - 3
apps/web-antd/src/app.vue

@@ -1,7 +1,7 @@
 <script lang="ts" setup>
 import { computed } from 'vue';
 
-import { useDesignTokens } from '@vben/hooks';
+import { useAntdDesignTokens } from '@vben/hooks';
 import { preferences, usePreferences } from '@vben/preferences';
 
 import { App, ConfigProvider, theme } from 'ant-design-vue';
@@ -11,7 +11,7 @@ import { antdLocale } from '#/locales';
 defineOptions({ name: 'App' });
 
 const { isDark } = usePreferences();
-const { antDesignTokens } = useDesignTokens();
+const { tokens } = useAntdDesignTokens();
 
 const tokenTheme = computed(() => {
   const algorithm = isDark.value
@@ -25,7 +25,7 @@ const tokenTheme = computed(() => {
 
   return {
     algorithm,
-    token: antDesignTokens,
+    token: tokens,
   };
 });
 </script>

+ 5 - 0
apps/web-naive/.env

@@ -0,0 +1,5 @@
+# 应用标题
+VITE_APP_TITLE=Vben Admin Naive
+
+# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
+VITE_APP_NAMESPACE=vben-web-naive

+ 7 - 0
apps/web-naive/.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-naive/.env.development

@@ -0,0 +1,16 @@
+# 端口号
+VITE_PORT=5566
+
+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

+ 16 - 0
apps/web-naive/.env.production

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

+ 35 - 0
apps/web-naive/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?d20a01273820422b6aa2ee41b6c9414d';
+          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>

+ 49 - 0
apps/web-naive/package.json

@@ -0,0 +1,49 @@
+{
+  "name": "@vben/web-naive",
+  "version": "5.0.0",
+  "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-naive"
+  },
+  "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/chart-ui": "workspace:*",
+    "@vben/common-ui": "workspace:*",
+    "@vben/constants": "workspace:*",
+    "@vben/hooks": "workspace:*",
+    "@vben/icons": "workspace:*",
+    "@vben/layouts": "workspace:*",
+    "@vben/locales": "workspace:*",
+    "@vben/preferences": "workspace:*",
+    "@vben/request": "workspace:*",
+    "@vben/stores": "workspace:*",
+    "@vben/styles": "workspace:*",
+    "@vben/types": "workspace:*",
+    "@vben/utils": "workspace:*",
+    "@vueuse/core": "^10.11.0",
+    "naive-ui": "^2.39.0",
+    "pinia": "2.1.7",
+    "vue": "^3.4.34",
+    "vue-router": "^4.4.0"
+  }
+}

+ 1 - 0
apps/web-naive/postcss.config.mjs

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

BIN
apps/web-naive/public/favicon.ico


+ 33 - 0
apps/web-naive/src/api/core/auth.ts

@@ -0,0 +1,33 @@
+import { requestClient } from '#/api/request';
+
+export namespace AuthApi {
+  /** 登录接口参数 */
+  export interface LoginParams {
+    password: string;
+    username: string;
+  }
+
+  /** 登录接口返回值 */
+  export interface LoginResult {
+    accessToken: string;
+    desc: string;
+    realName: string;
+    refreshToken: string;
+    userId: string;
+    username: string;
+  }
+}
+
+/**
+ * 登录
+ */
+export async function login(data: AuthApi.LoginParams) {
+  return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
+}
+
+/**
+ * 获取用户权限码
+ */
+export async function getAccessCodes() {
+  return requestClient.get<string[]>('/auth/codes');
+}

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

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

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

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

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

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

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

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

+ 66 - 0
apps/web-naive/src/api/request.ts

@@ -0,0 +1,66 @@
+/**
+ * 该文件可自行根据业务逻辑进行调整
+ */
+import type { HttpResponse } from '@vben/request';
+
+import { useAppConfig } from '@vben/hooks';
+import { preferences } from '@vben/preferences';
+import { RequestClient } from '@vben/request';
+import { useAccessStore } from '@vben/stores';
+
+import { message } from '#/naive';
+import { useAuthStore } from '#/store';
+
+const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
+
+function createRequestClient(baseURL: string) {
+  const client = new RequestClient({
+    baseURL,
+    // 为每个请求携带 Authorization
+    makeAuthorization: () => {
+      return {
+        // 默认
+        key: 'Authorization',
+        tokenHandler: () => {
+          const accessStore = useAccessStore();
+          return {
+            refreshToken: `${accessStore.refreshToken}`,
+            token: `${accessStore.accessToken}`,
+          };
+        },
+        unAuthorizedHandler: async () => {
+          const accessStore = useAccessStore();
+          const authStore = useAuthStore();
+          accessStore.setAccessToken(null);
+
+          if (preferences.app.loginExpiredMode === 'modal') {
+            accessStore.setLoginExpired(true);
+          } else {
+            // 退出登录
+            await authStore.logout();
+          }
+        },
+      };
+    },
+    makeErrorMessage: (msg) => message.error(msg),
+
+    makeRequestHeaders: () => {
+      return {
+        // 为每个请求携带 Accept-Language
+        'Accept-Language': preferences.app.locale,
+      };
+    },
+  });
+  client.addResponseInterceptor<HttpResponse>((response) => {
+    const { data: responseData, status } = response;
+
+    const { code, data, message: msg } = responseData;
+    if (status >= 200 && status < 400 && code === 0) {
+      return data;
+    }
+    throw new Error(msg);
+  });
+  return client;
+}
+
+export const requestClient = createRequestClient(apiURL);

+ 52 - 0
apps/web-naive/src/app.vue

@@ -0,0 +1,52 @@
+<script lang="ts" setup>
+import { computed } from 'vue';
+
+import { useNaiveDesignTokens } from '@vben/hooks';
+import { preferences } from '@vben/preferences';
+
+import {
+  darkTheme,
+  dateEnUS,
+  dateZhCN,
+  enUS,
+  GlobalThemeOverrides,
+  lightTheme,
+  zhCN,
+} from 'naive-ui';
+
+defineOptions({ name: 'App' });
+
+const { commonTokens } = useNaiveDesignTokens();
+
+const tokenLocale = computed(() =>
+  preferences.app.locale === 'zh-CN' ? zhCN : enUS,
+);
+const tokenDateLocale = computed(() =>
+  preferences.app.locale === 'zh-CN' ? dateZhCN : dateEnUS,
+);
+const tokenTheme = computed(() =>
+  preferences.theme.mode === 'dark' ? darkTheme : lightTheme,
+);
+
+const themeOverrides = computed((): GlobalThemeOverrides => {
+  return {
+    common: commonTokens,
+  };
+});
+</script>
+
+<template>
+  <NConfigProvider
+    :date-locale="tokenDateLocale"
+    :locale="tokenLocale"
+    :theme="tokenTheme"
+    :theme-overrides="themeOverrides"
+    class="h-full"
+  >
+    <NNotificationProvider>
+      <NMessageProvider>
+        <RouterView />
+      </NMessageProvider>
+    </NNotificationProvider>
+  </NConfigProvider>
+</template>

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

@@ -0,0 +1,35 @@
+import { createApp } from 'vue';
+
+import { registerAccessDirective } from '@vben/access';
+import { initStores } from '@vben/stores';
+import '@vben/styles';
+import '@vben/styles/antd';
+
+import naive from 'naive-ui';
+
+import { setupI18n } from '#/locales';
+
+import App from './app.vue';
+import { router } from './router';
+
+async function bootstrap(namespace: string) {
+  const app = createApp(App);
+
+  // 国际化 i18n 配置
+  await setupI18n(app);
+
+  // 配置 pinia-tore
+  await initStores(app, { namespace });
+
+  // 安装权限指令
+  registerAccessDirective(app);
+
+  // 配置路由及路由守卫
+  app.use(router);
+
+  app.use(naive);
+
+  app.mount('#app');
+}
+
+export { bootstrap };

+ 153 - 0
apps/web-naive/src/layouts/basic.vue

@@ -0,0 +1,153 @@
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
+import { LOGIN_PATH, VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
+import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
+import {
+  BasicLayout,
+  LockScreen,
+  Notification,
+  NotificationItem,
+  UserDropdown,
+} from '@vben/layouts';
+import { preferences } from '@vben/preferences';
+import {
+  resetAllStores,
+  storeToRefs,
+  useAccessStore,
+  useUserStore,
+} from '@vben/stores';
+import { openWindow } from '@vben/utils';
+
+import { $t } from '#/locales';
+import { resetRoutes } from '#/router';
+import { useAuthStore } from '#/store';
+
+const notifications = ref<NotificationItem[]>([
+  {
+    avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
+    date: '3小时前',
+    isRead: true,
+    message: '描述信息描述信息描述信息',
+    title: '收到了 14 份新周报',
+  },
+  {
+    avatar: 'https://avatar.vercel.sh/1',
+    date: '刚刚',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '朱偏右 回复了你',
+  },
+  {
+    avatar: 'https://avatar.vercel.sh/1',
+    date: '2024-01-01',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '曲丽丽 评论了你',
+  },
+  {
+    avatar: 'https://avatar.vercel.sh/satori',
+    date: '1天前',
+    isRead: false,
+    message: '描述信息描述信息描述信息',
+    title: '代办提醒',
+  },
+]);
+
+const userStore = useUserStore();
+const authStore = useAuthStore();
+const accessStore = useAccessStore();
+const showDot = computed(() =>
+  notifications.value.some((item) => !item.isRead),
+);
+
+const menus = computed(() => [
+  {
+    handler: () => {
+      openWindow(VBEN_DOC_URL, {
+        target: '_blank',
+      });
+    },
+    icon: BookOpenText,
+    text: $t('widgets.document'),
+  },
+  {
+    handler: () => {
+      openWindow(VBEN_GITHUB_URL, {
+        target: '_blank',
+      });
+    },
+    icon: MdiGithub,
+    text: 'GitHub',
+  },
+  {
+    handler: () => {
+      openWindow(`${VBEN_GITHUB_URL}/issues`, {
+        target: '_blank',
+      });
+    },
+    icon: CircleHelp,
+    text: $t('widgets.qa'),
+  },
+]);
+
+const { loginLoading } = storeToRefs(authStore);
+
+const avatar = computed(() => {
+  return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
+});
+
+const router = useRouter();
+
+async function handleLogout() {
+  resetAllStores();
+  resetRoutes();
+  await router.replace(LOGIN_PATH);
+}
+
+function handleNoticeClear() {
+  notifications.value = [];
+}
+
+function handleMakeAll() {
+  notifications.value.forEach((item) => (item.isRead = 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"
+        @make-all="handleMakeAll"
+      />
+    </template>
+    <template #extra>
+      <AuthenticationLoginExpiredModal
+        v-model:open="accessStore.loginExpired"
+        :avatar
+        :loading="loginLoading"
+        password-placeholder="123456"
+        username-placeholder="vben"
+        @submit="authStore.authLogin"
+      />
+    </template>
+    <template #lock-screen>
+      <LockScreen :avatar @to-login="handleLogout" />
+    </template>
+  </BasicLayout>
+</template>

+ 8 - 0
apps/web-naive/src/layouts/index.ts

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

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

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

+ 31 - 0
apps/web-naive/src/locales/index.ts

@@ -0,0 +1,31 @@
+import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
+
+import type { App } from 'vue';
+
+import { $t, setupI18n as coreSetup, loadLocalesMap } from '@vben/locales';
+import { preferences } from '@vben/preferences';
+
+const modules = import.meta.glob('./langs/*.json');
+
+const localesMap = loadLocalesMap(modules);
+
+/**
+ * 加载应用特有的语言包
+ * 这里也可以改造为从服务端获取翻译数据
+ * @param lang
+ */
+async function loadMessages(lang: SupportedLanguagesType) {
+  const appLocaleMessages = await localesMap[lang]();
+  return appLocaleMessages.default;
+}
+
+async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
+  await coreSetup(app, {
+    defaultLocale: preferences.app.locale,
+    loadMessages,
+    missingWarn: !import.meta.env.PROD,
+    ...options,
+  });
+}
+
+export { $t, loadMessages, setupI18n };

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

@@ -0,0 +1,8 @@
+{
+  "page": {
+    "demos": {
+      "title": "Demos",
+      "naive": "Naive UI"
+    }
+  }
+}

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

@@ -0,0 +1,8 @@
+{
+  "page": {
+    "demos": {
+      "title": "演示",
+      "naive": "Naive UI"
+    }
+  }
+}

+ 31 - 0
apps/web-naive/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();

+ 25 - 0
apps/web-naive/src/naive.ts

@@ -0,0 +1,25 @@
+import { computed } from 'vue';
+
+import { preferences } from '@vben/preferences';
+import '@vben/styles';
+
+import { createDiscreteApi, darkTheme, lightTheme } from 'naive-ui';
+
+const themeOverridesProviderProps = computed(() => ({
+  themeOverrides: preferences.theme.mode === 'light' ? lightTheme : darkTheme,
+}));
+
+const themeProviderProps = computed(() => ({
+  theme: preferences.theme.mode === 'light' ? lightTheme : darkTheme,
+}));
+
+export const { dialog, loadingBar, message, modal, notification } =
+  createDiscreteApi(
+    ['message', 'dialog', 'notification', 'loadingBar', 'modal'],
+    {
+      configProviderProps: themeProviderProps,
+      loadingBarProviderProps: themeOverridesProviderProps,
+      messageProviderProps: themeOverridesProviderProps,
+      notificationProviderProps: themeOverridesProviderProps,
+    },
+  );

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

@@ -0,0 +1,9 @@
+import { defineOverridesPreferences } from '@vben/preferences';
+
+/**
+ * @description 项目配置文件
+ * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
+ */
+export const overridesPreferences = defineOverridesPreferences({
+  // overrides
+});

+ 40 - 0
apps/web-naive/src/router/access.ts

@@ -0,0 +1,40 @@
+import type {
+  ComponentRecordType,
+  GenerateMenuAndRoutesOptions,
+} from '@vben/types';
+
+import { generateAccessible } from '@vben/access';
+import { preferences } from '@vben/preferences';
+
+import { getAllMenus } from '#/api';
+import { BasicLayout, IFrameView } from '#/layouts';
+import { $t } from '#/locales';
+import { message } from '#/naive';
+
+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(`${$t('common.loadingMenu')}...`, {
+        duration: 1.5,
+      });
+      return await getAllMenus();
+    },
+    // 可以指定没有权限跳转403页面
+    forbiddenComponent,
+    // 如果 route.meta.menuVisibleWithForbidden = true
+    layoutMap,
+    pageMap,
+  });
+}
+
+export { generateAccess };

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

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

+ 32 - 0
apps/web-naive/src/router/index.ts

@@ -0,0 +1,32 @@
+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: () => ({ left: 0, top: 0 }),
+  // 是否应该禁止尾部斜杠。
+  // strict: true,
+});
+
+const resetRoutes = () => resetStaticRoutes(router, routes);
+
+// 创建路由守卫
+createRouterGuard(router);
+
+export { resetRoutes, router };

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

@@ -0,0 +1,86 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { DEFAULT_HOME_PATH } from '@vben/constants';
+
+import { AuthPageLayout } from '#/layouts';
+import { $t } from '#/locales';
+import Login from '#/views/_core/authentication/login.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[] = [
+  {
+    meta: {
+      title: 'Root',
+    },
+    name: 'Root',
+    path: '/',
+    redirect: DEFAULT_HOME_PATH,
+  },
+  {
+    component: AuthPageLayout,
+    meta: {
+      title: 'Authentication',
+    },
+    name: 'Authentication',
+    path: '/auth',
+    children: [
+      {
+        name: 'Login',
+        path: 'login',
+        component: Login,
+        meta: {
+          title: $t('page.core.login'),
+        },
+      },
+      {
+        name: 'CodeLogin',
+        path: 'code-login',
+        component: () => import('#/views/_core/authentication/code-login.vue'),
+        meta: {
+          title: $t('page.core.codeLogin'),
+        },
+      },
+      {
+        name: 'QrCodeLogin',
+        path: 'qrcode-login',
+        component: () =>
+          import('#/views/_core/authentication/qrcode-login.vue'),
+        meta: {
+          title: $t('page.core.qrcodeLogin'),
+        },
+      },
+      {
+        name: 'ForgetPassword',
+        path: 'forget-password',
+        component: () =>
+          import('#/views/_core/authentication/forget-password.vue'),
+        meta: {
+          title: $t('page.core.forgetPassword'),
+        },
+      },
+      {
+        name: 'Register',
+        path: 'register',
+        component: () => import('#/views/_core/authentication/register.vue'),
+        meta: {
+          title: $t('page.core.register'),
+        },
+      },
+    ],
+  },
+];
+
+export { coreRoutes, fallbackNotFoundRoute };

+ 31 - 0
apps/web-naive/src/router/routes/index.ts

@@ -0,0 +1,31 @@
+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 staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
+
+/** 动态路由 */
+const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
+
+/** 静态路由列表,访问这些页面可以不需要权限 */
+// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
+const staticRoutes: RouteRecordRaw[] = [];
+
+/** 路由列表,由基本路由+静态路由组成 */
+const routes: RouteRecordRaw[] = [
+  ...coreRoutes,
+  ...staticRoutes,
+  fallbackNotFoundRoute,
+];
+
+/** 基本路由列表,这些路由不需要进入权限拦截 */
+const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
+
+export { coreRouteNames, dynamicRoutes, routes };

+ 39 - 0
apps/web-naive/src/router/routes/modules/dashboard.ts

@@ -0,0 +1,39 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { BasicLayout } from '#/layouts';
+import { $t } from '#/locales';
+
+const routes: RouteRecordRaw[] = [
+  {
+    component: BasicLayout,
+    meta: {
+      icon: 'lucide:layout-dashboard',
+      order: -1,
+      title: $t('page.dashboard.title'),
+    },
+    name: 'Dashboard',
+    path: '/',
+    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: {
+          title: $t('page.dashboard.workspace'),
+        },
+      },
+    ],
+  },
+];
+
+export default routes;

+ 32 - 0
apps/web-naive/src/router/routes/modules/demos.ts

@@ -0,0 +1,32 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { BasicLayout } from '#/layouts';
+import { $t } from '#/locales';
+
+const routes: RouteRecordRaw[] = [
+  {
+    component: BasicLayout,
+    meta: {
+      icon: 'ic:baseline-view-in-ar',
+      keepAlive: true,
+      order: 1000,
+      title: $t('page.demos.title'),
+    },
+    name: 'Demos',
+    path: '/demos',
+    children: [
+      // 权限控制
+      {
+        meta: {
+          icon: 'mdi:shield-key-outline',
+          title: $t('page.demos.naive'),
+        },
+        name: 'NaiveDemos',
+        path: '/demos/access',
+        component: () => import('#/views/demos/naive/index.vue'),
+      },
+    ],
+  },
+];
+
+export default routes;

+ 57 - 0
apps/web-naive/src/router/routes/modules/vben.ts

@@ -0,0 +1,57 @@
+import type { RouteRecordRaw } from 'vue-router';
+
+import { VBEN_DOC_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL } from '@vben/constants';
+
+import { BasicLayout, IFrameView } from '#/layouts';
+import { $t } from '#/locales';
+
+const routes: RouteRecordRaw[] = [
+  {
+    component: BasicLayout,
+    meta: {
+      badgeType: 'dot',
+      badgeVariants: 'destructive',
+      icon: VBEN_LOGO_URL,
+      order: 9999,
+      title: $t('page.vben.title'),
+    },
+    name: 'VbenProject',
+    path: '/vben-admin',
+    children: [
+      {
+        name: 'VbenAbout',
+        path: '/vben-admin/about',
+        component: () => import('#/views/_core/vben/about/index.vue'),
+        meta: {
+          badgeType: 'dot',
+          badgeVariants: 'destructive',
+          icon: 'lucide:copyright',
+          title: $t('page.vben.about'),
+        },
+      },
+      {
+        name: 'VbenDocument',
+        path: '/vben-admin/document',
+        component: IFrameView,
+        meta: {
+          icon: 'lucide:book-open-text',
+          iframeSrc: VBEN_DOC_URL,
+          keepAlive: true,
+          title: $t('page.vben.document'),
+        },
+      },
+      {
+        name: 'VbenGithub',
+        path: '/vben-admin/github',
+        component: IFrameView,
+        meta: {
+          icon: 'mdi:github',
+          link: VBEN_GITHUB_URL,
+          title: 'Github',
+        },
+      },
+    ],
+  },
+];
+
+export default routes;

+ 111 - 0
apps/web-naive/src/store/auth.ts

@@ -0,0 +1,111 @@
+import type { LoginAndRegisterParams } from '@vben/common-ui';
+import type { UserInfo } from '@vben/types';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
+import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
+
+import { defineStore } from 'pinia';
+
+import { getAccessCodes, getUserInfo, login } from '#/api';
+import { $t } from '#/locales';
+import { notification } from '#/naive';
+
+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: LoginAndRegisterParams,
+    onSuccess?: () => Promise<void> | void,
+  ) {
+    // 异步处理用户登录操作并获取 accessToken
+    let userInfo: null | UserInfo = null;
+    try {
+      loginLoading.value = true;
+      const { accessToken, refreshToken } = await login(params);
+
+      // 如果成功获取到 accessToken
+      if (accessToken) {
+        // 将 accessToken 存储到 accessStore 中
+        accessStore.setAccessToken(accessToken);
+        accessStore.setRefreshToken(refreshToken);
+
+        // 获取用户信息并存储到 accessStore 中
+        const [fetchUserInfoResult, accessCodes] = await Promise.all([
+          fetchUserInfo(),
+          getAccessCodes(),
+        ]);
+
+        userInfo = fetchUserInfoResult;
+
+        userStore.setUserInfo(userInfo);
+        accessStore.setAccessCodes(accessCodes);
+
+        if (accessStore.loginExpired) {
+          accessStore.setLoginExpired(false);
+        } else {
+          onSuccess
+            ? await onSuccess?.()
+            : await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
+        }
+
+        if (userInfo?.realName) {
+          notification.success({
+            content: $t('authentication.loginSuccess'),
+            description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
+            duration: 3000,
+          });
+        }
+      }
+    } finally {
+      loginLoading.value = false;
+    }
+
+    return {
+      userInfo,
+    };
+  }
+
+  async function logout() {
+    resetAllStores();
+    accessStore.setLoginExpired(false);
+
+    // 回登陆页带上当前路由地址
+    await router.replace({
+      path: LOGIN_PATH,
+      query: {
+        redirect: encodeURIComponent(router.currentRoute.value.fullPath),
+      },
+    });
+  }
+
+  async function fetchUserInfo() {
+    let userInfo: null | UserInfo = null;
+    userInfo = await getUserInfo();
+    userStore.setUserInfo(userInfo);
+    return userInfo;
+  }
+
+  function $reset() {
+    loginLoading.value = false;
+  }
+
+  return {
+    $reset,
+    authLogin,
+    fetchUserInfo,
+    loginLoading,
+    logout,
+  };
+});

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

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

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

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

+ 30 - 0
apps/web-naive/src/views/_core/authentication/code-login.vue

@@ -0,0 +1,30 @@
+<script lang="ts" setup>
+import type { LoginCodeParams } from '@vben/common-ui';
+
+import { ref } from 'vue';
+
+import { AuthenticationCodeLogin } from '@vben/common-ui';
+import { LOGIN_PATH } from '@vben/constants';
+
+defineOptions({ name: 'CodeLogin' });
+
+const loading = ref(false);
+
+/**
+ * 异步处理登录操作
+ * Asynchronously handle the login process
+ * @param values 登录表单数据
+ */
+async function handleLogin(values: LoginCodeParams) {
+  // eslint-disable-next-line no-console
+  console.log(values);
+}
+</script>
+
+<template>
+  <AuthenticationCodeLogin
+    :loading="loading"
+    :login-path="LOGIN_PATH"
+    @submit="handleLogin"
+  />
+</template>

+ 23 - 0
apps/web-naive/src/views/_core/authentication/forget-password.vue

@@ -0,0 +1,23 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+import { AuthenticationForgetPassword } from '@vben/common-ui';
+import { LOGIN_PATH } from '@vben/constants';
+
+defineOptions({ name: 'ForgetPassword' });
+
+const loading = ref(false);
+
+function handleSubmit(value: string) {
+  // eslint-disable-next-line no-console
+  console.log('reset email:', value);
+}
+</script>
+
+<template>
+  <AuthenticationForgetPassword
+    :loading="loading"
+    :login-path="LOGIN_PATH"
+    @submit="handleSubmit"
+  />
+</template>

+ 18 - 0
apps/web-naive/src/views/_core/authentication/login.vue

@@ -0,0 +1,18 @@
+<script lang="ts" setup>
+import { AuthenticationLogin } from '@vben/common-ui';
+
+import { useAuthStore } from '#/store';
+
+defineOptions({ name: 'Login' });
+
+const authStore = useAuthStore();
+</script>
+
+<template>
+  <AuthenticationLogin
+    :loading="authStore.loginLoading"
+    password-placeholder="123456"
+    username-placeholder="vben"
+    @submit="authStore.authLogin"
+  />
+</template>

+ 10 - 0
apps/web-naive/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>

+ 25 - 0
apps/web-naive/src/views/_core/authentication/register.vue

@@ -0,0 +1,25 @@
+<script lang="ts" setup>
+import type { LoginAndRegisterParams } from '@vben/common-ui';
+
+import { ref } from 'vue';
+
+import { AuthenticationRegister } from '@vben/common-ui';
+import { LOGIN_PATH } from '@vben/constants';
+
+defineOptions({ name: 'Register' });
+
+const loading = ref(false);
+
+function handleSubmit(value: LoginAndRegisterParams) {
+  // eslint-disable-next-line no-console
+  console.log('register submit:', value);
+}
+</script>
+
+<template>
+  <AuthenticationRegister
+    :loading="loading"
+    :login-path="LOGIN_PATH"
+    @submit="handleSubmit"
+  />
+</template>

+ 7 - 0
apps/web-naive/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-naive/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-naive/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-naive/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-naive/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>

+ 9 - 0
apps/web-naive/src/views/_core/vben/about/index.vue

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

+ 78 - 0
apps/web-naive/src/views/dashboard/analytics/analytics-trends.vue

@@ -0,0 +1,78 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
+
+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',
+    },
+    yAxis: [
+      {
+        axisTick: {
+          show: false,
+        },
+        max: 80_000,
+
+        type: 'value',
+      },
+    ],
+  });
+});
+</script>
+
+<template>
+  <EchartsUI ref="chartRef" />
+</template>

+ 80 - 0
apps/web-naive/src/views/dashboard/analytics/analytics-visits-data.vue

@@ -0,0 +1,80 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
+
+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>

+ 44 - 0
apps/web-naive/src/views/dashboard/analytics/analytics-visits-sales.vue

@@ -0,0 +1,44 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
+
+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 },
+        ].sort((a, b) => {
+          return a.value - b.value;
+        }),
+        name: '商业占比',
+        radius: '80%',
+        roseType: 'radius',
+        type: 'pie',
+      },
+    ],
+
+    tooltip: {
+      trigger: 'item',
+    },
+  });
+});
+</script>
+
+<template>
+  <EchartsUI ref="chartRef" />
+</template>

+ 63 - 0
apps/web-naive/src/views/dashboard/analytics/analytics-visits-source.vue

@@ -0,0 +1,63 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
+
+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>

+ 53 - 0
apps/web-naive/src/views/dashboard/analytics/analytics-visits.vue

@@ -0,0 +1,53 @@
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+
+import { EchartsUI, type EchartsUIType, useEcharts } from '@vben/chart-ui';
+
+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-naive/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 AnalyticsVisits from './analytics-visits.vue';
+import AnalyticsVisitsData from './analytics-visits-data.vue';
+import AnalyticsVisitsSales from './analytics-visits-sales.vue';
+import AnalyticsVisitsSource from './analytics-visits-source.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>

+ 225 - 0
apps/web-naive/src/views/dashboard/workspace/index.vue

@@ -0,0 +1,225 @@
+<script lang="ts" setup>
+import type {
+  WorkbenchProjectItem,
+  WorkbenchQuickNavItem,
+  WorkbenchTodoItem,
+  WorkbenchTrendItem,
+} from '@vben/common-ui';
+
+import { ref } from 'vue';
+
+import {
+  AnalysisChartCard,
+  WorkbenchHeader,
+  WorkbenchProject,
+  WorkbenchQuickNav,
+  WorkbenchTodo,
+  WorkbenchTrends,
+} from '@vben/common-ui';
+import { preferences } from '@vben/preferences';
+import { useUserStore } from '@vben/stores';
+
+import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue';
+
+const userStore = useUserStore();
+
+const projectItems: WorkbenchProjectItem[] = [
+  {
+    color: '',
+    content: '不要等待机会,而要创造机会。',
+    date: '2021-04-01',
+    group: '开源组',
+    icon: 'carbon:logo-github',
+    title: 'Github',
+  },
+  {
+    color: '#3fb27f',
+    content: '现在的你决定将来的你。',
+    date: '2021-04-01',
+    group: '算法组',
+    icon: 'ion:logo-vue',
+    title: 'Vue',
+  },
+  {
+    color: '#e18525',
+    content: '没有什么才能比努力更重要。',
+    date: '2021-04-01',
+    group: '上班摸鱼',
+    icon: 'ion:logo-html5',
+    title: 'Html5',
+  },
+  {
+    color: '#bf0c2c',
+    content: '热情和欲望可以突破一切难关。',
+    date: '2021-04-01',
+    group: 'UI',
+    icon: 'ion:logo-angular',
+    title: 'Angular',
+  },
+  {
+    color: '#00d8ff',
+    content: '健康的身体是实现目标的基石。',
+    date: '2021-04-01',
+    group: '技术牛',
+    icon: 'bx:bxl-react',
+    title: 'React',
+  },
+  {
+    color: '#EBD94E',
+    content: '路是走出来的,而不是空想出来的。',
+    date: '2021-04-01',
+    group: '架构组',
+    icon: 'ion:logo-javascript',
+    title: 'Js',
+  },
+];
+
+const quickNavItems: WorkbenchQuickNavItem[] = [
+  {
+    color: '#1fdaca',
+    icon: 'ion:home-outline',
+    title: '首页',
+  },
+  {
+    color: '#bf0c2c',
+    icon: 'ion:grid-outline',
+    title: '仪表盘',
+  },
+  {
+    color: '#e18525',
+    icon: 'ion:layers-outline',
+    title: '组件',
+  },
+  {
+    color: '#3fb27f',
+    icon: 'ion:settings-outline',
+    title: '系统管理',
+  },
+  {
+    color: '#4daf1bc9',
+    icon: 'ion:key-outline',
+    title: '权限管理',
+  },
+  {
+    color: '#00d8ff',
+    icon: 'ion:bar-chart-outline',
+    title: '图表',
+  },
+];
+
+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',
+  },
+];
+</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="项目" />
+        <WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
+      </div>
+      <div class="w-full lg:w-2/5">
+        <WorkbenchQuickNav :items="quickNavItems" title="快捷导航" />
+        <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" />
+        <AnalysisChartCard class="mt-5" title="访问来源">
+          <AnalyticsVisitsSource />
+        </AnalysisChartCard>
+      </div>
+    </div>
+  </div>
+</template>

+ 93 - 0
apps/web-naive/src/views/demos/naive/index.vue

@@ -0,0 +1,93 @@
+<script lang="ts" setup>
+import type { NotificationType } from 'naive-ui';
+
+import { NButton, NCard, NSpace, useMessage, useNotification } from 'naive-ui';
+
+const notification = useNotification();
+
+const message = useMessage();
+function error() {
+  message.error('Once upon a time you dressed so fine');
+}
+
+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 loading() {
+  message.loading(
+    'If I were you, I will realize that I love you more than any other guy',
+  );
+}
+
+function notify(type: NotificationType) {
+  notification[type]({
+    content: '说点啥呢',
+    duration: 2500,
+    keepAliveOnHover: true,
+    meta: '想不出来',
+  });
+}
+</script>
+
+<template>
+  <div class="p-5">
+    <div class="card-box p-5">
+      <h1 class="text-xl font-semibold">naive组件使用演示</h1>
+      <div class="text-foreground/80 mt-2">支持多语言,主题功能集成切换等</div>
+    </div>
+
+    <div class="card-box mt-5 p-5">
+      <div class="mb-3">
+        <span class="text-lg font-semibold">按钮</span>
+      </div>
+      <div>
+        <NSpace>
+          <NButton>Default</NButton>
+          <NButton type="tertiary"> Tertiary </NButton>
+          <NButton type="primary"> Primary </NButton>
+          <NButton type="info"> Info </NButton>
+          <NButton type="success"> Success </NButton>
+          <NButton type="warning"> Warning </NButton>
+          <NButton type="error"> Error </NButton>
+        </NSpace>
+      </div>
+    </div>
+
+    <div class="card-box mt-5 p-5">
+      <div class="mb-3">
+        <span class="text-lg font-semibold">卡片</span>
+      </div>
+      <div>
+        <NCard title="卡片"> 卡片内容 </NCard>
+      </div>
+    </div>
+    <div class="card-box mt-5 p-5">
+      <div class="mb-3">
+        <span class="text-lg font-semibold">信息 Message </span>
+      </div>
+      <div class="flex gap-3">
+        <NButton type="error" @click="error"> 错误 </NButton>
+        <NButton type="warning" @click="warning"> 警告 </NButton>
+        <NButton type="success" @click="success"> 成功 </NButton>
+        <NButton type="primary" @click="loading"> 加载中 </NButton>
+      </div>
+    </div>
+
+    <div class="card-box mt-5 p-5">
+      <div class="mb-3">
+        <span class="text-lg font-semibold">通知 Notification </span>
+      </div>
+      <div class="flex gap-3">
+        <NButton type="error" @click="notify('error')"> 错误 </NButton>
+        <NButton type="warning" @click="notify('warning')"> 警告 </NButton>
+        <NButton type="success" @click="notify('success')"> 成功 </NButton>
+        <NButton type="primary" @click="notify('info')"> 加载中 </NButton>
+      </div>
+    </div>
+  </div>
+</template>

+ 1 - 0
apps/web-naive/tailwind.config.mjs

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

+ 12 - 0
apps/web-naive/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-naive/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-naive/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
internal/vite-config/package.json

@@ -32,6 +32,7 @@
     "cheerio": "1.0.0-rc.12",
     "html-minifier-terser": "^7.2.0",
     "nitropack": "^2.9.7",
+    "portfinder": "^1.0.32",
     "resolve.exports": "^2.0.2",
     "vite-plugin-lib-inject-css": "^2.1.1",
     "vite-plugin-pwa": "^0.20.1",

+ 7 - 0
internal/vite-config/src/plugins/nitor-mock.ts

@@ -5,6 +5,7 @@ import type { NitroMockPluginOptions } from '../typing';
 import { colors, consola, getPackage } from '@vben/node-utils';
 
 import { build, createDevServer, createNitro, prepare } from 'nitropack';
+import portfinder from 'portfinder';
 
 const hmrKeyRe = /^runtimeConfig\.|routeRules\./;
 
@@ -75,7 +76,13 @@ async function runNitroServer(rootDir: string, port: number, verbose: boolean) {
       },
     );
     nitro.hooks.hookOnce('restart', reload);
+
     const server = createDevServer(nitro);
+    // 端口已经存在
+    const availablePort = await portfinder.getPortPromise({ port });
+    if (availablePort !== port) {
+      return;
+    }
     await server.listen(port, { showURL: false });
     await prepare(nitro);
     await build(nitro);

+ 2 - 0
package.json

@@ -38,6 +38,8 @@
     "clean": "vsh clean",
     "commit": "czg",
     "dev": "cross-env turbo run dev",
+    "dev:antd": "pnpm -F @vben/web-antd",
+    "dev:naive": "pnpm -F @vben/web-naive",
     "dev:docs": "pnpm -F @vben/website run docs:dev",
     "format": "vsh lint --format",
     "lint": "vsh lint",

+ 1 - 1
packages/@core/preferences/src/config.ts

@@ -18,7 +18,7 @@ const defaultPreferences: Preferences = {
     layout: 'sidebar-nav',
     locale: 'zh-CN',
     loginExpiredMode: 'modal',
-    name: 'Vben Admin',
+    name: 'Vben Admin Naive',
     watermark: false,
   },
   breadcrumb: {

+ 9 - 1
packages/@core/ui-kit/tabs-ui/src/tabs-view.vue

@@ -2,7 +2,7 @@
 import type { Sortable } from '@vben-core/composables';
 import type { TabDefinition } from '@vben-core/typings';
 
-import { nextTick, onMounted, onUnmounted, ref } from 'vue';
+import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
 
 import { useForwardPropsEmits, useSortable } from '@vben-core/composables';
 
@@ -105,6 +105,14 @@ async function initTabsSortable() {
 
 onMounted(initTabsSortable);
 
+watch(
+  () => props.styleType,
+  () => {
+    sortableInstance.value?.destroy();
+    initTabsSortable();
+  },
+);
+
 onUnmounted(() => {
   sortableInstance.value?.destroy();
 });

+ 120 - 15
packages/effects/hooks/src/use-design-tokens.ts

@@ -2,10 +2,14 @@ import { reactive, watch } from 'vue';
 
 import { preferences } from '@vben/preferences';
 
-export function useDesignTokens() {
+/**
+ * 用于适配各个框架的设计系统
+ */
+
+export function useAntdDesignTokens() {
   const rootStyles = getComputedStyle(document.documentElement);
 
-  const antDesignTokens = reactive({
+  const tokens = reactive({
     borderRadius: '' as any,
     colorBgBase: '',
     colorBgContainer: '',
@@ -29,23 +33,124 @@ export function useDesignTokens() {
   watch(
     () => preferences.theme,
     () => {
-      antDesignTokens.colorPrimary = getCssVariableValue('--primary');
-      antDesignTokens.colorError = getCssVariableValue('--destructive');
-      antDesignTokens.colorWarning = getCssVariableValue('--warning');
-      antDesignTokens.colorSuccess = getCssVariableValue('--success');
-      antDesignTokens.colorBgBase = getCssVariableValue('--background');
-      antDesignTokens.colorBgLayout = getCssVariableValue('--background-deep');
-      antDesignTokens.colorBgMask = getCssVariableValue('--overlay');
-      antDesignTokens.colorBorder = getCssVariableValue('--border');
-      antDesignTokens.colorTextBase = getCssVariableValue('--foreground');
-      antDesignTokens.colorBgElevated = getCssVariableValue('--popover');
-      antDesignTokens.colorBgContainer = getCssVariableValue('--card');
-      antDesignTokens.borderRadius = getCssVariableValue('--radius', false);
+      tokens.colorPrimary = getCssVariableValue('--primary');
+
+      tokens.colorError = getCssVariableValue('--destructive');
+
+      tokens.colorWarning = getCssVariableValue('--warning');
+
+      tokens.colorSuccess = getCssVariableValue('--success');
+
+      tokens.colorTextBase = getCssVariableValue('--foreground');
+
+      getCssVariableValue('--primary-foreground');
+
+      tokens.colorBorder = getCssVariableValue('--border');
+
+      tokens.colorBgElevated = getCssVariableValue('--popover');
+
+      tokens.colorBgContainer = getCssVariableValue('--card');
+
+      tokens.colorBgBase = getCssVariableValue('--background');
+
+      tokens.borderRadius = getCssVariableValue('--radius', false);
+
+      tokens.colorBgLayout = getCssVariableValue('--background-deep');
+      tokens.colorBgMask = getCssVariableValue('--overlay');
+    },
+    { immediate: true },
+  );
+
+  return {
+    tokens,
+  };
+}
+
+export function useNaiveDesignTokens() {
+  const rootStyles = getComputedStyle(document.documentElement);
+
+  const commonTokens = reactive({
+    baseColor: '',
+    bodyColor: '',
+    borderColor: '',
+    borderRadius: '',
+    cardColor: '',
+    dividerColor: '',
+    errorColor: '',
+    errorColorHover: '',
+    errorColorPressed: '',
+    errorColorSuppl: '',
+    invertedColor: '',
+    modalColor: '',
+    popoverColor: '',
+    primaryColor: '',
+    primaryColorHover: '',
+    primaryColorPressed: '',
+    primaryColorSuppl: '',
+    successColor: '',
+    successColorHover: '',
+    successColorPressed: '',
+    successColorSuppl: '',
+    tableColor: '',
+    textColorBase: '',
+    warningColor: '',
+    warningColorHover: '',
+    warningColorPressed: '',
+    warningColorSuppl: '',
+  });
+
+  const getCssVariableValue = (variable: string, isColor: boolean = true) => {
+    const value = rootStyles.getPropertyValue(variable);
+    return isColor ? `hsl(${value})` : value;
+  };
+
+  watch(
+    () => preferences.theme,
+    () => {
+      commonTokens.primaryColor = getCssVariableValue('--primary');
+      commonTokens.primaryColorHover = getCssVariableValue('--primary-600');
+      commonTokens.primaryColorPressed = getCssVariableValue('--primary-700');
+      commonTokens.primaryColorSuppl = getCssVariableValue('--primary-800');
+
+      commonTokens.errorColor = getCssVariableValue('--destructive');
+      commonTokens.errorColorHover = getCssVariableValue('--destructive-600');
+      commonTokens.errorColorPressed = getCssVariableValue('--destructive-700');
+      commonTokens.errorColorSuppl = getCssVariableValue('--destructive-800');
+
+      commonTokens.warningColor = getCssVariableValue('--warning');
+      commonTokens.warningColorHover = getCssVariableValue('--warning-600');
+      commonTokens.warningColorPressed = getCssVariableValue('--warning-700');
+      commonTokens.warningColorSuppl = getCssVariableValue('--warning-800');
+
+      commonTokens.successColor = getCssVariableValue('--success');
+      commonTokens.successColorHover = getCssVariableValue('--success-600');
+      commonTokens.successColorPressed = getCssVariableValue('--success-700');
+      commonTokens.successColorSuppl = getCssVariableValue('--success-800');
+
+      commonTokens.textColorBase = getCssVariableValue('--foreground');
+
+      commonTokens.baseColor = getCssVariableValue('--primary-foreground');
+
+      commonTokens.dividerColor = commonTokens.borderColor =
+        getCssVariableValue('--border');
+
+      commonTokens.modalColor = commonTokens.popoverColor =
+        getCssVariableValue('--popover');
+
+      commonTokens.tableColor = commonTokens.cardColor =
+        getCssVariableValue('--card');
+
+      commonTokens.bodyColor = getCssVariableValue('--background');
+      commonTokens.invertedColor = getCssVariableValue('--background-deep');
+
+      commonTokens.borderRadius = getCssVariableValue('--radius', false);
+
+      // antDesignTokens.colorBgMask = getCssVariableValue('--overlay');
     },
     { immediate: true },
   );
 
   return {
-    antDesignTokens,
+    commonTokens,
   };
 }

+ 287 - 13
pnpm-lock.yaml

@@ -167,6 +167,66 @@ importers:
         specifier: ^4.4.0
         version: 4.4.0(vue@3.4.34(typescript@5.5.4))
 
+  apps/web-naive:
+    dependencies:
+      '@vben/access':
+        specifier: workspace:*
+        version: link:../../packages/effects/access
+      '@vben/chart-ui':
+        specifier: workspace:*
+        version: link:../../packages/effects/chart-ui
+      '@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/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: ^10.11.0
+        version: 10.11.0(vue@3.4.34(typescript@5.5.4))
+      naive-ui:
+        specifier: ^2.39.0
+        version: 2.39.0(vue@3.4.34(typescript@5.5.4))
+      pinia:
+        specifier: 2.1.7
+        version: 2.1.7(typescript@5.5.4)(vue@3.4.34(typescript@5.5.4))
+      vue:
+        specifier: ^3.4.34
+        version: 3.4.34(typescript@5.5.4)
+      vue-router:
+        specifier: ^4.4.0
+        version: 4.4.0(vue@3.4.34(typescript@5.5.4))
+
   internal/lint-configs/commitlint-config:
     dependencies:
       '@commitlint/cli':
@@ -430,6 +490,9 @@ importers:
       nitropack:
         specifier: ^2.9.7
         version: 2.9.7(encoding@0.1.13)
+      portfinder:
+        specifier: ^1.0.32
+        version: 1.0.32
       resolve.exports:
         specifier: ^2.0.2
         version: 2.0.2
@@ -1070,36 +1133,42 @@ packages:
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-arm64-gnu@0.22.6':
     resolution: {integrity: sha512-9PAqNJlAQfFm1RW0DVCM/S4gFHdppxUTWacB3qEeJZXgdLnoH0KGQa4z3Xo559SPYDKZy0VnY02mZ3XJ+v6/Vw==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-x64-gnu@0.21.4':
     resolution: {integrity: sha512-U7jl8RGpxKV+pjFstY0y5qD+D+wm9dXNO7NBbIOnETgTMizTFiUuQWT7SOlIklhcxxuXqWzfwhNN1qwI0tGNWw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-x64-gnu@0.22.6':
     resolution: {integrity: sha512-nZf+gxXVrZqvP1LN6HwzOMA4brF3umBXfMequQzv8S6HeJ4c34P23F0Tw8mHtQpVYP9PQWJUvt3LJQ8Xvd5Hiw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@ast-grep/napi-linux-x64-musl@0.21.4':
     resolution: {integrity: sha512-SOGR93kGomRR+Vh87+jXI3pJLR+J+dekCI8a4S22kGX9iAen8/+Ew++lFouDueKLyszmmhCrIk1WnJvYPuSFBw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@ast-grep/napi-linux-x64-musl@0.22.6':
     resolution: {integrity: sha512-gcJeBMgJQf2pZZo0lgH0Vg4ycyujM7Am8VlomXhavC/dPpkddA1tiHSIC4fCNneLU1EqHITy3ALSmM4GLdsjBw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@ast-grep/napi-win32-arm64-msvc@0.21.4':
     resolution: {integrity: sha512-ciGaTbkPjbCGqUyLwIPvcNeftNXjSG3cXE+5NiLThRbDhh2yUOE8YJkElUQcu0xQCdSlXnb4l/imEED/65jGfw==}
@@ -2094,6 +2163,16 @@ packages:
     resolution: {integrity: sha512-+MEFtLb/VJK5WEwmtLCqfghkDBnPNK5XdUNt24tKfwinAlf4VSMxCmplqPwRjXVIbT5C4stSUY/ZrPadEWjw8Q==}
     engines: {node: '>=18.0'}
 
+  '@css-render/plugin-bem@0.15.14':
+    resolution: {integrity: sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==}
+    peerDependencies:
+      css-render: ~0.15.14
+
+  '@css-render/vue3-ssr@0.15.14':
+    resolution: {integrity: sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==}
+    peerDependencies:
+      vue: ^3.4.34
+
   '@csstools/cascade-layer-name-parser@1.0.13':
     resolution: {integrity: sha512-MX0yLTwtZzr82sQ0zOjqimpZbzjMaK/h2pmlrLK7DCzlmiZLYFpoO94WmN1akRVo6ll/TdpHb53vihHLUMyvng==}
     engines: {node: ^14 || ^16 || >=18}
@@ -2364,6 +2443,9 @@ packages:
   '@dual-bundle/import-meta-resolve@4.1.0':
     resolution: {integrity: sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==}
 
+  '@emotion/hash@0.8.0':
+    resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==}
+
   '@emotion/hash@0.9.2':
     resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
 
@@ -3082,6 +3164,9 @@ packages:
   '@jspm/import-map@1.1.0':
     resolution: {integrity: sha512-vmk583YnMi4fmqeXbWIBiyzFu+vqVZ5VCoaa6H4xeSQy5E6JAWtmcq72OAMFTeSTqw7xxHQIJFq2OlHKdUWitQ==}
 
+  '@juggle/resize-observer@3.4.0':
+    resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
+
   '@ls-lint/ls-lint@2.2.3':
     resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==}
     os: [darwin, linux, win32]
@@ -3186,30 +3271,35 @@ packages:
     engines: {node: '>= 10.0.0'}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-glibc@2.4.1':
     resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-arm64-musl@2.4.1':
     resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==}
     engines: {node: '>= 10.0.0'}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-linux-x64-glibc@2.4.1':
     resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@parcel/watcher-linux-x64-musl@2.4.1':
     resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==}
     engines: {node: '>= 10.0.0'}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@parcel/watcher-wasm@2.4.1':
     resolution: {integrity: sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA==}
@@ -3409,91 +3499,109 @@ packages:
     resolution: {integrity: sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm-gnueabihf@4.19.1':
     resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==}
     cpu: [arm]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm-musleabihf@4.19.0':
     resolution: {integrity: sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-arm-musleabihf@4.19.1':
     resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==}
     cpu: [arm]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-arm64-gnu@4.19.0':
     resolution: {integrity: sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-gnu@4.19.1':
     resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==}
     cpu: [arm64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-arm64-musl@4.19.0':
     resolution: {integrity: sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-arm64-musl@4.19.1':
     resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==}
     cpu: [arm64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.19.0':
     resolution: {integrity: sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==}
     cpu: [ppc64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
     resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==}
     cpu: [ppc64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.19.0':
     resolution: {integrity: sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-riscv64-gnu@4.19.1':
     resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==}
     cpu: [riscv64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.19.0':
     resolution: {integrity: sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==}
     cpu: [s390x]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-s390x-gnu@4.19.1':
     resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==}
     cpu: [s390x]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.19.0':
     resolution: {integrity: sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-gnu@4.19.1':
     resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==}
     cpu: [x64]
     os: [linux]
+    libc: [glibc]
 
   '@rollup/rollup-linux-x64-musl@4.19.0':
     resolution: {integrity: sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-linux-x64-musl@4.19.1':
     resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==}
     cpu: [x64]
     os: [linux]
+    libc: [musl]
 
   '@rollup/rollup-win32-arm64-msvc@4.19.0':
     resolution: {integrity: sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==}
@@ -3640,9 +3748,15 @@ packages:
   '@types/jsonfile@6.1.4':
     resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==}
 
+  '@types/katex@0.16.7':
+    resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+
   '@types/linkify-it@5.0.0':
     resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
 
+  '@types/lodash-es@4.17.12':
+    resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
+
   '@types/lodash.clonedeep@4.5.9':
     resolution: {integrity: sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==}
 
@@ -4228,6 +4342,9 @@ packages:
   async-validator@4.2.5:
     resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
 
+  async@2.6.4:
+    resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
+
   async@3.2.5:
     resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
 
@@ -4781,6 +4898,9 @@ packages:
     peerDependencies:
       postcss: ^8.4
 
+  css-render@0.15.14:
+    resolution: {integrity: sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==}
+
   css-select@4.3.0:
     resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
 
@@ -4833,6 +4953,9 @@ packages:
     resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==}
     engines: {node: '>=18'}
 
+  csstype@3.0.11:
+    resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==}
+
   csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
@@ -4872,6 +4995,15 @@ packages:
   dataloader@1.4.0:
     resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==}
 
+  date-fns-tz@2.0.1:
+    resolution: {integrity: sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==}
+    peerDependencies:
+      date-fns: 2.x
+
+  date-fns@2.30.0:
+    resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
+    engines: {node: '>=0.11'}
+
   dayjs@1.11.12:
     resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==}
 
@@ -5475,6 +5607,9 @@ packages:
     resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
     engines: {node: '>=0.8.x'}
 
+  evtd@0.2.4:
+    resolution: {integrity: sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==}
+
   execa@8.0.1:
     resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
     engines: {node: '>=16.17'}
@@ -5877,6 +6012,10 @@ packages:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
     hasBin: true
 
+  highlight.js@11.10.0:
+    resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==}
+    engines: {node: '>=12.0.0'}
+
   homedir-polyfill@1.0.3:
     resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==}
     engines: {node: '>=0.10.0'}
@@ -6693,6 +6832,10 @@ packages:
   mitt@3.0.1:
     resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
 
+  mkdirp@0.5.6:
+    resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+    hasBin: true
+
   mkdirp@1.0.4:
     resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
     engines: {node: '>=10'}
@@ -6743,6 +6886,11 @@ packages:
   mz@2.7.0:
     resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
 
+  naive-ui@2.39.0:
+    resolution: {integrity: sha512-5oUJzRG+rtLSH8eRU+fJvVYiQids2BxF9jp+fwGoAqHOptEINrBlgBu9uy+95RHE5FLJ7Q/z41o+qkoGnUrKxQ==}
+    peerDependencies:
+      vue: ^3.4.34
+
   nanoid@3.3.7:
     resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -7139,6 +7287,10 @@ packages:
     resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
     engines: {node: '>=10.13.0'}
 
+  portfinder@1.0.32:
+    resolution: {integrity: sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==}
+    engines: {node: '>= 0.12.0'}
+
   possible-typed-array-names@1.0.0:
     resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
     engines: {node: '>= 0.4'}
@@ -7989,6 +8141,9 @@ packages:
   search-insights@2.15.0:
     resolution: {integrity: sha512-ch2sPCUDD4sbPQdknVl9ALSi9H7VyoeVbsxznYz6QV55jJ8CI3EtwpO1i84keN4+hF5IeHWIeGvc08530JkVXQ==}
 
+  seemly@0.3.8:
+    resolution: {integrity: sha512-MW8Qs6vbzo0pHmDpFSYPna+lwpZ6Zk1ancbajw/7E8TKtHdV+1DfZZD+kKJEhG/cAoB/i+LiT+5msZOqj0DwRA==}
+
   semver-compare@1.0.0:
     resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==}
 
@@ -8554,6 +8709,9 @@ packages:
     resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
     engines: {node: '>=18'}
 
+  treemate@0.3.11:
+    resolution: {integrity: sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==}
+
   ts-api-utils@1.3.0:
     resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
     engines: {node: '>=16'}
@@ -8838,6 +8996,11 @@ packages:
   validate-npm-package-license@3.0.4:
     resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
 
+  vdirs@0.1.8:
+    resolution: {integrity: sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==}
+    peerDependencies:
+      vue: ^3.4.34
+
   vite-hot-client@0.2.3:
     resolution: {integrity: sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg==}
     peerDependencies:
@@ -8971,6 +9134,11 @@ packages:
       jsdom:
         optional: true
 
+  vooks@0.2.12:
+    resolution: {integrity: sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==}
+    peerDependencies:
+      vue: ^3.4.34
+
   vscode-languageserver-textdocument@1.0.11:
     resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==}
 
@@ -9058,6 +9226,11 @@ packages:
       typescript:
         optional: true
 
+  vueuc@0.4.58:
+    resolution: {integrity: sha512-Wnj/N8WbPRSxSt+9ji1jtDHPzda5h2OH/0sFBhvdxDRuyCZbjGg3/cKMaKqEoe+dErTexG2R+i6Q8S/Toq1MYg==}
+    peerDependencies:
+      vue: ^3.4.34
+
   w3c-xmlserializer@5.0.0:
     resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
     engines: {node: '>=18'}
@@ -9561,7 +9734,7 @@ snapshots:
       '@babel/traverse': 7.24.8
       '@babel/types': 7.24.9
       convert-source-map: 2.0.0
-      debug: 4.3.5
+      debug: 4.3.6
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -10349,7 +10522,7 @@ snapshots:
       '@babel/helper-split-export-declaration': 7.24.7
       '@babel/parser': 7.24.8
       '@babel/types': 7.24.9
-      debug: 4.3.5
+      debug: 4.3.6
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -10831,6 +11004,14 @@ snapshots:
 
   '@cspell/url@8.13.0': {}
 
+  '@css-render/plugin-bem@0.15.14(css-render@0.15.14)':
+    dependencies:
+      css-render: 0.15.14
+
+  '@css-render/vue3-ssr@0.15.14(vue@3.4.34(typescript@5.5.4))':
+    dependencies:
+      vue: 3.4.34(typescript@5.5.4)
+
   '@csstools/cascade-layer-name-parser@1.0.13(@csstools/css-parser-algorithms@2.7.1(@csstools/css-tokenizer@2.4.1))(@csstools/css-tokenizer@2.4.1)':
     dependencies:
       '@csstools/css-parser-algorithms': 2.7.1(@csstools/css-tokenizer@2.4.1)
@@ -11097,6 +11278,8 @@ snapshots:
 
   '@dual-bundle/import-meta-resolve@4.1.0': {}
 
+  '@emotion/hash@0.8.0': {}
+
   '@emotion/hash@0.9.2': {}
 
   '@emotion/unitless@0.8.1': {}
@@ -11445,7 +11628,7 @@ snapshots:
     dependencies:
       '@floating-ui/dom': 1.6.8
       '@floating-ui/utils': 0.2.5
-      vue-demi: 0.14.8(vue@3.4.34(typescript@5.5.4))
+      vue-demi: 0.14.10(vue@3.4.34(typescript@5.5.4))
     transitivePeerDependencies:
       - '@vue/composition-api'
       - vue
@@ -11574,6 +11757,8 @@ snapshots:
 
   '@jspm/import-map@1.1.0': {}
 
+  '@juggle/resize-observer@3.4.0': {}
+
   '@ls-lint/ls-lint@2.2.3': {}
 
   '@manypkg/find-root@1.1.0':
@@ -12195,8 +12380,14 @@ snapshots:
     dependencies:
       '@types/node': 22.0.0
 
+  '@types/katex@0.16.7': {}
+
   '@types/linkify-it@5.0.0': {}
 
+  '@types/lodash-es@4.17.12':
+    dependencies:
+      '@types/lodash': 4.17.7
+
   '@types/lodash.clonedeep@4.5.9':
     dependencies:
       '@types/lodash': 4.17.7
@@ -12326,7 +12517,7 @@ snapshots:
     dependencies:
       '@typescript-eslint/types': 7.17.0
       '@typescript-eslint/visitor-keys': 7.17.0
-      debug: 4.3.5
+      debug: 4.3.6
       globby: 11.1.0
       is-glob: 4.0.3
       minimatch: 9.0.5
@@ -12747,13 +12938,13 @@ snapshots:
 
   agent-base@6.0.2:
     dependencies:
-      debug: 4.3.5
+      debug: 4.3.6
     transitivePeerDependencies:
       - supports-color
 
   agent-base@7.1.1:
     dependencies:
-      debug: 4.3.5
+      debug: 4.3.6
     transitivePeerDependencies:
       - supports-color
 
@@ -12952,6 +13143,10 @@ snapshots:
 
   async-validator@4.2.5: {}
 
+  async@2.6.4:
+    dependencies:
+      lodash: 4.17.21
+
   async@3.2.5: {}
 
   asynckit@0.4.0: {}
@@ -13594,6 +13789,11 @@ snapshots:
     dependencies:
       postcss: 8.4.40
 
+  css-render@0.15.14:
+    dependencies:
+      '@emotion/hash': 0.8.0
+      csstype: 3.0.11
+
   css-select@4.3.0:
     dependencies:
       boolbase: 1.0.0
@@ -13678,6 +13878,8 @@ snapshots:
     dependencies:
       rrweb-cssom: 0.6.0
 
+  csstype@3.0.11: {}
+
   csstype@3.1.3: {}
 
   cz-git@1.9.4: {}
@@ -13713,6 +13915,14 @@ snapshots:
 
   dataloader@1.4.0: {}
 
+  date-fns-tz@2.0.1(date-fns@2.30.0):
+    dependencies:
+      date-fns: 2.30.0
+
+  date-fns@2.30.0:
+    dependencies:
+      '@babel/runtime': 7.24.8
+
   dayjs@1.11.12: {}
 
   db0@0.1.4: {}
@@ -14459,6 +14669,8 @@ snapshots:
 
   events@3.3.0: {}
 
+  evtd@0.2.4: {}
+
   execa@8.0.1:
     dependencies:
       cross-spawn: 7.0.3
@@ -14908,6 +15120,8 @@ snapshots:
 
   he@1.2.0: {}
 
+  highlight.js@11.10.0: {}
+
   homedir-polyfill@1.0.3:
     dependencies:
       parse-passwd: 1.0.0
@@ -14963,14 +15177,14 @@ snapshots:
     dependencies:
       '@tootallnate/once': 1.1.2
       agent-base: 6.0.2
-      debug: 4.3.5
+      debug: 4.3.6
     transitivePeerDependencies:
       - supports-color
 
   http-proxy-agent@7.0.2:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5
+      debug: 4.3.6
     transitivePeerDependencies:
       - supports-color
 
@@ -14979,14 +15193,14 @@ snapshots:
   https-proxy-agent@5.0.1:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.5
+      debug: 4.3.6
     transitivePeerDependencies:
       - supports-color
 
   https-proxy-agent@7.0.5:
     dependencies:
       agent-base: 7.1.1
-      debug: 4.3.5
+      debug: 4.3.6
     transitivePeerDependencies:
       - supports-color
 
@@ -15696,6 +15910,10 @@ snapshots:
 
   mitt@3.0.1: {}
 
+  mkdirp@0.5.6:
+    dependencies:
+      minimist: 1.2.8
+
   mkdirp@1.0.4: {}
 
   mkdist@1.5.4(sass@1.77.8)(typescript@5.5.4)(vue-tsc@2.0.29(typescript@5.5.4)):
@@ -15751,6 +15969,29 @@ snapshots:
       object-assign: 4.1.1
       thenify-all: 1.6.0
 
+  naive-ui@2.39.0(vue@3.4.34(typescript@5.5.4)):
+    dependencies:
+      '@css-render/plugin-bem': 0.15.14(css-render@0.15.14)
+      '@css-render/vue3-ssr': 0.15.14(vue@3.4.34(typescript@5.5.4))
+      '@types/katex': 0.16.7
+      '@types/lodash': 4.17.7
+      '@types/lodash-es': 4.17.12
+      async-validator: 4.2.5
+      css-render: 0.15.14
+      csstype: 3.1.3
+      date-fns: 2.30.0
+      date-fns-tz: 2.0.1(date-fns@2.30.0)
+      evtd: 0.2.4
+      highlight.js: 11.10.0
+      lodash: 4.17.21
+      lodash-es: 4.17.21
+      seemly: 0.3.8
+      treemate: 0.3.11
+      vdirs: 0.1.8(vue@3.4.34(typescript@5.5.4))
+      vooks: 0.2.12(vue@3.4.34(typescript@5.5.4))
+      vue: 3.4.34(typescript@5.5.4)
+      vueuc: 0.4.58(vue@3.4.34(typescript@5.5.4))
+
   nanoid@3.3.7: {}
 
   nanoid@5.0.7: {}
@@ -16192,6 +16433,14 @@ snapshots:
 
   pngjs@5.0.0: {}
 
+  portfinder@1.0.32:
+    dependencies:
+      async: 2.6.4
+      debug: 3.2.7
+      mkdirp: 0.5.6
+    transitivePeerDependencies:
+      - supports-color
+
   possible-typed-array-names@1.0.0: {}
 
   postcss-antd-fixes@0.2.0(postcss@8.4.40):
@@ -17061,6 +17310,8 @@ snapshots:
 
   search-insights@2.15.0: {}
 
+  seemly@0.3.8: {}
+
   semver-compare@1.0.0: {}
 
   semver-diff@4.0.0:
@@ -17206,7 +17457,7 @@ snapshots:
   socks-proxy-agent@5.0.1:
     dependencies:
       agent-base: 6.0.2
-      debug: 4.3.5
+      debug: 4.3.6
       socks: 2.8.3
     transitivePeerDependencies:
       - supports-color
@@ -17711,6 +17962,8 @@ snapshots:
     dependencies:
       punycode: 2.3.1
 
+  treemate@0.3.11: {}
+
   ts-api-utils@1.3.0(typescript@5.5.4):
     dependencies:
       typescript: 5.5.4
@@ -18020,6 +18273,11 @@ snapshots:
       spdx-correct: 3.2.0
       spdx-expression-parse: 3.0.1
 
+  vdirs@0.1.8(vue@3.4.34(typescript@5.5.4)):
+    dependencies:
+      evtd: 0.2.4
+      vue: 3.4.34(typescript@5.5.4)
+
   vite-hot-client@0.2.3(vite@5.3.5(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3)):
     dependencies:
       vite: 5.3.5(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3)
@@ -18027,7 +18285,7 @@ snapshots:
   vite-node@2.0.4(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3):
     dependencies:
       cac: 6.7.14
-      debug: 4.3.5
+      debug: 4.3.6
       pathe: 1.1.2
       tinyrainbow: 1.2.0
       vite: 5.3.5(@types/node@22.0.0)(sass@1.77.8)(terser@5.31.3)
@@ -18090,7 +18348,7 @@ snapshots:
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.0(rollup@4.19.1)
-      debug: 4.3.5
+      debug: 4.3.6
       error-stack-parser-es: 0.1.5
       fs-extra: 11.2.0
       open: 10.1.0
@@ -18242,6 +18500,11 @@ snapshots:
       - supports-color
       - terser
 
+  vooks@0.2.12(vue@3.4.34(typescript@5.5.4)):
+    dependencies:
+      evtd: 0.2.4
+      vue: 3.4.34(typescript@5.5.4)
+
   vscode-languageserver-textdocument@1.0.11: {}
 
   vscode-uri@3.0.8: {}
@@ -18320,6 +18583,17 @@ snapshots:
     optionalDependencies:
       typescript: 5.5.4
 
+  vueuc@0.4.58(vue@3.4.34(typescript@5.5.4)):
+    dependencies:
+      '@css-render/vue3-ssr': 0.15.14(vue@3.4.34(typescript@5.5.4))
+      '@juggle/resize-observer': 3.4.0
+      css-render: 0.15.14
+      evtd: 0.2.4
+      seemly: 0.3.8
+      vdirs: 0.1.8(vue@3.4.34(typescript@5.5.4))
+      vooks: 0.2.12(vue@3.4.34(typescript@5.5.4))
+      vue: 3.4.34(typescript@5.5.4)
+
   w3c-xmlserializer@5.0.0:
     dependencies:
       xml-name-validator: 5.0.0

+ 4 - 0
vben-admin.code-workspace

@@ -8,6 +8,10 @@
       "name": "@vben/web-antd",
       "path": "apps/web-antd",
     },
+    {
+      "name": "@vben/web-naive",
+      "path": "apps/web-naive",
+    },
     {
       "name": "@vben/commitlint-config",
       "path": "internal/lint-configs/commitlint-config",