# 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/ 不生效