mirror of https://github.com/halo-dev/halo
refactor: refactor the page management and remove the function page (halo-dev/console#816)
#### What type of PR is this? /kind api-change /kind improvement #### What this PR does / why we need it: 1. 重构页面管理,移除功能页面的功能,改为由插件自行配置菜单。 2. 改进自定义页面的 UI 权限绑定,不会再出现没有勾选相关角色,但仍然显示左侧**页面**菜单的问题。 原由请看:https://github.com/halo-dev/halo/issues/3124 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3124 #### Screenshots: <img width="1920" alt="image" src="https://user-images.githubusercontent.com/21301288/211480169-fd0490a6-bd1a-447c-bde4-155a16355734.png"> 插件需要自己定义菜单配置,如: <img width="1920" alt="image" src="https://user-images.githubusercontent.com/21301288/211480228-146e6b53-9da4-4a60-b691-dd183f0a45c7.png"> ```diff export default definePlugin({ - name: "PluginLinks", components: {}, routes: [ { parentName: "Root", route: { - path: "/pages/functional/links", + path: "/links", name: "Links", component: LinkList, meta: { permissions: ["plugin:links:view"], + menu: { + name: "链接", + group: "content", + icon: markRaw(RiLinksLine), + }, }, }, }, ], - extensionPoints: { - "page:functional:create": () => { - return [ - { - name: "链接", - url: "/links", - path: "/pages/functional/links", - permissions: ["plugin:links:view"], - }, - ]; - }, - }, }); ``` #### Special notes for your reviewer: 测试方式: 1. 测试左侧菜单的页面入口是否正常。 2. 创建一个新的角色,不勾选页面的查看权限,登录后检查是否可以在左侧菜单看到页面选项。 3. 测试插件添加菜单是否正常,可测试插件:[plugin-links-1.0.0-SNAPSHOT-plain.jar.zip](https://github.com/halo-dev/console/files/10379709/plugin-links-1.0.0-SNAPSHOT-plain.jar.zip) #### Does this PR introduce a user-facing change? ```release-note 重构页面管理,移除功能页面的功能。 ```pull/3445/head
parent
4785660b18
commit
589bd0d1b4
|
@ -1,3 +1,4 @@
|
|||
// @deprecated
|
||||
export interface FunctionalPage {
|
||||
name: string;
|
||||
path: string;
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface RouteRecordAppend {
|
|||
}
|
||||
|
||||
export interface ExtensionPoint {
|
||||
// @deprecated
|
||||
"page:functional:create"?: () => FunctionalPage[] | Promise<FunctionalPage[]>;
|
||||
|
||||
"attachment:selector:create"?: () =>
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import {
|
||||
VEmpty,
|
||||
VSpace,
|
||||
VButton,
|
||||
IconAddCircle,
|
||||
VEntity,
|
||||
VEntityField,
|
||||
} from "@halo-dev/components";
|
||||
import type { FunctionalPage, PluginModule } from "@halo-dev/console-shared";
|
||||
import { usePluginModuleStore } from "@/stores/plugin";
|
||||
|
||||
const functionalPages = ref<FunctionalPage[]>([] as FunctionalPage[]);
|
||||
|
||||
// resolve plugin extension points
|
||||
const { pluginModules } = usePluginModuleStore();
|
||||
|
||||
onMounted(() => {
|
||||
pluginModules.forEach((pluginModule: PluginModule) => {
|
||||
const { extensionPoints } = pluginModule;
|
||||
if (!extensionPoints?.["page:functional:create"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pages = extensionPoints[
|
||||
"page:functional:create"
|
||||
]() as FunctionalPage[];
|
||||
|
||||
if (pages) {
|
||||
pages.forEach((page) => {
|
||||
functionalPages.value.push(page);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VEmpty
|
||||
v-if="!functionalPages.length"
|
||||
message="当前没有功能页面,功能页面通常由各个插件提供,你可以尝试安装新插件以获得支持"
|
||||
title="当前没有功能页面"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :route="{ name: 'Plugins' }" type="primary">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
安装插件
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li
|
||||
v-for="(page, index) in functionalPages"
|
||||
:key="index"
|
||||
v-permission="page.permissions"
|
||||
>
|
||||
<VEntity>
|
||||
<template #start>
|
||||
<VEntityField
|
||||
:title="page.name"
|
||||
:route="page.path"
|
||||
:description="page.url"
|
||||
></VEntityField>
|
||||
</template>
|
||||
</VEntity>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
|
@ -9,6 +9,7 @@ import {
|
|||
IconAddCircle,
|
||||
IconRefreshLine,
|
||||
IconExternalLinkLine,
|
||||
IconPages,
|
||||
VButton,
|
||||
VCard,
|
||||
VPagination,
|
||||
|
@ -20,6 +21,7 @@ import {
|
|||
VEntity,
|
||||
VEntityField,
|
||||
VLoading,
|
||||
VPageHeader,
|
||||
Toast,
|
||||
} from "@halo-dev/components";
|
||||
import SinglePageSettingModal from "./components/SinglePageSettingModal.vue";
|
||||
|
@ -439,7 +441,36 @@ function handleClearFilters() {
|
|||
</span>
|
||||
</template>
|
||||
</SinglePageSettingModal>
|
||||
<VCard :body-class="['!p-0']" class="rounded-none border-none shadow-none">
|
||||
|
||||
<VPageHeader title="页面">
|
||||
<template #icon>
|
||||
<IconPages class="mr-2 self-center" />
|
||||
</template>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton
|
||||
v-permission="['system:singlepages:view']"
|
||||
:route="{ name: 'DeletedSinglePages' }"
|
||||
size="sm"
|
||||
>
|
||||
回收站
|
||||
</VButton>
|
||||
<VButton
|
||||
v-permission="['system:singlepages:manage']"
|
||||
:route="{ name: 'SinglePageEditor' }"
|
||||
type="secondary"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VPageHeader>
|
||||
|
||||
<div class="m-0 md:m-4">
|
||||
<VCard :body-class="['!p-0']">
|
||||
<template #header>
|
||||
<div class="block w-full bg-gray-50 px-4 py-3">
|
||||
<div
|
||||
|
@ -489,7 +520,10 @@ function handleClearFilters() {
|
|||
可见性:{{ selectedVisibleItem.label }}
|
||||
</FilterTag>
|
||||
|
||||
<FilterTag v-if="selectedContributor" @close="handleSelectUser()">
|
||||
<FilterTag
|
||||
v-if="selectedContributor"
|
||||
@close="handleSelectUser()"
|
||||
>
|
||||
作者:{{ selectedContributor?.spec.displayName }}
|
||||
</FilterTag>
|
||||
|
||||
|
@ -506,7 +540,9 @@ function handleClearFilters() {
|
|||
/>
|
||||
</div>
|
||||
<VSpace v-else>
|
||||
<VButton type="danger" @click="handleDeleteInBatch">删除</VButton>
|
||||
<VButton type="danger" @click="handleDeleteInBatch"
|
||||
>删除</VButton
|
||||
>
|
||||
</VSpace>
|
||||
</div>
|
||||
<div class="mt-4 flex sm:mt-0">
|
||||
|
@ -648,7 +684,10 @@ function handleClearFilters() {
|
|||
</VEmpty>
|
||||
</Transition>
|
||||
<Transition v-else appear name="fade">
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
<ul
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(singlePage, index) in singlePages.items" :key="index">
|
||||
<VEntity :is-selected="checkSelection(singlePage.page)">
|
||||
<template
|
||||
|
@ -808,4 +847,5 @@ function handleClearFilters() {
|
|||
</div>
|
||||
</template>
|
||||
</VCard>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref, watchEffect } from "vue";
|
||||
import {
|
||||
VCard,
|
||||
IconAddCircle,
|
||||
VPageHeader,
|
||||
VTabbar,
|
||||
IconPages,
|
||||
VButton,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import BasicLayout from "@/layouts/BasicLayout.vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
interface PageTab {
|
||||
id: string;
|
||||
label: string;
|
||||
route: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
const tabs = ref<PageTab[]>([
|
||||
{
|
||||
id: "functional",
|
||||
label: "功能页面",
|
||||
route: {
|
||||
name: "FunctionalPages",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "single",
|
||||
label: "自定义页面",
|
||||
route: {
|
||||
name: "SinglePages",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const activeTab = ref(tabs.value[0].id);
|
||||
|
||||
const onTabChange = (routeName: string) => {
|
||||
const tab = tabs.value.find((tab) => {
|
||||
return tab.route.name === routeName;
|
||||
});
|
||||
if (tab) {
|
||||
activeTab.value = tab.id;
|
||||
return;
|
||||
}
|
||||
activeTab.value = tabs.value[0].id;
|
||||
};
|
||||
|
||||
const handleTabChange = (id: string) => {
|
||||
const tab = tabs.value.find((item) => item.id === id);
|
||||
if (tab) {
|
||||
router.push(tab.route);
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
onTabChange(route.name as string);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicLayout>
|
||||
<VPageHeader title="页面">
|
||||
<template #icon>
|
||||
<IconPages class="mr-2 self-center" />
|
||||
</template>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :route="{ name: 'DeletedSinglePages' }" size="sm">
|
||||
回收站
|
||||
</VButton>
|
||||
<VButton
|
||||
v-permission="['system:singlepages:manage']"
|
||||
:route="{ name: 'SinglePageEditor' }"
|
||||
type="secondary"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新建
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VPageHeader>
|
||||
<div class="m-0 md:m-4">
|
||||
<VCard :body-class="['!p-0']">
|
||||
<template #header>
|
||||
<VTabbar
|
||||
v-model:active-id="activeTab"
|
||||
:items="tabs"
|
||||
class="w-full !rounded-none"
|
||||
type="outline"
|
||||
@change="handleTabChange"
|
||||
></VTabbar>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<RouterView :key="activeTab" />
|
||||
</div>
|
||||
</VCard>
|
||||
</div>
|
||||
</BasicLayout>
|
||||
</template>
|
|
@ -1,8 +1,5 @@
|
|||
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 DeletedSinglePageList from "./DeletedSinglePageList.vue";
|
||||
import SinglePageEditor from "./SinglePageEditor.vue";
|
||||
|
@ -16,78 +13,38 @@ export default definePlugin({
|
|||
},
|
||||
routes: [
|
||||
{
|
||||
path: "/pages",
|
||||
component: BlankLayout,
|
||||
name: "BasePages",
|
||||
redirect: {
|
||||
name: "FunctionalPages",
|
||||
},
|
||||
meta: {
|
||||
menu: {
|
||||
name: "页面",
|
||||
group: "content",
|
||||
icon: markRaw(IconPages),
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "functional",
|
||||
component: PageLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "FunctionalPages",
|
||||
component: FunctionalPageList,
|
||||
meta: {
|
||||
title: "功能页面",
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "single",
|
||||
component: BlankLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: PageLayout,
|
||||
path: "/single-pages",
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "SinglePages",
|
||||
component: SinglePageList,
|
||||
meta: {
|
||||
title: "自定义页面",
|
||||
title: "页面",
|
||||
searchable: true,
|
||||
permissions: ["system:singlepages:view"],
|
||||
menu: {
|
||||
name: "页面",
|
||||
group: "content",
|
||||
icon: markRaw(IconPages),
|
||||
priority: 1,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "deleted",
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "DeletedSinglePages",
|
||||
component: DeletedSinglePageList,
|
||||
meta: {
|
||||
title: "自定义页面回收站",
|
||||
title: "页面回收站",
|
||||
searchable: true,
|
||||
permissions: ["system:singlepages:view"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "editor",
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "SinglePageEditor",
|
||||
component: SinglePageEditor,
|
||||
meta: {
|
||||
|
@ -99,8 +56,4 @@ export default definePlugin({
|
|||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue