各种零碎中行政区域抉择的场景不少,咱们也有不少这样的场景。本想应用第三方的组件,然而大多有些小问题,不能满足需要。前面应用picker的mulitSelector模式写了一个,发现这种列模式的体验并好,最初仿京东模式自定义了一个。
一、造轮子的起因
1.1 数据要自定义
微信官网的picker的region模式应用的是规范的国家行政区域数据,而咱们的场景有一些自设的区域要退出;也不能够自定久抉择级数,只能选到县/区级。
1.2 picker的兼容性并不好。
uni-app的picker组件,在小程序模式是应用各自的picker,H5则是uni-app自的picker组件。所以在各平台中还是有差别的,在咱们测试中微信的picker的mulitSelector模式,在列级联滑动中如果呈现两次列数组值length不统一时,后绑定的选定索引时会有效,会主动致为0,且后续触发的change事件则仍是绑定索引,而在H5时不会。
1.3 picker是不适宜异步加载数据
级联就是要简便的管制后续列的变动,如1.2所示,绑定索引bug。而如果数据是异步加载,则更难于管制加载状态,特地是滑动过快网络不佳时,很容易呈现数据凌乱。
1.4 picker作级联,不如京东级联模式的体验好效率高。
如图所示
二、上代码
应用的了tui-drawer 、tui-loadmore等tui-xxx为uni-app第三方组件,具本应用参考官网文档,或应用别的组件代替。regionApi为行政区域节点异步加载封装,可根本人数据自行封装。
<!--* 行政区域选择器** @alphaiar* 20210408 created. --><template> <view class="region-picker"> <input v-if="!visibleInputer" placeholder-class="placeholder" :placeholder="placeholder" :value="selectorPath" disabled @tap="onPopupToggle" /> <view v-else @tap="onPopupToggle"> <slot name="inputer"></slot> </view> <view v-if="errorMessage" class="messager">{{errorMessage}}</view> <tui-drawer :visible="visibled" mode="bottom" @close="onPopupToggle"> <view class="header"> <text class="cancel" @tap="onPopupToggle">勾销</text> <text class="confirm" @tap="onConfirm">确认</text> </view> <view class="tab-wrapper"> <template v-for="(lab,idx) in labels"> <label v-if="idx!==labelIndex" :key="idx" @tap="onLabelChange({index:idx})"> {{lab}} </label> <template v-else> <label class="active"> {{lab}} </label> <iconfont class="indicator" name="arrow-down" /> </template> </template> </view> <tui-loadmore v-if="loading" :index="3" type="primary" text="加载中..." /> <view v-else class="region-view"> <template v-for="(n,idx) in regions"> <label v-if="idx !== selectorIndexs[labelIndex]" @tap="onSelector(idx)" :key="idx">{{n}}</label> <label v-else :key="idx"> <span class="selected">{{n}}</span> </label> </template> </view> <view v-if="errorTips" class="error-tips"> {{errorTips}} </view> </tui-drawer> </view></template><script> import utils from "../utils/utils.js"; import regionApi from "../apis/region.js"; export default { name: 'regionPicker', props: { /** * 选择器区级 * 0-省 * 1-地市 * 2-县区 * 3-乡镇 */ selectorLevel: { type: Number, default: 1, validator(val) { return [0, 1, 2, 3].some(x => x === val); } }, /** * 以后抉择值 */ value: { type: Array, default: null }, /** * 没有值时的占位符 */ placeholder: { type: String, default: '请抉择地区' }, /** * 表单验证谬误提醒音讯 */ errorMessage: { type: String, default: null }, /** * 启用自定义输入框 */ visibleInputer: { type: Boolean, default: false } }, watch: { selectorLevel(val) { this.$emit('input', null); this.initialize(); }, value(val) { this.initialize(); } }, data() { return { visibled: false, loading: false, labels: ['请抉择'], labelIndex: 0, regions: [], selectorIndexs: [], selectorNodes: [], errorTips: null }; }, computed: { selectorPath() { let nodes = this.selectorNodes; if (!nodes || nodes.length < 1) return null; let paths = nodes.map(x => x.name); let path = paths.join(' / '); return path; } }, mounted() { const self = this; regionApi.getNodes({ params: { endCategory: 1 }, loading: false, onLoading(ld) { self.loading = ld; }, showError: true, callback(fkb) { if (!fkb.success) return; let nodes = fkb.result; self.__rawRegions = nodes; if (!self.value || self.value.length < 1) self.bindViews(nodes); else self.initialize(); } }); }, methods: { /** * 初始化选择器 */ initialize() { //初始化数据没有执行实现 if (!this.__rawRegions) return; this.labels = ['请抉择']; this.labelIndex = 0; this.selectorIndexs = []; this.selectorNodes = []; this.bindViews(this.__rawRegions); //设定初始值 let values = this.value; if (!values || values.length < 1) return; const self = this; let prevs = this.__rawRegions; let setValue = function(idx) { let nd = values[idx]; let about = false; let exists = prevs.some((x, i) => { if (nd.name !== x.name && nd.code !== x.code) return false; prevs = x.children || prevs; //如果还有上级,但又未加载子节点,则先加载再来设定 if (!x.children && idx + 1 < values.length) { self.getNextRegions(x, () => { setValue(idx); }); about = true; return true; } self.selectorNodes.push({ category: x.category, code: x.code, name: x.name }); self.onSelector(i); return true; }); if (about) return; if (exists && idx + 1 < values.length) setValue(idx + 1); }; setValue(0); }, /** * 将待选节点绑定至待选视图 * * @param {Array} nodes 要绑定的原始节点 */ bindViews(nodes) { this.regions = nodes.map(x => x.name); }, /** * 获取上级节点 * * @param {Object} prevNode 下级选中的节点 * @param {function} cb 加载实现后回调 */ getNextRegions(prevNode, cb) { const self = this; regionApi.getChildren({ params: { category: prevNode.category + 1, prevCode: prevNode.code }, loading: false, onLoading(ld) { self.loading = ld; }, showError: true, callback(fkb) { if (!fkb.success) return; prevNode.children = fkb.result; if (!cb) self.bindViews(fkb.result); else cb(); } }); }, /** * 获取指定列抉择的节点 * * @param {Object} level 地区级别0-3 */ getSelectorNode(level) { let prevs = this.__rawRegions; for (let i = 0; i < level; i++) { let sidx = this.selectorIndexs[i]; if (!sidx) return null; prevs = prevs[sidx].children; if (!prevs) return null; } let cval = this.selectorIndexs[level]; let node = prevs[cval]; return node; }, /** * 切下至下一级区域抉择 * * @param {Object} current 以后选中级别0-3 */ moveNextLevel(current) { let node = this.getSelectorNode(current); if (node == null) return; if (node.children) this.bindViews(node.children); else this.getNextRegions(node); }, onPopupToggle(e) { this.visibled = !this.visibled; }, onConfirm(e) { if (this.selectorLevel + 1 > this.selectorIndexs.length) { this.errorTips = '*请将地区抉择残缺。'; return; } let nodes = []; for (let i = 0; i < this.selectorIndexs.length; i++) { let node = this.getSelectorNode(i); nodes.push({ category: node.category, code: node.code, name: node.name }); } this.selectorNodes = nodes; this.onPopupToggle(); this.$emit('input', nodes); this.$emit('change', nodes); }, onLabelChange(e) { //加载中,禁止切换 if (this.loading) return; let idx = e.index; this.labelIndex = idx; if (idx > 0) this.moveNextLevel(idx - 1); else this.bindViews(this.__rawRegions); }, onSelector(idx) { this.errorTips = null; let labIdx = this.labelIndex; //因为uni 对于数组的值监听不欠缺,只有复制数组更新才失效 let labs = utils.clone(this.labels); labs[labIdx] = this.regions[idx]; this.labels = labs; //起因上同 let idexs = utils.clone(this.selectorIndexs); if (idexs.length <= labIdx) idexs.push(idx); else idexs[labIdx] = idx; this.selectorIndexs = idexs; //有上级,全清空 if (labIdx >= this.selectorLevel) return; this.selectorIndexs.splice(labIdx + 1, 4); //最大只有4级 this.labels.splice(labIdx + 1, 4); //最大只有4级 this.labels.push('请抉择'); this.labelIndex = labIdx + 1; this.moveNextLevel(labIdx); } } }</script><style lang="scss"> .region-picker { .header { width: 100%; box-sizing: border-box; margin: 7.2463rpx 0; line-height: $uni-font-size-base+ 7.2463rpx; .cancel { padding: 0 18.1159rpx; float: left; //color: $uni-text-color-grey; } .confirm { padding: 0 18.1159rpx; float: right; color: $uni-color-primary; } text:hover { background-color: $uni-bg-color-hover; } } .tab-wrapper { width: 100%; margin-bottom: 28.9855rpx; display: flex; justify-content: center; box-sizing: border-box; label { margin: 7.2463rpx 28.9855rpx; padding: 7.2463rpx 0; color: $uni-text-color; border-bottom: solid 3.6231rpx transparent; } .active { color: $uni-color-primary; border-color: $uni-color-primary; } .indicator { margin-left: -10px; margin-top: 6px; color: $uni-color-primary; } } .region-view { width: 100%; display: flex; flex-wrap: wrap; padding: 7.2463rpx 14.4927rpx 28.9855rpx 14.4927rpx; box-sizing: border-box; label { margin: 7.2463rpx 0; width: 33%; text-align: center; color: $uni-text-color-grey; text-overflow: ellipsis; overflow: hidden; } .selected { padding: 3.6231rpx 14.4927rpx; background-color: $uni-color-light-primary; color: #FFF; border-radius: 10.8695rpx; } } .error-tips { width: 100%; height: auto; padding-bottom: 21.7391rpx; text-align: center; color: $uni-color-error; font-size: $uni-font-size-sm; } }</style>
行政区化节点数据,起源国家统计局,到县区级。
https://files.cnblogs.com/fil...
最终成果