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" />
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 VMenuItem } from "./MenuItem.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" />
|
||||
|
||||
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",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@halo-dev/components": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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 "./states/pages";
|
||||
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";
|
||||
|
||||
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<string, unknown>;
|
||||
children?: MenuItemType[];
|
||||
|
|
|
@ -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<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`,
|
||||
},
|
||||
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",
|
||||
|
|
|
@ -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
|
||||
|
|
22
src/App.vue
22
src/App.vue
|
@ -1,8 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
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 GlobalSearchModal from "@/components/global-search/GlobalSearchModal.vue";
|
||||
|
||||
const AppName = "Halo";
|
||||
const route = useRoute();
|
||||
|
@ -19,29 +18,10 @@ watch(
|
|||
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>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
<GlobalSearchModal v-model:visible="globalSearchVisible" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
IconPages,
|
||||
IconUserSettings,
|
||||
} 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 { apiClient } from "@/utils/api-client";
|
||||
import { usePermission } from "@/utils/permission";
|
||||
|
@ -356,8 +356,6 @@ watch(
|
|||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
handleBuildSearchIndex();
|
||||
|
||||
setTimeout(() => {
|
||||
globalSearchInput.value?.focus();
|
||||
}, 100);
|
||||
|
@ -371,6 +369,10 @@ watch(
|
|||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
handleBuildSearchIndex();
|
||||
});
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
};
|
||||
|
@ -381,7 +383,6 @@ const onVisibleChange = (visible: boolean) => {
|
|||
:visible="visible"
|
||||
:body-class="['!p-0']"
|
||||
:mount-to-body="true"
|
||||
class="items-start"
|
||||
:width="650"
|
||||
:centered="false"
|
||||
@update:visible="onVisibleChange"
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import type { Component, PropType } from "vue";
|
||||
import { computed, defineComponent } from "vue";
|
||||
import type { MenuGroupType, MenuItemType } from "./interface";
|
||||
import { VMenu, VMenuItem, VMenuLabel } from "./index";
|
||||
import type { MenuGroupType, MenuItemType } from "@halo-dev/console-shared";
|
||||
import { VMenu, VMenuItem, VMenuLabel } from "@halo-dev/components";
|
||||
import type { RouteLocationMatched } from "vue-router";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const VRoutesMenu = defineComponent({
|
||||
name: "VRoutesMenu",
|
||||
const RoutesMenu = defineComponent({
|
||||
name: "RoutesMenu",
|
||||
props: {
|
||||
menus: {
|
||||
type: Object as PropType<MenuGroupType[]>,
|
||||
|
@ -80,4 +80,4 @@ const VRoutesMenu = defineComponent({
|
|||
},
|
||||
});
|
||||
|
||||
export { VRoutesMenu };
|
||||
export { RoutesMenu };
|
|
@ -3,22 +3,30 @@ import {
|
|||
IconMore,
|
||||
IconSearch,
|
||||
IconUserSettings,
|
||||
VRoutesMenu,
|
||||
VTag,
|
||||
VAvatar,
|
||||
VSpace,
|
||||
VButton,
|
||||
Dialog,
|
||||
} 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 logo from "@/assets/logo.svg";
|
||||
import { RouterView, useRoute, useRouter } from "vue-router";
|
||||
import { computed, inject, ref, type Ref } from "vue";
|
||||
import {
|
||||
RouterView,
|
||||
useRoute,
|
||||
useRouter,
|
||||
type RouteRecordRaw,
|
||||
} from "vue-router";
|
||||
import { computed, inject, onMounted, onUnmounted, ref } from "vue";
|
||||
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 router = useRouter();
|
||||
|
||||
|
@ -26,18 +34,13 @@ const moreMenuVisible = ref(false);
|
|||
const moreMenuRootVisible = ref(false);
|
||||
|
||||
const currentUser = inject<User>("currentUser");
|
||||
const apiUrl = inject<string>("apiUrl");
|
||||
|
||||
const handleRouteToProfile = () => {
|
||||
router.push({ path: `/users/${currentUser?.metadata.name}/detail` });
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
Dialog.warning({
|
||||
title: "是否确认退出登录?",
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
await axios.post(`${apiUrl}/logout`, undefined, {
|
||||
await axios.post(`${import.meta.env.VITE_API_URL}/logout`, undefined, {
|
||||
withCredentials: true,
|
||||
});
|
||||
router.replace({ name: "Login" });
|
||||
|
@ -56,12 +59,126 @@ const currentRole = computed(() => {
|
|||
)[0];
|
||||
});
|
||||
|
||||
const globalSearchVisible = inject<Ref<boolean>>(
|
||||
"globalSearchVisible",
|
||||
ref(false)
|
||||
);
|
||||
// Global Search
|
||||
const globalSearchVisible = ref(false);
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
@ -84,7 +201,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VRoutesMenu :menus="menus" />
|
||||
<RoutesMenu :menus="menus" />
|
||||
<div class="current-profile">
|
||||
<div v-if="currentUser?.spec.avatar" class="profile-avatar">
|
||||
<VAvatar
|
||||
|
@ -118,7 +235,10 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|||
v-close-popper
|
||||
block
|
||||
type="secondary"
|
||||
@click="handleRouteToProfile"
|
||||
:route="{
|
||||
name: 'UserDetail',
|
||||
params: { name: currentUser?.metadata.name },
|
||||
}"
|
||||
>
|
||||
个人资料
|
||||
</VButton>
|
||||
|
@ -136,6 +256,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|||
</FloatingDropdown>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="content w-full overflow-y-auto pb-12 mb-safe md:pb-0">
|
||||
<slot v-if="$slots.default" />
|
||||
<RouterView v-else />
|
||||
|
@ -144,7 +265,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|||
<!--bottom nav bar-->
|
||||
<div
|
||||
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
|
||||
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"
|
||||
>
|
||||
<div class="drawer-body">
|
||||
<VRoutesMenu
|
||||
<RoutesMenu
|
||||
:menus="menus"
|
||||
class="p-0"
|
||||
@select="moreMenuVisible = false"
|
||||
|
@ -229,6 +350,7 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
<GlobalSearchModal v-model:visible="globalSearchVisible" />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -241,24 +363,24 @@ const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
|
|||
|
||||
.current-profile {
|
||||
height: 70px;
|
||||
@apply w-64
|
||||
bg-white
|
||||
p-3
|
||||
flex
|
||||
fixed
|
||||
@apply fixed
|
||||
left-0
|
||||
bottom-0
|
||||
gap-3;
|
||||
flex
|
||||
w-64
|
||||
gap-3
|
||||
bg-white
|
||||
p-3;
|
||||
|
||||
.profile-avatar {
|
||||
@apply self-center
|
||||
flex
|
||||
items-center;
|
||||
@apply flex
|
||||
items-center
|
||||
self-center;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
@apply self-center
|
||||
flex-1;
|
||||
@apply flex-1
|
||||
self-center;
|
||||
}
|
||||
|
||||
.profile-control {
|
51
src/main.ts
51
src/main.ts
|
@ -3,14 +3,9 @@ import { createApp } from "vue";
|
|||
import { createPinia } from "pinia";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import type {
|
||||
MenuGroupType,
|
||||
MenuItemType,
|
||||
Plugin,
|
||||
} from "@halo-dev/console-shared";
|
||||
import type { Plugin, RouteRecordAppend } from "@halo-dev/console-shared";
|
||||
import { Toast } from "@halo-dev/components";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import { menus, minimenus, registerMenu } from "./router/menus.config";
|
||||
// setup
|
||||
import "./setup/setupStyles";
|
||||
import { setupComponents } from "./setup/setupComponents";
|
||||
|
@ -21,6 +16,7 @@ import { usePluginStore } from "@/stores/plugin";
|
|||
import type { User } from "@halo-dev/api-client";
|
||||
import { hasPermission } from "@/utils/permission";
|
||||
import { useRoleStore } from "@/stores/role";
|
||||
import type { RouteRecordRaw } from "vue-router";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
|
@ -28,7 +24,7 @@ setupComponents(app);
|
|||
|
||||
app.use(createPinia());
|
||||
|
||||
function registerModule(pluginModule: Plugin) {
|
||||
function registerModule(pluginModule: Plugin, core: boolean) {
|
||||
if (pluginModule.components) {
|
||||
if (!Array.isArray(pluginModule.components)) {
|
||||
console.error(`${pluginModule.name}: Plugin components must be an array`);
|
||||
|
@ -46,6 +42,8 @@ function registerModule(pluginModule: Plugin) {
|
|||
return;
|
||||
}
|
||||
|
||||
resetRouteMeta(pluginModule.routes);
|
||||
|
||||
for (const route of pluginModule.routes) {
|
||||
if ("parentName" in route) {
|
||||
router.addRoute(route.parentName, route.route);
|
||||
|
@ -55,22 +53,37 @@ function registerModule(pluginModule: Plugin) {
|
|||
}
|
||||
}
|
||||
|
||||
if (pluginModule.menus) {
|
||||
if (!Array.isArray(pluginModule.menus)) {
|
||||
console.error(`${pluginModule.name}: Plugin menus must be an array`);
|
||||
return;
|
||||
function resetRouteMeta(routes: RouteRecordRaw[] | RouteRecordAppend[]) {
|
||||
for (const route of routes) {
|
||||
if ("parentName" in route) {
|
||||
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() {
|
||||
coreModules.forEach(registerModule);
|
||||
coreModules.forEach((module) => {
|
||||
registerModule(module, true);
|
||||
});
|
||||
}
|
||||
|
||||
const pluginStore = usePluginStore();
|
||||
|
@ -133,7 +146,7 @@ async function loadPluginModules() {
|
|||
if (pluginModule) {
|
||||
// @ts-ignore
|
||||
plugin.spec.module = pluginModule;
|
||||
registerModule(pluginModule);
|
||||
registerModule(pluginModule, false);
|
||||
}
|
||||
} catch (e) {
|
||||
const message = `${plugin.metadata.name}: 加载插件入口文件失败`;
|
||||
|
@ -215,10 +228,6 @@ async function initApp() {
|
|||
} catch (e) {
|
||||
console.error(e);
|
||||
} 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.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 AttachmentSelectorModal from "./components/AttachmentSelectorModal.vue";
|
||||
import { IconFolder } from "@halo-dev/components";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "attachmentModule",
|
||||
|
@ -18,19 +20,14 @@ export default definePlugin({
|
|||
meta: {
|
||||
title: "附件",
|
||||
permissions: ["system:attachments:view"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
menus: [
|
||||
{
|
||||
name: "内容",
|
||||
items: [
|
||||
{
|
||||
menu: {
|
||||
name: "附件",
|
||||
path: "/attachments",
|
||||
icon: IconFolder,
|
||||
group: "content",
|
||||
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 CommentList from "./CommentList.vue";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "commentModule",
|
||||
|
@ -18,19 +20,14 @@ export default definePlugin({
|
|||
title: "评论",
|
||||
searchable: true,
|
||||
permissions: ["system:comments:view"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
menus: [
|
||||
{
|
||||
name: "内容",
|
||||
items: [
|
||||
{
|
||||
menu: {
|
||||
name: "评论",
|
||||
path: "/comments",
|
||||
icon: IconMessage,
|
||||
group: "content",
|
||||
icon: markRaw(IconMessage),
|
||||
priority: 2,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
IconPages,
|
||||
VButton,
|
||||
} from "@halo-dev/components";
|
||||
import { BasicLayout } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import {
|
||||
BasicLayout,
|
||||
BlankLayout,
|
||||
definePlugin,
|
||||
} from "@halo-dev/console-shared";
|
||||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||
import PageLayout from "./layouts/PageLayout.vue";
|
||||
import FunctionalPageList from "./FunctionalPageList.vue";
|
||||
import SinglePageList from "./SinglePageList.vue";
|
||||
import SinglePageEditor from "./SinglePageEditor.vue";
|
||||
import { IconPages } from "@halo-dev/components";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "pageModule",
|
||||
|
@ -20,6 +19,14 @@ export default definePlugin({
|
|||
redirect: {
|
||||
name: "FunctionalPages",
|
||||
},
|
||||
meta: {
|
||||
menu: {
|
||||
name: "页面",
|
||||
group: "content",
|
||||
icon: markRaw(IconPages),
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "functional",
|
||||
|
@ -77,16 +84,4 @@ export default definePlugin({
|
|||
],
|
||||
},
|
||||
],
|
||||
menus: [
|
||||
{
|
||||
name: "内容",
|
||||
items: [
|
||||
{
|
||||
name: "页面",
|
||||
path: "/pages",
|
||||
icon: IconPages,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import {
|
||||
BasicLayout,
|
||||
BlankLayout,
|
||||
definePlugin,
|
||||
} from "@halo-dev/console-shared";
|
||||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||
import { IconBookRead } from "@halo-dev/components";
|
||||
import PostList from "./PostList.vue";
|
||||
import PostEditor from "./PostEditor.vue";
|
||||
import CategoryList from "./categories/CategoryList.vue";
|
||||
import TagList from "./tags/TagList.vue";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "postModule",
|
||||
|
@ -25,6 +24,13 @@ export default definePlugin({
|
|||
title: "文章",
|
||||
searchable: true,
|
||||
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 { IconDashboard } from "@halo-dev/components";
|
||||
|
||||
|
@ -9,6 +10,7 @@ import RecentLoginWidget from "./widgets/RecentLoginWidget.vue";
|
|||
import RecentPublishedWidget from "./widgets/RecentPublishedWidget.vue";
|
||||
import UserStatsWidget from "./widgets/UserStatsWidget.vue";
|
||||
import ViewsStatsWidget from "./widgets/ViewsStatsWidget.vue";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "dashboardModule",
|
||||
|
@ -25,6 +27,7 @@ export default definePlugin({
|
|||
{
|
||||
path: "/",
|
||||
component: BasicLayout,
|
||||
name: "Root",
|
||||
redirect: "/dashboard",
|
||||
children: [
|
||||
{
|
||||
|
@ -34,19 +37,14 @@ export default definePlugin({
|
|||
meta: {
|
||||
title: "仪表盘",
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
menus: [
|
||||
{
|
||||
name: "",
|
||||
items: [
|
||||
{
|
||||
menu: {
|
||||
name: "仪表盘",
|
||||
path: "/dashboard",
|
||||
icon: IconDashboard,
|
||||
group: "dashboard",
|
||||
icon: markRaw(IconDashboard),
|
||||
priority: 0,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -10,18 +10,32 @@ import userModule from "./system/users/module";
|
|||
import roleModule from "./system/roles/module";
|
||||
import settingModule from "./system/settings/module";
|
||||
|
||||
// const coreModules = [
|
||||
// dashboardModule,
|
||||
// postModule,
|
||||
// pageModule,
|
||||
// commentModule,
|
||||
// attachmentModule,
|
||||
// themeModule,
|
||||
// menuModule,
|
||||
// pluginModule,
|
||||
// userModule,
|
||||
// roleModule,
|
||||
// settingModule,
|
||||
// ];
|
||||
|
||||
const coreModules = [
|
||||
dashboardModule,
|
||||
postModule,
|
||||
pageModule,
|
||||
pluginModule,
|
||||
settingModule,
|
||||
dashboardModule,
|
||||
menuModule,
|
||||
commentModule,
|
||||
attachmentModule,
|
||||
pageModule,
|
||||
themeModule,
|
||||
menuModule,
|
||||
pluginModule,
|
||||
userModule,
|
||||
roleModule,
|
||||
settingModule,
|
||||
];
|
||||
|
||||
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 { IconListSettings } from "@halo-dev/components";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "menuModule",
|
||||
|
@ -18,19 +20,13 @@ export default definePlugin({
|
|||
title: "菜单",
|
||||
searchable: true,
|
||||
permissions: ["system:menus:view"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
menus: [
|
||||
{
|
||||
name: "外观",
|
||||
items: [
|
||||
{
|
||||
menu: {
|
||||
name: "菜单",
|
||||
path: "/menus",
|
||||
icon: IconListSettings,
|
||||
group: "interface",
|
||||
icon: markRaw(IconListSettings),
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ import cloneDeep from "lodash.clonedeep";
|
|||
// hooks
|
||||
import { useThemeLifeCycle } from "../composables/use-theme";
|
||||
// types
|
||||
import { BasicLayout } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import { useSettingForm } from "@/composables/use-setting-form";
|
||||
|
||||
// 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 ThemeDetail from "./ThemeDetail.vue";
|
||||
import ThemeSetting from "./ThemeSetting.vue";
|
||||
import Visual from "./Visual.vue";
|
||||
import { IconPalette } from "@halo-dev/components";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "themeModule",
|
||||
|
@ -21,6 +23,12 @@ export default definePlugin({
|
|||
title: "主题",
|
||||
searchable: true,
|
||||
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
|
||||
import { VButton, VCard, VPageHeader, VTabbar } from "@halo-dev/components";
|
||||
import { BasicLayout } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
|
||||
// types
|
||||
import type { Ref } from "vue";
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import {
|
||||
BasicLayout,
|
||||
BlankLayout,
|
||||
definePlugin,
|
||||
} from "@halo-dev/console-shared";
|
||||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||
import PluginLayout from "./layouts/PluginLayout.vue";
|
||||
import PluginList from "./PluginList.vue";
|
||||
import PluginSetting from "./PluginSetting.vue";
|
||||
import PluginDetail from "./PluginDetail.vue";
|
||||
import { IconPlug } from "@halo-dev/components";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "pluginModule",
|
||||
|
@ -29,6 +28,12 @@ export default definePlugin({
|
|||
title: "插件",
|
||||
searchable: true,
|
||||
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 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";
|
||||
|
||||
// types
|
||||
import { BasicLayout } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import { useSettingForm } from "@/composables/use-setting-form";
|
||||
|
||||
// components
|
||||
|
|
|
@ -2,6 +2,7 @@ import { definePlugin } from "@halo-dev/console-shared";
|
|||
import SystemSettingsLayout from "./layouts/SystemSettingsLayout.vue";
|
||||
import SystemSetting from "./SystemSetting.vue";
|
||||
import { IconSettings } from "@halo-dev/components";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "settingModule",
|
||||
|
@ -11,27 +12,21 @@ export default definePlugin({
|
|||
path: "/settings",
|
||||
component: SystemSettingsLayout,
|
||||
redirect: "/settings/basic",
|
||||
meta: {
|
||||
title: "系统设置",
|
||||
permissions: ["system:settings:view"],
|
||||
menu: {
|
||||
name: "设置",
|
||||
group: "system",
|
||||
icon: markRaw(IconSettings),
|
||||
priority: 2,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: ":group",
|
||||
name: "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>
|
||||
import { BasicLayout } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import {
|
||||
IconUpload,
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import {
|
||||
BasicLayout,
|
||||
BlankLayout,
|
||||
definePlugin,
|
||||
} from "@halo-dev/console-shared";
|
||||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import BlankLayout from "@/layouts/BlankLayout.vue";
|
||||
import UserProfileLayout from "./layouts/UserProfileLayout.vue";
|
||||
import UserList from "./UserList.vue";
|
||||
import UserDetail from "./UserDetail.vue";
|
||||
import PersonalAccessTokens from "./PersonalAccessTokens.vue";
|
||||
import Login from "./Login.vue";
|
||||
import { IconUserSettings } from "@halo-dev/components";
|
||||
import { markRaw } from "vue";
|
||||
|
||||
export default definePlugin({
|
||||
name: "userModule",
|
||||
|
@ -35,6 +34,13 @@ export default definePlugin({
|
|||
title: "用户",
|
||||
searchable: true,
|
||||
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 NotFound from "@/views/exceptions/NotFound.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 type { MenuGroupType } from "@halo-dev/console-shared";
|
||||
|
||||
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;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import "@halo-dev/richtext-editor/dist/style.css";
|
||||
import "@halo-dev/components/dist/style.css";
|
||||
import "@/styles/tailwind.css";
|
||||
import "@halo-dev/console-shared/dist/style.css";
|
||||
|
|
Loading…
Reference in New Issue