一、需求背景
之前开发过的动态表单,都是单个按钮点击后在数组中push一个新内容实现尾部插入,这次的需求需要横向和纵向都实现数据插入,即 提供两个push按钮:
按钮一实现纵向插入内容(插入行)
按钮二实现横向插入内容(插入列)
还有一个删除整行的按钮;
每行之间是或的关系,行中每一列是且的关系
出现两行后左侧出现“或”字样,出现两列后用“且”连接
二、期望效果
三、数据准备
后端提供的数据结构如下所示:
{
checkItemHolds: [
{
fatherItem: [
{
name: 'aaaa',
id: 1
},
{
name: 'bbbb',
id: 2
}
]
},
{
fatherItem: [
{
name: '磁盘只读',
id: 3
}
]
}
]
};
四、编码实现
step1:前端数据结构定义
const formData = ref({
checkItemHolds: []
});
step2:vue中渲染,如果要兼容回显,发接口拿到后端的checkItemHolds进行填充即可,否则是formData里定义的空数组;其实是根据formData.checkItemHolds来渲染数据,然后根据每一项具体的fatherItem渲染el-select的个数,所以需要两个v-for循环;
<el-form-item
label="xxxx:"
prop="checkItemHoldIds"
:rules="[
{
validator: checkItemHoldIdsValidator,
trigger: 'change'
}
]"
>
<el-link @click="addFatherAlertRules" class="holds-text-button">增加行规则</el-link>
<!-- 若需动态控制左边框 :class="{ 'border-left': formData.checkItemHolds.length > 1 } -->
<div class="holds-container">
<div v-for="(item, index) in formData.checkItemHolds" :key="item.id" class="holds-line">
<p class="label">
<span>行{{ index + 1 }}:</span>
</p>
<div class="center">
<div v-for="(selectItem, selectIndex) in item.fatherItem" :key="selectIndex" class="center-selects">
<el-select
v-model="item.fatherItem[selectIndex].id"
placeholder="请选择"
class="select-container"
filterable
>
<el-option v-for="item in holdOptions" :key="item.id" :value="item.id" :label="item.name"></el-option>
</el-select>
<span v-if="selectIndex !== item.fatherItem.length - 1" class="and-text">且</span>
</div>
</div>
<p class="right-buttons">
<el-icon @click="addSonAlertRules(index)">
<CirclePlusFilled />
</el-icon>
<el-icon @click="deleteAlertRules(index)">
<DeleteFilled />
</el-icon>
</p>
</div>
<span v-if="formData.checkItemHolds.length > 1" class="or-text">或</span>
</div>
</el-form-item>
step3:点击“增加行规则”
按钮新增行
const addFatherAlertRules = () => {
// 用户点击新增行规则后拿下拉列表数据
if (holdOptions.value.length === 0) {
getHoldOptions(); // 这里是根据业务需求写的代码,因为编辑侧进入时就发请求了,所以这里不需要再次拿el-select的接口,但是新建弹框在点击增加行规则的时候才会去拿option的数据;
}
formData.value.checkItemHolds.push({
fatherItem: [
{
name: '',
id: null
}
]
});
};
step4:点击“+”
号新增列,注意传入的index是当前行的行数,来自第一层for循环
const addSonAlertRules = (index) => {
formData.value.checkItemHolds[index].fatherItem.push({
name: '',
id: null
});
};
step5:点击“删除”
按钮,删除整行,每次删除需要触发该表单的校验,否则编辑态的时候,可能存在为空的select触发了校验,但是该项删除后,校验提示依然存在的情况!!
const deleteAlertRules = (index) => {
formData.value.checkItemHolds.splice(index, 1);
// 每次删除触发单条校验 fix校验出现后一直存在的问题
formRef.value.validateField('checkItemHoldIds', () => {});
};
另外还有自定义校验
的代码:
// 自定义校验checkItemHoldIds不为空
const checkItemHoldIdsValidator = (rule, value, callback) => {
if (formData.value.checkItemHolds.length > 0) {
const hasNullId = formData.value.checkItemHolds.some((hold) => hold.fatherItem.some((item) => item.id == null));
if (hasNullId) {
callback(new Error('存在空值'));
return;
}
}
callback();
};
scss样式代码:
.holds-container {
width: 100%;
border-left: 3px solid rgb(55, 136, 184);
padding-left: 10px;
position: relative;
.holds-line {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
margin-top: 5px;
margin-bottom: 12px;
.label {
// margin-right: 8px;
align-self: flex-start;
}
.center {
display: flex;
flex-wrap: wrap;
flex: 1;
// margin-right: 8px;
.center-selects {
display: flex;
align-items: center;
margin-right: 2px;
margin-bottom: 5px;
.select-container {
flex: 1 1 auto;
margin-right: 2px;
width: 238px;
}
.and-text {
flex: 0 0 auto;
color: rgb(55, 136, 184);
font-weight: bold;
}
}
}
.right-buttons {
display: flex;
padding-top: 4px; // icon对齐
gap: 5px;
font-size: 22px;
cursor: pointer;
color: rgb(55, 136, 184);
align-self: flex-start;
}
}
.or-text {
position: absolute;
left: -6px;
top: 50%;
transform: translateY(-50%);
color: rgb(55, 136, 184);
font-size: 14px;
font-weight: bold;
white-space: nowrap;
background-color: white;
}
}
中间还遇到了el-select匹配不到值的问题
,是因为el-option中,后端把之前的值删了,那el-select就会默认展示id,那肯定是不行的!!!--优化
,拿到el-option后,匹配一下formData.value.checkItemHolds也就是后端给的数据中,是不是id和name都在option中存在,不然就把id设置为null,不影响编辑态的展示:
const filterCheckItemHolds = () => {
// 去除select中匹配不到的情况-case1 id值null
formData.value.checkItemHolds.forEach((checkItemHold) => {
checkItemHold.fatherItem = checkItemHold.fatherItem.map((fatherItem) => {
const hasMatch = holdOptions.value.some((option) => option.id === fatherItem.id);
return hasMatch ? fatherItem : { ...fatherItem, id: null };
});
});
};
五、最终效果
只有一行规则:
两行规则出现“或”样式
两列后出现“且”样式
存在空值校验