mirror of https://github.com/halo-dev/halo-admin
refactor: router and menu generation (#651)
#### 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 重构路由和侧边菜单生成的逻辑。 ```pull/652/head
parent
dd17087b8c
commit
54755c5842
|
@ -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"]`。如果当前用户没有对应权限,那么将不会注册路由和菜单。
|
|
@ -1,8 +1,30 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
import type { CoreMenuGroupId } from "@halo-dev/console-shared";
|
||||||
|
|
||||||
|
import "vue-router";
|
||||||
|
|
||||||
declare module "*.vue" {
|
declare module "*.vue" {
|
||||||
import type { DefineComponent } from "vue";
|
import type { DefineComponent } from "vue";
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const component: DefineComponent<{}, {}, any>;
|
const component: DefineComponent<{}, {}, any>;
|
||||||
export default component;
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
|
"lodash.sortby": "^4.7.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"pinia": "^2.0.23",
|
"pinia": "^2.0.23",
|
||||||
"pretty-bytes": "^6.0.0",
|
"pretty-bytes": "^6.0.0",
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
import { VRoutesMenu } from "../RoutesMenu";
|
|
||||||
|
|
||||||
describe("RoutesMenu", () => {
|
|
||||||
it("should render", () => {
|
|
||||||
expect(VRoutesMenu).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,5 +1,3 @@
|
||||||
export { default as VMenu } from "./Menu.vue";
|
export { default as VMenu } from "./Menu.vue";
|
||||||
export { default as VMenuItem } from "./MenuItem.vue";
|
export { default as VMenuItem } from "./MenuItem.vue";
|
||||||
export { default as VMenuLabel } from "./MenuLabel.vue";
|
export { default as VMenuLabel } from "./MenuLabel.vue";
|
||||||
// @ts-ignore
|
|
||||||
export { VRoutesMenu } from "./RoutesMenu.tsx";
|
|
||||||
|
|
|
@ -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<string, unknown>;
|
|
||||||
children?: MenuItemType[];
|
|
||||||
}
|
|
|
@ -1 +1,29 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -37,9 +37,6 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/halo-dev/console/tree/main/packages/shared#readme",
|
"homepage": "https://github.com/halo-dev/console/tree/main/packages/shared#readme",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"@halo-dev/components": "workspace:*"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite-plugin-dts": "^1.6.5"
|
"vite-plugin-dts": "^1.6.5"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
...require("../../postcss.config"),
|
|
||||||
};
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.5 KiB |
|
@ -3,4 +3,3 @@ export * from "./types/menus";
|
||||||
export * from "./core/plugins";
|
export * from "./core/plugins";
|
||||||
export * from "./states/pages";
|
export * from "./states/pages";
|
||||||
export * from "./states/attachment-selector";
|
export * from "./states/attachment-selector";
|
||||||
export * from "./layouts";
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
describe("BasicLayout", () => {
|
|
||||||
it("renders", () => {
|
|
||||||
expect(true).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { default as BlankLayout } from "./BlankLayout.vue";
|
|
||||||
export { default as BasicLayout } from "./BasicLayout.vue";
|
|
|
@ -1,13 +1,23 @@
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
|
|
||||||
|
export type CoreMenuGroupId =
|
||||||
|
| "dashboard"
|
||||||
|
| "content"
|
||||||
|
| "interface"
|
||||||
|
| "system"
|
||||||
|
| "tool";
|
||||||
|
|
||||||
export interface MenuGroupType {
|
export interface MenuGroupType {
|
||||||
|
id: CoreMenuGroupId | string;
|
||||||
name?: string;
|
name?: string;
|
||||||
items: MenuItemType[];
|
priority: number;
|
||||||
|
items?: MenuItemType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuItemType {
|
export interface MenuItemType {
|
||||||
name: string;
|
name: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
mobile?: boolean;
|
||||||
icon?: Component;
|
icon?: Component;
|
||||||
meta?: Record<string, unknown>;
|
meta?: Record<string, unknown>;
|
||||||
children?: MenuItemType[];
|
children?: MenuItemType[];
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import type { Component, Ref } from "vue";
|
import type { Component, Ref } from "vue";
|
||||||
import type { RouteRecordRaw, RouteRecordName } from "vue-router";
|
import type { RouteRecordRaw, RouteRecordName } from "vue-router";
|
||||||
import type { MenuGroupType } from "./menus";
|
|
||||||
import type { PagesPublicState } from "../states/pages";
|
import type { PagesPublicState } from "../states/pages";
|
||||||
import type { AttachmentSelectorPublicState } from "../states/attachment-selector";
|
import type { AttachmentSelectorPublicState } from "../states/attachment-selector";
|
||||||
|
|
||||||
|
@ -10,7 +9,7 @@ export type ExtensionPointState =
|
||||||
| PagesPublicState
|
| PagesPublicState
|
||||||
| AttachmentSelectorPublicState;
|
| AttachmentSelectorPublicState;
|
||||||
|
|
||||||
interface RouteRecordAppend {
|
export interface RouteRecordAppend {
|
||||||
parentName: RouteRecordName;
|
parentName: RouteRecordName;
|
||||||
route: RouteRecordRaw;
|
route: RouteRecordRaw;
|
||||||
}
|
}
|
||||||
|
@ -35,8 +34,6 @@ export interface Plugin {
|
||||||
|
|
||||||
routes?: RouteRecordRaw[] | RouteRecordAppend[];
|
routes?: RouteRecordRaw[] | RouteRecordAppend[];
|
||||||
|
|
||||||
menus?: MenuGroupType[];
|
|
||||||
|
|
||||||
extensionPoints?: {
|
extensionPoints?: {
|
||||||
[key in ExtensionPointName]?: (state: Ref<ExtensionPointState>) => void;
|
[key in ExtensionPointName]?: (state: Ref<ExtensionPointState>) => void;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
...require("../../tailwind.config"),
|
|
||||||
};
|
|
|
@ -30,12 +30,11 @@ export default defineConfig({
|
||||||
fileName: (format) => `halo-console-shared.${format}.js`,
|
fileName: (format) => `halo-console-shared.${format}.js`,
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ["vue", "vue-router", "@halo-dev/components"],
|
external: ["vue", "vue-router"],
|
||||||
output: {
|
output: {
|
||||||
globals: {
|
globals: {
|
||||||
vue: "Vue",
|
vue: "Vue",
|
||||||
"vue-router": "VueRouter",
|
"vue-router": "VueRouter",
|
||||||
"@halo-dev/components": "HaloComponents",
|
|
||||||
},
|
},
|
||||||
exports: "named",
|
exports: "named",
|
||||||
generatedCode: "es5",
|
generatedCode: "es5",
|
||||||
|
|
|
@ -60,6 +60,7 @@ importers:
|
||||||
lodash.clonedeep: ^4.5.0
|
lodash.clonedeep: ^4.5.0
|
||||||
lodash.isequal: ^4.5.0
|
lodash.isequal: ^4.5.0
|
||||||
lodash.merge: ^4.6.2
|
lodash.merge: ^4.6.2
|
||||||
|
lodash.sortby: ^4.7.0
|
||||||
path-browserify: ^1.0.1
|
path-browserify: ^1.0.1
|
||||||
pinia: ^2.0.23
|
pinia: ^2.0.23
|
||||||
postcss: ^8.4.17
|
postcss: ^8.4.17
|
||||||
|
@ -119,6 +120,7 @@ importers:
|
||||||
lodash.clonedeep: 4.5.0
|
lodash.clonedeep: 4.5.0
|
||||||
lodash.isequal: 4.5.0
|
lodash.isequal: 4.5.0
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
|
lodash.sortby: 4.7.0
|
||||||
path-browserify: 1.0.1
|
path-browserify: 1.0.1
|
||||||
pinia: 2.0.23_rg374xhldfcyvjtaj3qktyfz5y
|
pinia: 2.0.23_rg374xhldfcyvjtaj3qktyfz5y
|
||||||
pretty-bytes: 6.0.0
|
pretty-bytes: 6.0.0
|
||||||
|
@ -166,7 +168,7 @@ importers:
|
||||||
randomstring: 1.2.2
|
randomstring: 1.2.2
|
||||||
sass: 1.55.0
|
sass: 1.55.0
|
||||||
start-server-and-test: 1.14.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-safe-area: 0.2.2
|
||||||
tailwindcss-themer: 2.0.2_tailwindcss@3.1.8
|
tailwindcss-themer: 2.0.2_tailwindcss@3.1.8
|
||||||
typescript: 4.7.4
|
typescript: 4.7.4
|
||||||
|
@ -210,10 +212,7 @@ importers:
|
||||||
|
|
||||||
packages/shared:
|
packages/shared:
|
||||||
specifiers:
|
specifiers:
|
||||||
'@halo-dev/components': workspace:*
|
|
||||||
vite-plugin-dts: ^1.6.5
|
vite-plugin-dts: ^1.6.5
|
||||||
dependencies:
|
|
||||||
'@halo-dev/components': link:../components
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
vite-plugin-dts: 1.6.5
|
vite-plugin-dts: 1.6.5
|
||||||
|
|
||||||
|
@ -1862,7 +1861,7 @@ packages:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@formkit/core': 1.0.0-beta.11
|
'@formkit/core': 1.0.0-beta.11
|
||||||
tailwindcss: 3.1.8_postcss@8.4.17
|
tailwindcss: 3.1.8
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@formkit/utils/1.0.0-beta.11:
|
/@formkit/utils/1.0.0-beta.11:
|
||||||
|
@ -2563,7 +2562,7 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
|
tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1'
|
||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 3.1.8_postcss@8.4.17
|
tailwindcss: 3.1.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@tiptap/core/2.0.0-beta.195:
|
/@tiptap/core/2.0.0-beta.195:
|
||||||
|
@ -6552,7 +6551,6 @@ packages:
|
||||||
|
|
||||||
/lodash.sortby/4.7.0:
|
/lodash.sortby/4.7.0:
|
||||||
resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
|
resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/lodash.startcase/4.4.0:
|
/lodash.startcase/4.4.0:
|
||||||
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
|
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
|
||||||
|
@ -8180,15 +8178,13 @@ packages:
|
||||||
just-unique: 4.1.1
|
just-unique: 4.1.1
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
lodash.mergewith: 4.6.2
|
lodash.mergewith: 4.6.2
|
||||||
tailwindcss: 3.1.8_postcss@8.4.17
|
tailwindcss: 3.1.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/tailwindcss/3.1.8_postcss@8.4.17:
|
/tailwindcss/3.1.8:
|
||||||
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
|
resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==}
|
||||||
engines: {node: '>=12.13.0'}
|
engines: {node: '>=12.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
|
||||||
postcss: ^8.0.9
|
|
||||||
dependencies:
|
dependencies:
|
||||||
arg: 5.0.2
|
arg: 5.0.2
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
|
|
22
src/App.vue
22
src/App.vue
|
@ -1,8 +1,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { RouterView, useRoute } from "vue-router";
|
import { RouterView, useRoute } from "vue-router";
|
||||||
import { onMounted, provide, ref, watch, type Ref } from "vue";
|
import { watch } from "vue";
|
||||||
import { useTitle } from "@vueuse/core";
|
import { useTitle } from "@vueuse/core";
|
||||||
import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue";
|
|
||||||
|
|
||||||
const AppName = "Halo";
|
const AppName = "Halo";
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -19,29 +18,10 @@ watch(
|
||||||
title.value = AppName;
|
title.value = AppName;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const globalSearchVisible = ref(false);
|
|
||||||
|
|
||||||
provide<Ref<boolean>>("globalSearchVisible", globalSearchVisible);
|
|
||||||
|
|
||||||
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
const handleKeybinding = (e: KeyboardEvent) => {
|
|
||||||
const { key, ctrlKey, metaKey } = e;
|
|
||||||
if (key === "k" && ((ctrlKey && !isMac) || metaKey)) {
|
|
||||||
globalSearchVisible.value = true;
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener("keydown", handleKeybinding);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
<GlobalSearchModal v-model:visible="globalSearchVisible" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
IconPages,
|
IconPages,
|
||||||
IconUserSettings,
|
IconUserSettings,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { computed, markRaw, ref, watch, type Component } from "vue";
|
import { computed, markRaw, onMounted, ref, watch, type Component } from "vue";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
|
@ -356,8 +356,6 @@ watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
handleBuildSearchIndex();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
globalSearchInput.value?.focus();
|
globalSearchInput.value?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -371,6 +369,10 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
handleBuildSearchIndex();
|
||||||
|
});
|
||||||
|
|
||||||
const onVisibleChange = (visible: boolean) => {
|
const onVisibleChange = (visible: boolean) => {
|
||||||
emit("update:visible", visible);
|
emit("update:visible", visible);
|
||||||
};
|
};
|
||||||
|
@ -381,7 +383,6 @@ const onVisibleChange = (visible: boolean) => {
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
:body-class="['!p-0']"
|
:body-class="['!p-0']"
|
||||||
:mount-to-body="true"
|
:mount-to-body="true"
|
||||||
class="items-start"
|
|
||||||
:width="650"
|
:width="650"
|
||||||
:centered="false"
|
:centered="false"
|
||||||
@update:visible="onVisibleChange"
|
@update:visible="onVisibleChange"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import type { Component, PropType } from "vue";
|
import type { Component, PropType } from "vue";
|
||||||
import { computed, defineComponent } from "vue";
|
import { computed, defineComponent } from "vue";
|
||||||
import type { MenuGroupType, MenuItemType } from "./interface";
|
import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared";
|
||||||
import { VMenu, VMenuItem, VMenuLabel } from "./index";
|
import { VMenu, VMenuItem, VMenuLabel } from "@halo-dev/components";
|
||||||
import type { RouteLocationMatched } from "vue-router";
|
import type { RouteLocationMatched } from "vue-router";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
const VRoutesMenu = defineComponent({
|
const RoutesMenu = defineComponent({
|
||||||
name: "VRoutesMenu",
|
name: "RoutesMenu",
|
||||||
props: {
|
props: {
|
||||||
menus: {
|
menus: {
|
||||||
type: Object as PropType<MenuGroupType[]>,
|
type: Object as PropType<MenuGroupType[]>,
|
||||||
|
@ -80,4 +80,4 @@ const VRoutesMenu = defineComponent({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export { VRoutesMenu };
|
export { RoutesMenu };
|
|
@ -3,22 +3,30 @@ import {
|
||||||
IconMore,
|
IconMore,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
IconUserSettings,
|
IconUserSettings,
|
||||||
VRoutesMenu,
|
|
||||||
VTag,
|
VTag,
|
||||||
VAvatar,
|
VAvatar,
|
||||||
VSpace,
|
VSpace,
|
||||||
VButton,
|
VButton,
|
||||||
Dialog,
|
Dialog,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import type { MenuGroupType, MenuItemType } from "../types/menus";
|
import { RoutesMenu } from "@/components/menu/RoutesMenu";
|
||||||
|
import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared";
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
import logo from "@/assets/logo.svg";
|
import logo from "@/assets/logo.svg";
|
||||||
import { RouterView, useRoute, useRouter } from "vue-router";
|
import {
|
||||||
import { computed, inject, ref, type Ref } from "vue";
|
RouterView,
|
||||||
|
useRoute,
|
||||||
|
useRouter,
|
||||||
|
type RouteRecordRaw,
|
||||||
|
} from "vue-router";
|
||||||
|
import { computed, inject, onMounted, onUnmounted, ref } from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue";
|
||||||
|
import { coreMenuGroups } from "@/router/routes.config";
|
||||||
|
import sortBy from "lodash.sortby";
|
||||||
|
import { useRoleStore } from "@/stores/role";
|
||||||
|
import { hasPermission } from "@/utils/permission";
|
||||||
|
|
||||||
const menus = inject<MenuGroupType[]>("menus");
|
|
||||||
const minimenus = inject<MenuItemType[]>("minimenus");
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -26,18 +34,13 @@ const moreMenuVisible = ref(false);
|
||||||
const moreMenuRootVisible = ref(false);
|
const moreMenuRootVisible = ref(false);
|
||||||
|
|
||||||
const currentUser = inject<User>("currentUser");
|
const currentUser = inject<User>("currentUser");
|
||||||
const apiUrl = inject<string>("apiUrl");
|
|
||||||
|
|
||||||
const handleRouteToProfile = () => {
|
|
||||||
router.push({ path: `/users/${currentUser?.metadata.name}/detail` });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
Dialog.warning({
|
Dialog.warning({
|
||||||
title: "是否确认退出登录?",
|
title: "是否确认退出登录?",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post(`${apiUrl}/logout`, undefined, {
|
await axios.post(`${import.meta.env.VITE_API_URL}/logout`, undefined, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
router.replace({ name: "Login" });
|
router.replace({ name: "Login" });
|
||||||
|
@ -56,12 +59,126 @@ const currentRole = computed(() => {
|
||||||
)[0];
|
)[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
const globalSearchVisible = inject<Ref<boolean>>(
|
// Global Search
|
||||||
"globalSearchVisible",
|
const globalSearchVisible = ref(false);
|
||||||
ref(false)
|
|
||||||
);
|
|
||||||
|
|
||||||
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
const handleGlobalSearchKeybinding = (e: KeyboardEvent) => {
|
||||||
|
const { key, ctrlKey, metaKey } = e;
|
||||||
|
if (key === "k" && ((ctrlKey && !isMac) || metaKey)) {
|
||||||
|
globalSearchVisible.value = true;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener("keydown", handleGlobalSearchKeybinding);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.addEventListener("keydown", handleGlobalSearchKeybinding);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate menus by routes
|
||||||
|
const menus = ref<MenuGroupType[]>([] as MenuGroupType[]);
|
||||||
|
const minimenus = ref<MenuItemType[]>([] as MenuItemType[]);
|
||||||
|
|
||||||
|
const roleStore = useRoleStore();
|
||||||
|
const { uiPermissions } = roleStore.permissions;
|
||||||
|
|
||||||
|
const generateMenus = () => {
|
||||||
|
// sort by menu.priority and meta.core
|
||||||
|
const currentRoutes = sortBy(
|
||||||
|
router.getRoutes().filter((route) => {
|
||||||
|
const { meta } = route;
|
||||||
|
if (!meta?.menu) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (meta.permissions) {
|
||||||
|
return hasPermission(uiPermissions, meta.permissions as string[], true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
(route: RouteRecordRaw) => !route.meta?.core,
|
||||||
|
(route: RouteRecordRaw) => route.meta?.menu?.priority || 0,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// group by menu.group
|
||||||
|
menus.value = currentRoutes.reduce((acc, route) => {
|
||||||
|
const { menu } = route.meta;
|
||||||
|
if (!menu) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
const group = acc.find((item) => item.id === menu.group);
|
||||||
|
const childRoute = route.children[0];
|
||||||
|
const childMetaMenu = childRoute?.meta?.menu;
|
||||||
|
|
||||||
|
// only support one level
|
||||||
|
const menuChildren = childMetaMenu
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: childMetaMenu.name,
|
||||||
|
path: childRoute.path,
|
||||||
|
icon: childMetaMenu.icon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined;
|
||||||
|
if (group) {
|
||||||
|
group.items?.push({
|
||||||
|
name: menu.name,
|
||||||
|
path: route.path,
|
||||||
|
icon: menu.icon,
|
||||||
|
mobile: menu.mobile,
|
||||||
|
children: menuChildren,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const menuGroup = coreMenuGroups.find((item) => item.id === menu.group);
|
||||||
|
let name = "";
|
||||||
|
if (!menuGroup) {
|
||||||
|
name = menu.group;
|
||||||
|
} else if (menuGroup.name) {
|
||||||
|
name = menuGroup.name;
|
||||||
|
}
|
||||||
|
acc.push({
|
||||||
|
id: menuGroup?.id || menu.group,
|
||||||
|
name: name,
|
||||||
|
priority: menuGroup?.priority || 0,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: menu.name,
|
||||||
|
path: route.path,
|
||||||
|
icon: menu.icon,
|
||||||
|
mobile: menu.mobile,
|
||||||
|
children: menuChildren,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [] as MenuGroupType[]);
|
||||||
|
|
||||||
|
// sort by menu.priority
|
||||||
|
menus.value = sortBy(menus.value, [
|
||||||
|
(menu: MenuGroupType) => {
|
||||||
|
return coreMenuGroups.findIndex((item) => item.id === menu.id) < 0;
|
||||||
|
},
|
||||||
|
(menu: MenuGroupType) => menu.priority || 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
minimenus.value = menus.value
|
||||||
|
.reduce((acc, group) => {
|
||||||
|
if (group?.items) {
|
||||||
|
acc.push(...group.items);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [] as MenuItemType[])
|
||||||
|
.filter((item) => item.mobile);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(generateMenus);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -84,7 +201,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VRoutesMenu :menus="menus" />
|
<RoutesMenu :menus="menus" />
|
||||||
<div class="current-profile">
|
<div class="current-profile">
|
||||||
<div v-if="currentUser?.spec.avatar" class="profile-avatar">
|
<div v-if="currentUser?.spec.avatar" class="profile-avatar">
|
||||||
<VAvatar
|
<VAvatar
|
||||||
|
@ -118,7 +235,10 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
v-close-popper
|
v-close-popper
|
||||||
block
|
block
|
||||||
type="secondary"
|
type="secondary"
|
||||||
@click="handleRouteToProfile"
|
:route="{
|
||||||
|
name: 'UserDetail',
|
||||||
|
params: { name: currentUser?.metadata.name },
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
个人资料
|
个人资料
|
||||||
</VButton>
|
</VButton>
|
||||||
|
@ -136,6 +256,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
</FloatingDropdown>
|
</FloatingDropdown>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="content w-full overflow-y-auto pb-12 mb-safe md:pb-0">
|
<main class="content w-full overflow-y-auto pb-12 mb-safe md:pb-0">
|
||||||
<slot v-if="$slots.default" />
|
<slot v-if="$slots.default" />
|
||||||
<RouterView v-else />
|
<RouterView v-else />
|
||||||
|
@ -144,7 +265,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
<!--bottom nav bar-->
|
<!--bottom nav bar-->
|
||||||
<div
|
<div
|
||||||
v-if="minimenus"
|
v-if="minimenus"
|
||||||
class="bottom-nav-bar fixed left-0 bottom-0 right-0 grid grid-cols-6 border-t-2 border-black drop-shadow-2xl mt-safe pb-safe md:hidden bg-secondary"
|
class="bottom-nav-bar fixed left-0 bottom-0 right-0 grid grid-cols-6 border-t-2 border-black bg-secondary drop-shadow-2xl mt-safe pb-safe md:hidden"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(menu, index) in minimenus"
|
v-for="(menu, index) in minimenus"
|
||||||
|
@ -217,7 +338,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
class="drawer-content relative flex h-3/4 w-screen flex-col items-stretch overflow-y-auto rounded-t-md bg-white shadow-xl"
|
class="drawer-content relative flex h-3/4 w-screen flex-col items-stretch overflow-y-auto rounded-t-md bg-white shadow-xl"
|
||||||
>
|
>
|
||||||
<div class="drawer-body">
|
<div class="drawer-body">
|
||||||
<VRoutesMenu
|
<RoutesMenu
|
||||||
:menus="menus"
|
:menus="menus"
|
||||||
class="p-0"
|
class="p-0"
|
||||||
@select="moreMenuVisible = false"
|
@select="moreMenuVisible = false"
|
||||||
|
@ -229,6 +350,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<GlobalSearchModal v-model:visible="globalSearchVisible" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -241,24 +363,24 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
||||||
|
|
||||||
.current-profile {
|
.current-profile {
|
||||||
height: 70px;
|
height: 70px;
|
||||||
@apply w-64
|
@apply fixed
|
||||||
bg-white
|
|
||||||
p-3
|
|
||||||
flex
|
|
||||||
fixed
|
|
||||||
left-0
|
left-0
|
||||||
bottom-0
|
bottom-0
|
||||||
gap-3;
|
flex
|
||||||
|
w-64
|
||||||
|
gap-3
|
||||||
|
bg-white
|
||||||
|
p-3;
|
||||||
|
|
||||||
.profile-avatar {
|
.profile-avatar {
|
||||||
@apply self-center
|
@apply flex
|
||||||
flex
|
items-center
|
||||||
items-center;
|
self-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-name {
|
.profile-name {
|
||||||
@apply self-center
|
@apply flex-1
|
||||||
flex-1;
|
self-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-control {
|
.profile-control {
|
51
src/main.ts
51
src/main.ts
|
@ -3,14 +3,9 @@ import { createApp } from "vue";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import type {
|
import type { Plugin, RouteRecordAppend } from "@halo-dev/console-shared";
|
||||||
MenuGroupType,
|
|
||||||
MenuItemType,
|
|
||||||
Plugin,
|
|
||||||
} from "@halo-dev/console-shared";
|
|
||||||
import { Toast } from "@halo-dev/components";
|
import { Toast } from "@halo-dev/components";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { menus, minimenus, registerMenu } from "./router/menus.config";
|
|
||||||
// setup
|
// setup
|
||||||
import "./setup/setupStyles";
|
import "./setup/setupStyles";
|
||||||
import { setupComponents } from "./setup/setupComponents";
|
import { setupComponents } from "./setup/setupComponents";
|
||||||
|
@ -21,6 +16,7 @@ import { usePluginStore } from "@/stores/plugin";
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
import { hasPermission } from "@/utils/permission";
|
import { hasPermission } from "@/utils/permission";
|
||||||
import { useRoleStore } from "@/stores/role";
|
import { useRoleStore } from "@/stores/role";
|
||||||
|
import type { RouteRecordRaw } from "vue-router";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
@ -28,7 +24,7 @@ setupComponents(app);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
|
|
||||||
function registerModule(pluginModule: Plugin) {
|
function registerModule(pluginModule: Plugin, core: boolean) {
|
||||||
if (pluginModule.components) {
|
if (pluginModule.components) {
|
||||||
if (!Array.isArray(pluginModule.components)) {
|
if (!Array.isArray(pluginModule.components)) {
|
||||||
console.error(`${pluginModule.name}: Plugin components must be an array`);
|
console.error(`${pluginModule.name}: Plugin components must be an array`);
|
||||||
|
@ -46,6 +42,8 @@ function registerModule(pluginModule: Plugin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetRouteMeta(pluginModule.routes);
|
||||||
|
|
||||||
for (const route of pluginModule.routes) {
|
for (const route of pluginModule.routes) {
|
||||||
if ("parentName" in route) {
|
if ("parentName" in route) {
|
||||||
router.addRoute(route.parentName, route.route);
|
router.addRoute(route.parentName, route.route);
|
||||||
|
@ -55,22 +53,37 @@ function registerModule(pluginModule: Plugin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pluginModule.menus) {
|
function resetRouteMeta(routes: RouteRecordRaw[] | RouteRecordAppend[]) {
|
||||||
if (!Array.isArray(pluginModule.menus)) {
|
for (const route of routes) {
|
||||||
console.error(`${pluginModule.name}: Plugin menus must be an array`);
|
if ("parentName" in route) {
|
||||||
return;
|
if (route.route.meta?.menu) {
|
||||||
|
route.route.meta = {
|
||||||
|
...route.route.meta,
|
||||||
|
core,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (route.route.children) {
|
||||||
|
resetRouteMeta(route.route.children);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (route.meta?.menu) {
|
||||||
|
route.meta = {
|
||||||
|
...route.meta,
|
||||||
|
core,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (route.children) {
|
||||||
|
resetRouteMeta(route.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const group of pluginModule.menus) {
|
|
||||||
for (const menu of group.items) {
|
|
||||||
registerMenu(group.name, menu);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadCoreModules() {
|
function loadCoreModules() {
|
||||||
coreModules.forEach(registerModule);
|
coreModules.forEach((module) => {
|
||||||
|
registerModule(module, true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginStore = usePluginStore();
|
const pluginStore = usePluginStore();
|
||||||
|
@ -133,7 +146,7 @@ async function loadPluginModules() {
|
||||||
if (pluginModule) {
|
if (pluginModule) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
plugin.spec.module = pluginModule;
|
plugin.spec.module = pluginModule;
|
||||||
registerModule(pluginModule);
|
registerModule(pluginModule, false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = `${plugin.metadata.name}: 加载插件入口文件失败`;
|
const message = `${plugin.metadata.name}: 加载插件入口文件失败`;
|
||||||
|
@ -215,10 +228,6 @@ async function initApp() {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
app.provide<MenuGroupType[]>("menus", menus);
|
|
||||||
app.provide<MenuItemType[]>("minimenus", minimenus);
|
|
||||||
app.provide<string>("apiUrl", import.meta.env.VITE_API_URL);
|
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { BasicLayout, definePlugin } from "@halo-dev/console-shared";
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import AttachmentList from "./AttachmentList.vue";
|
import AttachmentList from "./AttachmentList.vue";
|
||||||
import AttachmentSelectorModal from "./components/AttachmentSelectorModal.vue";
|
import AttachmentSelectorModal from "./components/AttachmentSelectorModal.vue";
|
||||||
import { IconFolder } from "@halo-dev/components";
|
import { IconFolder } from "@halo-dev/components";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "attachmentModule",
|
name: "attachmentModule",
|
||||||
|
@ -18,19 +20,14 @@ export default definePlugin({
|
||||||
meta: {
|
meta: {
|
||||||
title: "附件",
|
title: "附件",
|
||||||
permissions: ["system:attachments:view"],
|
permissions: ["system:attachments:view"],
|
||||||
},
|
menu: {
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "内容",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "附件",
|
name: "附件",
|
||||||
path: "/attachments",
|
group: "content",
|
||||||
icon: IconFolder,
|
icon: markRaw(IconFolder),
|
||||||
|
priority: 3,
|
||||||
|
mobile: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { BasicLayout, definePlugin } from "@halo-dev/console-shared";
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import { IconMessage } from "@halo-dev/components";
|
import { IconMessage } from "@halo-dev/components";
|
||||||
import CommentList from "./CommentList.vue";
|
import CommentList from "./CommentList.vue";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "commentModule",
|
name: "commentModule",
|
||||||
|
@ -18,19 +20,14 @@ export default definePlugin({
|
||||||
title: "评论",
|
title: "评论",
|
||||||
searchable: true,
|
searchable: true,
|
||||||
permissions: ["system:comments:view"],
|
permissions: ["system:comments:view"],
|
||||||
},
|
menu: {
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "内容",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "评论",
|
name: "评论",
|
||||||
path: "/comments",
|
group: "content",
|
||||||
icon: IconMessage,
|
icon: markRaw(IconMessage),
|
||||||
|
priority: 2,
|
||||||
|
mobile: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
IconPages,
|
IconPages,
|
||||||
VButton,
|
VButton,
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import { BasicLayout } from "@halo-dev/console-shared";
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import {
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
BasicLayout,
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
BlankLayout,
|
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||||
definePlugin,
|
|
||||||
} from "@halo-dev/console-shared";
|
|
||||||
import PageLayout from "./layouts/PageLayout.vue";
|
import PageLayout from "./layouts/PageLayout.vue";
|
||||||
import FunctionalPageList from "./FunctionalPageList.vue";
|
import FunctionalPageList from "./FunctionalPageList.vue";
|
||||||
import SinglePageList from "./SinglePageList.vue";
|
import SinglePageList from "./SinglePageList.vue";
|
||||||
import SinglePageEditor from "./SinglePageEditor.vue";
|
import SinglePageEditor from "./SinglePageEditor.vue";
|
||||||
import { IconPages } from "@halo-dev/components";
|
import { IconPages } from "@halo-dev/components";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "pageModule",
|
name: "pageModule",
|
||||||
|
@ -20,6 +19,14 @@ export default definePlugin({
|
||||||
redirect: {
|
redirect: {
|
||||||
name: "FunctionalPages",
|
name: "FunctionalPages",
|
||||||
},
|
},
|
||||||
|
meta: {
|
||||||
|
menu: {
|
||||||
|
name: "页面",
|
||||||
|
group: "content",
|
||||||
|
icon: markRaw(IconPages),
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "functional",
|
path: "functional",
|
||||||
|
@ -77,16 +84,4 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "内容",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "页面",
|
|
||||||
path: "/pages",
|
|
||||||
icon: IconPages,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import {
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
BasicLayout,
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
BlankLayout,
|
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||||
definePlugin,
|
|
||||||
} from "@halo-dev/console-shared";
|
|
||||||
import { IconBookRead } from "@halo-dev/components";
|
import { IconBookRead } from "@halo-dev/components";
|
||||||
import PostList from "./PostList.vue";
|
import PostList from "./PostList.vue";
|
||||||
import PostEditor from "./PostEditor.vue";
|
import PostEditor from "./PostEditor.vue";
|
||||||
import CategoryList from "./categories/CategoryList.vue";
|
import CategoryList from "./categories/CategoryList.vue";
|
||||||
import TagList from "./tags/TagList.vue";
|
import TagList from "./tags/TagList.vue";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "postModule",
|
name: "postModule",
|
||||||
|
@ -25,6 +24,13 @@ export default definePlugin({
|
||||||
title: "文章",
|
title: "文章",
|
||||||
searchable: true,
|
searchable: true,
|
||||||
permissions: ["system:posts:view"],
|
permissions: ["system:posts:view"],
|
||||||
|
menu: {
|
||||||
|
name: "文章",
|
||||||
|
group: "content",
|
||||||
|
icon: markRaw(IconBookRead),
|
||||||
|
priority: 0,
|
||||||
|
mobile: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -72,16 +78,4 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "内容",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "文章",
|
|
||||||
path: "/posts",
|
|
||||||
icon: IconBookRead,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { BasicLayout, definePlugin } from "@halo-dev/console-shared";
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import Dashboard from "./Dashboard.vue";
|
import Dashboard from "./Dashboard.vue";
|
||||||
import { IconDashboard } from "@halo-dev/components";
|
import { IconDashboard } from "@halo-dev/components";
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ import RecentLoginWidget from "./widgets/RecentLoginWidget.vue";
|
||||||
import RecentPublishedWidget from "./widgets/RecentPublishedWidget.vue";
|
import RecentPublishedWidget from "./widgets/RecentPublishedWidget.vue";
|
||||||
import UserStatsWidget from "./widgets/UserStatsWidget.vue";
|
import UserStatsWidget from "./widgets/UserStatsWidget.vue";
|
||||||
import ViewsStatsWidget from "./widgets/ViewsStatsWidget.vue";
|
import ViewsStatsWidget from "./widgets/ViewsStatsWidget.vue";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "dashboardModule",
|
name: "dashboardModule",
|
||||||
|
@ -25,6 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
component: BasicLayout,
|
component: BasicLayout,
|
||||||
|
name: "Root",
|
||||||
redirect: "/dashboard",
|
redirect: "/dashboard",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -34,19 +37,14 @@ export default definePlugin({
|
||||||
meta: {
|
meta: {
|
||||||
title: "仪表盘",
|
title: "仪表盘",
|
||||||
searchable: true,
|
searchable: true,
|
||||||
},
|
menu: {
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "仪表盘",
|
name: "仪表盘",
|
||||||
path: "/dashboard",
|
group: "dashboard",
|
||||||
icon: IconDashboard,
|
icon: markRaw(IconDashboard),
|
||||||
|
priority: 0,
|
||||||
|
mobile: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,18 +10,32 @@ import userModule from "./system/users/module";
|
||||||
import roleModule from "./system/roles/module";
|
import roleModule from "./system/roles/module";
|
||||||
import settingModule from "./system/settings/module";
|
import settingModule from "./system/settings/module";
|
||||||
|
|
||||||
|
// const coreModules = [
|
||||||
|
// dashboardModule,
|
||||||
|
// postModule,
|
||||||
|
// pageModule,
|
||||||
|
// commentModule,
|
||||||
|
// attachmentModule,
|
||||||
|
// themeModule,
|
||||||
|
// menuModule,
|
||||||
|
// pluginModule,
|
||||||
|
// userModule,
|
||||||
|
// roleModule,
|
||||||
|
// settingModule,
|
||||||
|
// ];
|
||||||
|
|
||||||
const coreModules = [
|
const coreModules = [
|
||||||
dashboardModule,
|
|
||||||
postModule,
|
postModule,
|
||||||
pageModule,
|
pluginModule,
|
||||||
|
settingModule,
|
||||||
|
dashboardModule,
|
||||||
|
menuModule,
|
||||||
commentModule,
|
commentModule,
|
||||||
attachmentModule,
|
attachmentModule,
|
||||||
|
pageModule,
|
||||||
themeModule,
|
themeModule,
|
||||||
menuModule,
|
|
||||||
pluginModule,
|
|
||||||
userModule,
|
userModule,
|
||||||
roleModule,
|
roleModule,
|
||||||
settingModule,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export { coreModules };
|
export { coreModules };
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { BasicLayout, definePlugin } from "@halo-dev/console-shared";
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import Menus from "./Menus.vue";
|
import Menus from "./Menus.vue";
|
||||||
import { IconListSettings } from "@halo-dev/components";
|
import { IconListSettings } from "@halo-dev/components";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "menuModule",
|
name: "menuModule",
|
||||||
|
@ -18,19 +20,13 @@ export default definePlugin({
|
||||||
title: "菜单",
|
title: "菜单",
|
||||||
searchable: true,
|
searchable: true,
|
||||||
permissions: ["system:menus:view"],
|
permissions: ["system:menus:view"],
|
||||||
},
|
menu: {
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "外观",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "菜单",
|
name: "菜单",
|
||||||
path: "/menus",
|
group: "interface",
|
||||||
icon: IconListSettings,
|
icon: markRaw(IconListSettings),
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,7 +10,7 @@ import cloneDeep from "lodash.clonedeep";
|
||||||
// hooks
|
// hooks
|
||||||
import { useThemeLifeCycle } from "../composables/use-theme";
|
import { useThemeLifeCycle } from "../composables/use-theme";
|
||||||
// types
|
// types
|
||||||
import { BasicLayout } from "@halo-dev/console-shared";
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import { useSettingForm } from "@/composables/use-setting-form";
|
import { useSettingForm } from "@/composables/use-setting-form";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { BlankLayout, definePlugin } from "@halo-dev/console-shared";
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
|
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||||
import ThemeLayout from "./layouts/ThemeLayout.vue";
|
import ThemeLayout from "./layouts/ThemeLayout.vue";
|
||||||
import ThemeDetail from "./ThemeDetail.vue";
|
import ThemeDetail from "./ThemeDetail.vue";
|
||||||
import ThemeSetting from "./ThemeSetting.vue";
|
import ThemeSetting from "./ThemeSetting.vue";
|
||||||
import Visual from "./Visual.vue";
|
import Visual from "./Visual.vue";
|
||||||
import { IconPalette } from "@halo-dev/components";
|
import { IconPalette } from "@halo-dev/components";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "themeModule",
|
name: "themeModule",
|
||||||
|
@ -21,6 +23,12 @@ export default definePlugin({
|
||||||
title: "主题",
|
title: "主题",
|
||||||
searchable: true,
|
searchable: true,
|
||||||
permissions: ["system:themes:view"],
|
permissions: ["system:themes:view"],
|
||||||
|
menu: {
|
||||||
|
name: "主题",
|
||||||
|
group: "interface",
|
||||||
|
icon: markRaw(IconPalette),
|
||||||
|
priority: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -49,16 +57,4 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "外观",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "主题",
|
|
||||||
path: "/theme",
|
|
||||||
icon: IconPalette,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { useSettingForm } from "@/composables/use-setting-form";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
import { VButton, VCard, VPageHeader, VTabbar } from "@halo-dev/components";
|
import { VButton, VCard, VPageHeader, VTabbar } from "@halo-dev/components";
|
||||||
import { BasicLayout } from "@halo-dev/console-shared";
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import {
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
BasicLayout,
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
BlankLayout,
|
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||||
definePlugin,
|
|
||||||
} from "@halo-dev/console-shared";
|
|
||||||
import PluginLayout from "./layouts/PluginLayout.vue";
|
import PluginLayout from "./layouts/PluginLayout.vue";
|
||||||
import PluginList from "./PluginList.vue";
|
import PluginList from "./PluginList.vue";
|
||||||
import PluginSetting from "./PluginSetting.vue";
|
import PluginSetting from "./PluginSetting.vue";
|
||||||
import PluginDetail from "./PluginDetail.vue";
|
import PluginDetail from "./PluginDetail.vue";
|
||||||
import { IconPlug } from "@halo-dev/components";
|
import { IconPlug } from "@halo-dev/components";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "pluginModule",
|
name: "pluginModule",
|
||||||
|
@ -29,6 +28,12 @@ export default definePlugin({
|
||||||
title: "插件",
|
title: "插件",
|
||||||
searchable: true,
|
searchable: true,
|
||||||
permissions: ["system:plugins:view"],
|
permissions: ["system:plugins:view"],
|
||||||
|
menu: {
|
||||||
|
name: "插件",
|
||||||
|
group: "system",
|
||||||
|
icon: markRaw(IconPlug),
|
||||||
|
priority: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -60,16 +65,4 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "系统",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "插件",
|
|
||||||
path: "/plugins",
|
|
||||||
icon: IconPlug,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { BasicLayout, definePlugin } from "@halo-dev/console-shared";
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import RoleList from "./RoleList.vue";
|
import RoleList from "./RoleList.vue";
|
||||||
import RoleDetail from "./RoleDetail.vue";
|
import RoleDetail from "./RoleDetail.vue";
|
||||||
|
|
||||||
|
@ -32,5 +33,4 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
menus: [],
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ref, watch } from "vue";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { BasicLayout } from "@halo-dev/console-shared";
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import { useSettingForm } from "@/composables/use-setting-form";
|
import { useSettingForm } from "@/composables/use-setting-form";
|
||||||
|
|
||||||
// components
|
// components
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { definePlugin } from "@halo-dev/console-shared";
|
||||||
import SystemSettingsLayout from "./layouts/SystemSettingsLayout.vue";
|
import SystemSettingsLayout from "./layouts/SystemSettingsLayout.vue";
|
||||||
import SystemSetting from "./SystemSetting.vue";
|
import SystemSetting from "./SystemSetting.vue";
|
||||||
import { IconSettings } from "@halo-dev/components";
|
import { IconSettings } from "@halo-dev/components";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "settingModule",
|
name: "settingModule",
|
||||||
|
@ -11,27 +12,21 @@ export default definePlugin({
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
component: SystemSettingsLayout,
|
component: SystemSettingsLayout,
|
||||||
redirect: "/settings/basic",
|
redirect: "/settings/basic",
|
||||||
|
meta: {
|
||||||
|
title: "系统设置",
|
||||||
|
permissions: ["system:settings:view"],
|
||||||
|
menu: {
|
||||||
|
name: "设置",
|
||||||
|
group: "system",
|
||||||
|
icon: markRaw(IconSettings),
|
||||||
|
priority: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ":group",
|
path: ":group",
|
||||||
name: "SystemSetting",
|
name: "SystemSetting",
|
||||||
component: SystemSetting,
|
component: SystemSetting,
|
||||||
meta: {
|
|
||||||
title: "系统设置",
|
|
||||||
permissions: ["system:settings:view"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "系统",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "设置",
|
|
||||||
path: "/settings",
|
|
||||||
icon: IconSettings,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BasicLayout } from "@halo-dev/console-shared";
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import {
|
import {
|
||||||
IconUpload,
|
IconUpload,
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import {
|
import { definePlugin } from "@halo-dev/console-shared";
|
||||||
BasicLayout,
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
BlankLayout,
|
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||||
definePlugin,
|
|
||||||
} from "@halo-dev/console-shared";
|
|
||||||
import UserProfileLayout from "./layouts/UserProfileLayout.vue";
|
import UserProfileLayout from "./layouts/UserProfileLayout.vue";
|
||||||
import UserList from "./UserList.vue";
|
import UserList from "./UserList.vue";
|
||||||
import UserDetail from "./UserDetail.vue";
|
import UserDetail from "./UserDetail.vue";
|
||||||
import PersonalAccessTokens from "./PersonalAccessTokens.vue";
|
import PersonalAccessTokens from "./PersonalAccessTokens.vue";
|
||||||
import Login from "./Login.vue";
|
import Login from "./Login.vue";
|
||||||
import { IconUserSettings } from "@halo-dev/components";
|
import { IconUserSettings } from "@halo-dev/components";
|
||||||
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "userModule",
|
name: "userModule",
|
||||||
|
@ -35,6 +34,13 @@ export default definePlugin({
|
||||||
title: "用户",
|
title: "用户",
|
||||||
searchable: true,
|
searchable: true,
|
||||||
permissions: ["system:users:view"],
|
permissions: ["system:users:view"],
|
||||||
|
menu: {
|
||||||
|
name: "用户",
|
||||||
|
group: "system",
|
||||||
|
icon: markRaw(IconUserSettings),
|
||||||
|
priority: 1,
|
||||||
|
mobile: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -65,16 +71,4 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
menus: [
|
|
||||||
{
|
|
||||||
name: "系统",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: "用户",
|
|
||||||
path: "/users",
|
|
||||||
icon: IconUserSettings,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
import {
|
|
||||||
IconBookRead,
|
|
||||||
IconDashboard,
|
|
||||||
IconFolder,
|
|
||||||
IconMessage,
|
|
||||||
IconUserSettings,
|
|
||||||
} from "@halo-dev/components";
|
|
||||||
|
|
||||||
import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared";
|
|
||||||
|
|
||||||
export const menus: MenuGroupType[] = [
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "内容",
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "外观",
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "系统",
|
|
||||||
items: [],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const minimenus: MenuItemType[] = [
|
|
||||||
{
|
|
||||||
name: "仪表盘",
|
|
||||||
path: "/dashboard",
|
|
||||||
icon: IconDashboard,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "文章",
|
|
||||||
path: "/posts",
|
|
||||||
icon: IconBookRead,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "评论",
|
|
||||||
path: "/comments",
|
|
||||||
icon: IconMessage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "附件",
|
|
||||||
path: "/attachments",
|
|
||||||
icon: IconFolder,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "用户",
|
|
||||||
path: "/users/profile/detail",
|
|
||||||
icon: IconUserSettings,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function registerMenu(group: string | undefined, menu: MenuItemType) {
|
|
||||||
const groupIndex = menus.findIndex((g) => g.name === group);
|
|
||||||
if (groupIndex !== -1) {
|
|
||||||
menus[groupIndex].items.push(menu);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
menus.push({
|
|
||||||
name: group,
|
|
||||||
items: [menu],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { MenuItemType, MenuGroupType };
|
|
||||||
|
|
||||||
export default menus;
|
|
|
@ -1,8 +1,9 @@
|
||||||
import type { RouteRecordRaw } from "vue-router";
|
import type { RouteRecordRaw } from "vue-router";
|
||||||
import NotFound from "@/views/exceptions/NotFound.vue";
|
import NotFound from "@/views/exceptions/NotFound.vue";
|
||||||
import Forbidden from "@/views/exceptions/Forbidden.vue";
|
import Forbidden from "@/views/exceptions/Forbidden.vue";
|
||||||
import { BasicLayout } from "@halo-dev/console-shared";
|
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||||
import Setup from "@/views/system/Setup.vue";
|
import Setup from "@/views/system/Setup.vue";
|
||||||
|
import type { MenuGroupType } from "@halo-dev/console-shared";
|
||||||
|
|
||||||
export const routes: Array<RouteRecordRaw> = [
|
export const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
|
@ -31,4 +32,32 @@ export const routes: Array<RouteRecordRaw> = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const coreMenuGroups: MenuGroupType[] = [
|
||||||
|
{
|
||||||
|
id: "dashboard",
|
||||||
|
name: undefined,
|
||||||
|
priority: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "content",
|
||||||
|
name: "内容",
|
||||||
|
priority: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "interface",
|
||||||
|
name: "外观",
|
||||||
|
priority: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "system",
|
||||||
|
name: "系统",
|
||||||
|
priority: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "tool",
|
||||||
|
name: "工具",
|
||||||
|
priority: 4,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import "@halo-dev/richtext-editor/dist/style.css";
|
import "@halo-dev/richtext-editor/dist/style.css";
|
||||||
import "@halo-dev/components/dist/style.css";
|
import "@halo-dev/components/dist/style.css";
|
||||||
import "@/styles/tailwind.css";
|
import "@/styles/tailwind.css";
|
||||||
import "@halo-dev/console-shared/dist/style.css";
|
|
||||||
|
|
Loading…
Reference in New Issue