251 lines
6.4 KiB
Vue
251 lines
6.4 KiB
Vue
<template>
|
||
<a-modal :title="title" :open="visible" :confirm-loading="isSaving" :footer="null" @cancel="handleCancel" width="600px">
|
||
<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 maxlength="50" show-count />
|
||
</a-form-item>
|
||
|
||
<!-- 字典编码 -->
|
||
<a-form-item label="字典编码" name="code" required>
|
||
<a-input v-model:value="form.code" placeholder="如:user_status" allow-clear :disabled="isEdit" />
|
||
<div class="form-tip">系统唯一标识,只能包含字母、数字、下划线,且必须以字母开头</div>
|
||
</a-form-item>
|
||
|
||
<!-- 值类型 -->
|
||
<a-form-item label="值类型" name="value_type" required>
|
||
<a-select v-model:value="form.value_type" placeholder="请选择值类型" allow-clear>
|
||
<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="json">JSON</a-select-option>
|
||
</a-select>
|
||
<div class="form-tip">指定字典项值的类型,系统会根据类型自动格式化返回数据</div>
|
||
</a-form-item>
|
||
|
||
<!-- 排序 -->
|
||
<a-form-item label="排序" name="sort">
|
||
<a-input-number v-model:value="form.sort" :min="0" :max="10000" style="width: 100%" />
|
||
<div class="form-tip">数值越小越靠前</div>
|
||
</a-form-item>
|
||
|
||
<!-- 状态 -->
|
||
<a-form-item label="状态" name="status">
|
||
<sc-select v-model:value="form.status" source-type="dictionary" dictionary-code="dictionary_status" placeholder="请选择状态" allow-clear />
|
||
</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 scSelect from '@/components/scSelect/index.vue'
|
||
import systemApi from '@/api/system'
|
||
|
||
// ===== Props =====
|
||
const props = defineProps({
|
||
visible: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
record: {
|
||
type: Object,
|
||
default: null
|
||
},
|
||
dictionaryList: {
|
||
type: Array,
|
||
default: () => []
|
||
}
|
||
})
|
||
|
||
// ===== Emits =====
|
||
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 form = ref({
|
||
id: '',
|
||
name: '',
|
||
code: '',
|
||
value_type: 'string',
|
||
description: '',
|
||
status: null,
|
||
sort: 0
|
||
})
|
||
|
||
// ===== 验证规则 =====
|
||
// 编码唯一性验证
|
||
const validateCodeUnique = async (rule, value) => {
|
||
if (!value) return Promise.resolve()
|
||
|
||
// 检查编码是否已存在(编辑时排除自己)
|
||
const exists = props.dictionaryList.some(
|
||
item => item.code === value && item.id !== props.record?.id
|
||
)
|
||
|
||
if (exists) {
|
||
return Promise.reject('该编码已存在,请使用其他编码')
|
||
}
|
||
|
||
return Promise.resolve()
|
||
}
|
||
|
||
const rules = {
|
||
name: [
|
||
{ required: true, message: '请输入字典名称', trigger: 'blur' },
|
||
{ min: 2, max: 50, message: '字典名称长度在 2 到 50 个字符', trigger: 'blur' }
|
||
],
|
||
code: [
|
||
{ required: true, message: '请输入字典编码', trigger: 'blur' },
|
||
{
|
||
pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/,
|
||
message: '编码格式不正确,只能包含字母、数字、下划线,且必须以字母开头',
|
||
trigger: 'blur'
|
||
},
|
||
{ validator: validateCodeUnique, trigger: 'blur' }
|
||
],
|
||
value_type: [
|
||
{ required: true, message: '请选择值类型', trigger: 'change' }
|
||
]
|
||
}
|
||
|
||
// ===== 方法:重置表单 =====
|
||
const resetForm = () => {
|
||
form.value = {
|
||
id: '',
|
||
name: '',
|
||
code: '',
|
||
value_type: 'string',
|
||
description: '',
|
||
status: null,
|
||
sort: 0
|
||
}
|
||
formRef.value?.clearValidate()
|
||
}
|
||
|
||
// ===== 方法:设置数据(编辑时) =====
|
||
const setData = (data) => {
|
||
if (data) {
|
||
form.value = {
|
||
id: data.id || '',
|
||
name: data.name || '',
|
||
code: data.code || '',
|
||
value_type: data.value_type || 'string',
|
||
description: data.description || '',
|
||
status: data.status !== undefined ? data.status : null,
|
||
sort: data.sort !== undefined ? data.sort : 0
|
||
}
|
||
}
|
||
}
|
||
|
||
// ===== 方法:提交表单 =====
|
||
const handleSubmit = async () => {
|
||
try {
|
||
// 验证表单
|
||
await formRef.value.validate()
|
||
|
||
isSaving.value = true
|
||
|
||
const submitData = {
|
||
name: form.value.name,
|
||
code: form.value.code,
|
||
value_type: form.value.value_type,
|
||
description: form.value.description,
|
||
status: form.value.status,
|
||
sort: form.value.sort
|
||
}
|
||
|
||
let res = {}
|
||
if (isEdit.value) {
|
||
// 编辑
|
||
res = await systemApi.dictionaries.edit.put(form.value.id, submitData)
|
||
} else {
|
||
// 新增
|
||
res = await systemApi.dictionaries.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 {
|
||
// API 调用失败
|
||
console.error('提交失败:', error)
|
||
message.error('操作失败')
|
||
}
|
||
} finally {
|
||
isSaving.value = false
|
||
}
|
||
}
|
||
|
||
// ===== 方法:取消 =====
|
||
const handleCancel = () => {
|
||
resetForm()
|
||
emit('update:visible', false)
|
||
}
|
||
|
||
// ===== 监听 visible 变化 =====
|
||
watch(() => props.visible, (newVal) => {
|
||
if (newVal) {
|
||
// 打开弹窗时,如果有 record 则设置数据
|
||
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>
|