diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f60b04778..b81bc4ad4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: - windows-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/changeset-version.yml b/.github/workflows/changeset-version.yml index cdccceb54..32421e264 100644 --- a/.github/workflows/changeset-version.yml +++ b/.github/workflows/changeset-version.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1bc01c1c..254ffd8e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -67,7 +67,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -90,7 +90,7 @@ jobs: - windows-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e0fbcacf4..4824318e5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -57,7 +57,7 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4916d417a..8c12d408f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -98,7 +98,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index a745510c0..d87a70112 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/apps/web-antd/src/router/routes/modules/dashboard.ts b/apps/web-antd/src/router/routes/modules/dashboard.ts index 2cccc8115..fc4737eda 100644 --- a/apps/web-antd/src/router/routes/modules/dashboard.ts +++ b/apps/web-antd/src/router/routes/modules/dashboard.ts @@ -12,18 +12,9 @@ const routes: RouteRecordRaw[] = [ name: 'Dashboard', path: '/dashboard', children: [ - { - name: 'Workspace', - path: '/workspace', - component: () => import('#/views/dashboard/workspace/index.vue'), - meta: { - icon: 'carbon:workspace', - title: $t('page.dashboard.workspace'), - }, - }, { name: 'Analytics', - path: '/analytics', + path: 'analytics', component: () => import('#/views/dashboard/analytics/index.vue'), meta: { affixTab: true, @@ -31,6 +22,15 @@ const routes: RouteRecordRaw[] = [ title: $t('page.dashboard.analytics'), }, }, + { + name: 'Workspace', + path: 'workspace', + component: () => import('#/views/dashboard/workspace/index.vue'), + meta: { + icon: 'carbon:workspace', + title: $t('page.dashboard.workspace'), + }, + }, ], }, { diff --git a/apps/web-antdv-next/src/main.ts b/apps/web-antdv-next/src/main.ts index 110acbd7c..da0d81d38 100644 --- a/apps/web-antdv-next/src/main.ts +++ b/apps/web-antdv-next/src/main.ts @@ -1,7 +1,7 @@ import { initPreferences } from '@vben/preferences'; import { unmountGlobalLoading } from '@vben/utils'; -import { overridesPreferences, preferencesExtension } from './preferences'; +import { overridesPreferences } from './preferences'; /** * 应用初始化完成之后再进行页面加载渲染 @@ -15,7 +15,7 @@ async function initApplication() { // app偏好设置初始化 await initPreferences({ - extension: preferencesExtension, + // extension: preferencesExtension, namespace, overrides: overridesPreferences, }); diff --git a/apps/web-antdv-next/src/preferences.ts b/apps/web-antdv-next/src/preferences.ts index 7880de3d0..cf92fc6d7 100644 --- a/apps/web-antdv-next/src/preferences.ts +++ b/apps/web-antdv-next/src/preferences.ts @@ -1,14 +1,4 @@ -import { - defineOverridesPreferences, - definePreferencesExtension, -} from '@vben/preferences'; - -interface WebAntdPreferencesExtension { - defaultTableSize: number; - enableFormFullscreen: boolean; - reportTitle: string; - tenantMode: 'multi' | 'single'; -} +import { defineOverridesPreferences } from '@vben/preferences'; /** * @description 项目配置文件 @@ -35,51 +25,51 @@ export const overridesPreferences = defineOverridesPreferences({ // copyright: appCopyrightPreferences, }); -export const preferencesExtension = - definePreferencesExtension({ - tabLabel: 'preferences.antd.tabLabel', - title: 'preferences.antd.title', - fields: [ - { - component: 'switch', - defaultValue: true, - key: 'enableFormFullscreen', - label: 'preferences.antd.fields.enableFormFullscreen.label', - tip: 'preferences.antd.fields.enableFormFullscreen.tip', - }, - { - component: 'select', - defaultValue: 'single', - key: 'tenantMode', - label: 'preferences.antd.fields.tenantMode.label', - options: [ - { - label: 'preferences.antd.fields.tenantMode.options.single.label', - value: 'single', - }, - { - label: 'preferences.antd.fields.tenantMode.options.multi.label', - value: 'multi', - }, - ], - }, - { - component: 'number', - componentProps: { - max: 200, - min: 10, - step: 10, - }, - defaultValue: 20, - key: 'defaultTableSize', - label: 'preferences.antd.fields.defaultTableSize.label', - }, - { - component: 'input', - defaultValue: '', - key: 'reportTitle', - label: 'preferences.antd.fields.reportTitle.label', - placeholder: 'preferences.antd.fields.reportTitle.placeholder', - }, - ], - }); +// export const preferencesExtension = +// definePreferencesExtension({ +// tabLabel: 'preferences.antd.tabLabel', +// title: 'preferences.antd.title', +// fields: [ +// { +// component: 'switch', +// defaultValue: true, +// key: 'enableFormFullscreen', +// label: 'preferences.antd.fields.enableFormFullscreen.label', +// tip: 'preferences.antd.fields.enableFormFullscreen.tip', +// }, +// { +// component: 'select', +// defaultValue: 'single', +// key: 'tenantMode', +// label: 'preferences.antd.fields.tenantMode.label', +// options: [ +// { +// label: 'preferences.antd.fields.tenantMode.options.single.label', +// value: 'single', +// }, +// { +// label: 'preferences.antd.fields.tenantMode.options.multi.label', +// value: 'multi', +// }, +// ], +// }, +// { +// component: 'number', +// componentProps: { +// max: 200, +// min: 10, +// step: 10, +// }, +// defaultValue: 20, +// key: 'defaultTableSize', +// label: 'preferences.antd.fields.defaultTableSize.label', +// }, +// { +// component: 'input', +// defaultValue: '', +// key: 'reportTitle', +// label: 'preferences.antd.fields.reportTitle.label', +// placeholder: 'preferences.antd.fields.reportTitle.placeholder', +// }, +// ], +// }); diff --git a/apps/web-antdv-next/src/router/routes/modules/dashboard.ts b/apps/web-antdv-next/src/router/routes/modules/dashboard.ts index 2cccc8115..fc4737eda 100644 --- a/apps/web-antdv-next/src/router/routes/modules/dashboard.ts +++ b/apps/web-antdv-next/src/router/routes/modules/dashboard.ts @@ -12,18 +12,9 @@ const routes: RouteRecordRaw[] = [ name: 'Dashboard', path: '/dashboard', children: [ - { - name: 'Workspace', - path: '/workspace', - component: () => import('#/views/dashboard/workspace/index.vue'), - meta: { - icon: 'carbon:workspace', - title: $t('page.dashboard.workspace'), - }, - }, { name: 'Analytics', - path: '/analytics', + path: 'analytics', component: () => import('#/views/dashboard/analytics/index.vue'), meta: { affixTab: true, @@ -31,6 +22,15 @@ const routes: RouteRecordRaw[] = [ title: $t('page.dashboard.analytics'), }, }, + { + name: 'Workspace', + path: 'workspace', + component: () => import('#/views/dashboard/workspace/index.vue'), + meta: { + icon: 'carbon:workspace', + title: $t('page.dashboard.workspace'), + }, + }, ], }, { diff --git a/apps/web-ele/src/router/routes/modules/dashboard.ts b/apps/web-ele/src/router/routes/modules/dashboard.ts index 2cccc8115..5b926aad0 100644 --- a/apps/web-ele/src/router/routes/modules/dashboard.ts +++ b/apps/web-ele/src/router/routes/modules/dashboard.ts @@ -23,7 +23,7 @@ const routes: RouteRecordRaw[] = [ }, { name: 'Analytics', - path: '/analytics', + path: 'analytics', component: () => import('#/views/dashboard/analytics/index.vue'), meta: { affixTab: true, @@ -31,6 +31,15 @@ const routes: RouteRecordRaw[] = [ title: $t('page.dashboard.analytics'), }, }, + { + name: 'Workspace', + path: 'workspace', + component: () => import('#/views/dashboard/workspace/index.vue'), + meta: { + icon: 'carbon:workspace', + title: $t('page.dashboard.workspace'), + }, + }, ], }, { diff --git a/apps/web-naive/src/router/routes/modules/dashboard.ts b/apps/web-naive/src/router/routes/modules/dashboard.ts index 5254dc65d..44f15ef9a 100644 --- a/apps/web-naive/src/router/routes/modules/dashboard.ts +++ b/apps/web-naive/src/router/routes/modules/dashboard.ts @@ -14,7 +14,7 @@ const routes: RouteRecordRaw[] = [ children: [ { name: 'Analytics', - path: '/analytics', + path: 'analytics', component: () => import('#/views/dashboard/analytics/index.vue'), meta: { affixTab: true, @@ -24,7 +24,7 @@ const routes: RouteRecordRaw[] = [ }, { name: 'Workspace', - path: '/workspace', + path: 'workspace', component: () => import('#/views/dashboard/workspace/index.vue'), meta: { icon: 'carbon:workspace', diff --git a/apps/web-tdesign/src/router/routes/modules/dashboard.ts b/apps/web-tdesign/src/router/routes/modules/dashboard.ts index 5254dc65d..44f15ef9a 100644 --- a/apps/web-tdesign/src/router/routes/modules/dashboard.ts +++ b/apps/web-tdesign/src/router/routes/modules/dashboard.ts @@ -14,7 +14,7 @@ const routes: RouteRecordRaw[] = [ children: [ { name: 'Analytics', - path: '/analytics', + path: 'analytics', component: () => import('#/views/dashboard/analytics/index.vue'), meta: { affixTab: true, @@ -24,7 +24,7 @@ const routes: RouteRecordRaw[] = [ }, { name: 'Workspace', - path: '/workspace', + path: 'workspace', component: () => import('#/views/dashboard/workspace/index.vue'), meta: { icon: 'carbon:workspace', diff --git a/cspell.json b/cspell.json index 8e43d3293..64f9a9758 100644 --- a/cspell.json +++ b/cspell.json @@ -30,6 +30,7 @@ "gitee", "iconify", "iconoir", + "indexeddb", "intlify", "isequal", "jspm", @@ -41,6 +42,7 @@ "mkdist", "mockjs", "moddle", + "myapp", "naiveui", "napi", "nocheck", diff --git a/docs/src/en/guide/essentials/route.md b/docs/src/en/guide/essentials/route.md index 8fb0a6d1c..71540b2a5 100644 --- a/docs/src/en/guide/essentials/route.md +++ b/docs/src/en/guide/essentials/route.md @@ -61,6 +61,13 @@ The configuration method of static routes and dynamic routes is the same. Below ### Secondary Routes +::: tip + +- Only top-level routes should start their `path` with `/`; routes inside `children` should use relative segments such as `about` or `menu1`. +- When a parent route does not define `redirect`, the framework automatically fills it with the first child route. This only works when child paths stay relative. + +::: + ::: details Secondary Route Example Code ```ts @@ -68,7 +75,6 @@ import type { RouteRecordRaw } from 'vue-router'; import { VBEN_LOGO_URL } from '@vben/constants'; -import { BasicLayout } from '#/layouts'; import { $t } from '#/locales'; const routes: RouteRecordRaw[] = [ @@ -82,11 +88,10 @@ const routes: RouteRecordRaw[] = [ }, name: 'VbenProject', path: '/vben-admin', - redirect: '/vben-admin/about', children: [ { name: 'VbenAbout', - path: '/vben-admin/about', + path: 'about', component: () => import('#/views/_core/about/index.vue'), meta: { badgeType: 'dot', @@ -108,6 +113,7 @@ export default routes; ::: tip +- Multi-level routes follow the same rule: keep top-level paths absolute and child paths relative, instead of repeating the full path inside `children`. - The parent route of multi-level routes does not need to set the `component` property, just set the `children` property. Unless you really need to display content nested under the parent route. - In most cases, the `redirect` property of the parent route does not need to be specified, it will default to the first child route. @@ -118,7 +124,6 @@ export default routes; ```ts import type { RouteRecordRaw } from 'vue-router'; -import { BasicLayout } from '#/layouts'; import { $t } from '#/locales'; const routes: RouteRecordRaw[] = [ @@ -131,7 +136,6 @@ const routes: RouteRecordRaw[] = [ }, name: 'Demos', path: '/demos', - redirect: '/demos/access', children: [ // Nested menu { @@ -140,12 +144,11 @@ const routes: RouteRecordRaw[] = [ title: $t('demos.nested.title'), }, name: 'NestedDemos', - path: '/demos/nested', - redirect: '/demos/nested/menu1', + path: 'nested', children: [ { name: 'Menu1Demo', - path: '/demos/nested/menu1', + path: 'menu1', component: () => import('#/views/demos/nested/menu-1.vue'), meta: { icon: 'ic:round-menu', @@ -155,17 +158,16 @@ const routes: RouteRecordRaw[] = [ }, { name: 'Menu2Demo', - path: '/demos/nested/menu2', + path: 'menu2', meta: { icon: 'ic:round-menu', keepAlive: true, title: $t('demos.nested.menu2'), }, - redirect: '/demos/nested/menu2/menu2-1', children: [ { name: 'Menu21Demo', - path: '/demos/nested/menu2/menu2-1', + path: 'menu2-1', component: () => import('#/views/demos/nested/menu-2-1.vue'), meta: { icon: 'ic:round-menu', @@ -177,12 +179,11 @@ const routes: RouteRecordRaw[] = [ }, { name: 'Menu3Demo', - path: '/demos/nested/menu3', + path: 'menu3', meta: { icon: 'ic:round-menu', title: $t('demos.nested.menu3'), }, - redirect: '/demos/nested/menu3/menu3-1', children: [ { name: 'Menu31Demo', @@ -201,11 +202,10 @@ const routes: RouteRecordRaw[] = [ icon: 'ic:round-menu', title: $t('demos.nested.menu3_2'), }, - redirect: '/demos/nested/menu3/menu3-2/menu3-2-1', children: [ { name: 'Menu321Demo', - path: '/demos/nested/menu3/menu3-2/menu3-2-1', + path: 'menu3-2-1', component: () => import('#/views/demos/nested/menu-3-2-1.vue'), meta: { @@ -242,7 +242,6 @@ import type { RouteRecordRaw } from 'vue-router'; import { VBEN_LOGO_URL } from '@vben/constants'; -import { BasicLayout } from '#/layouts'; import { $t } from '#/locales'; const routes: RouteRecordRaw[] = [ @@ -253,11 +252,10 @@ const routes: RouteRecordRaw[] = [ }, name: 'Home', path: '/home', - redirect: '/home/index', children: [ { name: 'HomeIndex', - path: '/home/index', + path: 'index', component: () => import('#/views/home/index.vue'), meta: { icon: 'mdi:home', @@ -294,11 +292,11 @@ The route configuration items are mainly in the `meta` property of the route obj ```ts {5-8} const routes = [ { - name: 'HomeIndex', - path: '/home/index', + name: 'Home', + path: '/home', meta: { icon: 'mdi:home', - title: $t('page.home.index'), + title: $t('page.home.title'), }, }, ]; diff --git a/docs/src/en/guide/essentials/server.md b/docs/src/en/guide/essentials/server.md index b17db80dd..770a0e453 100644 --- a/docs/src/en/guide/essentials/server.md +++ b/docs/src/en/guide/essentials/server.md @@ -4,7 +4,7 @@ This document explains how to use Mock data and interact with the server in a development environment, involving technologies such as: -- [Nitro](https://nitro.unjs.io/) A lightweight backend server that can be deployed anywhere, used as a Mock server in the project. +- [Nitro](https://nitro.build/) A lightweight backend server that can be deployed anywhere, used as a Mock server in the project. - [axios](https://axios-http.com/docs/intro) Used to send HTTP requests to interact with the server. ::: @@ -63,16 +63,18 @@ Based on the above configuration, we can use `/api` as the prefix for API reques ```ts import axios from 'axios'; -axios.get('/api/user').then((res) => { - console.log(res); -}); +axios + .post('/api/auth/login', { username: 'vben', password: '123456' }) + .then((res) => { + console.log(res); + }); ``` -At this point, the request will be proxied to `http://localhost:5320/api/user`. +At this point, the request will be proxied to `http://localhost:5320/api/auth/login`. ::: warning Note -From the browser's console Network tab, the request appears as `http://localhost:5555/api/user`. This is because the proxy configuration does not change the local request's URL. +From the browser's console Network tab, the request appears as `http://localhost:5555/api/auth/login`. This is because the proxy configuration does not change the local request's URL. ::: @@ -338,13 +340,13 @@ The new version no longer supports mock in the production environment. Please us Mock data is an indispensable part of frontend development, serving as a key link in separating frontend and backend development. By agreeing on interfaces with the server side in advance and simulating request data and even logic, frontend development can proceed independently, without being blocked by the backend development process. -The project uses [Nitro](https://nitro.unjs.io/) for local mock data processing. The principle is to start an additional backend service locally, which is a real backend service that can handle requests and return data. +The project uses [Nitro](https://nitro.build/) for local mock data processing. The principle is to start an additional backend service locally, which is a real backend service that can handle requests and return data. ### Using Nitro The mock service code is located in the `apps/backend-mock` directory. It does not need to be started manually and is already integrated into the project. You only need to run `pnpm dev` in the project root directory. After running successfully, the console will print `http://localhost:5320/api`, and you can access this address to view the mock service. -[Nitro](https://nitro.unjs.io/) syntax is simple, and you can configure and develop according to your needs. For specific configurations, you can refer to the [Nitro documentation](https://nitro.unjs.io/). +[Nitro](https://nitro.build/) syntax is simple, and you can configure and develop according to your needs. For specific configurations, you can refer to the [Nitro documentation](https://nitro.build/docs/). ## Disabling Mock Service diff --git a/docs/src/en/guide/essentials/settings.md b/docs/src/en/guide/essentials/settings.md index 3bbc4383b..5c934dfa1 100644 --- a/docs/src/en/guide/essentials/settings.md +++ b/docs/src/en/guide/essentials/settings.md @@ -18,7 +18,7 @@ The rules are consistent with [Vite Env Variables and Modes](https://vitejs.dev/ - Only variables starting with `VITE_` will be embedded into the client-side package. You can access them in the project code like this: ```ts - console.log(import.meta.env.VITE_PROT); + console.log(import.meta.env.VITE_PORT); ``` - Variables starting with `VITE_GLOB_*` will be added to the `_app-config-{version}-{hash}.js` configuration file during packaging. @@ -365,7 +365,7 @@ const defaultPreferences: Preferences = { contentPaddingTop: 0, defaultAvatar: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp', - defaultHomePath: '/analytics', + defaultHomePath: '/dashboard/analytics', dynamicTitle: true, enableCheckUpdates: true, enablePreferences: true, diff --git a/docs/src/en/index.md b/docs/src/en/index.md index 42aeef3ff..a91cc4803 100644 --- a/docs/src/en/index.md +++ b/docs/src/en/index.md @@ -69,7 +69,7 @@ features: icon: src: /logos/nitro.svg details: Built-in Nitro Mock service makes your mock service more powerful. - link: https://nitro.unjs.io/ + link: https://nitro.build/ linkText: Official Site --- diff --git a/docs/src/guide/essentials/route.md b/docs/src/guide/essentials/route.md index 8383e7620..9668e8d54 100644 --- a/docs/src/guide/essentials/route.md +++ b/docs/src/guide/essentials/route.md @@ -55,6 +55,13 @@ const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); ### 二级路由 +::: tip + +- 只有顶级路由的 `path` 需要以 `/` 开头;写在 `children` 中的子路由请使用相对路径片段,例如 `about`、`menu1`。 +- 当父级路由未显式配置 `redirect` 时,框架会自动补全到第一个子路由的重定向;这依赖子路由使用相对路径。 + +::: + ::: details 二级路由示例代码 ```ts @@ -75,11 +82,10 @@ const routes: RouteRecordRaw[] = [ }, name: 'VbenProject', path: '/vben-admin', - redirect: '/vben-admin/about', children: [ { name: 'VbenAbout', - path: '/vben-admin/about', + path: 'about', component: () => import('#/views/_core/about/index.vue'), meta: { badgeType: 'dot', @@ -101,6 +107,7 @@ export default routes; ::: tip +- 多级路由同样遵循“顶级绝对路径、子级相对路径”的组织方式,避免在 `children` 中重复写完整路径。 - 如果没有特殊情况,父级路由的 `redirect` 属性,不需要指定,默认会指向第一个子路由。 ::: @@ -122,7 +129,6 @@ const routes: RouteRecordRaw[] = [ }, name: 'Demos', path: '/demos', - redirect: '/demos/access', children: [ // 嵌套菜单 { @@ -131,12 +137,11 @@ const routes: RouteRecordRaw[] = [ title: $t('demos.nested.title'), }, name: 'NestedDemos', - path: '/demos/nested', - redirect: '/demos/nested/menu1', + path: 'nested', children: [ { name: 'Menu1Demo', - path: '/demos/nested/menu1', + path: 'menu1', component: () => import('#/views/demos/nested/menu-1.vue'), meta: { icon: 'ic:round-menu', @@ -146,17 +151,16 @@ const routes: RouteRecordRaw[] = [ }, { name: 'Menu2Demo', - path: '/demos/nested/menu2', + path: 'menu2', meta: { icon: 'ic:round-menu', keepAlive: true, title: $t('demos.nested.menu2'), }, - redirect: '/demos/nested/menu2/menu2-1', children: [ { name: 'Menu21Demo', - path: '/demos/nested/menu2/menu2-1', + path: 'menu2-1', component: () => import('#/views/demos/nested/menu-2-1.vue'), meta: { icon: 'ic:round-menu', @@ -168,12 +172,11 @@ const routes: RouteRecordRaw[] = [ }, { name: 'Menu3Demo', - path: '/demos/nested/menu3', + path: 'menu3', meta: { icon: 'ic:round-menu', title: $t('demos.nested.menu3'), }, - redirect: '/demos/nested/menu3/menu3-1', children: [ { name: 'Menu31Demo', @@ -192,11 +195,10 @@ const routes: RouteRecordRaw[] = [ icon: 'ic:round-menu', title: $t('demos.nested.menu3_2'), }, - redirect: '/demos/nested/menu3/menu3-2/menu3-2-1', children: [ { name: 'Menu321Demo', - path: '/demos/nested/menu3/menu3-2/menu3-2-1', + path: 'menu3-2-1', component: () => import('#/views/demos/nested/menu-3-2-1.vue'), meta: { @@ -243,11 +245,10 @@ const routes: RouteRecordRaw[] = [ }, name: 'Home', path: '/home', - redirect: '/home/index', children: [ { name: 'HomeIndex', - path: '/home/index', + path: 'index', component: () => import('#/views/home/index.vue'), meta: { icon: 'mdi:home', @@ -284,11 +285,11 @@ export default routes; ```ts {5-8} const routes = [ { - name: 'HomeIndex', - path: '/home/index', + name: 'Home', + path: '/home', meta: { icon: 'mdi:home', - title: $t('page.home.index'), + title: $t('page.home.title'), }, }, ]; diff --git a/docs/src/guide/essentials/server.md b/docs/src/guide/essentials/server.md index 2ae16e763..304c500ee 100644 --- a/docs/src/guide/essentials/server.md +++ b/docs/src/guide/essentials/server.md @@ -4,7 +4,7 @@ 本文档介绍如何在开发环境下使用 Mock 数据和与服务端进行交互,涉及到的技术有: -- [Nitro](https://nitro.unjs.io/) 轻量级后端服务器,可部署在任何地方,项目用作于 Mock 服务器。 +- [Nitro](https://nitro.build/) 轻量级后端服务器,可部署在任何地方,项目用作于 Mock 服务器。 - [axios](https://axios-http.com/docs/intro) 用于发送 HTTP 请求与服务端进行交互。 ::: @@ -63,16 +63,18 @@ export default defineConfig(async () => { ```ts import axios from 'axios'; -axios.get('/api/user').then((res) => { - console.log(res); -}); +axios + .post('/api/auth/login', { username: 'vben', password: '123456' }) + .then((res) => { + console.log(res); + }); ``` -此时,请求会被代理到 `http://localhost:5320/api/user`。 +此时,请求会被代理到 `http://localhost:5320/api/auth/login`。 ::: warning 注意 -从浏览器控制台的 Network 看,请求是 `http://localhost:5555/api/user`, 这是因为 proxy 配置不会改变本地请求的 url。 +从浏览器控制台的 Network 看,请求是 `http://localhost:5555/api/auth/login`, 这是因为 proxy 配置不会改变本地请求的 url。 ::: @@ -369,13 +371,13 @@ async function doRefreshToken() { Mock 数据是前端开发过程中必不可少的一环,是分离前后端开发的关键链路。通过预先跟服务器端约定好的接口,模拟请求数据甚至逻辑,能够让前端开发独立自主,不会被服务端的开发进程所阻塞。 -项目使用 [Nitro](https://nitro.unjs.io/) 来进行本地 mock 数据处理。其原理是本地额外启动一个后端服务,是一个真实的后端服务,可以处理请求,返回数据。 +项目使用 [Nitro](https://nitro.build/) 来进行本地 mock 数据处理。其原理是本地额外启动一个后端服务,是一个真实的后端服务,可以处理请求,返回数据。 ### Nitro 使用 Mock 服务代码位于`apps/backend-mock`目录下,无需手动启动,已经集成在项目中,只需要在项目根目录下运行`pnpm dev`即可,运行成功之后,控制台会打印 `http://localhost:5320/api`, 访问该地址即可查看 mock 服务。 -[Nitro](https://nitro.unjs.io/) 语法简单,可以根据自己的需求进行配置及开发,具体配置可以查看 [Nitro 文档](https://nitro.unjs.io/)。 +[Nitro](https://nitro.build/) 语法简单,可以根据自己的需求进行配置及开发,具体配置可以查看 [Nitro 文档](https://nitro.build/docs/)。 ## 关闭 Mock 服务 diff --git a/docs/src/guide/essentials/settings.md b/docs/src/guide/essentials/settings.md index 32111d4f1..ec50b18b4 100644 --- a/docs/src/guide/essentials/settings.md +++ b/docs/src/guide/essentials/settings.md @@ -18,7 +18,7 @@ - 只有以 `VITE_` 开头的变量会被嵌入到客户端侧的包中,你可以在项目代码中这样访问它们: ```ts - console.log(import.meta.env.VITE_PROT); + console.log(import.meta.env.VITE_PORT); ``` - 以 `VITE_GLOB_*` 开头的的变量,在打包的时候,会被加入 `_app-config-{version}-{hash}.js`配置文件当中. @@ -364,7 +364,7 @@ const defaultPreferences: Preferences = { contentPaddingTop: 0, defaultAvatar: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp', - defaultHomePath: '/analytics', + defaultHomePath: '/dashboard/analytics', dynamicTitle: true, enableCheckUpdates: true, enablePreferences: true, diff --git a/docs/src/index.md b/docs/src/index.md index 3a346d04c..82703af5e 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -72,7 +72,7 @@ features: icon: src: /logos/nitro.svg details: 内置 Nitro Mock 服务,让你的 mock 服务更加强大。 - link: https://nitro.unjs.io/ + link: https://nitro.build/ linkText: 官方站点 --- diff --git a/internal/lint-configs/eslint-config/src/configs/javascript.ts b/internal/lint-configs/eslint-config/src/configs/javascript.ts index bd9bd852b..0cab71f11 100644 --- a/internal/lint-configs/eslint-config/src/configs/javascript.ts +++ b/internal/lint-configs/eslint-config/src/configs/javascript.ts @@ -16,6 +16,7 @@ const rulesCoveredByOxlint = new Set([ 'no-const-assign', 'no-constant-binary-expression', 'no-constant-condition', + 'no-control-regex', 'no-debugger', 'no-delete-var', 'no-dupe-args', @@ -48,6 +49,8 @@ const rulesCoveredByOxlint = new Set([ 'no-shadow-restricted-names', 'no-sparse-arrays', 'no-this-before-super', + 'no-unassigned-vars', + 'no-unexpected-multiline', 'no-unreachable', 'no-unsafe-finally', 'no-unsafe-negation', @@ -59,6 +62,7 @@ const rulesCoveredByOxlint = new Set([ 'no-useless-catch', 'no-useless-escape', 'no-with', + 'preserve-caught-error', 'require-yield', 'use-isnan', 'valid-typeof', @@ -102,7 +106,6 @@ export async function javascript(): Promise { ...recommendedRules, 'dot-notation': ['error', { allowKeywords: true }], 'keyword-spacing': 'off', - 'no-control-regex': 'error', 'no-empty-function': 'off', 'no-octal': 'error', 'no-octal-escape': 'error', diff --git a/internal/lint-configs/oxlint-config/src/configs/javascript.ts b/internal/lint-configs/oxlint-config/src/configs/javascript.ts index 4352c8017..89784dc36 100644 --- a/internal/lint-configs/oxlint-config/src/configs/javascript.ts +++ b/internal/lint-configs/oxlint-config/src/configs/javascript.ts @@ -41,7 +41,7 @@ const javascript: OxlintConfig = { 'no-caller': 'error', 'no-case-declarations': 'error', 'no-console': ['error', { allow: ['warn', 'error'] }], - 'no-control-regex': 'off', + 'no-control-regex': 'error', 'no-debugger': 'error', 'no-empty': ['error', { allowEmptyCatch: true }], 'no-fallthrough': 'error', @@ -68,6 +68,8 @@ const javascript: OxlintConfig = { ], 'no-template-curly-in-string': 'error', 'no-throw-literal': 'error', + 'no-unassigned-vars': 'error', + 'no-unexpected-multiline': 'error', 'no-unused-expressions': [ 'error', { @@ -108,6 +110,12 @@ const javascript: OxlintConfig = { 'prefer-rest-params': 'error', 'prefer-spread': 'error', 'prefer-template': 'error', + 'preserve-caught-error': [ + 'error', + { + requireCatchParameter: false, + }, + ], 'symbol-description': 'error', 'unicode-bom': ['error', 'never'], 'use-isnan': [ diff --git a/internal/lint-configs/oxlint-config/src/configs/vue.ts b/internal/lint-configs/oxlint-config/src/configs/vue.ts index 172507d69..6e140b85d 100644 --- a/internal/lint-configs/oxlint-config/src/configs/vue.ts +++ b/internal/lint-configs/oxlint-config/src/configs/vue.ts @@ -2,6 +2,7 @@ import type { OxlintConfig } from 'oxlint'; const vue: OxlintConfig = { rules: { + 'vue/no-reserved-component-names': 'off', 'vue/prefer-import-from-vue': 'error', }, }; diff --git a/internal/node-utils/src/git.ts b/internal/node-utils/src/git.ts index 88f159cc5..da6dc74cc 100644 --- a/internal/node-utils/src/git.ts +++ b/internal/node-utils/src/git.ts @@ -20,7 +20,13 @@ async function getStagedFiles(): Promise { '-z', ]); - let changedList = stdout ? stdout.replace(/\0$/, '').split('\0') : []; + const nullSeparator = '\u0000'; + const normalizedStdout = stdout.endsWith(nullSeparator) + ? stdout.slice(0, -1) + : stdout; + let changedList = normalizedStdout + ? normalizedStdout.split(nullSeparator) + : []; changedList = changedList.map((item) => path.resolve(process.cwd(), item)); const changedSet = new Set(changedList); changedSet.delete(''); diff --git a/packages/@core/base/shared/src/utils/tree.ts b/packages/@core/base/shared/src/utils/tree.ts index 6a2e98af4..3f6080b63 100644 --- a/packages/@core/base/shared/src/utils/tree.ts +++ b/packages/@core/base/shared/src/utils/tree.ts @@ -192,16 +192,22 @@ function filterTree>( */ function mapTree>( tree: T[], - mapper: (node: T) => V, + mapper: (node: T, parent: null | V) => V, options?: TreeConfigOptions, + parent: null | V = null, ): V[] { const { childProps } = options || { childProps: 'children', }; return tree.map((node) => { - const mapperNode: Record = mapper(node); + const mapperNode: Record = mapper(node, parent as null | V); if (mapperNode[childProps]) { - mapperNode[childProps] = mapTree(mapperNode[childProps], mapper, options); + mapperNode[childProps] = mapTree( + mapperNode[childProps], + mapper, + options, + mapperNode as V, + ); } return mapperNode as V; }); diff --git a/packages/@core/base/typings/src/basic.d.ts b/packages/@core/base/typings/src/basic.d.ts index 04e25d5e9..3082d4d05 100644 --- a/packages/@core/base/typings/src/basic.d.ts +++ b/packages/@core/base/typings/src/basic.d.ts @@ -8,6 +8,7 @@ type SelectOption = BasicOption; type TabOption = BasicOption; interface BasicUserInfo { + [key: string]: any; /** * 头像 */ diff --git a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap index 6a74bb4f6..7746430a7 100644 --- a/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap @@ -17,7 +17,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj "contentPaddingRight": 0, "contentPaddingTop": 0, "defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp", - "defaultHomePath": "/analytics", + "defaultHomePath": "/dashboard", "dynamicTitle": true, "enableCheckUpdates": true, "enableCopyPreferences": true, @@ -66,6 +66,8 @@ exports[`defaultPreferences immutability test > should not modify the config obj "logo": { "enable": true, "fit": "contain", + "logoMode": "icon", + "showText": true, "source": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp", }, "navigation": { diff --git a/packages/@core/preferences/src/config.ts b/packages/@core/preferences/src/config.ts index 98df0cbf5..9160a8705 100644 --- a/packages/@core/preferences/src/config.ts +++ b/packages/@core/preferences/src/config.ts @@ -17,7 +17,7 @@ const defaultPreferences: Preferences = { contentPaddingTop: 0, defaultAvatar: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp', - defaultHomePath: '/analytics', + defaultHomePath: '/dashboard', dynamicTitle: true, enableCheckUpdates: true, enableCopyPreferences: true, @@ -68,6 +68,8 @@ const defaultPreferences: Preferences = { enable: true, fit: 'contain', source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', + showText: true, + logoMode: 'icon', }, navigation: { accordion: true, diff --git a/packages/@core/preferences/src/types.ts b/packages/@core/preferences/src/types.ts index 759d78934..5834c34c0 100644 --- a/packages/@core/preferences/src/types.ts +++ b/packages/@core/preferences/src/types.ts @@ -228,6 +228,12 @@ interface LogoPreferences { enable: boolean; /** logo图片适应方式 */ fit: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'; + /** logo高度, 只在 logoMode=full时生效 */ + fullLogoHeight?: number | string; + /** logo 展示类型,icon 图标模式, full 铺满logo区域 */ + logoMode: 'full' | 'icon'; + /** logo text是否展示 */ + showText: boolean; /** logo地址 */ source: string; /** 暗色主题logo地址 (可选,若不设置则使用 source) */ diff --git a/packages/@core/ui-kit/layout-ui/src/components/layout-header.vue b/packages/@core/ui-kit/layout-ui/src/components/layout-header.vue index fba07d4e6..0f980b389 100644 --- a/packages/@core/ui-kit/layout-ui/src/components/layout-header.vue +++ b/packages/@core/ui-kit/layout-ui/src/components/layout-header.vue @@ -16,6 +16,10 @@ interface Props { * 是否移动端 */ isMobile: boolean; + /** + * logo是否显示 + */ + logoVisible?: boolean; /** * 是否显示 */ @@ -38,7 +42,9 @@ interface Props { zIndex: number; } -const props = withDefaults(defineProps(), {}); +const props = withDefaults(defineProps(), { + logoVisible: true, +}); const slots = useSlots(); @@ -54,6 +60,12 @@ const style = computed((): CSSProperties => { }); const logoStyle = computed((): CSSProperties => { + if (!props.logoVisible) { + return { + minWidth: '12px', + }; + } + return { minWidth: `${props.isMobile ? 40 : props.sidebarWidth}px`, }; @@ -66,7 +78,7 @@ const logoStyle = computed((): CSSProperties => { :style="style" class="top-0 flex w-full flex-[0_0_auto] items-center border-b border-border bg-header pl-2 transition-[margin-top] duration-200" > -
+
diff --git a/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue b/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue index 11e4081c4..b7e2fab61 100644 --- a/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue +++ b/packages/@core/ui-kit/layout-ui/src/components/layout-sidebar.vue @@ -26,6 +26,10 @@ interface Props { * @default true */ domVisible?: boolean; + /** + * 扩展区域extra-title的高度 + */ + extraTitleHeight?: number; /** * 扩展区域宽度 */ @@ -97,6 +101,7 @@ const props = withDefaults(defineProps(), { collapseHeight: 42, collapseWidth: 48, domVisible: true, + extraTitleHeight: undefined, fixedExtra: false, isSidebarMixed: false, marginTop: 0, @@ -149,10 +154,10 @@ const extraStyle = computed((): CSSProperties => { }); const extraTitleStyle = computed((): CSSProperties => { - const { headerHeight } = props; + const { extraTitleHeight, headerHeight } = props; return { - height: `${headerHeight - 1}px`, + height: `${extraTitleHeight ?? headerHeight - 1}px`, }; }); @@ -185,9 +190,10 @@ const headerStyle = computed((): CSSProperties => { }); const extraContentStyle = computed((): CSSProperties => { - const { collapseHeight, headerHeight } = props; + const { collapseHeight, extraTitleHeight, headerHeight } = props; + const titleHeight = extraTitleHeight ?? headerHeight; return { - height: `calc(100% - ${headerHeight + collapseHeight}px)`, + height: `calc(100% - ${titleHeight + collapseHeight}px)`, }; }); diff --git a/packages/@core/ui-kit/layout-ui/src/vben-layout.ts b/packages/@core/ui-kit/layout-ui/src/vben-layout.ts index b6b413751..6cbe54f5b 100644 --- a/packages/@core/ui-kit/layout-ui/src/vben-layout.ts +++ b/packages/@core/ui-kit/layout-ui/src/vben-layout.ts @@ -126,6 +126,10 @@ interface VbenLayoutProps { * @default 48 */ sidebarExtraCollapsedWidth?: number; + /** + * 扩展区域extra-title的高度 + */ + sidebarExtraTitleHeight?: number; /** * 侧边菜单折叠按钮是否固定 * @default true @@ -136,6 +140,10 @@ interface VbenLayoutProps { * @default false */ sidebarHidden?: boolean; + /** + * 侧边栏 Logo 区域是否显示 + */ + sidebarLogoVisible: boolean; /** * 混合侧边栏宽度 * @default 80 diff --git a/packages/@core/ui-kit/layout-ui/src/vben-layout.vue b/packages/@core/ui-kit/layout-ui/src/vben-layout.vue index d8a2c422c..b6bfcca20 100644 --- a/packages/@core/ui-kit/layout-ui/src/vben-layout.vue +++ b/packages/@core/ui-kit/layout-ui/src/vben-layout.vue @@ -368,6 +368,17 @@ const maskStyle = computed((): CSSProperties => { return { zIndex: props.zIndex }; }); +/** + * 侧边栏 Logo 区域是否显示 + */ +const sidebarHeaderHeight = computed(() => { + if (isMixedNav.value || !props.sidebarLogoVisible) { + return 0; + } + + return props.headerHeight; +}); + const showHeaderToggleButton = computed(() => { return ( props.isMobile || @@ -510,7 +521,10 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT; :dom-visible="!isMobile" :extra-width="sidebarExtraWidth" :fixed-extra="sidebarExpandOnHover" - :header-height="isMixedNav ? 0 : headerHeight" + :header-height="sidebarHeaderHeight" + :extra-title-height=" + isSidebarMixedNav || isHeaderMixedNav ? sidebarExtraTitleHeight : 0 + " :is-sidebar-mixed="isSidebarMixedNav || isHeaderMixedNav" :margin-top="sidebarMarginTop" :mixed-width="sidebarMixedWidth" @@ -522,7 +536,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT; @leave="() => emit('sideMouseLeave')" @update:width="(val) => emit('update:sidebarWidth', val)" > -