mirror of https://github.com/halo-dev/halo
feat: add system setup support (halo-dev/console#632)
#### What type of PR is this? /kind feature /milestone 2.0 #### What this PR does / why we need it: 支持初始化默认数据。 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2428 #### Special notes for your reviewer: /cc @halo-dev/sig-halo-console 测试方式: 1. 清空数据库。 2. 启动之后登录即可跳转到初始化界面。 3. 测试初始化的功能是否正常。 #### Does this PR introduce a user-facing change? ```release-note None ```pull/3445/head
parent
4b6096ce20
commit
0e81366754
|
@ -0,0 +1,21 @@
|
||||||
|
import { useSystemStatesStore } from "@/stores/system-states";
|
||||||
|
import type { Router } from "vue-router";
|
||||||
|
|
||||||
|
export function setupCheckStatesGuard(router: Router) {
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
if (to.name === "Setup" || to.name === "Login") {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemStateStore = useSystemStatesStore();
|
||||||
|
|
||||||
|
await systemStateStore.fetchSystemStates();
|
||||||
|
|
||||||
|
if (!systemStateStore.states.isSetup) {
|
||||||
|
next({ name: "Setup" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { createRouter, createWebHashHistory } from "vue-router";
|
import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
import routesConfig from "@/router/routes.config";
|
import routesConfig from "@/router/routes.config";
|
||||||
import { setupPermissionGuard } from "./guards/permission";
|
import { setupPermissionGuard } from "./guards/permission";
|
||||||
|
import { setupCheckStatesGuard } from "./guards/check-states";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||||
|
@ -9,5 +10,6 @@ const router = createRouter({
|
||||||
});
|
});
|
||||||
|
|
||||||
setupPermissionGuard(router);
|
setupPermissionGuard(router);
|
||||||
|
setupCheckStatesGuard(router);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -2,6 +2,7 @@ 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/admin-shared";
|
import { BasicLayout } from "@halo-dev/admin-shared";
|
||||||
|
import Setup from "@/views/system/Setup.vue";
|
||||||
|
|
||||||
export const routes: Array<RouteRecordRaw> = [
|
export const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
|
@ -20,6 +21,14 @@ export const routes: Array<RouteRecordRaw> = [
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/setup",
|
||||||
|
name: "Setup",
|
||||||
|
component: Setup,
|
||||||
|
meta: {
|
||||||
|
title: "系统初始化",
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
interface SystemState {
|
||||||
|
isSetup: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SystemStatesState {
|
||||||
|
states: SystemState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSystemStatesStore = defineStore({
|
||||||
|
id: "system-states",
|
||||||
|
state: (): SystemStatesState => ({
|
||||||
|
states: {
|
||||||
|
isSetup: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async fetchSystemStates() {
|
||||||
|
try {
|
||||||
|
const { data } =
|
||||||
|
await apiClient.extension.configMap.getv1alpha1ConfigMap({
|
||||||
|
name: "system-states",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.data) {
|
||||||
|
this.states = JSON.parse(data.data["states"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.states.isSetup = false;
|
||||||
|
} catch (error) {
|
||||||
|
this.states.isSetup = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,148 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import logo from "@/assets/logo.svg";
|
||||||
|
import { useSettingForm } from "@/composables/use-setting-form";
|
||||||
|
import { useSystemStatesStore } from "@/stores/system-states";
|
||||||
|
import { apiClient } from "@/utils/api-client";
|
||||||
|
import { VButton } from "@halo-dev/components";
|
||||||
|
import { onMounted, ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import category from "./setup-data/category.json";
|
||||||
|
import tag from "./setup-data/tag.json";
|
||||||
|
import post from "./setup-data/post.json";
|
||||||
|
import singlePage from "./setup-data/singlePage.json";
|
||||||
|
import menu from "./setup-data/menu.json";
|
||||||
|
import menuItems from "./setup-data/menu-items.json";
|
||||||
|
import type {
|
||||||
|
Category,
|
||||||
|
PostRequest,
|
||||||
|
SinglePageRequest,
|
||||||
|
Tag,
|
||||||
|
} from "@halo-dev/api-client";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const {
|
||||||
|
configMapFormData,
|
||||||
|
handleSaveConfigMap,
|
||||||
|
handleFetchSettings,
|
||||||
|
handleFetchConfigMap,
|
||||||
|
} = useSettingForm(ref("system"), ref("system"));
|
||||||
|
|
||||||
|
const siteTitle = ref("");
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
// Set site title
|
||||||
|
if (configMapFormData.value) {
|
||||||
|
configMapFormData.value["basic"].title = siteTitle.value;
|
||||||
|
await handleSaveConfigMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create category / tag / post
|
||||||
|
await apiClient.extension.category.createcontentHaloRunV1alpha1Category({
|
||||||
|
category: category as Category,
|
||||||
|
});
|
||||||
|
await apiClient.extension.tag.createcontentHaloRunV1alpha1Tag({
|
||||||
|
tag: tag as Tag,
|
||||||
|
});
|
||||||
|
await apiClient.post.draftPost({ postRequest: post as PostRequest });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiClient.post.publishPost({ name: post.post.metadata.name });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to publish post", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create singlePage
|
||||||
|
await apiClient.singlePage.draftSinglePage({
|
||||||
|
singlePageRequest: singlePage as SinglePageRequest,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiClient.singlePage.publishSinglePage({
|
||||||
|
name: singlePage.page.metadata.name,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to publish singlePage", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create menu and menu items
|
||||||
|
const menuItemPromises = menuItems.map((item) => {
|
||||||
|
return apiClient.extension.menuItem.createv1alpha1MenuItem({
|
||||||
|
menuItem: item,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await Promise.all(menuItemPromises);
|
||||||
|
await apiClient.extension.menu.createv1alpha1Menu({ menu: menu });
|
||||||
|
|
||||||
|
// Create system-states ConfigMap
|
||||||
|
await apiClient.extension.configMap.createv1alpha1ConfigMap({
|
||||||
|
configMap: {
|
||||||
|
metadata: {
|
||||||
|
name: "system-states",
|
||||||
|
},
|
||||||
|
kind: "ConfigMap",
|
||||||
|
apiVersion: "v1alpha1",
|
||||||
|
data: {
|
||||||
|
states: JSON.stringify({ isSetup: true }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
router.push({ name: "Dashboard" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to setup", error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const systemStatesStore = useSystemStatesStore();
|
||||||
|
|
||||||
|
if (systemStatesStore.states.isSetup) {
|
||||||
|
router.push({ name: "Dashboard" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFetchSettings();
|
||||||
|
handleFetchConfigMap();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex h-screen flex-col items-center justify-center">
|
||||||
|
<img :src="logo" alt="Logo" class="mb-8 w-20" />
|
||||||
|
<div class="flex w-72 flex-col gap-4">
|
||||||
|
<FormKit
|
||||||
|
id="setup-form"
|
||||||
|
name="setup-form"
|
||||||
|
:actions="false"
|
||||||
|
type="form"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
@keyup.enter="$formkit.submit('setup-form')"
|
||||||
|
>
|
||||||
|
<FormKit
|
||||||
|
v-model="siteTitle"
|
||||||
|
:validation-messages="{
|
||||||
|
required: '请输入站点名称',
|
||||||
|
}"
|
||||||
|
type="text"
|
||||||
|
placeholder="站点名称"
|
||||||
|
validation="required"
|
||||||
|
></FormKit>
|
||||||
|
</FormKit>
|
||||||
|
<VButton
|
||||||
|
block
|
||||||
|
type="secondary"
|
||||||
|
:loading="loading"
|
||||||
|
@click="$formkit.submit('setup-form')"
|
||||||
|
>
|
||||||
|
初始化
|
||||||
|
</VButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"displayName": "默认分类",
|
||||||
|
"slug": "default",
|
||||||
|
"description": "这是你的默认分类,如不需要,删除即可。",
|
||||||
|
"cover": "",
|
||||||
|
"template": "",
|
||||||
|
"priority": 0,
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
"apiVersion": "content.halo.run/v1alpha1",
|
||||||
|
"kind": "Category",
|
||||||
|
"metadata": {
|
||||||
|
"name": "76514a40-6ef1-4ed9-b58a-e26945bde3ca"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"displayName": "首页",
|
||||||
|
"href": "/",
|
||||||
|
"children": [],
|
||||||
|
"priority": 0
|
||||||
|
},
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "MenuItem",
|
||||||
|
"metadata": { "name": "88c3f10b-321c-4092-86a8-70db00251b74" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"children": [],
|
||||||
|
"priority": 1,
|
||||||
|
"categoryRef": {
|
||||||
|
"version": "content.halo.run/v1alpha1",
|
||||||
|
"kind": "Post",
|
||||||
|
"name": "76514a40-6ef1-4ed9-b58a-e26945bde3ca"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "MenuItem",
|
||||||
|
"metadata": { "name": "c4c814d1-0c2c-456b-8c96-4864965fee94" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"displayName": "",
|
||||||
|
"href": "",
|
||||||
|
"children": [],
|
||||||
|
"priority": 2,
|
||||||
|
"tagRef": {
|
||||||
|
"version": "content.halo.run/v1alpha1",
|
||||||
|
"kind": "Tag",
|
||||||
|
"name": "c33ceabb-d8f1-4711-8991-bb8f5c92ad7c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "MenuItem",
|
||||||
|
"metadata": { "name": "35869bd3-33b5-448b-91ee-cf6517a59644" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"displayName": "",
|
||||||
|
"href": "",
|
||||||
|
"children": [],
|
||||||
|
"priority": 3,
|
||||||
|
"singlePageRef": {
|
||||||
|
"version": "content.halo.run/v1alpha1",
|
||||||
|
"kind": "SinglePage",
|
||||||
|
"name": "373a5f79-f44f-441a-9df1-85a4f553ece8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "MenuItem",
|
||||||
|
"metadata": { "name": "b0d041fa-dc99-48f6-a193-8604003379cf" }
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"displayName": "主菜单",
|
||||||
|
"menuItems": [
|
||||||
|
"88c3f10b-321c-4092-86a8-70db00251b74",
|
||||||
|
"c4c814d1-0c2c-456b-8c96-4864965fee94",
|
||||||
|
"35869bd3-33b5-448b-91ee-cf6517a59644",
|
||||||
|
"b0d041fa-dc99-48f6-a193-8604003379cf"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"apiVersion": "v1alpha1",
|
||||||
|
"kind": "Menu",
|
||||||
|
"metadata": { "name": "2f0ef354-6f8f-40b9-b41d-2163f3e51b4f" }
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"post": {
|
||||||
|
"spec": {
|
||||||
|
"title": "Hello Halo",
|
||||||
|
"slug": "hello-halo",
|
||||||
|
"template": "",
|
||||||
|
"cover": "",
|
||||||
|
"deleted": false,
|
||||||
|
"published": false,
|
||||||
|
"publishTime": "",
|
||||||
|
"pinned": false,
|
||||||
|
"allowComment": true,
|
||||||
|
"visible": "PUBLIC",
|
||||||
|
"version": 1,
|
||||||
|
"priority": 0,
|
||||||
|
"excerpt": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"raw": "如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo 进行创作,希望能够使用愉快。"
|
||||||
|
},
|
||||||
|
"categories": ["76514a40-6ef1-4ed9-b58a-e26945bde3ca"],
|
||||||
|
"tags": ["c33ceabb-d8f1-4711-8991-bb8f5c92ad7c"],
|
||||||
|
"htmlMetas": []
|
||||||
|
},
|
||||||
|
"apiVersion": "content.halo.run/v1alpha1",
|
||||||
|
"kind": "Post",
|
||||||
|
"metadata": {
|
||||||
|
"name": "5152aea5-c2e8-4717-8bba-2263d46e19d5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"raw": "<h2 id=\"hello-halo\"><strong>Hello Halo</strong></h2><p>如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://halo.run/\">Halo</a> 进行创作,希望能够使用愉快。</p><h2 id=\"%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5\"><strong>相关链接</strong></h2><ul><li><p>官网:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://halo.run\">https://halo.run</a></p></li><li><p>文档:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://docs.halo.run\">https://docs.halo.run</a></p></li><li><p>社区:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://bbs.halo.run\">https://bbs.halo.run</a></p></li><li><p>主题仓库:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://halo.run/themes.html\">https://halo.run/themes.html</a></p></li><li><p>开源地址:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://github.com/halo-dev/halo\">https://github.com/halo-dev/halo</a></p></li></ul><p>在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。</p><blockquote><p>这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!</p></blockquote>",
|
||||||
|
"content": "<h2 id=\"hello-halo\"><strong>Hello Halo</strong></h2><p>如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 <a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://halo.run/\">Halo</a> 进行创作,希望能够使用愉快。</p><h2 id=\"%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5\"><strong>相关链接</strong></h2><ul><li><p>官网:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://halo.run\">https://halo.run</a></p></li><li><p>文档:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://docs.halo.run\">https://docs.halo.run</a></p></li><li><p>社区:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://bbs.halo.run\">https://bbs.halo.run</a></p></li><li><p>主题仓库:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://halo.run/themes.html\">https://halo.run/themes.html</a></p></li><li><p>开源地址:<a target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://github.com/halo-dev/halo\">https://github.com/halo-dev/halo</a></p></li></ul><p>在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。</p><blockquote><p>这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!</p></blockquote>",
|
||||||
|
"rawType": "HTML"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"page": {
|
||||||
|
"spec": {
|
||||||
|
"title": "关于",
|
||||||
|
"slug": "about",
|
||||||
|
"template": "",
|
||||||
|
"cover": "",
|
||||||
|
"deleted": false,
|
||||||
|
"published": false,
|
||||||
|
"publishTime": "",
|
||||||
|
"pinned": false,
|
||||||
|
"allowComment": true,
|
||||||
|
"visible": "PUBLIC",
|
||||||
|
"version": 1,
|
||||||
|
"priority": 0,
|
||||||
|
"excerpt": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"raw": "这是一个自定义页面,你可以在后台的 页面 -> 自定义页面 找到它,你可以用于新建关于页面、联系我们页面等等。"
|
||||||
|
},
|
||||||
|
"htmlMetas": []
|
||||||
|
},
|
||||||
|
"apiVersion": "content.halo.run/v1alpha1",
|
||||||
|
"kind": "SinglePage",
|
||||||
|
"metadata": { "name": "373a5f79-f44f-441a-9df1-85a4f553ece8" }
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"raw": "<h2><strong>关于页面</strong></h2><p>这是一个自定义页面,你可以在后台的 <code>页面</code> -> <code>自定义页面</code> 找到它,你可以用于新建关于页面、联系我们页面等等。</p><blockquote><p>这是一篇自动生成的页面,你可以在后台删除它。</p></blockquote>",
|
||||||
|
"content": "<h2><strong>关于页面</strong></h2><p>这是一个自定义页面,你可以在后台的 <code>页面</code> -> <code>自定义页面</code> 找到它,你可以用于新建关于页面、联系我们页面等等。</p><blockquote><p>这是一篇自动生成的页面,你可以在后台删除它。</p></blockquote>",
|
||||||
|
"rawType": "HTML"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"spec": {
|
||||||
|
"displayName": "Halo",
|
||||||
|
"slug": "halo",
|
||||||
|
"color": "#ad95b2",
|
||||||
|
"cover": ""
|
||||||
|
},
|
||||||
|
"apiVersion": "content.halo.run/v1alpha1",
|
||||||
|
"kind": "Tag",
|
||||||
|
"metadata": {
|
||||||
|
"name": "c33ceabb-d8f1-4711-8991-bb8f5c92ad7c"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue