311 lines
8.2 KiB
Vue
311 lines
8.2 KiB
Vue
<template>
|
||
<a-modal :title="title" :open="visible" :confirm-loading="isSaving" :footer="null" @cancel="handleCancel" width="700px">
|
||
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 5 }" :wrapper-col="{ span: 18 }">
|
||
<!-- 配置名称 -->
|
||
<a-form-item label="配置名称" name="name" required>
|
||
<a-input v-model:value="form.name" placeholder="如:网站名称" allow-clear />
|
||
</a-form-item>
|
||
|
||
<!-- 配置键名 -->
|
||
<a-form-item label="配置键名" name="key" required>
|
||
<a-input v-model:value="form.key" placeholder="如:site_name" allow-clear :disabled="isEdit" />
|
||
<div class="form-tip">系统唯一标识,只能包含字母、数字、下划线</div>
|
||
</a-form-item>
|
||
|
||
<!-- 配置分组 -->
|
||
<a-form-item label="配置分组" name="group" required>
|
||
<a-select v-model:value="form.group" placeholder="请选择配置分组" :options="groupOptions" />
|
||
</a-form-item>
|
||
|
||
<!-- 配置类型 -->
|
||
<a-form-item label="配置类型" name="type" required>
|
||
<a-select v-model:value="form.type" placeholder="请选择配置类型">
|
||
<a-select-option value="string">字符串</a-select-option>
|
||
<a-select-option value="number">数字</a-select-option>
|
||
<a-select-option value="boolean">布尔值</a-select-option>
|
||
<a-select-option value="text">文本</a-select-option>
|
||
<a-select-option value="file">文件</a-select-option>
|
||
<a-select-option value="image">图片</a-select-option>
|
||
<a-select-option value="select">选择框</a-select-option>
|
||
</a-select>
|
||
</a-form-item>
|
||
|
||
<!-- 配置值 -->
|
||
<a-form-item label="配置值" name="value">
|
||
<!-- 字符串/文本 -->
|
||
<template v-if="['string', 'text'].includes(form.type)">
|
||
<a-input v-if="form.type === 'string'" v-model:value="form.value" placeholder="请输入配置值" allow-clear />
|
||
<a-textarea v-else v-model:value="form.value" placeholder="请输入配置值" :rows="4" />
|
||
</template>
|
||
|
||
<!-- 数字 -->
|
||
<a-input-number v-else-if="form.type === 'number'" v-model:value="form.value" :min="0" style="width: 100%" />
|
||
|
||
<!-- 布尔值 -->
|
||
<a-switch v-else-if="form.type === 'boolean'" v-model:checked="valueChecked" checked-children="启用" un-checked-children="禁用" />
|
||
|
||
<!-- 文件/图片 -->
|
||
<a-input v-else-if="['file', 'image'].includes(form.type)" v-model:value="form.value" placeholder="请输入文件地址" />
|
||
|
||
<!-- 选择框 -->
|
||
<a-input v-else v-model:value="form.optionsText" placeholder="选项值,用逗号分隔,如:选项1,选项2,选项3" />
|
||
</a-form-item>
|
||
|
||
<!-- 默认值 -->
|
||
<a-form-item label="默认值" name="default_value">
|
||
<a-input v-model:value="form.default_value" placeholder="默认值(可选)" allow-clear />
|
||
</a-form-item>
|
||
|
||
<!-- 排序 -->
|
||
<a-form-item label="排序" name="sort">
|
||
<a-input-number v-model:value="form.sort" :min="0" :max="10000" style="width: 100%" />
|
||
</a-form-item>
|
||
|
||
<!-- 状态 -->
|
||
<a-form-item label="状态" name="status">
|
||
<a-switch v-model:checked="statusChecked" checked-children="启用" un-checked-children="禁用" />
|
||
</a-form-item>
|
||
|
||
<!-- 描述 -->
|
||
<a-form-item label="描述" name="description">
|
||
<a-textarea v-model:value="form.description" placeholder="请输入配置描述" :rows="3" maxlength="200" show-count />
|
||
</a-form-item>
|
||
</a-form>
|
||
|
||
<!-- 底部按钮 -->
|
||
<div class="dialog-footer">
|
||
<a-space>
|
||
<a-button @click="handleCancel">取消</a-button>
|
||
<a-button type="primary" :loading="isSaving" @click="handleSubmit">保存</a-button>
|
||
</a-space>
|
||
</div>
|
||
</a-modal>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch } from 'vue'
|
||
import { message } from 'ant-design-vue'
|
||
import systemApi from '@/api/system'
|
||
import { useDictionaryStore } from '@/stores/modules/dictionary'
|
||
|
||
const props = defineProps({
|
||
visible: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
record: {
|
||
type: Object,
|
||
default: null
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['update:visible', 'success'])
|
||
|
||
const formRef = ref(null)
|
||
const isSaving = ref(false)
|
||
const isEdit = computed(() => !!props.record?.id)
|
||
|
||
const title = computed(() => {
|
||
return isEdit.value ? '编辑配置' : '新增配置'
|
||
})
|
||
|
||
// 配置分组选项
|
||
const groupOptions = ref([])
|
||
|
||
// 表单数据
|
||
const form = ref({
|
||
id: '',
|
||
name: '',
|
||
key: '',
|
||
group: '',
|
||
type: 'string',
|
||
value: '',
|
||
default_value: '',
|
||
description: '',
|
||
status: true,
|
||
sort: 0,
|
||
options: []
|
||
})
|
||
|
||
// 选项文本(用于选择框类型)
|
||
const optionsText = ref('')
|
||
|
||
// 计算属性:状态开关
|
||
const statusChecked = computed({
|
||
get: () => form.value.status === true,
|
||
set: (val) => {
|
||
form.value.status = val ? true : false
|
||
}
|
||
})
|
||
|
||
// 计算属性:值开关(布尔类型)
|
||
const valueChecked = computed({
|
||
get: () => form.value.value === '1' || form.value.value === true,
|
||
set: (val) => {
|
||
form.value.value = val ? '1' : '0'
|
||
}
|
||
})
|
||
|
||
// 初始化字典 store
|
||
const dictionaryStore = useDictionaryStore()
|
||
|
||
// 加载配置分组
|
||
const loadGroups = async () => {
|
||
const groups = await dictionaryStore.getDictionary('config_group')
|
||
groupOptions.value = groups.map(item => ({
|
||
label: item.label,
|
||
value: item.value
|
||
}))
|
||
}
|
||
|
||
// 验证规则
|
||
const rules = {
|
||
name: [
|
||
{ required: true, message: '请输入配置名称', trigger: 'blur' },
|
||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
|
||
],
|
||
key: [
|
||
{ required: true, message: '请输入配置键名', trigger: 'blur' },
|
||
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, message: '格式不正确', trigger: 'blur' }
|
||
],
|
||
group: [
|
||
{ required: true, message: '请选择配置分组', trigger: 'change' }
|
||
],
|
||
type: [
|
||
{ required: true, message: '请选择配置类型', trigger: 'change' }
|
||
]
|
||
}
|
||
|
||
// 重置表单
|
||
const resetForm = () => {
|
||
form.value = {
|
||
id: '',
|
||
name: '',
|
||
key: '',
|
||
group: '',
|
||
type: 'string',
|
||
value: '',
|
||
default_value: '',
|
||
description: '',
|
||
status: true,
|
||
sort: 0,
|
||
options: []
|
||
}
|
||
optionsText.value = ''
|
||
formRef.value?.clearValidate()
|
||
}
|
||
|
||
// 设置数据
|
||
const setData = (data) => {
|
||
if (data) {
|
||
form.value = {
|
||
id: data.id || '',
|
||
name: data.name || '',
|
||
key: data.key || '',
|
||
group: data.group || '',
|
||
type: data.type || 'string',
|
||
value: data.value || '',
|
||
default_value: data.default_value || '',
|
||
description: data.description || '',
|
||
status: data.status !== undefined ? data.status : true,
|
||
sort: data.sort !== undefined ? data.sort : 0,
|
||
options: data.options || []
|
||
}
|
||
|
||
// 如果是选择框类型,设置选项文本
|
||
if (data.type === 'select' && data.options) {
|
||
optionsText.value = Array.isArray(data.options) ? data.options.join(',') : data.options
|
||
}
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
const handleSubmit = async () => {
|
||
try {
|
||
await formRef.value.validate()
|
||
|
||
isSaving.value = true
|
||
|
||
// 处理选项
|
||
if (form.value.type === 'select') {
|
||
form.value.options = optionsText.value ? optionsText.value.split(',').map(s => s.trim()) : []
|
||
}
|
||
|
||
// 处理值
|
||
let submitValue = form.value.value
|
||
if (form.value.type === 'boolean') {
|
||
submitValue = valueChecked.value ? '1' : '0'
|
||
}
|
||
|
||
const submitData = {
|
||
...form.value,
|
||
value: submitValue
|
||
}
|
||
|
||
let res = {}
|
||
if (isEdit.value) {
|
||
res = await systemApi.config.update.put(form.value.id, submitData)
|
||
} else {
|
||
res = await systemApi.config.add.post(submitData)
|
||
}
|
||
|
||
if (res.code === 200) {
|
||
message.success(isEdit.value ? '编辑成功' : '新增成功')
|
||
emit('success')
|
||
handleCancel()
|
||
} else {
|
||
message.error(res.message || '操作失败')
|
||
}
|
||
} catch (error) {
|
||
if (error.errorFields) {
|
||
console.log('表单验证失败:', error)
|
||
} else {
|
||
console.error('提交失败:', error)
|
||
message.error('操作失败')
|
||
}
|
||
} finally {
|
||
isSaving.value = false
|
||
}
|
||
}
|
||
|
||
// 取消
|
||
const handleCancel = () => {
|
||
resetForm()
|
||
emit('update:visible', false)
|
||
}
|
||
|
||
// 监听 visible 变化
|
||
watch(() => props.visible, (newVal) => {
|
||
if (newVal) {
|
||
loadGroups()
|
||
if (props.record) {
|
||
setData(props.record)
|
||
} else {
|
||
resetForm()
|
||
}
|
||
}
|
||
}, { immediate: true })
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.form-tip {
|
||
font-size: 12px;
|
||
color: #8c8c8c;
|
||
margin-top: 4px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
padding-top: 16px;
|
||
border-top: 1px solid #f0f0f0;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
:deep(.ant-modal-body) {
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
}
|
||
</style>
|