refactor: load admin core as modules (#582)

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/583/head
Ryan Wang 2022-06-17 14:12:15 +08:00 committed by GitHub
parent b142b3ca80
commit 8bc6ff798e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 615 additions and 354 deletions

View File

@ -26,8 +26,8 @@
],
"dependencies": {
"@halo-dev/admin-api": "^1.1.0",
"@halo-dev/components": "workspace:*",
"@halo-dev/admin-shared": "workspace:*",
"@halo-dev/components": "workspace:*",
"@vueuse/core": "^8.6.0",
"filepond": "^4.30.4",
"filepond-plugin-image-preview": "^4.6.11",
@ -72,6 +72,7 @@
"vite": "^2.9.12",
"vite-compression-plugin": "^0.0.4",
"vite-plugin-pwa": "^0.12.0",
"vite-plugin-vue-setup-extend": "^0.4.0",
"vitest": "^0.15.1",
"vue-tsc": "^0.34.17"
}

View File

@ -45,6 +45,7 @@ importers:
vite: ^2.9.12
vite-compression-plugin: ^0.0.4
vite-plugin-pwa: ^0.12.0
vite-plugin-vue-setup-extend: ^0.4.0
vitest: ^0.15.1
vue: ^3.2.37
vue-filepond: ^7.0.3
@ -98,6 +99,7 @@ importers:
vite: 2.9.12_sass@1.52.3
vite-compression-plugin: 0.0.4
vite-plugin-pwa: 0.12.0_vite@2.9.12
vite-plugin-vue-setup-extend: 0.4.0_vite@2.9.12
vitest: 0.15.1_fiumxgyk2tfafw3c4rsaverrnm
vue-tsc: 0.34.17_typescript@4.7.3
@ -6783,6 +6785,16 @@ packages:
- supports-color
dev: true
/vite-plugin-vue-setup-extend/0.4.0_vite@2.9.12:
resolution: {integrity: sha512-WMbjPCui75fboFoUTHhdbXzu4Y/bJMv5N9QT9a7do3wNMNHHqrk+Tn2jrSJU0LS5fGl/EG+FEDBYVUeWIkDqXQ==}
peerDependencies:
vite: '>=2.0.0'
dependencies:
'@vue/compiler-sfc': 3.2.37
magic-string: 0.25.9
vite: 2.9.12_sass@1.52.3
dev: true
/vite/2.9.12:
resolution: {integrity: sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==}
engines: {node: '>=12.2.0'}

View File

@ -2,16 +2,78 @@ import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
import type { Plugin } from "@halo-dev/admin-shared";
// setup
import "./setup/setupStyles";
import { setupComponents } from "./setup/setupComponents";
import { registerMenu } from "@/router/menus.config";
// modules
import dashboardModule from "./modules/dashboard/module";
import postModule from "./modules/contents/posts/module";
import sheetModule from "./modules/contents/sheets/module";
import commentModule from "./modules/contents/comments/module";
import attachmentModule from "./modules/contents/attachments/module";
import themeModule from "./modules/interface/themes/module";
import menuModule from "./modules/interface/menus/module";
import pluginModule from "./modules/system/plugins/module";
import userModule from "./modules/system/users/module";
import roleModule from "./modules/system/roles/module";
import settingModule from "./modules/system/settings/module";
const app = createApp(App);
setupComponents(app);
app.use(createPinia());
app.use(router);
app.mount("#app");
async function registerModule(pluginModule: Plugin) {
if (pluginModule.components) {
for (const component of pluginModule.components) {
component.name && app.component(component.name, component);
}
}
if (pluginModule.routes) {
for (const route of pluginModule.routes) {
router.addRoute(route);
}
}
if (pluginModule.menus) {
for (const group of pluginModule.menus) {
for (const menu of group.items) {
registerMenu(group.name, menu);
}
}
}
}
function loadCoreModules() {
[
dashboardModule,
postModule,
sheetModule,
commentModule,
attachmentModule,
themeModule,
menuModule,
pluginModule,
userModule,
roleModule,
settingModule,
].forEach(registerModule);
}
function loadPluginModules() {
// TODO: load plugin modules
}
initApp();
async function initApp() {
loadCoreModules();
loadPluginModules();
app.use(router);
app.mount("#app");
}

View File

