From 54755c58420e707e63422faa9b3f9d0566f1f833 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 19 Oct 2022 16:54:13 +0800 Subject: [PATCH] refactor: router and menu generation (#651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind api-change /kind improvement /milestone 2.0 #### What this PR does / why we need it: Ref https://github.com/halo-dev/halo/issues/2595 重构路由和侧边菜单生成的逻辑,**注意,此 PR 对插件的 Console 入口文件中的路由和菜单定义包含破坏性更新。** 1. 移除 `definePlugin` 方法的 `menus` 字段,改为在 route 的 meta 中定义。 2. 将 `RoutesMenu` 组件从 `@halo-dev/components` 包中移出。 3. 将 `BasicLayout` 组件从 `@halo-dev/console-shared` 包中移出。 定义路由的方式: ```ts import { definePlugin } from "@halo-dev/console-shared"; import BasicLayout from "@/layouts/BasicLayout.vue"; import AttachmentList from "./AttachmentList.vue"; import AttachmentSelectorModal from "./components/AttachmentSelectorModal.vue"; import { IconFolder } from "@halo-dev/components"; import { markRaw } from "vue"; export default definePlugin({ name: "attachmentModule", components: [AttachmentSelectorModal], routes: [ { path: "/attachments", component: BasicLayout, children: [ { path: "", name: "Attachments", component: AttachmentList, meta: { title: "附件", permissions: ["system:attachments:view"], menu: { name: "附件", group: "内容", icon: markRaw(IconFolder), priority: 4, mobile: true, }, }, }, ], }, ], }); ``` menu 字段类型: ```ts interface RouteMeta { title?: string; searchable?: boolean; permissions?: string[]; menu?: { name: string; group?: string; icon?: Component; priority: number; mobile?: true; }; } ``` 插件适配需要做的改动: 1. 移除 `definePlugin` 中的 menus 字段。 2. 在需要添加到菜单的 route 中提供 `meta.menu` 对象,可参考上方的 menu 字段类型。 详细文档可查阅:https://github.com/ruibaby/halo-console/tree/refactor/route-map-setting/docs/routes-generation todolist: - [x] 完善预设的菜单分组定义。 - [x] 绑定权限,根据权限决定是否需要将路由添加到菜单。 - [x] 优化菜单排序的定义方式。 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2595 #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式: 1. 需要 `pnpm build:packages` 2. 测试后台的菜单及路由是否有异常。 3. 新建角色测试路由和菜单对权限的绑定。 4. 按照 https://github.com/ruibaby/halo-console/tree/refactor/route-map-setting/docs/routes-generation 文档,创建插件,测试插件添加路由和菜单是否正常。 #### Does this PR introduce a user-facing change? ```release-note 重构路由和侧边菜单生成的逻辑。 ``` --- docs/routes-generation/README.md | 126 ++++++++++++ env.d.ts | 22 +++ package.json | 1 + .../menu/__tests__/RoutesMenu.spec.tsx | 8 - .../components/src/components/menu/index.ts | 2 - .../src/components/menu/interface.ts | 14 -- packages/shared/env.d.ts | 28 +++ packages/shared/package.json | 3 - packages/shared/postcss.config.js | 3 - packages/shared/src/assets/logo.svg | 1 - packages/shared/src/index.ts | 1 - .../src/layouts/__tests__/BasicLayout.spec.ts | 7 - packages/shared/src/layouts/index.ts | 2 - packages/shared/src/types/menus.ts | 12 +- packages/shared/src/types/plugin.ts | 5 +- packages/shared/tailwind.config.js | 3 - packages/shared/vite.config.ts | 3 +- pnpm-lock.yaml | 18 +- src/App.vue | 22 +-- .../global-search/GlobalSearchModal.vue | 9 +- .../components/menu/RoutesMenu.tsx | 10 +- .../src => src}/layouts/BasicLayout.vue | 184 +++++++++++++++--- .../src => src}/layouts/BlankLayout.vue | 0 src/main.ts | 53 ++--- src/modules/contents/attachments/module.ts | 23 +-- src/modules/contents/comments/module.ts | 23 +-- .../contents/pages/layouts/PageLayout.vue | 2 +- src/modules/contents/pages/module.ts | 29 ++- src/modules/contents/posts/module.ts | 28 ++- src/modules/dashboard/module.ts | 24 ++- src/modules/index.ts | 24 ++- src/modules/interface/menus/module.ts | 22 +-- .../interface/themes/layouts/ThemeLayout.vue | 2 +- src/modules/interface/themes/module.ts | 22 +-- .../system/plugins/layouts/PluginLayout.vue | 2 +- src/modules/system/plugins/module.ts | 27 +-- src/modules/system/roles/module.ts | 4 +- .../settings/layouts/SystemSettingsLayout.vue | 2 +- src/modules/system/settings/module.ts | 27 ++- .../users/layouts/UserProfileLayout.vue | 2 +- src/modules/system/users/module.ts | 28 ++- src/router/menus.config.ts | 72 ------- src/router/routes.config.ts | 31 ++- src/setup/setupStyles.ts | 1 - 44 files changed, 552 insertions(+), 380 deletions(-) create mode 100644 docs/routes-generation/README.md delete mode 100644 packages/components/src/components/menu/__tests__/RoutesMenu.spec.tsx delete mode 100644 packages/components/src/components/menu/interface.ts delete mode 100644 packages/shared/postcss.config.js delete mode 100644 packages/shared/src/assets/logo.svg delete mode 100644 packages/shared/src/layouts/__tests__/BasicLayout.spec.ts delete mode 100644 packages/shared/src/layouts/index.ts delete mode 100644 packages/shared/tailwind.config.js rename {packages/components/src => src}/components/menu/RoutesMenu.tsx (89%) rename {packages/shared/src => src}/layouts/BasicLayout.vue (62%) rename {packages/shared/src => src}/layouts/BlankLayout.vue (100%) delete mode 100644 src/router/menus.config.ts diff --git a/docs/routes-generation/README.md b/docs/routes-generation/README.md new file mode 100644 index 00000000..cbec57be --- /dev/null +++ b/docs/routes-generation/README.md @@ -0,0 +1,126 @@ +# 路由和 Console 端菜单的生成 + +## 简述 + +目前的路由以及菜单都是动态生成的,由 `基础路由`、`核心模块路由`、`插件模块路由` 三部分组成。 + +定义文件位置: + +- 基础路由:`src/router/routes.config.ts`, +- 核心模块路由:`src/modules/**/module.ts`, + +## 定义方式 + +统一由 `@halo-dev/console-shared` 包中的 `definePlugin` 方法配置。如: + +```ts +import { definePlugin } from "@halo-dev/console-shared"; +import BasicLayout from "@/layouts/BasicLayout.vue"; +import AttachmentList from "./AttachmentList.vue"; +import AttachmentSelectorModal from "./components/AttachmentSelectorModal.vue"; +import { IconFolder } from "@halo-dev/components"; +import { markRaw } from "vue"; + +export default definePlugin({ + name: "attachmentModule", + components: [AttachmentSelectorModal], + routes: [ + { + path: "/attachments", + component: BasicLayout, + children: [ + { + path: "", + name: "Attachments", + component: AttachmentList, + meta: { + title: "附件", + permissions: ["system:attachments:view"], + menu: { + name: "附件", + group: "content", + icon: markRaw(IconFolder), + priority: 3, + mobile: true, + }, + }, + }, + ], + }, + ], +}); +``` + +其中,如果要将路由添加到侧边的菜单,那么需要在 `meta` 中定义好 `menu` 对象,menu 对象类型详解如下: + +```ts +interface RouteMeta { + title?: string; + searchable?: boolean; + permissions?: string[]; + core?: boolean; + menu?: { + name: string; // 菜单名称 + group?: CoreMenuGroupId; // 菜单分组 ID,详见下方 CoreMenuGroupId 定义 + icon?: Component; // 菜单图标,类型为 Vue 组件,可以使用 `@halo-dev/components` 包中的图标组件,或者自行接入 https://github.com/antfu/unplugin-icons + priority: number; // 排序字段,相对于 group,插件中提供的菜单将始终放在最后 + mobile?: boolean; // 是否添加到移动端底部的菜单 + }; +} +``` + +CoreMenuGroupId: + +```ts +declare type CoreMenuGroupId = "dashboard" | "content" | "interface" | "system" | "tool"; +``` + +这是核心内置的菜单分组,但如果插件需要自定义分组,可以直接填写分组名,如: + +```ts +{ + name: "帖子", + group: "社区", + icon: markRaw(IconCummunity), + priority: 1, + mobile: false, +} +``` + +## 插件接入 + +定义方式与系统核心模块的定义方式一致,在 `definePlugin` 方法配置即可。主要额外注意的是,如果插件的路由需要基础布局(继承 BasicLayout),需要配置 `parentName`,如: + +```ts +export default definePlugin({ + routes: [ + { + parentName: "Root", + route: { + path: "/migrate", + children: [ + { + path: "", + name: "Migrate", + component: MigrateView, + meta: { + title: "迁移", + searchable: true, + menu: { + name: "迁移", + group: "tool", + icon: markRaw(IconGrid), + priority: 0, + }, + }, + }, + ], + }, + }, + ] +}) +``` + +## 权限 + +在 `meta` 中配置 `permissions` 即可。类型为 UI 权限标识的数组,如 `["system:attachments:view"]`。如果当前用户没有对应权限,那么将不会注册路由和菜单。 diff --git a/env.d.ts b/env.d.ts index 9f8f1e2c..accae153 100644 --- a/env.d.ts +++ b/env.d.ts @@ -1,8 +1,30 @@ /// +export {}; + +import type { CoreMenuGroupId } from "@halo-dev/console-shared"; + +import "vue-router"; + declare module "*.vue" { import type { DefineComponent } from "vue"; // eslint-disable-next-line const component: DefineComponent<{}, {}, any>; export default component; } + +declare module "vue-router" { + interface RouteMeta { + title?: string; + searchable?: boolean; + permissions?: string[]; + core?: boolean; + menu?: { + name: string; + group?: CoreMenuGroupId; + icon?: Component; + priority: number; + mobile?: boolean; + }; + } +} diff --git a/package.json b/package.json index b3db739f..7c08efa8 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "lodash.clonedeep": "^4.5.0", "lodash.isequal": "^4.5.0", "lodash.merge": "^4.6.2", + "lodash.sortby": "^4.7.0", "path-browserify": "^1.0.1", "pinia": "^2.0.23", "pretty-bytes": "^6.0.0", diff --git a/packages/components/src/components/menu/__tests__/RoutesMenu.spec.tsx b/packages/components/src/components/menu/__tests__/RoutesMenu.spec.tsx deleted file mode 100644 index 4514321b..00000000 --- a/packages/components/src/components/menu/__tests__/RoutesMenu.spec.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { VRoutesMenu } from "../RoutesMenu"; - -describe("RoutesMenu", () => { - it("should render", () => { - expect(VRoutesMenu).toBeDefined(); - }); -}); diff --git a/packages/components/src/components/menu/index.ts b/packages/components/src/components/menu/index.ts index c0355c32..91bb174e 100644 --- a/packages/components/src/components/menu/index.ts +++ b/packages/components/src/components/menu/index.ts @@ -1,5 +1,3 @@ export { default as VMenu } from "./Menu.vue"; export { default as VMenuItem } from "./MenuItem.vue"; export { default as VMenuLabel } from "./MenuLabel.vue"; -// @ts-ignore -export { VRoutesMenu } from "./RoutesMenu.tsx"; diff --git a/packages/components/src/components/menu/interface.ts b/packages/components/src/components/menu/interface.ts deleted file mode 100644 index b09929c2..00000000 --- a/packages/components/src/components/menu/interface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Component } from "vue"; - -export interface MenuGroupType { - name?: string; - items: MenuItemType[]; -} - -export interface MenuItemType { - name: string; - path: string; - icon?: Component; - meta?: Record; - children?: MenuItemType[]; -} diff --git a/packages/shared/env.d.ts b/packages/shared/env.d.ts index 11f02fe2..db12ea31 100644 --- a/packages/shared/env.d.ts +++ b/packages/shared/env.d.ts @@ -1 +1,29 @@ /// + +export {}; + +import type { CoreMenuGroupId } from "./src/types/menus"; + + +declare module "*.vue" { + import type { DefineComponent } from "vue"; + // eslint-disable-next-line + const component: DefineComponent<{}, {}, any>; + export default component; +} + +declare module "vue-router" { + interface RouteMeta { + title?: string; + searchable?: boolean; + permissions?: string[]; + core?: boolean; + menu?: { + name: string; + group?: CoreMenuGroupId; + icon?: Component; + priority: number; + mobile?: boolean; + }; + } +} diff --git a/packages/shared/package.json b/packages/shared/package.json index c68e99d8..ea0274af 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -37,9 +37,6 @@ }, "homepage": "https://github.com/halo-dev/console/tree/main/packages/shared#readme", "license": "MIT", - "dependencies": { - "@halo-dev/components": "workspace:*" - }, "devDependencies": { "vite-plugin-dts": "^1.6.5" }, diff --git a/packages/shared/postcss.config.js b/packages/shared/postcss.config.js deleted file mode 100644 index 7a8564c0..00000000 --- a/packages/shared/postcss.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - ...require("../../postcss.config"), -}; diff --git a/packages/shared/src/assets/logo.svg b/packages/shared/src/assets/logo.svg deleted file mode 100644 index 86fbed95..00000000 --- a/packages/shared/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 03234b78..0e22b9e4 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -3,4 +3,3 @@ export * from "./types/menus"; export * from "./core/plugins"; export * from "./states/pages"; export * from "./states/attachment-selector"; -export * from "./layouts"; diff --git a/packages/shared/src/layouts/__tests__/BasicLayout.spec.ts b/packages/shared/src/layouts/__tests__/BasicLayout.spec.ts deleted file mode 100644 index 42154633..00000000 --- a/packages/shared/src/layouts/__tests__/BasicLayout.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, expect, it } from "vitest"; - -describe("BasicLayout", () => { - it("renders", () => { - expect(true).toBe(true); - }); -}); diff --git a/packages/shared/src/layouts/index.ts b/packages/shared/src/layouts/index.ts deleted file mode 100644 index 907c3136..00000000 --- a/packages/shared/src/layouts/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as BlankLayout } from "./BlankLayout.vue"; -export { default as BasicLayout } from "./BasicLayout.vue"; diff --git a/packages/shared/src/types/menus.ts b/packages/shared/src/types/menus.ts index b09929c2..9c7f9216 100644 --- a/packages/shared/src/types/menus.ts +++ b/packages/shared/src/types/menus.ts @@ -1,13 +1,23 @@ import type { Component } from "vue"; +export type CoreMenuGroupId = + | "dashboard" + | "content" + | "interface" + | "system" + | "tool"; + export interface MenuGroupType { + id: CoreMenuGroupId | string; name?: string; - items: MenuItemType[]; + priority: number; + items?: MenuItemType[]; } export interface MenuItemType { name: string; path: string; + mobile?: boolean; icon?: Component; meta?: Record; children?: MenuItemType[]; diff --git a/packages/shared/src/types/plugin.ts b/packages/shared/src/types/plugin.ts index 4ed1b0bc..30a9e621 100644 --- a/packages/shared/src/types/plugin.ts +++ b/packages/shared/src/types/plugin.ts @@ -1,6 +1,5 @@ import type { Component, Ref } from "vue"; import type { RouteRecordRaw, RouteRecordName } from "vue-router"; -import type { MenuGroupType } from "./menus"; import type { PagesPublicState } from "../states/pages"; import type { AttachmentSelectorPublicState } from "../states/attachment-selector"; @@ -10,7 +9,7 @@ export type ExtensionPointState = | PagesPublicState | AttachmentSelectorPublicState; -interface RouteRecordAppend { +export interface RouteRecordAppend { parentName: RouteRecordName; route: RouteRecordRaw; } @@ -35,8 +34,6 @@ export interface Plugin { routes?: RouteRecordRaw[] | RouteRecordAppend[]; - menus?: MenuGroupType[]; - extensionPoints?: { [key in ExtensionPointName]?: (state: Ref) => void; }; diff --git a/packages/shared/tailwind.config.js b/packages/shared/tailwind.config.js deleted file mode 100644 index e7d10c90..00000000 --- a/packages/shared/tailwind.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - ...require("../../tailwind.config"), -}; diff --git a/packages/shared/vite.config.ts b/packages/shared/vite.config.ts index 70b64146..2cbc78ce 100644 --- a/packages/shared/vite.config.ts +++ b/packages/shared/vite.config.ts @@ -30,12 +30,11 @@ export default defineConfig({ fileName: (format) => `halo-console-shared.${format}.js`, }, rollupOptions: { - external: ["vue", "vue-router", "@halo-dev/components"], + external: ["vue", "vue-router"], output: { globals: { vue: "Vue", "vue-router": "VueRouter", - "@halo-dev/components": "HaloComponents", }, exports: "named", generatedCode: "es5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39fa14fb..45a1b455 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,6 +60,7 @@ importers: lodash.clonedeep: ^4.5.0 lodash.isequal: ^4.5.0 lodash.merge: ^4.6.2 + lodash.sortby: ^4.7.0 path-browserify: ^1.0.1 pinia: ^2.0.23 postcss: ^8.4.17 @@ -119,6 +120,7 @@ importers: lodash.clonedeep: 4.5.0 lodash.isequal: 4.5.0 lodash.merge: 4.6.2 + lodash.sortby: 4.7.0 path-browserify: 1.0.1 pinia: 2.0.23_rg374xhldfcyvjtaj3qktyfz5y pretty-bytes: 6.0.0 @@ -166,7 +168,7 @@ importers: randomstring: 1.2.2 sass: 1.55.0 start-server-and-test: 1.14.0 - tailwindcss: 3.1.8_postcss@8.4.17 + tailwindcss: 3.1.8 tailwindcss-safe-area: 0.2.2 tailwindcss-themer: 2.0.2_tailwindcss@3.1.8 typescript: 4.7.4 @@ -210,10 +212,7 @@ importers: packages/shared: specifiers: - '@halo-dev/components': workspace:* vite-plugin-dts: ^1.6.5 - dependencies: - '@halo-dev/components': link:../components devDependencies: vite-plugin-dts: 1.6.5 @@ -1862,7 +1861,7 @@ packages: optional: true dependencies: '@formkit/core': 1.0.0-beta.11 - tailwindcss: 3.1.8_postcss@8.4.17 + tailwindcss: 3.1.8 dev: false /@formkit/utils/1.0.0-beta.11: @@ -2563,7 +2562,7 @@ packages: peerDependencies: tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' dependencies: - tailwindcss: 3.1.8_postcss@8.4.17 + tailwindcss: 3.1.8 dev: true /@tiptap/core/2.0.0-beta.195: @@ -6552,7 +6551,6 @@ packages: /lodash.sortby/4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} - dev: true /lodash.startcase/4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -8180,15 +8178,13 @@ packages: just-unique: 4.1.1 lodash.merge: 4.6.2 lodash.mergewith: 4.6.2 - tailwindcss: 3.1.8_postcss@8.4.17 + tailwindcss: 3.1.8 dev: true - /tailwindcss/3.1.8_postcss@8.4.17: + /tailwindcss/3.1.8: resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==} engines: {node: '>=12.13.0'} hasBin: true - peerDependencies: - postcss: ^8.0.9 dependencies: arg: 5.0.2 chokidar: 3.5.3 diff --git a/src/App.vue b/src/App.vue index 2152c201..47c7be25 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,8 +1,7 @@