一、项目需求
用户登录后展示条件筛选页面,多层条件筛选,存在单选和多选情况,以及视图样式根据选中情况有相应的变,点击清空按钮后恢复初始状态。效果图如下:二、数据分析
列表渲染的数据部分前端写死,部分是从后端接口调取:
// 获取接口数据
mounted () {
let param = {
enableFlag: 'Y',
}
let promise0 = fetchCarBrand(param)
let promise1 = fetchSelectTList('APP.CAR.ENERGY_TYPE')
let promise2 = fetchSelectTList('APP.CAR.DISPLACEMENT')
let promise3 = fetchSelectTList('APP.CAR.CLOUR')
let promise4 = fetchSelectTList('APP.CAR.PASSENGERS')
Promise.all([promise0, promise1, promise2, promise3, promise4]).then(
res => {
console.log(res)
this.carBrandData = res[0].rows
this.newCarBrandData = this.carBrandData.map(item => {
return Object.assign(item, { isPiont: false })
})
this.energyType = res[1].rows
this.displacement = res[2].rows
this.carColor = res[3].rows
this.persons = res[4].rows
}
)
}
(1)三级联动---以车辆品牌为例:- dom结构:
<!-- 一层列表 -->
<li
:class="{ switch: activeIndex === 1 }"
class="car-model"
@click="handleSwitchLiTwo"
>
<div
:class="{
bluecolor: selectParam.brandList || selectParam.modelList
}"
>
<i
:class="{
bluepoint: selectParam.brandList || selectParam.modelList
}"
/>车辆品牌
</div>
<!-- 二层列表 -->
<ul v-if="isModelShow">
<li
v-for="(item, index) in newCarBrandData"
:key="index"
@click="selectCarModel(item)"
>
<div
:class="{
bluecolor:
selectParam.brandList &&
selectParam.brandList.find(e => e === item.brandId)
}"
>
<i
:class="{
bluepoint: modelMap[item.brandId] && modelMap[item.brandId].length
}"
/>
{{ item.brandName }}
</div>
</li>
</ul>
<!-- 三层列表 -->
<ul v-if="isModel" class="car-model-child">
<li
v-for="(v, i) in newModelData"
:key="i"
:class="{
bluecolor:
modelMap[v.brandId] && modelMap[v.brandId].includes(v.modelId)
}"
@click.stop="handleSelectModel(v)"
>
{{ v.modelName }}
</li>
</ul>
</li>
此处将二层列表和三层列表写成并列结构,原本写的是嵌套结构,但由于后来属性控制出现问题,改成了并列结构。
同时针对动态类名的控制这里使用的是我点击选中后生成的条件列表数组去控制,如果用boolean值就会有一堆的变量需要在不同阶段去改变,很繁琐。
首先一层列表的切换通过判断当前点击下标是否等于该元素在列表中的下标相等,来控制状态。关于tab切换操作方法可参考vue写tab切换。
- 接下来的事件处理才是实现这个功能的重点:
- 第二级列表的点击事件
// 品牌选择
selectCarModel (item) {
this.isModel = true
// 判断筛选参数里是否存在该列表属性,若没有给一个初始空数组
if (!this.selectParam.brandList) this.selectParam.brandList = []
// 若存在则先判断是首次点击选中还是要再次点击取消
if (this.selectParam.brandList.indexOf(item.brandId) > -1) {
// 再次点击取消操作
this.newModelData = [] // 清空第三层列表
// 过滤掉对应品牌列表里的当前点击项
this.selectParam.brandList = this.selectParam.brandList.filter(
i => i !== item.brandId
)
} else {
// 点击选中操作
// 设置第三层列表项的数据
this.newModelData = item.modelList
// 向对应品牌列表里添加当前点击的品牌id
this.selectParam.brandList.push(item.brandId)
}
// 如果品牌列表为空
if (this.selectParam.brandList.length === 0) {
delete this.selectParam.brandList // 删除筛选参数中的品牌列表属性
this.isModel = false // 隐藏第三层列表
}
this.$forceUpdate() // 强制页面更新(重点)
},
- 第三级列表的点击事件
其实实现该模块最难得点就在于这第三层列表要与第二层以及第一层列表的关联,此处我采用的做法是创建一个类似Map结构的对象属性来存选中的数据,利用第三层列表与第二层列表之间的共同的属性brandId作为属性,将选中的modelId存到对应的brandId中,每次点击就判断该map对象下是否存在该modelId,存在则过滤掉不存在则添加。当某个brandId为空是则要删除该属性,最终还要将改对象解构成数组拼接到筛选接口的参数里。
const mapList = {
brandId1:[modelId1,model2,...],
brandId2:[modelId1,model2,...],
...
}
上代码:
// 车型选择
handleSelectModel (item) {
if (!this.selectParam.modelList) this.selectParam.modelList = []
if (!this.modelMap[item.brandId]) this.modelMap[item.brandId] = []
if (this.modelMap[item.brandId].indexOf(item.modelId) > -1) {
this.modelMap[item.brandId] = this.modelMap[item.brandId].filter(
i => i !== item.modelId
)
if (this.modelMap[item.brandId].length === 0) {
delete this.modelMap[item.brandId]
}
} else {
this.modelMap[item.brandId].push(item.modelId)
}
let modelList = Object.values(this.modelMap)
if (modelList.length === 0) {
delete this.selectParam.modelList
} else {
this.selectParam.modelList = [].concat(...modelList)
}
this.$forceUpdate()
},
总结
当拿到一个复杂的页面时首先要分析业务需求理清业务逻辑,然后根据接口数据格式去构思页面结构,这样子写起来相对轻松一点。