@ -0,0 +1,36 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout } from "@/layouts";
import AttachmentList from "./AttachmentList.vue";
import { IconFolder } from "@halo-dev/components";
const attachmentModule: Plugin = {
name: "attachmentModule",
components: [],
routes: [
{
path: "/attachments",
component: BasicLayout,
children: [
{
path: "",
name: "Attachments",
component: AttachmentList,
},
],
},
],
menus: [
{
name: "内容",
items: [
{
name: "附件",
path: "/attachments",
icon: IconFolder,
},
],
},
],
};
export default attachmentModule;

View File

@ -0,0 +1,36 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout } from "@/layouts";
import { IconMessage } from "@halo-dev/components";
import CommentList from "./CommentList.vue";
const commentModule: Plugin = {
name: "commentModule",
components: [],
routes: [
{
path: "/comments",
component: BasicLayout,
children: [
{
path: "",
name: "Comments",
component: CommentList,
},
],
},
],
menus: [
{
name: "内容",
items: [
{
name: "评论",
path: "/comments",
icon: IconMessage,
},
],
},
],
};
export default commentModule;

View File

@ -0,0 +1,66 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout, BlankLayout } from "@/layouts";
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";
const postModule: Plugin = {
name: "postModule",
components: [],
routes: [
{
path: "/posts",
component: BasicLayout,
children: [
{
path: "",
name: "Posts",
component: PostList,
},
{
path: "editor",
name: "PostEditor",
component: PostEditor,
},
{
path: "categories",
component: BlankLayout,
children: [
{
path: "",
name: "Categories",
component: CategoryList,
},
],
},
{
path: "tags",
component: BlankLayout,
children: [
{
path: "",
name: "Tags",
component: TagList,
},
],
},
],
},
],
menus: [
{
name: "内容",
items: [
{
name: "文章",
path: "/posts",
icon: IconBookRead,
},
],
},
],
};
export default postModule;

View File

@ -0,0 +1,36 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout } from "@/layouts";
import SheetList from "./SheetList.vue";
import { IconPages } from "@halo-dev/components";
const sheetModule: Plugin = {
name: "sheetModule",
components: [],
routes: [
{
path: "/sheets",
component: BasicLayout,
children: [
{
path: "",
name: "Sheets",
component: SheetList,
},
],
},
],
menus: [
{
name: "内容",
items: [
{
name: "页面",
path: "/sheets",
icon: IconPages,
},
],
},
],
};
export default sheetModule;

View File

@ -0,0 +1,55 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout } from "@/layouts";
import Dashboard from "./Dashboard.vue";
import { IconDashboard } from "@halo-dev/components";
import CommentStatsWidget from "./widgets/CommentStatsWidget.vue";
import JournalPublishWidget from "./widgets/JournalPublishWidget.vue";
import PostStatsWidget from "./widgets/PostStatsWidget.vue";
import QuickLinkWidget from "./widgets/QuickLinkWidget.vue";
import RecentLoginWidget from "./widgets/RecentLoginWidget.vue";
import RecentPublishedWidget from "./widgets/RecentPublishedWidget.vue";
import UserStatsWidget from "./widgets/UserStatsWidget.vue";
import ViewsStatsWidget from "./widgets/ViewsStatsWidget.vue";
const dashboardModule: Plugin = {
name: "dashboardModule",
components: [
CommentStatsWidget,
JournalPublishWidget,
PostStatsWidget,
QuickLinkWidget,
RecentLoginWidget,
RecentPublishedWidget,
UserStatsWidget,
ViewsStatsWidget,
],
routes: [
{
path: "/",
component: BasicLayout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "Dashboard",
component: Dashboard,
},
],
},
],
menus: [
{
name: "",
items: [
{
name: "仪表盘",
path: "/dashboard",
icon: IconDashboard,
},
],
},
],
};
export default dashboardModule;

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="CommentStatsWidget" setup>
import { VCard } from "@halo-dev/components";
</script>
<template>

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="JournalPublishWidget" setup>
import { VButton, VCard, VTextarea } from "@halo-dev/components";
</script>
<template>

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="PostStatsWidget" setup>
import { VCard } from "@halo-dev/components";
</script>
<template>

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="QuickLinkWidget" setup>
import {
IconArrowRight,
IconBookRead,

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="RecentLoginWidget" setup>
import { VCard } from "@halo-dev/components";
import { users } from "@/modules/system/users/users-mock";
</script>

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="RecentPublishedWidget" setup>
import { VCard, VSpace } from "@halo-dev/components";
import { posts } from "@/modules/contents/posts/posts-mock";
</script>

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="UserStatsWidget" setup>
import { VCard } from "@halo-dev/components";
</script>
<template>

View File

@ -1,4 +1,4 @@
<script lang="ts" setup>
<script lang="ts" name="ViewsStatsWidget" setup>
import { VCard } from "@halo-dev/components";
</script>
<template>

View File

@ -1,23 +0,0 @@
import type { App } from "vue";
import PostStatsWidget from "./PostStatsWidget.vue";
import UserStatsWidget from "./UserStatsWidget.vue";
import CommentStatsWidget from "./CommentStatsWidget.vue";
import ViewsStatsWidget from "./ViewsStatsWidget.vue";
import RecentLoginWidget from "./RecentLoginWidget.vue";
import RecentPublishedWidget from "./RecentPublishedWidget.vue";
import JournalPublishWidget from "./JournalPublishWidget.vue";
import QuickLinkWidget from "./QuickLinkWidget.vue";
const install = (app: App) => {
app.component("PostStatsWidget", PostStatsWidget);
app.component("UserStatsWidget", UserStatsWidget);
app.component("CommentStatsWidget", CommentStatsWidget);
app.component("ViewsStatsWidget", ViewsStatsWidget);
app.component("RecentLoginWidget", RecentLoginWidget);
app.component("RecentPublishedWidget", RecentPublishedWidget);
app.component("JournalPublishWidget", JournalPublishWidget);
app.component("QuickLinkWidget", QuickLinkWidget);
};
export default install;

View File

@ -0,0 +1,36 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout } from "@/layouts";
import MenuList from "./MenuList.vue";
import { IconListSettings } from "@halo-dev/components";
const menuModule: Plugin = {
name: "menuModule",
components: [],
routes: [
{
path: "/menus",
component: BasicLayout,
children: [
{
path: "",
name: "Menus",
component: MenuList,
},
],
},
],
menus: [
{
name: "外观",
items: [
{
name: "菜单",
path: "/menus",
icon: IconListSettings,
},
],
},
],
};
export default menuModule;

