代码格式修复

This commit is contained in:
李琦
2026-01-20 12:02:01 +08:00
parent 4aa757082b
commit 05b315013c
7 changed files with 308 additions and 125 deletions

View File

@@ -8,76 +8,139 @@
</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>
<!-- Tab + 卡片布局 -->
<div v-if="settings.length === 0" class="admin-card 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>
<div v-else class="space-y-6">
<!-- Tab 导航 -->
<div class="border-b border-white/10 overflow-x-auto">
<div class="flex gap-2 min-w-max">
<button
v-for="setting in allSettings"
:key="setting.id"
@click="activeTab = setting.id"
:class="[
'px-4 py-3 text-sm font-medium transition-all duration-200 border-b-2 relative',
activeTab === setting.id
? 'text-art-accent border-art-accent'
: 'text-art-muted border-transparent hover:text-white/80'
]"
>
{{ getSettingDisplayName(setting.keyName) }}
</button>
</div>
</div>
<form v-else @submit.prevent="saveAllSettings" class="space-y-6">
<div
v-for="setting 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 class="admin-card p-6 border border-white/10 rounded-lg bg-white/5 shadow-lg">
<!-- 菜单显示配置卡片 -->
<div v-if="activeSetting && activeSetting.keyName === 'visible_menus'" class="space-y-6">
<!-- 标题 -->
<div class="flex items-center justify-between pb-4 border-b border-white/10">
<div>
<h3 class="text-xl font-semibold text-white mb-1">{{ activeSetting.keyName }}</h3>
<p class="text-sm text-art-muted">{{ activeSetting.description || '前台菜单显示配置' }}</p>
</div>
<button
@click="deleteSetting(activeSetting.keyName)"
class="text-red-400 hover:text-red-300 transition-colors p-2 hover:bg-red-500/10 rounded"
title="删除配置"
>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" 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>
<!-- 菜单多选框优化UI -->
<div class="space-y-3">
<label class="block text-base font-medium text-white mb-4">显示菜单项</label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<label
v-for="menu in menuOptions"
:key="menu.key"
class="flex items-center gap-3 cursor-pointer group p-3 rounded-lg border border-white/10 bg-white/5 hover:bg-white/10 hover:border-white/20 transition-all"
>
<div class="relative flex items-center justify-center">
<input
type="checkbox"
:value="menu.key"
v-model="selectedMenus"
class="sr-only peer"
>
<div class="w-5 h-5 rounded border-2 border-white/30 bg-white/5 peer-checked:bg-art-accent peer-checked:border-art-accent transition-all duration-200 flex items-center justify-center">
<svg v-if="selectedMenus.includes(menu.key)" class="w-3 h-3 text-white" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
<span class="text-sm font-medium text-white/90 group-hover:text-white transition-colors flex-1">{{ menu.label }}</span>
</label>
</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 class="pt-6 border-t border-white/10">
<button
@click="saveMenuSetting"
:disabled="savingSettings.has(activeSetting.id)"
class="admin-btn-primary w-full py-3 text-base font-medium"
>
{{ savingSettings.has(activeSetting.id) ? '保存中...' : '保存配置' }}
</button>
</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 v-else-if="activeSetting" class="space-y-6">
<!-- 标题 -->
<div class="flex items-center justify-between pb-4 border-b border-white/10">
<div>
<h3 class="text-xl font-semibold text-white mb-1 font-mono">{{ activeSetting.keyName }}</h3>
<p class="text-sm text-art-muted">{{ activeSetting.description || '无描述' }}</p>
</div>
<button
@click="deleteSetting(activeSetting.keyName)"
class="text-red-400 hover:text-red-300 transition-colors p-2 hover:bg-red-500/10 rounded"
title="删除配置"
>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" 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 class="space-y-3">
<label class="block text-base font-medium text-white">配置值</label>
<textarea
v-if="activeSetting.keyName === 'site_description' || activeSetting.keyName === 'site_keywords'"
v-model="activeSetting.value"
rows="5"
class="admin-input resize-none w-full"
placeholder="配置项的值"
></textarea>
<input
v-else
type="text"
v-model="activeSetting.value"
class="admin-input w-full text-base"
placeholder="配置项的值"
>
</div>
<!-- 保存按钮 -->
<div class="pt-6 border-t border-white/10">
<button
@click="saveSingleSetting(activeSetting)"
:disabled="savingSettings.has(activeSetting.id)"
class="admin-btn-primary w-full py-3 text-base font-medium"
>
{{ savingSettings.has(activeSetting.id) ? '保存中...' : '保存配置' }}
</button>
</div>
</div>
</form>
</div>
</div>
<!-- 新增配置 Modal -->
@@ -141,14 +204,65 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } 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 savingSettings = ref<Set<number>>(new Set())
const activeTab = ref<number | null>(null)
// 菜单选项配置
const menuOptions = [
{ key: 'home', label: '首页' },
{ key: 'blog', label: '思考' },
{ key: 'columns', label: '专栏' },
{ key: 'works', label: '作品' },
{ key: 'snippets', label: '代码' },
{ key: 'about', label: '关于' },
{ key: 'services', label: '合作' }
]
// 选中的菜单
const selectedMenus = ref<string[]>([])
// 菜单配置项
const menuSetting = computed(() => {
return settings.value.find(s => s.keyName === 'visible_menus')
})
// 所有配置项用于tab
const allSettings = computed(() => {
// 将菜单配置放在第一位
const menu = settings.value.find(s => s.keyName === 'visible_menus')
const others = settings.value.filter(s => s.keyName !== 'visible_menus')
return menu ? [menu, ...others] : others
})
// 当前激活的配置项
const activeSetting = computed(() => {
if (activeTab.value === null && allSettings.value.length > 0) {
return allSettings.value[0]
}
return allSettings.value.find(s => s.id === activeTab.value) || null
})
// 获取配置项显示名称
const getSettingDisplayName = (keyName: string) => {
const nameMap: Record<string, string> = {
'visible_menus': '菜单显示',
'site_title': '网站标题',
'site_description': '网站描述',
'site_author': '网站作者',
'site_keywords': '网站关键词',
'posts_per_page': '文章分页',
'works_per_page': '作品分页',
'snippets_per_page': '代码分页'
}
return nameMap[keyName] || keyName
}
const newForm = ref({
keyName: '',
@@ -159,34 +273,82 @@ const newForm = ref({
const fetchSettings = async () => {
try {
settings.value = await getSettings()
// 初始化激活的tab第一个配置项
if (settings.value.length > 0 && activeTab.value === null) {
activeTab.value = allSettings.value[0]?.id || null
}
// 初始化菜单选择
if (menuSetting.value) {
const menuValue = menuSetting.value.value || ''
if (menuValue) {
// 支持逗号分隔或JSON数组格式
try {
const parsed = JSON.parse(menuValue)
selectedMenus.value = Array.isArray(parsed) ? parsed : []
} catch {
// 如果不是JSON按逗号分隔
selectedMenus.value = menuValue.split(',').map(m => m.trim()).filter(m => m)
}
} else {
// 默认显示所有菜单
selectedMenus.value = menuOptions.map(m => m.key)
}
} else {
// 如果没有菜单配置,默认显示所有菜单
selectedMenus.value = menuOptions.map(m => m.key)
}
} catch (error) {
console.error('Error fetching settings:', error)
toast.showToast('获取系统配置失败', 'error')
}
}
const saveAllSettings = async () => {
isSaving.value = true
// 保存单个配置项
const saveSingleSetting = async (setting: Setting) => {
savingSettings.value.add(setting.id)
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 updateSetting({
id: setting.id,
keyName: setting.keyName,
value: setting.value,
description: setting.description
})
toast.showToast('配置保存成功', 'success')
// 重新获取配置以确保数据同步
await fetchSettings()
} catch (error) {
console.error('Error saving settings:', error)
console.error('Error saving setting:', error)
toast.showToast('保存配置失败', 'error')
} finally {
isSaving.value = false
savingSettings.value.delete(setting.id)
}
}
// 保存菜单配置
const saveMenuSetting = async () => {
if (!menuSetting.value) return
savingSettings.value.add(menuSetting.value.id)
try {
// 将选中的菜单保存为JSON数组格式
const menuValue = JSON.stringify(selectedMenus.value)
await updateSetting({
id: menuSetting.value.id,
keyName: menuSetting.value.keyName,
value: menuValue,
description: menuSetting.value.description
})
toast.showToast('菜单配置保存成功', 'success')
// 重新获取配置以确保数据同步
await fetchSettings()
} catch (error) {
console.error('Error saving menu setting:', error)
toast.showToast('保存菜单配置失败', 'error')
} finally {
savingSettings.value.delete(menuSetting.value.id)
}
}