mirror of https://github.com/halo-dev/halo
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
parent
b9a8f5055c
commit
35b244dba8
|
@ -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": {
|
||||
|
|
|
@ -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;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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==}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue