feat: system configmap setting page (halo-dev/console#603)

Signed-off-by: Ryan Wang <i@ryanc.cc>

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/3445/head
Ryan Wang 2022-09-06 15:22:37 +08:00 committed by GitHub
parent b9a8f5055c
commit 35b244dba8
13 changed files with 203 additions and 297 deletions

View File

@ -40,9 +40,11 @@
"dependencies": {
"@halo-dev/api-client": "^0.0.14",
"@halo-dev/components": "workspace:*",
"axios": "^0.27.2"
"axios": "^0.27.2",
"lodash.merge": "^4.6.2"
},
"devDependencies": {
"@types/lodash.merge": "^4.6.7",
"vite-plugin-dts": "^1.4.1"
},
"peerDependencies": {

View File

@ -6,8 +6,10 @@ import { apiClient } from "../utils/api-client";
// libs
import cloneDeep from "lodash.clonedeep";
import merge from "lodash.merge";
import type { FormKitSetting, FormKitSettingSpec } from "../types/formkit";
import type { ConfigMap } from "@halo-dev/api-client";
import type { FormKitSchemaNode } from "@formkit/core";
const initialConfigMap: ConfigMap = {
apiVersion: "v1alpha1",
@ -38,6 +40,7 @@ export function useSettingForm(
const configMapFormData = ref<
Record<string, Record<string, string>> | undefined
>();
const saving = ref(false);
const handleFetchSettings = async () => {
@ -50,6 +53,27 @@ export function useSettingForm(
name: settingName.value,
});
settings.value = response.data as FormKitSetting;
// init configMapFormData
if (!configMapFormData.value) {
const { spec: schemaGroups } = settings.value;
const initialConfigMapFormData: Record<
string,
Record<string, string>
> = {};
schemaGroups.forEach((schemaGroup) => {
initialConfigMapFormData[schemaGroup.group] = {};
const formSchema = schemaGroup.formSchema as FormKitSchemaNode[];
formSchema.forEach((schema) => {
// @ts-ignore
if ("name" in schema && "$formkit" in schema) {
initialConfigMapFormData[schemaGroup.group][schema.name] =
schema.value || undefined;
}
});
});
configMapFormData.value = cloneDeep(initialConfigMapFormData);
}
} catch (e) {
console.error(e);
}
@ -67,27 +91,27 @@ export function useSettingForm(
name: configMapName.value,
}
);
configMap.value = response.data;
const { data } = configMap.value;
if (data) {
configMapFormData.value = Object.keys(data).reduce((acc, key) => {
// @ts-ignore
acc[key] = JSON.parse(data[key]);
return acc;
}, {});
// merge objects value
const { spec: schemaGroups } = settings.value || {};
schemaGroups?.forEach((schemaGroup) => {
if (!configMapFormData.value) {
return;
}
configMapFormData.value[schemaGroup.group] = merge(
configMapFormData.value[schemaGroup.group] || {},
JSON.parse(data[schemaGroup.group] || "{}")
);
});
}
} catch (e) {
console.error(e);
} finally {
if (!configMapFormData.value) {
configMapFormData.value = settings.value?.spec.reduce((acc, item) => {
// @ts-ignore
acc[item.group] = {};
return acc;
}, {});
}
}
};

View File

@ -196,13 +196,17 @@ importers:
specifiers:
'@halo-dev/api-client': ^0.0.14
'@halo-dev/components': workspace:*
'@types/lodash.merge': ^4.6.7
axios: ^0.27.2
lodash.merge: ^4.6.2
vite-plugin-dts: ^1.4.1
dependencies:
'@halo-dev/api-client': 0.0.14
'@halo-dev/components': link:../components
axios: 0.27.2
lodash.merge: 4.6.2
devDependencies:
'@types/lodash.merge': 4.6.7
vite-plugin-dts: 1.4.1
packages:
@ -3196,6 +3200,12 @@ packages:
'@types/lodash': 4.14.182
dev: true
/@types/lodash.merge/4.6.7:
resolution: {integrity: sha512-OwxUJ9E50gw3LnAefSHJPHaBLGEKmQBQ7CZe/xflHkyy/wH2zVyEIAKReHvVrrn7zKdF58p16We9kMfh7v0RRQ==}
dependencies:
'@types/lodash': 4.14.182
dev: true
/@types/lodash/4.14.182:
resolution: {integrity: sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==}
dev: true
@ -6724,7 +6734,6 @@ packages:
/lodash.merge/4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash.mergewith/4.6.2:
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}

View File

@ -1,29 +0,0 @@
<script lang="ts" setup>
import { VButton } from "@halo-dev/components";
</script>
<template>
<div class="w-1/3">
<FormKit type="form">
<FormKit
help="插入代码到所有页面的 head 标签部分"
label="全局 head"
type="textarea"
></FormKit>
<FormKit
help="插入代码到文章页面和自定义页面的 head 标签部分"
label="内容页 head"
type="textarea"
></FormKit>
<FormKit
help="插入代码到所有页面的页脚部分"
label="页脚"
type="textarea"
></FormKit>
</FormKit>
</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</template>