View File

@ -0,0 +1,48 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout, BlankLayout } from "@/layouts";
import ThemeDetail from "./ThemeDetail.vue";
import Visual from "./Visual.vue";
import { IconPalette } from "@halo-dev/components";
const themeModule: Plugin = {
name: "themeModule",
components: [],
routes: [
{
path: "/theme",
component: BasicLayout,
children: [
{
path: "",
name: "Theme",
component: ThemeDetail,
},
],
},
{
path: "/theme/visual",
component: BlankLayout,
children: [
{
path: "",
name: "ThemeVisual",
component: Visual,
},
],
},
],
menus: [
{
name: "外观",
items: [
{
name: "主题",
path: "/theme",
icon: IconPalette,
},
],
},
],
};
export default themeModule;

View File

@ -0,0 +1,42 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout } from "@/layouts";
import PluginList from "./PluginList.vue";
import PluginDetail from "./PluginDetail.vue";
import { IconPlug } from "@halo-dev/components";
const pluginModule: Plugin = {
name: "pluginModule",
components: [],
routes: [
{
path: "/plugins",
component: BasicLayout,
children: [
{
path: "",
name: "Plugins",
component: PluginList,
},
{
path: ":id",
name: "PluginDetail",
component: PluginDetail,
},
],
},
],
menus: [
{
name: "系统",
items: [
{
name: "插件",
path: "/plugins",
icon: IconPlug,
},
],
},
],
};
export default pluginModule;

View File

@ -0,0 +1,30 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout } from "@/layouts";
import RoleList from "./RoleList.vue";
import RoleDetail from "./RoleDetail.vue";
const roleModule: Plugin = {
name: "roleModule",
components: [],
routes: [
{
path: "/users",
component: BasicLayout,
children: [
{
path: "roles",
name: "Roles",
component: RoleList,
},
{
path: "roles/:id",
name: "RoleDetail",
component: RoleDetail,
},
],
},
],
menus: [],
};
export default roleModule;

View File

@ -0,0 +1,43 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { SystemSettingsLayout } from "@/layouts";
import GeneralSettings from "./GeneralSettings.vue";
import NotificationSettings from "./NotificationSettings.vue";
import { IconSettings } from "@halo-dev/components";
const settingModule: Plugin = {
name: "settingModule",
components: [],
routes: [
{
path: "/settings",
component: SystemSettingsLayout,
redirect: "/settings/general",
children: [
{
path: "general",
name: "GeneralSettings",
component: GeneralSettings,
},
{
path: "notification",
name: "NotificationSettings",
component: NotificationSettings,
},
],
},
],
menus: [
{
name: "系统",
items: [
{
name: "设置",
path: "/settings",
icon: IconSettings,
},
],
},
],
};
export default settingModule;

View File

@ -0,0 +1,73 @@
import type { Plugin } from "@halo-dev/admin-shared";
import { BasicLayout, BlankLayout, UserProfileLayout } from "@/layouts";
import UserList from "./UserList.vue";
import UserDetail from "./UserDetail.vue";
import ProfileModification from "./ProfileModification.vue";
import PasswordChange from "./PasswordChange.vue";
import PersonalAccessTokens from "./PersonalAccessTokens.vue";
import { IconUserSettings } from "@halo-dev/components";
const userModule: Plugin = {
name: "userModule",
components: [],
routes: [
{
path: "/users",
component: BlankLayout,
children: [
{
path: "",
component: BasicLayout,
children: [
{
path: "",
name: "Users",
component: UserList,
},
],
},
{
path: ":username",
component: UserProfileLayout,
alias: ["profile"],
children: [
{
path: "detail",
name: "UserDetail",
component: UserDetail,
},
{
path: "profile-modification",
name: "ProfileModification",
component: ProfileModification,
},
{
path: "password-change",
name: "PasswordChange",
component: PasswordChange,
},
{
path: "tokens",
name: "PersonalAccessTokens",
component: PersonalAccessTokens,
},
],
},
],
},
],
menus: [
{
name: "系统",
items: [
{
name: "用户",
path: "/users",
icon: IconUserSettings,
},
],
},
],
};
export default userModule;

View File

@ -2,98 +2,28 @@ import {
IconBookRead,
IconDashboard,
IconFolder,
IconListSettings,
IconMessage,
IconPages,
IconPalette,
IconPlug,
IconSettings,
IconUserSettings,
} from "@halo-dev/components";
import type { Component } from "vue";
declare interface MenuGroupType {
name?: string;
items: MenuItemType[];
}
declare interface MenuItemType {
name: string;
path: string;
icon?: Component;
meta?: Record<string, unknown>;
children?: MenuItemType[];
}
import type { MenuGroupType, MenuItemType } from "@halo-dev/admin-shared";
export const menus: MenuGroupType[] = [
{
items: [
{
name: "仪表盘",
path: "/dashboard",
icon: IconDashboard,
},
],
name: "",
items: [],
},
{
name: "内容",
items: [
{
name: "文章",
path: "/posts",
icon: IconBookRead,
},
{
name: "页面",
path: "/sheets",
icon: IconPages,
},
{
name: "评论",
path: "/comments",
icon: IconMessage,
},
{
name: "附件",
path: "/attachments",
icon: IconFolder,
},
],
items: [],
},
{
name: "外观",
items: [
{
name: "主题",
path: "/theme",
icon: IconPalette,
},
{
name: "菜单",
path: "/menus",
icon: IconListSettings,
},
],
items: [],
},
{
name: "系统",
items: [
{
name: "插件",
path: "/plugins",
icon: IconPlug,
},
{
name: "用户",
path: "/users",
icon: IconUserSettings,
},
{
name: "设置",
path: "/settings",
icon: IconSettings,
},
],
items: [],
},
];
@ -125,6 +55,18 @@ export const minimenus: MenuItemType[] = [
},
];
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;

View File

