feat: add system settings panel
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, watch } from "vue";
|
||||
import type { SystemSetting } from "../types";
|
||||
|
||||
const props = defineProps<{
|
||||
settings: SystemSetting[];
|
||||
saveState: "idle" | "saving" | "saved" | "error";
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
saveSettings: [settings: SystemSetting[]];
|
||||
}>();
|
||||
|
||||
const draft = reactive<Record<string, SystemSetting>>({});
|
||||
|
||||
const groups = [
|
||||
{
|
||||
title: "基础",
|
||||
eyebrow: "general",
|
||||
keys: ["site.title"],
|
||||
},
|
||||
{
|
||||
title: "认证",
|
||||
eyebrow: "auth",
|
||||
keys: ["auth.allow_email_login", "auth.allow_github_login", "auth.allow_registration"],
|
||||
},
|
||||
{
|
||||
title: "SMTP",
|
||||
eyebrow: "mail",
|
||||
keys: ["smtp.host", "smtp.port", "smtp.username", "smtp.password", "smtp.sender_email"],
|
||||
},
|
||||
{
|
||||
title: "GitHub",
|
||||
eyebrow: "github",
|
||||
keys: ["github.api_base_url", "github.web_base_url", "github.oauth_client_id", "github.oauth_client_secret"],
|
||||
},
|
||||
];
|
||||
|
||||
const labels: Record<string, string> = {
|
||||
"site.title": "CMS 标题",
|
||||
"auth.allow_email_login": "允许邮箱登录",
|
||||
"auth.allow_github_login": "允许 GitHub 登录",
|
||||
"auth.allow_registration": "开放注册",
|
||||
"smtp.host": "SMTP 地址",
|
||||
"smtp.port": "SMTP 端口",
|
||||
"smtp.username": "SMTP 用户名",
|
||||
"smtp.password": "SMTP 密码",
|
||||
"smtp.sender_email": "发件人邮箱",
|
||||
"github.api_base_url": "GitHub API 地址",
|
||||
"github.web_base_url": "GitHub Web 地址",
|
||||
"github.oauth_client_id": "OAuth Client ID",
|
||||
"github.oauth_client_secret": "OAuth Client Secret",
|
||||
};
|
||||
|
||||
const booleanKeys = new Set([
|
||||
"auth.allow_email_login",
|
||||
"auth.allow_github_login",
|
||||
"auth.allow_registration",
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => props.settings,
|
||||
(settings) => {
|
||||
Object.keys(draft).forEach((key) => delete draft[key]);
|
||||
settings.forEach((setting) => {
|
||||
draft[setting.key] = { ...setting };
|
||||
});
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const draftSettings = computed(() => Object.values(draft));
|
||||
|
||||
function getSetting(key: string) {
|
||||
if (!draft[key]) {
|
||||
draft[key] = {
|
||||
key,
|
||||
value: "",
|
||||
encrypted: key.includes("password") || key.includes("secret"),
|
||||
updatedAt: "",
|
||||
};
|
||||
}
|
||||
|
||||
return draft[key];
|
||||
}
|
||||
|
||||
function isBooleanKey(key: string) {
|
||||
return booleanKeys.has(key);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="panel is-visible">
|
||||
<div class="settings-toolbar">
|
||||
<p class="save-status" :class="`is-${saveState}`">
|
||||
<template v-if="saveState === 'saved'">系统设置已保存</template>
|
||||
<template v-else-if="saveState === 'error'">保存失败,请查看日志</template>
|
||||
<template v-else-if="saveState === 'saving'">正在保存系统设置</template>
|
||||
<template v-else>系统设置保存到 SQLite 数据库</template>
|
||||
</p>
|
||||
<button class="primary-button" type="button" @click="$emit('saveSettings', draftSettings)">
|
||||
{{ saveState === "saving" ? "保存中" : "保存设置" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-grid">
|
||||
<section v-for="group in groups" :key="group.title" class="management-card">
|
||||
<p class="eyebrow">{{ group.eyebrow }}</p>
|
||||
<h3>{{ group.title }}</h3>
|
||||
<label
|
||||
v-for="key in group.keys"
|
||||
:key="key"
|
||||
:class="{ 'toggle-row': isBooleanKey(key) }"
|
||||
>
|
||||
<span>{{ labels[key] || key }}</span>
|
||||
<input
|
||||
v-if="isBooleanKey(key)"
|
||||
:checked="getSetting(key).value === 'true'"
|
||||
type="checkbox"
|
||||
@change="getSetting(key).value = ($event.target as HTMLInputElement).checked ? 'true' : 'false'"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
v-model="getSetting(key).value"
|
||||
:placeholder="getSetting(key).encrypted ? '留空表示不修改敏感值' : ''"
|
||||
:type="getSetting(key).encrypted ? 'password' : 'text'"
|
||||
/>
|
||||
</label>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user