View File

@ -1,41 +0,0 @@
<script lang="ts" setup>
import { VButton } from "@halo-dev/components";
</script>
<template>
<div class="w-1/3">
<FormKit :actions="false" type="form">
<FormKit
:options="[
{ label: '启用', value: true },
{ label: '禁用', value: false },
]"
:value="true"
label="启用评论"
type="radio"
></FormKit>
<FormKit
:options="[
{ label: '是', value: true },
{ label: '否', value: false },
]"
:value="true"
label="新评论审核"
type="radio"
></FormKit>
<FormKit
:options="[
{ label: '是', value: true },
{ label: '否', value: false },
]"
:value="true"
label="仅限注册用户评论"
type="radio"
></FormKit>
</FormKit>
</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</template>

View File

@ -1,18 +0,0 @@
<script lang="ts" setup>
import { VButton } from "@halo-dev/components";
</script>
<template>
<div class="w-1/3">
<FormKit :actions="false" type="form">
<FormKit label="站点标题" type="text"></FormKit>
<FormKit label="站点副标题" type="text"></FormKit>
<FormKit label="Logo" type="url"></FormKit>
<FormKit label="Favicon" type="url"></FormKit>
</FormKit>
</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</template>

View File

@ -1,11 +0,0 @@
<script lang="ts" setup>
import { VButton } from "@halo-dev/components";
</script>
<template>
<div class="w-1/3">// TODO</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</template>

View File

@ -1,34 +0,0 @@
<script lang="ts" setup>
import { VButton } from "@halo-dev/components";
</script>
<template>
<div class="w-1/3">
<FormKit type="form">
<FormKit
:options="[
{ label: '发布时间', value: '' },
{ label: '更新时间', value: '' },
{ label: '浏览量', value: '' },
]"
label="默认排序方式"
type="select"
></FormKit>
<FormKit label="默认显示条数" type="number"></FormKit>
<FormKit
:options="[
{ label: '是', value: true },
{ label: '否', value: false },
]"
:value="false"
label="新文章审核"
help="用户发布文章是否需要管理员审核"
type="radio"
></FormKit>
</FormKit>
</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</template>

View File

@ -1,25 +0,0 @@
<script lang="ts" setup>
import { VButton } from "@halo-dev/components";
</script>
<template>
<div class="w-1/3">
<FormKit type="form">
<FormKit
:options="[
{ label: '是', value: true },
{ label: '否', value: false },
]"
:value="false"
label="屏蔽搜索引擎"
type="radio"
></FormKit>
<FormKit label="站点关键词" type="textarea"></FormKit>
<FormKit label="站点描述" type="textarea"></FormKit>
</FormKit>
</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</template>

View File

@ -0,0 +1,67 @@
<script lang="ts" setup>
// core libs
import { computed, inject, onMounted, ref } from "vue";
// components
import { VButton } from "@halo-dev/components";
// types
import type { Ref } from "vue";
// hooks
import { useSettingForm } from "@halo-dev/admin-shared";
const group = inject<Ref<string | undefined>>("activeTab");
const settingName = ref("system");
const configMapName = ref("system");
const {
settings,
configMapFormData,
saving,
handleFetchConfigMap,
handleFetchSettings,
handleSaveConfigMap,
} = useSettingForm(settingName, configMapName);
const formSchema = computed(() => {
if (!settings?.value?.spec) {
return;
}
return settings.value.spec.find((item) => item.group === group?.value)
?.formSchema;
});
onMounted(async () => {
await handleFetchSettings();
await handleFetchConfigMap();
});
</script>
<template>
<div class="bg-white p-4 sm:px-6">
<div class="w-1/3">
<FormKit
v-if="group && formSchema && configMapFormData"
:id="group"
v-model="configMapFormData[group]"
:actions="false"
:preserve="true"
type="form"
@submit="handleSaveConfigMap"
>
<FormKitSchema :schema="formSchema" />
</FormKit>
</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton
:loading="saving"
type="secondary"
@click="$formkit.submit(group || '')"
>
保存
</VButton>
</div>
</div>
</div>
</template>

View File

@ -1,24 +0,0 @@
<script lang="ts" setup>
import { VButton } from "@halo-dev/components";
</script>
<template>
<div class="w-1/3">
<FormKit type="form">
<FormKit
:options="[
{ label: '是', value: true },
{ label: '否', value: false },
]"
:value="false"
label="是否公开注册"
type="radio"
></FormKit>
<FormKit label="默认角色" type="select"></FormKit>
</FormKit>
</div>
<div class="pt-5">
<div class="flex justify-start">
<VButton type="secondary"> 保存</VButton>
</div>
</div>
</template>

View File