@ -1,246 +1,12 @@
import type { RouteRecordRaw } from "vue-router";
import {
BasicLayout,
BlankLayout,
SystemSettingsLayout,
UserProfileLayout,
} from "@/layouts";
import Dashboard from "../modules/dashboard/Dashboard.vue";
import PostList from "../modules/contents/posts/PostList.vue";
import PostEditor from "../modules/contents/posts/PostEditor.vue";
import SheetList from "../modules/contents/sheets/SheetList.vue";
import CategoryList from "../modules/contents/posts/categories/CategoryList.vue";
import TagList from "../modules/contents/posts/tags/TagList.vue";
import CommentList from "../modules/contents/comments/CommentList.vue";
import AttachmentList from "../modules/contents/attachments/AttachmentList.vue";
import ThemeDetail from "../modules/interface/themes/ThemeDetail.vue";
import MenuList from "../modules/interface/menus/MenuList.vue";
import Visual from "../modules/interface/themes/Visual.vue";
import PluginList from "../modules/system/plugins/PluginList.vue";
import PluginDetail from "../modules/system/plugins/PluginDetail.vue";
import UserList from "../modules/system/users/UserList.vue";
import RoleList from "../modules/system/roles/RoleList.vue";
import RoleDetail from "../modules/system/roles/RoleDetail.vue";
import UserDetail from "../modules/system/users/UserDetail.vue";
import ProfileModification from "../modules/system/users/ProfileModification.vue";
import PasswordChange from "../modules/system/users/PasswordChange.vue";
import PersonalAccessTokens from "../modules/system/users/PersonalAccessTokens.vue";
import GeneralSettings from "../modules/system/settings/GeneralSettings.vue";
import NotificationSettings from "../modules/system/settings/NotificationSettings.vue";
import NotFound from "@/views/exceptions/NotFound.vue";
import { BasicLayout } from "@/layouts";
export const routes: Array<RouteRecordRaw> = [
{
path: "/",
path: "/:pathMatch(.*)*",
component: BasicLayout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "Dashboard",
component: Dashboard,
},
],
},
{
path: "/posts",
component: BasicLayout,
children: [
{
path: "",
name: "Posts",
component: PostList,
},
{
path: "editor",
name: "PostEditor",
component: PostEditor,
},
{
path: "categories",
component: BlankLayout,
children: [
{
path: "",
name: "Categories",
component: CategoryList,
},
],
},
{
path: "tags",
component: BlankLayout,
children: [
{
path: "",
name: "Tags",
component: TagList,
},
],
},
],
},
{
path: "/sheets",
component: BasicLayout,
children: [
{
path: "",
name: "Sheets",
component: SheetList,
},
],
},
{
path: "/comments",
component: BasicLayout,
children: [
{
path: "",
name: "Comments",
component: CommentList,
},
],
},
{
path: "/attachments",
component: BasicLayout,
children: [
{
path: "",
name: "Attachments",
component: AttachmentList,
},
],
},
{
path: "/theme",
component: BasicLayout,
children: [
{
path: "",
name: "Theme",
component: ThemeDetail,
},
],
},
{
path: "/theme/visual",
component: BlankLayout,
children: [
{
path: "",
name: "ThemeVisual",
component: Visual,
},
],
},
{
path: "/menus",
component: BasicLayout,
children: [
{
path: "",
name: "Menus",
component: MenuList,
},
],
},
{
path: "/plugins",
component: BasicLayout,
children: [
{
path: "",
name: "Plugins",
component: PluginList,
},
{
path: ":id",
name: "PluginDetail",
component: PluginDetail,
},
],
},
{
path: "/users",
component: BlankLayout,
children: [
{
path: "",
component: BasicLayout,
children: [
{
path: "",
name: "Users",
component: UserList,
},
],
},
{
path: ":username",
component: UserProfileLayout,
alias: ["profile"],
children: [
{
path: "detail",
name: "UserDetail",
component: UserDetail,
},
{
path: "profile-modification",
name: "ProfileModification",
component: ProfileModification,
},
{
path: "password-change",
name: "PasswordChange",
component: PasswordChange,
},
{
path: "tokens",
name: "PersonalAccessTokens",
component: PersonalAccessTokens,
},
],
},
{
path: "",
component: BasicLayout,
children: [
{
path: "roles",
name: "Roles",
component: RoleList,
},
{
path: "roles/:id",
name: "RoleDetail",
component: RoleDetail,
},
],
},
],
},
{
path: "/settings",
component: SystemSettingsLayout,
redirect: "/settings/general",
children: [
{
path: "general",
name: "GeneralSettings",
component: GeneralSettings,
},
{
path: "notification",
name: "NotificationSettings",
component: NotificationSettings,
},
],
children: [{ path: "", name: "NotFound", component: NotFound }],
},
];

View File

@ -3,11 +3,9 @@ import { Dropdown, Menu, Tooltip, VClosePopper, VTooltip } from "floating-vue";
import "floating-vue/dist/style.css";
// @ts-ignore
import VueGridLayout from "vue-grid-layout";
import Widgets from "@/modules/dashboard/widgets";
export function setupComponents(app: App) {
app.use(VueGridLayout);
app.use(Widgets);
app.directive("tooltip", VTooltip);
app.directive("close-popper", VClosePopper);

View File

@ -0,0 +1 @@
<template>404</template>

View File

@ -3,14 +3,15 @@ import { fileURLToPath, URL } from "url";
import { defineConfig } from "vite";
import Vue from "@vitejs/plugin-vue";
import VueJsx from "@vitejs/plugin-vue-jsx";
import VueSetupExtend from "vite-plugin-vue-setup-extend";
import Compression from "vite-compression-plugin";
import { VitePWA } from "vite-plugin-pwa";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
Vue(),
VueJsx(),
VueSetupExtend(),
Compression(),
VitePWA({
manifest: {