# picker的三级联动
## 序言
uView2的picker和平常其他的picker不同,因为它接收的数据源是三列数组,需要开发者自己根据需求调整列索引来满足多列联动的需求,但后端给的数据格式通常是一个带children的树形结构,无法直接适用于uView2的picker。
本文主要讲解对于不同数据格式实现多列联动的示例,开发者可以根据实际需求更加定制化的开发。
## 实际应用场景
**全国省市区的三级联动**。
这种组件因为应用场景广泛,DCloud的插件市场也有很多的示例,但是有一个缺陷:对于需要保证数据统一性的场景下无法满足需求,因为这样的插件都是直接把省市区数据源放前端源码里(如city,json, area.js),而中国区县行政规划又年年在变动,一旦前后端使用的数据源不一致,就有可能出现用户选择的位置在服务端查不到的情况。
## 应该如何做
我们来考虑用户点开省市区的picker时候所看到的现象和交互逻辑:首先,用户不会一下子看到全国3500多个区县的数据,一开始只会看到【全国的省(包括直辖市在内),北京市,北京市的区】,等用户滚动省的时候,才需要展示对应的市,滚动市的时候,才需要展示对应的区县,发现没有,这个渲染实际上是类似于**懒加载**的。
所以我们只需要一个**能够根据父id区列出所有子id**的方法就可以了,并不需要纠结如何动态设置滚动索引列。当然树形格式不好做查询,所以我们先把数据格式化成扁平的数组,如果后端本来返回的就是平铺的数组,则我们可以省略此步骤。
```JS
function parseTree(tree) {
const res = []
array.forEach(item => {
// 如果item中有children,则递归调用
item.parentId = item.parentId || 0;
let id = item.id
let children = item.children
if (children) {
children.forEach(child => child.parentId = id)
res.push(...parseTree(children))
}
res.push(item)
})
return res
}
```
通过上述步骤,我们就把带children树形嵌套的树形结构格式化成了带parentId的扁平数组。
然后我们还需要根据parentId去筛选数据的函数
```js
let data = parseTree(cities) // 先保存好数据
function findByParentId(id) {
return data.filter(item => item.parentId === id)
}
```
完成了上述步骤后,我们就可以设计picker的交互流程了:
1. 先设计用户一开始点击picker时候出现的场景
```js
let col1 = findByParentId(0) // 这个主要取决于数据格式,如果说返回的有一个唯一的祖先节点(例如中国,id是100000),则直接传入100000即可,如果没有这个,而是直接就给34个省级行政区,则需要传0
let col2 = findByParentId(col1[0].id) // 因为默认时候肯定停留在第一个选项,所以查出它的直接子节点放到第二列就可以。
let col3 = findByParentId(col2[0].id) // 同理设置第三列
let columns = [col1, col2, col3]
```
这个columns就可以直接作为picker的columns属性去设置picker的默认值了。
2. 用户滚动列的时候出现的场景
在picker的change回调中我们可以获得用户当前滚动的列和三列的值,通过它,我们可以设置它子列的值(使用setColumnValues)以达到联动的效果。
```vue
<template>
<u-picker ref="uPicker" @change="handlePickerChange" :columns="columns" />
</template>
<script>
let areas;
export default {
data() {
return {
columns: [],
}
},
// 见上方说明
async mounted() {
let res = await api.getAreas()
areas = parseTree(res)
let col1 = findByParentId(0) // 这个主要取决于数据格式,如果说返回的有一个唯一的祖先节点(例如中国,id是100000),则直接传入100000即可,如果没有这个,而是直接就给34个省级行政区,则需要传0
let col2 = findByParentId(col1[0].id)
let col3 = findByParentId(col2[0].id)
this.columns = [col1, col2, col3]
},
methods: {
handlePickerChange({columnIndex, value]}) {
const { uPicker } = this.$refs; // 获取组件实例
if(columnIndex == 0) {
// 用户滚动的是第一列,要同时设置第二列和第三列的值
let currentVal= value[0]
let col2 = findByParentId(currentVal.id)
let col3 = findByParentId(col2[0].id)
uPicker.setColumValues(1, col2)
uPicker.setColumValues(2, col3)
} else if (columnIndex == 1) {
// 用户滚动的是第二列,只需要设置第三列的值就可以
let currentVal= value[1]
let col3 = findByParentId(currentVal.id)
uPicker.setColumValues(col3)
}
},
findByParentId(id) {
return areas.filter(item => item.parentId === id)
}
}
}
</script>
```
## 源码示例
下面给出完整的TreeSelect类的实现供大家参考,仅通过基础方式构建,可自己扩展。
```js
class TreeSelect {
constructor(data, {key,props,root}) {
this._key = key || 'id';
this._childrenProps = props || 'children';
this._root = root || '0';
this._arr = this.parseTreeToArray(data);
this._len = this._arr.length || 1;
}
// 把树形结构变成带parentID的二维数组
parseTreeToArray = (array) => {
const res = []
array.forEach(item => {
// 如果item中有children,则递归调用
item.parentId = item.parentId || this._root;
let id = item[this._key]
let children = item[this._childrenProps]
if (children) {
children.forEach(child => child.parentId = id)
res.push(...this.parseTreeToArray(children))
}
res.push(item)
})
return res
}
// 根据父id获取直接子节点
findChildrenPyParentID = (id) => this._arr.filter(item => item.parentId === id)
init = () => {
const arr = new Array(this._len)
let cols = [[],[],[]]
cols[1] = this.findChildrenPyParentID(this._root)
cols[2] = cols[1] ? this.findChildrenPyParentID(cols[1][0][this._key]) : []
cols[3] = cols[2] ? this.findChildrenPyParentID(cols[2][0][this._key]) : []
for (let i = 0; i < this._len; i++) {
arr[i] = cols[i]
}
return arr
}
}
```
> 本文作者:不爱喝橙子汁
- 自述
- 学会提问
- 起步
- 安装
- 版本升级
- 1.x 升级 2.x 常见问题
- 命令行模式下node-sass安装错误
- 查看版本
- uView UI 1.x 相关问题
- 安装
- Popup 弹窗
- tabs 标签
- Waterfall 瀑布流
- Table 表格
- Dropdown 下拉菜单
- uview-ui组件篇
- u-upload监听beforeRead事件无效
- 组件怎么关不了
- 导航栏不默认返回好麻烦
- ref怎么获取不到
- z-index拉满都覆盖不了map
- u-text对手机号脱敏
- u-input的placeholder去不掉
- 服务端返回数据,form表单验证错误
- checkbox增加选中面积
- uview-ui组件篇/checkbox无法取消选中
- 小程序输入框的placeholder会穿透到弹出层
- JavaScript篇
- 判断数据类型
- 数组操作
- 节流与防抖函数
- this怎么就不对
- 计算地图上两点间的距离
- CSS篇
- 我要超出显示省略号
- uniapp中小程序样式穿透问题
- 关键帧与动画
- CSS动画属性总结
- 过渡与动画
- 正则表达式篇
- 身份证号
- 手机号
- 是否合法的http/https域名
- 数据处理篇
- 对数组分组
- 深拷贝对象
- 提取数组属性
- 提取对象属性
- 常见问题
- 如何给由组件触发的事件中传入自定义的参数
- 分类的双列联动
- 三级联动的实现
- 小程序预览提示包过大
- 框架安装失败
- 表格、瀑布流、下拉列表 组件为什么没有了
- tabBar组件怎么用
- 时间、日历、选择器相关问题
- 字体图标不显示
- class 或 /deep/ 不生效