各种零碎中行政区域抉择的场景不少,咱们也有不少这样的场景。本想应用第三方的组件,然而大多有些小问题,不能满足需要。前面应用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...

最终成果