🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 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 } } ``` > 本文作者:不爱喝橙子汁