Files
vueadmin/src/pages/system/setting/index.vue
2026-01-20 22:10:55 +08:00

339 lines
8.9 KiB
Vue

<template>
<div class="system-setting">
<a-card :bordered="false">
<template #title>
<div class="page-title">
<SettingOutlined />
<span>{{ $t('common.systemSettings') }}</span>
</div>
</template>
<!-- Tab 页签 -->
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
<template #rightExtra>
<a-button type="primary" @click="handleAddConfig">
<PlusOutlined />
{{ $t('common.addConfig') }}
</a-button>
</template>
<a-tab-pane v-for="category in categories" :key="category.name" :tab="category.title">
<a-form :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }" class="setting-form">
<a-form-item v-for="field in fields.filter(f => f.category === category.name)" :key="field.name"
:label="field.title">
<div class="form-item-content">
<div class="form-input-wrapper">
<!-- 文本输入 -->
<a-input v-if="field.type === 'text'" v-model:value="formData[field.name]"
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
<!-- 文本域 -->
<a-textarea v-else-if="field.type === 'textarea'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || $t('common.pleaseEnter')" :rows="4" />
<!-- 数字输入 -->
<a-input-number v-else-if="field.type === 'number'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || $t('common.pleaseEnter')"
style="width: 100%" />
<!-- 开关 -->
<a-switch v-else-if="field.type === 'switch'"
v-model:checked="formData[field.name]" />
<!-- 下拉选择 -->
<a-select v-else-if="field.type === 'select'" v-model:value="formData[field.name]"
:placeholder="field.placeholder || $t('common.pleaseSelect')"
style="width: 100%">
<a-select-option v-for="option in field.options" :key="option.value"
:value="option.value">
{{ option.label }}
</a-select-option>
</a-select>
<!-- 多选 -->
<a-select v-else-if="field.type === 'multiselect'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || $t('common.pleaseSelect')" mode="multiple"
style="width: 100%">
<a-select-option v-for="option in field.options" :key="option.value"
:value="option.value">
{{ option.label }}
</a-select-option>
</a-select>
<!-- 日期时间 -->
<a-date-picker v-else-if="field.type === 'datetime'"
v-model:value="formData[field.name]"
:placeholder="field.placeholder || $t('common.pleaseSelect')"
style="width: 100%" show-time format="YYYY-MM-DD HH:mm:ss" />
<!-- 颜色选择器 -->
<a-input v-else-if="field.type === 'color'" v-model:value="formData[field.name]"
type="color" style="width: 100px" />
<!-- 默认文本输入 -->
<a-input v-else v-model:value="formData[field.name]"
:placeholder="field.placeholder || $t('common.pleaseEnter')" />
</div>
<div class="form-actions">
<EditOutlined class="action-icon edit-icon" :title="$t('common.edit')"
@click="handleEditField(field)" />
</div>
</div>
<div v-if="field.tip" class="field-tip">{{ field.tip }}</div>
</a-form-item>
</a-form>
<!-- 空状态 -->
<a-empty v-if="fields.filter(f => f.category === category.name).length === 0"
:description="$t('common.noConfig')" />
</a-tab-pane>
</a-tabs>
<!-- 底部保存按钮 -->
<div class="save-actions">
<a-space>
<a-button @click="handleReset">
{{ $t('common.reset') }}
</a-button>
<a-button type="primary" :loading="saving" @click="handleSave">
<SaveOutlined />
{{ $t('common.save') }}
</a-button>
</a-space>
</div>
</a-card>
<!-- 配置弹窗 -->
<ConfigModal v-model:visible="modalVisible" :is-edit="isEditMode" :categories="categories"
:initial-data="currentEditData" @confirm="handleModalConfirm" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { SettingOutlined, PlusOutlined, EditOutlined, SaveOutlined } from '@ant-design/icons-vue'
import { useI18n } from 'vue-i18n'
import systemApi from '@/api/system'
import ConfigModal from './components/ConfigModal.vue'
// 定义组件名称
defineOptions({
name: 'SystemSetting',
})
const { t } = useI18n()
const activeTab = ref('basic')
const saving = ref(false)
// 配置分类
const categories = ref([
{ name: 'basic', title: '基础设置' },
{ name: 'security', title: '安全设置' },
{ name: 'upload', title: '上传设置' },
{ name: 'email', title: '邮件设置' },
{ name: 'sms', title: '短信设置' },
])
// 配置字段
const fields = ref([])
// 表单数据
const formData = reactive({})
// 弹窗相关
const modalVisible = ref(false)
const isEditMode = ref(false)
const currentEditData = ref({})
// 获取配置字段
const fetchFields = async () => {
try {
const res = await systemApi.setting.fields.get()
if (res.code === 200) {
fields.value = res.data.fields || []
categories.value = res.data.categories || categories.value
// 初始化表单数据
fields.value.forEach(field => {
formData[field.name] = field.value || ''
})
// 设置第一个 tab 为默认激活
if (categories.value.length > 0) {
activeTab.value = categories.value[0].name
}
}
} catch (error) {
message.error(t('common.fetchConfigFailed'))
console.error('获取配置字段失败:', error)
}
}
// 切换 Tab
const handleTabChange = (key) => {
activeTab.value = key
}
// 添加配置
const handleAddConfig = () => {
isEditMode.value = false
currentEditData.value = {
category: activeTab.value,
name: '',
title: '',
type: 'text',
value: '',
tip: '',
}
modalVisible.value = true
}
// 编辑字段
const handleEditField = (field) => {
isEditMode.value = true
currentEditData.value = {
category: field.category,
name: field.name,
title: field.title,
type: field.type,
value: formData[field.name],
tip: field.tip || '',
}
modalVisible.value = true
}
// 弹窗确认处理
const handleModalConfirm = async (values) => {
try {
let res
if (isEditMode.value) {
// 编辑模式
res = await systemApi.setting.edit.post({
...values,
name: currentEditData.value.name,
})
if (res.code === 200) {
message.success(t('common.editSuccess'))
modalVisible.value = false
// 更新表单数据
formData[currentEditData.value.name] = values.value
// 更新字段信息
const fieldIndex = fields.value.findIndex(f => f.name === currentEditData.value.name)
if (fieldIndex > -1) {
fields.value[fieldIndex].title = values.title
fields.value[fieldIndex].value = values.value
fields.value[fieldIndex].tip = values.tip
}
}
} else {
// 添加模式
res = await systemApi.setting.add.post(values)
if (res.code === 200) {
message.success(t('common.addSuccess'))
modalVisible.value = false
// 重新获取配置字段
await fetchFields()
}
}
} catch (error) {
if (error.errorFields) {
return // 表单验证错误
}
message.error(isEditMode.value ? t('common.editFailed') : t('common.addFailed'))
console.error(isEditMode.value ? '编辑配置失败:' : '添加配置失败:', error)
}
}
// 保存配置
const handleSave = async () => {
try {
saving.value = true
const res = await systemApi.setting.save.post(formData)
if (res.code === 200) {
message.success(t('common.saveSuccess'))
}
} catch (error) {
message.error(t('common.saveFailed'))
console.error('保存配置失败:', error)
} finally {
saving.value = false
}
}
// 重置配置
const handleReset = () => {
Object.keys(formData).forEach(key => {
const field = fields.value.find(f => f.name === key)
if (field) {
formData[key] = field.value || ''
}
})
message.info(t('common.resetSuccess'))
}
onMounted(() => {
fetchFields()
})
</script>
<style scoped lang="scss">
.system-setting {
.page-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 500;
}
.setting-form {
margin-top: 20px;
.form-item-content {
display: flex;
align-items: center;
gap: 12px;
.form-input-wrapper {
flex: 1;
}
.form-actions {
.action-icon {
font-size: 16px;
color: #8c8c8c;
cursor: pointer;
transition: color 0.2s;
&:hover {
color: #1890ff;
}
}
}
}
.field-tip {
margin-top: 4px;
font-size: 12px;
color: #8c8c8c;
line-height: 1.4;
}
}
.save-actions {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: center;
}
}
:deep(.ant-tabs-tab) {
font-size: 14px;
}
:deep(.ant-form-item) {
margin-bottom: 20px;
}
</style>