小程序picker地区级联选择的问题及解决方案 - 新闻资讯 - 云南小程序开发|云南软件开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 小程序相关 >

小程序picker地区级联选择的问题及解决方案

发表时间:2021-4-30

发布人:葵宇科技

浏览次数:86

一、造轮子的原因

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="https://www.wxapp-union.com/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 = https://www.wxapp-union.com/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...

最终效果

相关案例查看更多