2026-01-15 13:51:44 +08:00
|
|
|
|
<template>
|
2026-01-16 11:06:59 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
<!-- Optional: Add Create Button if needed -->
|
|
|
|
|
|
<!-- <button @click="openCreateModal" class="admin-btn-primary">+ 新增配置</button> -->
|
2026-01-15 13:51:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-16 11:06:59 +08:00
|
|
|
|
<div class="admin-card overflow-hidden">
|
|
|
|
|
|
<div class="overflow-x-auto">
|
|
|
|
|
|
<table class="admin-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th class="w-1/4">键名</th>
|
|
|
|
|
|
<th class="w-1/3">值</th>
|
|
|
|
|
|
<th>描述</th>
|
|
|
|
|
|
<th class="text-right w-32">操作</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="setting in settings" :key="setting.id" class="group transition-colors duration-200">
|
|
|
|
|
|
<td class="font-mono text-xs text-art-accent">{{ setting.keyName }}</td>
|
|
|
|
|
|
<td class="max-w-xs truncate text-white/80" :title="setting.value">{{ setting.value }}</td>
|
|
|
|
|
|
<td class="text-art-muted text-sm">{{ setting.description }}</td>
|
|
|
|
|
|
<td class="text-right">
|
|
|
|
|
|
<div class="flex items-center justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
|
|
|
|
|
|
<button @click="editSetting(setting)" class="admin-btn-secondary py-1 px-3 text-xs">
|
|
|
|
|
|
编辑
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button @click="deleteSetting(setting.keyName)" class="admin-btn-danger py-1 px-3 text-xs">
|
|
|
|
|
|
删除
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
</div>
|
2026-01-15 13:51:44 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Add/Edit Modal -->
|
2026-01-16 11:06:59 +08:00
|
|
|
|
<div v-if="showModal" 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="closeModal"></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">{{ editingSetting ? '编辑配置' : '新增配置' }}</h2>
|
|
|
|
|
|
<button @click="closeModal" 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>
|
2026-01-15 13:51:44 +08:00
|
|
|
|
</div>
|
2026-01-16 11:06:59 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="p-6 overflow-y-auto">
|
|
|
|
|
|
<form @submit.prevent="saveSetting" 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>
|
2026-01-15 13:51:44 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
id="keyName"
|
|
|
|
|
|
v-model="form.keyName"
|
|
|
|
|
|
:disabled="editingSetting"
|
|
|
|
|
|
required
|
2026-01-16 11:06:59 +08:00
|
|
|
|
class="admin-input font-mono disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
|
placeholder="例如: site_title"
|
2026-01-15 13:51:44 +08:00
|
|
|
|
>
|
|
|
|
|
|
</div>
|
2026-01-16 11:06:59 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
<label for="value" class="block text-xs font-medium text-art-muted uppercase tracking-wider">值</label>
|
2026-01-15 13:51:44 +08:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
id="value"
|
|
|
|
|
|
v-model="form.value"
|
|
|
|
|
|
required
|
2026-01-16 11:06:59 +08:00
|
|
|
|
class="admin-input"
|
|
|
|
|
|
placeholder="配置项的值"
|
2026-01-15 13:51:44 +08:00
|
|
|
|
>
|
|
|
|
|
|
</div>
|
2026-01-16 11:06:59 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
<label for="description" class="block text-xs font-medium text-art-muted uppercase tracking-wider">描述</label>
|
2026-01-15 13:51:44 +08:00
|
|
|
|
<textarea
|
|
|
|
|
|
id="description"
|
|
|
|
|
|
v-model="form.description"
|
|
|
|
|
|
rows="3"
|
2026-01-16 11:06:59 +08:00
|
|
|
|
class="admin-input resize-none"
|
|
|
|
|
|
placeholder="该配置项的用途说明"
|
2026-01-15 13:51:44 +08:00
|
|
|
|
></textarea>
|
|
|
|
|
|
</div>
|
2026-01-16 11:06:59 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="pt-4 flex justify-end gap-3">
|
|
|
|
|
|
<button type="button" @click="closeModal" class="admin-btn-secondary">取消</button>
|
|
|
|
|
|
<button type="submit" class="admin-btn-primary">保存</button>
|
2026-01-15 13:51:44 +08:00
|
|
|
|
</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 showModal = ref(false)
|
|
|
|
|
|
const editingSetting = ref(false)
|
|
|
|
|
|
|
|
|
|
|
|
const form = ref({
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
keyName: '',
|
|
|
|
|
|
value: '',
|
|
|
|
|
|
description: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const fetchSettings = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
settings.value = await getSettings()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error fetching settings:', error)
|
2026-01-16 11:06:59 +08:00
|
|
|
|
toast.showToast('获取系统配置失败', 'error')
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const editSetting = (setting: Setting) => {
|
|
|
|
|
|
editingSetting.value = true
|
|
|
|
|
|
form.value = {
|
|
|
|
|
|
id: setting.id,
|
|
|
|
|
|
keyName: setting.keyName,
|
|
|
|
|
|
value: setting.value,
|
|
|
|
|
|
description: setting.description
|
|
|
|
|
|
}
|
|
|
|
|
|
showModal.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const saveSetting = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (editingSetting.value) {
|
|
|
|
|
|
await updateSetting(form.value)
|
2026-01-16 11:06:59 +08:00
|
|
|
|
toast.showToast('配置更新成功', 'success')
|
2026-01-15 13:51:44 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
await createSetting({
|
|
|
|
|
|
keyName: form.value.keyName,
|
|
|
|
|
|
value: form.value.value,
|
|
|
|
|
|
description: form.value.description
|
|
|
|
|
|
})
|
2026-01-16 11:06:59 +08:00
|
|
|
|
toast.showToast('配置创建成功', 'success')
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
closeModal()
|
|
|
|
|
|
fetchSettings()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error saving setting:', error)
|
2026-01-16 11:06:59 +08:00
|
|
|
|
toast.showToast(editingSetting.value ? '更新配置失败' : '创建配置失败', 'error')
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const deleteSetting = async (keyName: string) => {
|
|
|
|
|
|
if (confirm(`确定要删除配置项 "${keyName}" 吗?`)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await deleteSettingApi(keyName)
|
2026-01-16 11:06:59 +08:00
|
|
|
|
toast.showToast('配置删除成功', 'success')
|
2026-01-15 13:51:44 +08:00
|
|
|
|
fetchSettings()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error deleting setting:', error)
|
2026-01-16 11:06:59 +08:00
|
|
|
|
toast.showToast('删除配置失败', 'error')
|
2026-01-15 13:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const closeModal = () => {
|
|
|
|
|
|
showModal.value = false
|
|
|
|
|
|
editingSetting.value = false
|
|
|
|
|
|
form.value = {
|
|
|
|
|
|
id: 0,
|
|
|
|
|
|
keyName: '',
|
|
|
|
|
|
value: '',
|
|
|
|
|
|
description: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
fetchSettings()
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-01-16 11:06:59 +08:00
|
|
|
|
/* Scoped styles removed */
|
|
|
|
|
|
</style>
|