效果图:
实现逻辑梳理:
点击新增时,表单整体数据结构,需要先请求接口,包含布尔,字典,小数,整数,文本五大类型, 点击修改时,需要将表单数据进行回显,需要在点击表格修改时,将数据传入表格中,最后根据接口表单名称和表格表单名称进行匹配,最后将值进行回显。表单的必填规则,也是读取接口数据的必填属性字段,进行校验。
新增数据结构:
修改数据结构:
完整代码:
<template>
<div class="attribute-warehouse-dialog">
<el-dialog :title="operateType == 'create' ? '新增数据': '修改数据'" :append-to-body="true" :visible.sync="dialogVisible" :close-on-click-modal="false" width="5.6rem" @close="closeDialog" class="customer-dialog">
<div class="dialog-box dialog-box1">
<div class="table">
<el-form size="small" @submit.native.prevent :model="ruleForm" ref="ruleForm" :rules="rules" class="demo-ruleForm" label-width="0.9rem" label-position='right'>
<el-form-item label="ts" prop="ts" class="customer-date-picker">
<el-date-picker
v-model="ruleForm.ts"
type="datetime"
placeholder="选择ts"
value-format="yyyy-MM-dd HH:mm:ss"
:disabled="operateType == 'edit'">
</el-date-picker>
</el-form-item>
<!-- 动态生成表单项 -->
<el-form-item v-for="(item, index) in dynamicFormItems" :key="index" :prop="item.prop" :class="item.dataType === '5' ? 'customer-date-picker' : ''">
<template #label>
<!-- <el-tooltip :content="item.label" placement="top" :disabled="!isLabelOverflow(item.label)"> -->
<span class="label-with-tooltip" :title="item.label">{{ item.label }}</span>
<!-- </el-tooltip> -->
</template>
<template v-if="item.dataType === '5'">
<el-date-picker
v-model="ruleForm[item.prop]"
type="datetime"
:placeholder="item.placeholder"
value-format="timestamp">
</el-date-picker>
</template>
<template v-else-if="item.dataType === '4'">
<el-select v-model="ruleForm[item.prop]" clearable>
<el-option v-for="option in getOptions(item.dataType)" :key="option.value" :label="option.dictName" :value="option.id"></el-option>
</el-select>
</template>
<template v-else-if="item.dataType === '6'">
<el-select v-model="ruleForm[item.prop]" clearable>
<el-option v-for="option in getOptions(item.dataType, item.enumId)" :key="option.value" :label="option.dictName" :value="option.id"></el-option>
</el-select>
</template>
<template v-else>
<el-input
type="text"
:placeholder="item.placeholder"
v-model.trim="ruleForm[item.prop]"
class="input-normal"
:maxlength="item.dataType === '3' ? item.length : 32"
show-word-limit>
</el-input>
</template>
</el-form-item>
</el-form>
</div>
</div>
<!-- 底部按钮 -->
<div slot="footer" class="dialog-footer">
<el-button v-debounce="[()=>closeDialog(),`click`]" class="dialog-button">取消</el-button>
<el-button v-if="operateType !== 'detail'" type="primary" v-debounce="[()=>confirm(),`click`]" class="btn-onelevel dialog-button">
确认
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { updateDataSet, getMetaDataAttrByMetaDataId, addDynamicAttr } from "@/api/AIModel";
import { dictitemList } from "@/api/myapi";
export default {
name: 'AttributeWarehouseDialog',
data() {
return {
dictionaryLists: {}, // 用于存储每个 enumId 对应的字典属性列表
pickerOptions: {
disabledDate(time) {
return time.getTime() < Date.now() - 8.64e7;
},
},
metadataStatus: false,
operateType: '',
detailData: {},
dialogVisible: false,
ruleForm: {
ts: ''
},
DataList: [],
BooleanList: [
{ dictName: 'true', id: 'true' },
{ dictName: 'false', id: 'false' }
],
dynamicFormItems: [], // 动态生成的表单项
rules: {
ts: [
{
required: true,
trigger: ['blur', 'change'],
message: '选择ts',
}]
},
}
},
methods: {
openDialog(data, type) {
this.operateType = type
this.dialogVisible = true
this.detailData = data
this.getMoveFormData().then(() => {
if (type == 'edit') {
// 初始化动态字段
this.dynamicFormItems.forEach(item => {
if (item.dataType === '5' && item.prop !== 'ts') {
this.$set(this.ruleForm, item.prop, new Date(data[item.prop]).getTime())
} else {
this.$set(this.ruleForm, item.prop, String(data[item.prop] || ''))
}
})
// 将 data 数据赋值给表单,进行数据回显
Object.keys(data).forEach(key => {
if (this.ruleForm.hasOwnProperty(key)) {
if (data[key] === '--') {
data[key] = ''
}
if (this.dynamicFormItems.find(item => item.prop === key && item.dataType === '5' && key !== 'ts')) {
this.$set(this.ruleForm, key, new Date(data[key]).getTime())
} else {
const item = this.dynamicFormItems.find(item => item.prop === key)
// 根据详情返回的字典名称,匹配字典列表中的字典项,获取字典项的值,并进行数据回显
if (item && (item.dataType === '4' || item.dataType === '6')) {
const dictionaryList = this.dictionaryLists[item.enumId] || []
const matchedItem = dictionaryList.find(dictItem => dictItem.dictName === String(data[key]))
if (matchedItem) {
this.$set(this.ruleForm, key, matchedItem.id)
} else {
this.$set(this.ruleForm, key, String(data[key]))
}
} else {
this.$set(this.ruleForm, key, String(data[key]))
}
}
}
})
// 新增逻辑:遍历 data 对象,匹配字典项列表
Object.keys(data).forEach(key => {
const item = this.dynamicFormItems.find(item => item.prop === key)
if (item && (item.dataType === '4' || item.dataType === '6')) {
const dictionaryList = this.dictionaryLists[item.enumId] || []
const matchedItem = dictionaryList.find(dictItem => dictItem.dictName === String(data[key]))
if (matchedItem) {
this.$set(this.ruleForm, key, matchedItem.id)
}
}
})
// 确保 ts 字段正确回显
if (data.ts) {
this.$set(this.ruleForm, 'ts', data.ts)
}
} else {
this.reset()
}
})
},
async getMoveFormData() {
let { data: res } = await getMetaDataAttrByMetaDataId(this.$route.query.metadataId)
const dictionaryPromises = res.data.map(item => {
if (item.enumId) {
return this.getDictionaryAttributeList(item.enumId).then(data => {
this.$set(this.dictionaryLists, item.enumId, data)
})
}
return Promise.resolve()
})
await Promise.all(dictionaryPromises)
this.dynamicFormItems = res.data.map(item => ({
length: item.length,
label: item.name,
prop: item.name,
isMust: item.isMust,
dataType: item.dataType,
placeholder: `请输入 ${item.name}`,
enumId: item.enumId // 添加 enumId 到 dynamicFormItems
}))
// 动态生成校验规则
this.dynamicFormItems.forEach(item => {
const rules = []
if (item.isMust) {
rules.unshift({
required: true,
trigger: ['blur', 'change'],
message: `请输入${item.label}`,
})
}
// if (item.isMust) {
// 根据 dataType 添加不同的校验规则
if (item.dataType == '1') { // 正整数
rules.push({
pattern: /^\d+$/,
trigger: ['blur', 'change'],
message: `请输入正整数`,
})
} else if (item.dataType == '2') { // 小数
rules.push({
pattern: /^\d*\.\d+$/,
trigger: ['blur', 'change'],
message: `请输入小数`,
})
}
// else if (item.dataType == '4') { // 布尔值
// rules.push({
// validator: (rule, value, callback) => {
// if (value !== 'true' && value !== 'false') {
// callback(new Error('请输入true或false'))
// } else {
// callback()
// }
// },
// trigger: ['blur', 'change'],
// })
// }
this.$set(this.rules, item.prop, rules)
// }
})
// 初始化 ruleForm 中的动态字段
this.dynamicFormItems.forEach(item => {
this.$set(this.ruleForm, item.prop, '')
})
return Promise.resolve()
},
// 获取字典项属性列表
async getDictionaryAttributeList(enumId) {
const { data: res } = await dictitemList({ dictId: enumId })
if (res.code == '0' && res.data) {
return res.data.map(item => ({
id: item.itemValue,
dictName: item.itemText
}))
} else {
return []
}
},
getOptions(dataType, enumId) {
if (dataType === '4') {
return this.BooleanList
} else if (dataType === '6' && enumId) {
return this.dictionaryLists[enumId] || []
}
return []
},
confirm() {
this.$refs['ruleForm'].validate(async valid => {
if (!valid) return;
let res = {}
// 过滤掉值为空字符串,或null,NaN类型的字段
// const filteredFormData = Object.fromEntries(Object.entries(this.ruleForm).filter(([key, value]) => value !== '' && value !== null && !Number.isNaN(value)))
res = await addDynamicAttr(this.ruleForm, this.$route.query.attributeId)
if (res.data.code == '0') {
this.$message({
message: res.data.message,
type: 'success'
})
this.$emit('getInit')
this.closeDialog();
}
});
},
closeDialog() {
this.dialogVisible = false
},
reset() {
this.$nextTick(() => {
this.ruleForm = {}
this.dynamicFormItems.forEach(item => {
this.$set(this.ruleForm, item.prop, '')
})
this.$refs.ruleForm.resetFields();
})
},
isLabelOverflow(label) {
const labelWidth = 90; // 假设label-width为0.9rem,这里假设1rem=100px
return label.length * 10 > labelWidth; // 粗略估计每个字符宽度为10px
},
}
}
</script>
<style lang="scss" scoped>
/deep/ .el-dialog__body {
padding: .2rem .2rem 0 !important;
}
.dialog-box1 {
min-height: 1rem;
//max-height: 6.36rem;
box-sizing: border-box;
position: relative;
padding: 0 0.12rem 0 0.12rem;
overflow: hidden;
.demo-ruleForm {
height: 100%;
/deep/ .el-form-item__error {
width: 3.14rem;
}
}
}
.el-checkbox {
margin-right: 0.4rem !important;
padding: 0.05rem 0 0.05rem;
}
.el-button--mini {
padding: 0.1rem 0.15rem;
}
/deep/ .el-dialog__footer {
padding: 0 0.85rem 0.42rem 0.32rem;
}
.dialog-button {
padding: 0.09rem 0.25rem !important;
}
.table {
max-height: 4.41rem;
overflow-y: auto;
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
word-break: normal;
height: 0.44rem;
padding: 0 0.1rem;
border-bottom: 1px solid #ebebeb;
font-size: 0.14rem;
&:last-child {
border-bottom: none;
}
.item {
display: flex;
align-items: center;
width: 2.46rem;
span {
flex-shrink: 0;
}
.auto {
margin-left: 0.1rem;
}
/deep/ .el-input {
padding: 0 0.1rem;
width: .7rem;
}
::v-deep input::-webkit-outer-spin-button,
::v-deep input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
::v-deep input[type="number"] {
-moz-appearance: textfield;
}
/deep/ .el-input__inner {
height: 0.3rem;
line-height: 0.3rem;
}
}
.item-px {
justify-content: flex-end;
&-w {
width: 1.2rem
}
}
}
/deep/.el-textarea {
width: 86% !important;
}
// .label-with-tooltip {
// display: inline-block;
// width: 90px; // 与label-width对应
// white-space: nowrap;
// overflow: hidden;
// text-overflow: ellipsis;
// }
END...