@ -1,70 +1,94 @@
<script lang="ts" setup>
import { BasicLayout } from "@halo-dev/admin-shared";
// core libs
import { onMounted, type Ref } from "vue";
import { provide, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
// types
import type { FormKitSettingSpec } from "@halo-dev/admin-shared";
import { BasicLayout, useSettingForm } from "@halo-dev/admin-shared";
// components
import {
IconSettings,
VButton,
VCard,
VPageHeader,
VTabbar,
IconSettings,
} from "@halo-dev/components";
import { onMounted, ref } from "vue";
import { RouterView, useRoute, useRouter } from "vue-router";
const SettingTabs = [
{
id: "general",
label: "基本设置",
routeName: "GeneralSettings",
},
{
id: "user",
label: "用户设置",
routeName: "UserSettings",
},
{
id: "post",
label: "文章设置",
routeName: "PostSettings",
},
{
id: "seo",
label: "SEO 设置",
routeName: "SeoSettings",
},
{
id: "comment",
label: "评论设置",
routeName: "CommentSettings",
},
{
id: "code-inject",
label: "代码注入",
routeName: "CodeInjectSettings",
},
{
id: "notification",
label: "通知设置",
routeName: "NotificationSettings",
},
];
interface SettingTab {
id: string;
label: string;
route: {
name: string;
params?: Record<string, string>;
};
}
const activeTab = ref();
const tabs = ref<SettingTab[]>([] as SettingTab[]);
const activeTab = ref("");
const { name: currentRouteName } = useRoute();
const settingName = ref("system");
const configMapName = ref("system");
const { settings, handleFetchSettings } = useSettingForm(
settingName,
configMapName
);
provide<Ref<string | undefined>>("activeTab", activeTab);
const route = useRoute();
const router = useRouter();
// set default active tab
onMounted(() => {
const tab = SettingTabs.find((tab) => tab.routeName === currentRouteName);
activeTab.value = tab ? tab.id : SettingTabs[0].id;
});
const handleTabChange = (id: string) => {
const tab = SettingTabs.find((tab) => tab.id === id);
const tab = tabs.value.find((item) => item.id === id);
if (tab) {
router.push({ name: tab.routeName });
router.push(tab.route);
}
};
onMounted(async () => {
await handleFetchSettings();
if (settings.value && settings.value.spec) {
tabs.value = settings.value.spec.map((item: FormKitSettingSpec) => {
return {
id: item.group,
label: item.label || "",
route: {
name: "SystemSetting",
params: {
group: item.group,
},
},
};
});
onTabChange(route.name as string);
}
});
const onTabChange = (routeName: string) => {
const tab = tabs.value.find((tab) => {
return (
tab.route.name === routeName &&
tab.route.params?.group === route.params.group
);
});
if (tab) {
activeTab.value = tab.id;
return;
}
activeTab.value = tabs.value[0].id;
};
watch(
() => route.name,
async (newRouteName) => {
onTabChange(newRouteName as string);
}
);
</script>
<template>
<BasicLayout>
@ -72,24 +96,23 @@ const handleTabChange = (id: string) => {
<template #icon>
<IconSettings class="mr-2 self-center" />
</template>
<template #actions>
<VButton class="opacity-0" type="secondary">安装</VButton>
</template>
</VPageHeader>
<div class="m-0 md:m-4">
<VCard>
<VCard :body-class="['!p-0']">
<template #header>
<VTabbar
v-model:active-id="activeTab"
:items="SettingTabs"
:items="tabs"
class="w-full !rounded-none"
type="outline"
@change="handleTabChange"
></VTabbar>
</template>
<RouterView></RouterView>
</VCard>
<div>
<RouterView :key="activeTab" />
</div>
</div>
</BasicLayout>
</template>

View File

@ -1,12 +1,6 @@
import { definePlugin } from "@halo-dev/admin-shared";
import SystemSettingsLayout from "./layouts/SystemSettingsLayout.vue";
import GeneralSettings from "./GeneralSettings.vue";
import UserSettings from "./UserSettings.vue";
import PostSettings from "./PostSettings.vue";
import SeoSettings from "./SeoSettings.vue";
import CommentSettings from "./CommentSettings.vue";
import CodeInjectSettings from "./CodeInjectSettings.vue";
import NotificationSettings from "./NotificationSettings.vue";
import SystemSetting from "./SystemSetting.vue";
import { IconSettings } from "@halo-dev/components";
export default definePlugin({
@ -16,42 +10,11 @@ export default definePlugin({
{
path: "/settings",
component: SystemSettingsLayout,
redirect: "/settings/general",
children: [
{
path: "general",
name: "GeneralSettings",
component: GeneralSettings,
},
{
path: "user",
name: "UserSettings",
component: UserSettings,
},
{
path: "post",
name: "PostSettings",
component: PostSettings,
},
{
path: "seo",
name: "SeoSettings",
component: SeoSettings,
},
{
path: "comment",
name: "CommentSettings",
component: CommentSettings,
},
{
path: "code-inject",
name: "CodeInjectSettings",
component: CodeInjectSettings,
},
{
path: "notification",
name: "NotificationSettings",
component: NotificationSettings,
path: ":group",
name: "SystemSetting",
component: SystemSetting,
},
],
},
@ -62,7 +25,7 @@ export default definePlugin({
items: [
{
name: "设置",
path: "/settings",
path: "/settings/basic",
icon: IconSettings,
},
],