Files
nl-blogs/client/src/pages/admin/Settings.vue
2026-01-20 08:31:42 +08:00

239 lines
8.8 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="w-full animate-reveal">
<div class="flex items-center justify-between mb-6">
<h1 class="text-2xl font-serif italic text-white">系统配置管理</h1>
<button @click="showAddModal = true" class="admin-btn-primary flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
新增配置
</button>
</div>
<!-- 配置表单 -->
<div class="admin-card p-6">
<div v-if="settings.length === 0" class="p-16 text-center">
<div class="w-16 h-16 bg-white/5 rounded-full flex items-center justify-center mx-auto mb-4 text-2xl"></div>
<h3 class="text-white font-medium mb-2">暂无配置</h3>
<button @click="showAddModal = true" class="admin-btn-primary mt-4">添加第一个配置</button>
</div>
<form v-else @submit.prevent="saveAllSettings" class="space-y-6">
<div
v-for="(setting, index) in settings"
:key="setting.id"
class="p-4 border border-white/5 rounded-lg bg-white/2 hover:bg-white/5 transition-colors"
>
<div class="grid grid-cols-1 md:grid-cols-12 gap-4 items-start">
<!-- 键名只读 -->
<div class="md:col-span-3 space-y-2">
<label class="block text-xs font-medium text-art-muted uppercase tracking-wider">键名</label>
<input
type="text"
:value="setting.keyName"
disabled
class="admin-input font-mono disabled:opacity-50 disabled:cursor-not-allowed bg-white/5"
>
</div>
<!-- 可编辑 -->
<div class="md:col-span-4 space-y-2">
<label class="block text-xs font-medium text-art-muted uppercase tracking-wider"></label>
<input
type="text"
v-model="setting.value"
class="admin-input"
placeholder="配置项的值"
>
</div>
<!-- 描述可编辑 -->
<div class="md:col-span-4 space-y-2">
<label class="block text-xs font-medium text-art-muted uppercase tracking-wider">描述</label>
<textarea
v-model="setting.description"
rows="2"
class="admin-input resize-none"
placeholder="该配置项的用途说明"
></textarea>
</div>
<!-- 删除按钮 -->
<div class="md:col-span-1 space-y-2 flex items-end">
<button
type="button"
@click="deleteSetting(setting.keyName)"
class="admin-btn-danger py-2 px-3 text-xs w-full"
title="删除配置"
>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
</button>
</div>
</div>
</div>
<!-- 保存按钮 -->
<div class="pt-4 flex justify-end gap-3 border-t border-white/5">
<button type="button" @click="fetchSettings" class="admin-btn-secondary">重置</button>
<button type="submit" class="admin-btn-primary" :disabled="isSaving">
{{ isSaving ? '保存中...' : '保存所有配置' }}
</button>
</div>
</form>
</div>
<!-- 新增配置 Modal -->
<div v-if="showAddModal" class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm transition-opacity" @click="closeAddModal"></div>
<div class="admin-card w-full max-w-lg relative z-10 flex flex-col max-h-[90vh] shadow-2xl animate-reveal">
<div class="flex items-center justify-between p-6 border-b border-white/5">
<h2 class="text-lg font-medium text-white">新增配置</h2>
<button @click="closeAddModal" class="text-white/40 hover:text-white transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</div>
<div class="p-6 overflow-y-auto">
<form @submit.prevent="saveNewSetting" class="space-y-4">
<div class="space-y-2">
<label for="keyName" class="block text-xs font-medium text-art-muted uppercase tracking-wider">键名</label>
<input
type="text"
id="keyName"
v-model="newForm.keyName"
required
class="admin-input font-mono"
placeholder="例如: site_title"
>
</div>
<div class="space-y-2">
<label for="value" class="block text-xs font-medium text-art-muted uppercase tracking-wider"></label>
<input
type="text"
id="value"
v-model="newForm.value"
required
class="admin-input"
placeholder="配置项的值"
>
</div>
<div class="space-y-2">
<label for="description" class="block text-xs font-medium text-art-muted uppercase tracking-wider">描述</label>
<textarea
id="description"
v-model="newForm.description"
rows="3"
class="admin-input resize-none"
placeholder="该配置项的用途说明"
></textarea>
</div>
<div class="pt-4 flex justify-end gap-3">
<button type="button" @click="closeAddModal" class="admin-btn-secondary">取消</button>
<button type="submit" class="admin-btn-primary">保存</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getSettings, createSetting, updateSetting, deleteSetting as deleteSettingApi, Setting } from '../../services/api'
import { useToast } from '../../composables/useToast'
const toast = useToast()
const settings = ref<Setting[]>([])
const showAddModal = ref(false)
const isSaving = ref(false)
const newForm = ref({
keyName: '',
value: '',
description: ''
})
const fetchSettings = async () => {
try {
settings.value = await getSettings()
} catch (error) {
console.error('Error fetching settings:', error)
toast.showToast('获取系统配置失败', 'error')
}
}
const saveAllSettings = async () => {
isSaving.value = true
try {
// 批量更新所有配置项
const updatePromises = settings.value.map(setting =>
updateSetting({
id: setting.id,
keyName: setting.keyName,
value: setting.value,
description: setting.description
})
)
await Promise.all(updatePromises)
toast.showToast('所有配置保存成功', 'success')
// 重新获取配置以确保数据同步
await fetchSettings()
} catch (error) {
console.error('Error saving settings:', error)
toast.showToast('保存配置失败', 'error')
} finally {
isSaving.value = false
}
}
const saveNewSetting = async () => {
try {
await createSetting({
keyName: newForm.value.keyName,
value: newForm.value.value,
description: newForm.value.description
})
toast.showToast('配置创建成功', 'success')
closeAddModal()
fetchSettings()
} catch (error) {
console.error('Error creating setting:', error)
toast.showToast('创建配置失败', 'error')
}
}
const deleteSetting = async (keyName: string) => {
if (confirm(`确定要删除配置项 "${keyName}" 吗?`)) {
try {
await deleteSettingApi(keyName)
toast.showToast('配置删除成功', 'success')
fetchSettings()
} catch (error) {
console.error('Error deleting setting:', error)
toast.showToast('删除配置失败', 'error')
}
}
}
const closeAddModal = () => {
showAddModal.value = false
newForm.value = {
keyName: '',
value: '',
description: ''
}
}
onMounted(() => {
fetchSettings()
})
</script>
<style scoped>
/* Scoped styles removed */
</style>