今天有这样一个需求:
1 左侧第一列一个树形结构数组,第二列也是一个树形结构数组,第二列的属性过滤这一栏的数据在第一列点击(或者勾选)的时候获取的。
2第二列勾选某个checkbox,会触发联动,比如勾选图中的“简约现代”会让其父级风格的勾选状态发生变化
3当第二列全部勾选,会引发第一列当前激活项的勾选状态发生变化:(第二列全部勾选,则第一列当前激活项的checkbox处于勾选状态,如果全部不勾选,则第二列激活项的chekcbox处于空状态,第二列只要有个勾选,则第一列的当前激活项处于横杆状态)
这个场景的难点在如下几点:
1 第一列,第二列的树形数据没有关联但是却进行了强行关联
2 第一列当前激活项的状态更新不仅会影响到本身还会影响到第二列的勾选项
解决的思路:
1 受控组件。改造数据,把checkbox的状态变化由某个字段来控制,这里的状态不仅包括勾选还包括横杆这种状态,也就是我们需要加两个字段分别控制其checked和indeterminate.前者代表是否勾选,后者代表是否有横杆
2 由于勾选多个第一列的checkbox以后再返回来点击或者勾选之前的某个checkbox以后,要请求接口,会导致第二列数据更新,但是我们要记住之前的勾选项。此时我们通过记录之前的第二列勾选值的集合来记录。
3 我们要通过一个类似map集合来记录第一列激活项-第二列勾选项,类似:
{
21:[
{id:23,parentId:11},
{id:24,parentId:11},
{id:25,parentId:11},
], //21表示左侧第一列当前激活项的id,数组表示它对应的右侧勾选了的值的集合
31:[
{id:33,parentId:21},
{id:34,parentId:21},
{id:35,parentId:21},
],
}
4 右侧哪些被勾选了,我们通过this.$refs.attrTree.setCheckedKeys([ids...])来设置,只要记录了对应的id的集合就能正常让右侧的checkbox被勾选上,点击左侧获取数据以后第二列展示以后立马做回显this.$refs.attrTree.setCheckedKeys([ids...]),但是这里有个问题。就是勾选checkbox的时候element的tree组件会自动把其父级也塞到checkedNodes和checkeKeys里。所以我们要过滤,把父级过滤掉。只保留第二列的子集。因为子集里有parentid,所以我们只需要保留一个
<template>
<el-dialog
title="绑定商品"
visible
width="1620px"
:close-on-click-modal="false"
@close="$emit('close')"
>
<div class="bind-prod-wrap">
<div class="bind-prod-head">
<span>施工项:(套餐)顶面新粉墙面</span>
<span>单位:平方</span>
</div>
<div class="bind-prod-content">
<div class="category">
<el-tree
:data="categroyList"
:props="defaultProps"
node-key="id"
:highlight-current="false"
@node-click="handleNodeClick"
>
<div slot-scope="{ node, data }">
<el-checkbox
v-model="data.checked"
:indeterminate="data.indeterminate"
@change="selectCatagory(data)"
v-show="data.children.length == 0"
></el-checkbox>
<img
v-if="data.children.length > 0"
src="../../images/folder.png"
width="20px"
height="20px"
class="mr6"
/>
<img
v-else
src="../../images/category-little@2x.png"
width="24px"
height="24px"
class="mr6"
/>
{{ data.name }}
</div>
<!-- <div v-if="node.children.length == 0" slot-scope="{ node, data }">
<img src="../../images/project-none@2x.png" />
</div> -->
</el-tree>
</div>
<div class="attribute">
<h4>属性过滤</h4>
<div>
<el-tree
:data="attrList"
@check="handlerProperty"
node-key="id"
show-checkbox
default-expand-all
ref="attrTree"
>
<div slot-scope="{ node, data }">
<div v-if="data.children && data.children.length > 0">
{{ data.propertyName }}
</div>
<div v-else>{{ data.value }}</div>
</div>
</el-tree>
</div>
</div>
<div class="prod-list">
<div class="prod-title-wrap">
<div class="prod-head">品类基本信息</div>
<ul class="prod-head-list">
<li><span>类型</span>:主材</li>
<li><span>施工单位</span>:平方</li>
<li><span>模型分类</span>:铺贴类</li>
<li><span>绑定铺法</span>:连续直铺</li>
</ul>
</div>
<div class="prod-cont-box">
<div class="prod-item" v-for="item in 16" :key="item">
<div class="img-wrap">
<span class="tag">商品</span>
</div>
<div class="info-wrap">
<div class="info-title">博得瓷砖bmbh7234*123</div>
<div class="info-item">博得</div>
<div class="info-item">型号:600*1239</div>
<div class="info-item">产品尺寸:600*1239</div>
</div>
</div>
</div>
</div>
</div>
<div slot="footer" class="footer">
<el-button type="primary" @click="submit">
确定
</el-button>
<el-button @click="$emit('close')">
取消
</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import { getCategories } from "@/service_fe";
import { getPropertiesByCatagory } from "@/service";
export default {
name: "BindProdDialog",
data() {
return {
bindPordFlag: false,
defaultProps: {
label: "name",
children: "children"
},
curCatatgoryItem: {}, //当前激活的品类
curPropertiesIds: [], //当前展示的属性和属性值的长度
attrList: [],
categroyList: [],
resMap: {}
};
},
async mounted() {
await this.getCatagroyList();
this.parseAttr(false);
this.parseCatagory(this.categroyList, false);
//初始化记录当前展示的属性和属性值的集合
},
methods: {
//获取品类
async getCatagroyList() {
try {
let res = await getCategories();
if (res.code === 1) {
this.categroyList = res.data || [];
}
} catch (err) {
throw new Error(err);
}
},
//获取属性和属性值
async getPropertiesList(id) {
try {
let res = await getPropertiesByCatagory(id);
if (res.code === 1) {
this.attrList = res.data || [];
this.getCurrentPropertyIds(this.attrList);
}
} catch (err) {
throw new Error(err);
}
},
parseAttr(flag) {
this.attrList.forEach(
item =>
item.children &&
item.children.forEach(j => {
this.$set(j, "checked", flag);
})
);
},
parseCatagory(list, flag) {
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (item.children.length == 0) {
this.$set(item, "checked", flag);
this.$set(item, "indeterminate", flag);
} else {
this.parseCatagory(item.children, flag);
}
}
},
//记录当前展示的属性值的数据的集合(用于计算总数)
getCurrentPropertyIds(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
if (item.children) {
for (let j = 0; j < item.children.length; j++) {
let k = item.children[j];
res.push(k);
}
}
}
this.curPropertiesIds = res;
return res;
},
//记录当前展示的属性和属性值的数据的集合(用于计算总数)
getCurPropertyAndValsNodes(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
res.push(item);
if (item.children) {
res.push(...this.getCurPropertyAndValsNodes(item.children));
}
}
return res;
},
// let value = null;
// for (let item of data) {
// if (item.id === id) {
// value = item;
// break;
// }
// if (item.childList) {
// let _value = this.getItemById(item.childList, id);
// if (_value) {
// return _value;
// }
// }
// }
// return value;
// },
handlerProperty(data, checkListData) {
console.log("data:", data);
console.log("checkListData:", checkListData);
let checkedNodes = checkListData.checkedNodes.filter(
item => !item.children
);
let len = this.curPropertiesIds.length;
let ckLen = checkedNodes.length;
console.log("len:", len);
console.log("ckLen:", ckLen);
console.log("this.curCatatgoryItem.id:", this.curCatatgoryItem.id);
//获取属性值的集合
let nodes = [...checkedNodes];
//更新resMap
if (this.resMap[this.curCatatgoryItem.id]) {
this.resMap[this.curCatatgoryItem.id] = nodes || [];
} else {
this.$set(this.resMap, this.curCatatgoryItem.id, nodes);
}
console.log("this.resMap", this.resMap);
// debugger;
//更改品类的状态
if (ckLen == len) {
this.changeCatagoryStatus(
this.categroyList,
this.curCatatgoryItem.id,
"checked",
true
);
this.changeCatagoryStatus(
this.categroyList,
this.curCatatgoryItem.id,
"indeterminate",
false
);
} else {
//属性-属性值全部取消
if (ckLen == 0) {
this.changeCatagoryStatus(
this.categroyList,
this.curCatatgoryItem.id,
"checked",
false
);
this.changeCatagoryStatus(
this.categroyList,
this.curCatatgoryItem.id,
"indeterminate",
false
);
} else {
属性-属性值部分取消
this.changeCatagoryStatus(
this.categroyList,
this.curCatatgoryItem.id,
"checked",
false
);
this.changeCatagoryStatus(
this.categroyList,
this.curCatatgoryItem.id,
"indeterminate",
true
);
}
}
},
//更改某个品类的状态
changeCatagoryStatus(arr, id, attr, val) {
if (!(arr instanceof Array)) return null;
for (let i in arr) {
let item = arr[i];
if (item.id === id) {
item[attr] = val;
} else {
if (item.children.length > 0) {
this.changeCatagoryStatus(item.children, id, attr, val);
}
}
}
},
selectCatagory(data) {
console.log("data:", data);
this.curCatatgoryItem = data;
data.indeterminate = false;
this.setPropertyStatusByCatagory(data);
console.log("resMap:", this.resMap);
},
//根据左侧类目状态设置右侧的属性和属性值的勾选状态
setPropertyStatusByCatagory(item) {
if (!this.resMap[this.curCatatgoryItem.id]) {
this.$set(this.resMap, this.curCatatgoryItem.id, []);
}
if (item.checked) {
//更新当前的resMap对应品类的激活项(它是当前品类下所有属性值的集合不包括属性本身)
let curProperIds = this.getCurrentPropertyIds(this.attrList);
this.resMap[this.curCatatgoryItem.id] = curProperIds;
//得到所有属性+属性值的集合
let arr = this.getCurPropertyAndValsNodes(this.attrList);
this.$refs.attrTree.setCheckedNodes(arr);
} else {
if (item.indeterminate) {
let arr = this.getCheckedPropertyVals();
this.$refs.attrTree.setCheckedNodes(arr);
} else {
this.resMap[this.curCatatgoryItem.id] = [];
this.$refs.attrTree.setCheckedNodes([]);
}
}
},
submit() {
//提交数据
console.log("resMap:", this.resMap);
},
//通过配得到其对应的勾选的值的集合
getCheckedPropertyVals() {
let val = [];
let temp = Object.entries(this.resMap) || [];
temp.forEach(item => {
if (item && item[0] == this.curCatatgoryItem.id) {
val = item[1];
}
});
return val;
},
async handleNodeClick(item) {
console.log("item----:", item);
this.curCatatgoryItem = item;
if (item.children.length == 0) {
//当前类目状态是横杆或者勾选的时候无需请求接口
await this.getPropertiesList(item.id);
//当前类目是否勾选
this.setPropertyStatusByCatagory(item);
} else {
return;
}
},
handleAttrNodeClick(data) {
console.log("data:", data);
}
}
};
</script>
<style scoped lang="scss">
.bind-prod-wrap {
padding: 20px;
box-sizing: border-box;
// height: 740px;
// overflow: scroll;
display: flex;
flex-direction: column;
}
.bind-prod-content {
display: flex;
padding: 16px 0;
box-sizing: border-box;
height: 740px;
overflow: scroll;
}
.category {
width: 260px;
border: 1px solid #ddd;
padding: 12px;
max-height: 760px;
overflow: scroll;
flex-shrink: 0;
}
.attribute {
padding: 12px;
max-height: 760px;
overflow-y: scroll;
width: 260px;
flex-shrink: 0;
border: 1px solid #ddd;
border-left: none;
}
.prod-list {
flex: 1;
flex-wrap: nowrap;
}
.mr6 {
display: inline-block;
margin: 6px;
vertical-align: middle;
}
.mt10 {
margin-top: 10px;
}
.footer {
padding: 20px;
display: flex;
align-items: flex-end;
}
.bind-prod-wrap {
::v-deep.el-tree .is-focusable {
background: #fff;
}
}
.prod-title {
padding: 12px;
border-bottom: 1px solid #ddd;
}
.prod-title-wrap {
display: flex;
padding: 16px;
box-sizing: border-box;
}
.prod-head {
font-weight: bold;
width: 140px;
}
.prod-head-list {
flex: 1;
display: flex;
}
.prod-head-list li {
width: 120px;
margin-right: 10px;
}
.prod-cont-box {
display: flex;
flex-wrap: wrap;
max-height: 700px;
overflow: scroll;
justify-content: space-around;
}
.prod-item {
display: flex;
width: 420px;
margin: 10px;
border: 1px solid #ddd;
padding: 20px;
box-shadow: 0 0 2px 2px #eee;
}
.prod-item:hover {
box-shadow: 0 0 12px 2px #ccc;
}
.img-wrap {
width: 120px;
background: #eee;
margin-right: 10px;
position: relative;
}
.info-wrap {
flex: 1;
}
.info-title {
font-weight: bold;
}
.info-item {
margin: 6px;
}
.tag {
display: inline-block;
padding: 6px;
text-align: center;
position: absolute;
top: 0;
left: 0;
z-index: 10;
background: #ddd;
}
</style>