uniapp省市区选择

104 阅读3分钟

ns-picker选择器

<template>
    <view>
        <view class="picker-modal over-max-width" v-if="showPicker"></view>
        <view class="picker-content over-max-width"
            :style="{ transform: showPicker ? 'translateY(0)' : 'translateY(100%)' }">
            <view class="picker-header flex-justify">
                <view class="text-other" @click="cancel">取消</view>
                <view class="title">{{ title }}</view>
                <view class="confirm" @click="confirm">确定</view>
            </view>
            <picker-view mask-style="background:transparent" indicator-style="height: 90rpx;" class="picker-view"
                :value="pickerValue" @change="changePicker" @pickstart="pickstart" @pickend="pickend"
                v-if="'selector' === mode">
                <picker-view-column>
                    <view class="picker-item" :class="pickerValue[0] === index ? 'text-current' : 'text-other'"
                        v-for="(item, index) in pickerList" :key="index">
                        {{ item.label }}
                    </view>
                </picker-view-column>
            </picker-view>
            <picker-view mask-style="background:transparent" indicator-style="height: 90rpx;" class="picker-view"
                :value="pickerValue" @change="changePicker" @pickstart="pickstart" @pickend="pickend"
                v-if="'date' === mode">
                <picker-view-column>
                    <view class="picker-item" :class="pickerValue[0] === index ? 'text-current' : 'text-other'"
                        v-for="(item, index) in years" :key="index">{{ item }}年</view>
                </picker-view-column>
                <picker-view-column>
                    <view class="picker-item" :class="pickerValue[1] === index ? 'text-current' : 'text-other'"
                        v-for="(item, index) in months" :key="index">{{ item }}月</view>
                </picker-view-column>
                <picker-view-column v-if="'days' === fields">
                    <view class="picker-item" :class="pickerValue[2] === index ? 'text-current' : 'text-other'"
                        v-for="(item, index) in days" :key="index">{{ item }}日</view>
                </picker-view-column>
            </picker-view>
            <picker-view mask-style="background:transparent" indicator-style="height: 90rpx;" class="picker-view"
                :value="pickerValue" @change="changePicker" @pickstart="pickstart" @pickend="pickend"
                v-if="'multiSelector' === mode">
                <picker-view-column v-for="(list,index) in pickerList" :key="index">
                    <view class="picker-item" :class="pickerValue[index] === index1 ? 'text-current' : 'text-other'"
                        v-for="(item, index1) in list" :key="index1">{{ item }}</view>
                </picker-view-column>
            </picker-view>
        </view>
    </view>
</template>

<script>
    export default {
        props: {
            title: {
                type: String,
                default: ''
            },
            pickerList: {
                type: Array
            },
            mode: {
                type: String,
                default: 'selector'
            },
            fields: {
                type: String,
                default: 'month'
            },
            value: {
                type: Array
            },
            type: {
                type: String,
                default: 'all'
            }
        },
        watch: {
            pickerList() {},
            value() {
                this.pickerValue = this.value
            }
        },
        data() {
            let date = new Date();
            if ('current' === this.type) {
                let now = new Date();
                // 5天后
                date = new Date(now.getTime() + 5 * 24 * 3600 * 1000);
            }
            const years = [];
            const year = date.getFullYear();
            const months = [];
            const month = date.getMonth() + 1;
            const days = [];
            const day = date.getDate();
            let pickerValue = 'multiSelector' === this.mode ? this.value : ('date' === this.mode ? [9999, month - 1,
                day - 1
            ] : [0]);
            if ('current' === this.type) {
                years.push(year)
                for (let i = 1; i <= 12; i++) {
                    if (i >= month) {
                        months.push(i < 10 ? ('0' + i) : i);
                    }
                }
                pickerValue = 'multiSelector' === this.mode ? this.value : ('date' === this.mode ? [0, 0, 0] : [
                    0
                ]);
            } else {
                for (let i = 1990; i <= date.getFullYear(); i++) {
                    years.push(i);
                }
                for (let i = 1; i <= 12; i++) {
                    months.push(i < 10 ? ('0' + i) : i);
                }
            }
            for (let i = 1; i <= 31; i++) {
                days.push(i < 10 ? ('0' + i) : i);
            }
            return {
                day: day,
                month: month,
                alldays: days,
                years: years,
                months: months,
                pickerValue: pickerValue,
                days: [],
                isMoving: false,
                showPicker: false
            };
        },
        options: {
            styleIsolation: 'shared'
        },
        methods: {
            cancel() {
                this.showPicker = false;
            },
            open() {
                this.showPicker = true;
                this.daysChange()
            },
            pickstart(e) {
                this.isMoving = true;
            },
            pickend() {
                this.isMoving = false;
            },
            confirm() {
                if (this.isMoving) {
                    uni.$showMsg('滚动选择中,请稍后确定');
                    return;
                }
                if ('selector' === this.mode) {
                    this.$emit('onConfirm', this.pickerValue[0]);
                }
                if ('date' === this.mode) {
                    if ('month' === this.fields) {
                        this.$emit('onConfirm', this.years[this.pickerValue[0]] + '-' + this.months[this.pickerValue[
                            1]]);
                    } else {
                        this.$emit('onConfirm', this.years[this.pickerValue[0]] + '-' + this.months[this.pickerValue[
                            1]] + '-' + this.days[this.pickerValue[2]]);
                    }
                }
                if ('multiSelector' === this.mode) {
                    this.$emit('onConfirm', this.pickerValue)
                }
                this.cancel();
            },
            daysChange() {
                /// 各月份的天数处理
                const thirtys_plus = [1, 3, 5, 7, 8, 10, 12]
                if (thirtys_plus.includes(Number(this.months[this.pickerValue[1]]))) {
                    this.days = this.alldays
                } else if (2 === Number(this.months[this.pickerValue[1]])) {
                    this.days = this.alldays.filter((item, index) => index < 28)
                } else {
                    this.days = this.alldays.filter((item, index) => index < 30)
                }
                // 当前月份不能选当天之前的日期
                if ('current' === this.type && this.month === Number(this.months[this.pickerValue[1]])) {
                    this.days = this.days.filter((item, index) => index >= this.day - 1)
                } else {
                    this.days = this.days
                }
            },
            changePicker(e) {
                const oldValue = this.pickerValue;
                this.pickerValue = e.detail.value;
                if ('date' === this.mode) {
                    this.daysChange()
                }
                if ('multiSelector' === this.mode) {
                    oldValue.forEach((item, index) => {
                        if (item !== this.pickerValue[index]) {
                            this.$emit('columnChange', {
                                column: index,
                                value: this.pickerValue[index]
                            })
                        }
                    })
                }
            }
        }
    };
</script>

<style lang="scss">
    .picker-modal {
        z-index: 90;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.7);
    }

    .picker-content {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 99;
        transition: all 0.3s;
        border-radius: 30rpx 30rpx 0 0;
        padding: 30rpx 20rpx;
        box-sizing: border-box;
        background: #393A3E;

        .picker-header {
            height: 100rpx;
            line-height: 100rpx;
            position: absolute;
            top: 0;
            left: 40rpx;
            right: 40rpx;

            .confirm {
                color: $color-3;
            }

            .text-other {
                color: $color-4;
            }

            .title {
                font-size: 32rpx;
                font-weight: bold;
            }
        }

        .indecator {
            font-size: 50rpx;
        }

        .picker-view {
            position: relative;
            top: 60rpx;
            left: 0;
            right: 0;
            height: 490rpx;

            .text-current {
                color: $color-3;
            }

            .text-other {
                color: #ACABA5;
            }

            .picker-item {
                line-height: 90rpx;

                // #ifdef MP-WEIXIN
                line-height: 70rpx;
                // #endif

                text-align: center;
                text-overflow: ellipsis;
            }
        }
    }
    
    ::v-deep .uni-picker-view-indicator{
        border-top:1rpx solid #D8D8D8;
        border-bottom:1rpx solid #D8D8D8;
    }
</style>

pick-regions地址选择组件

<template>
    <view class="pick-regions">
        <ns-picker ref="logistics" mode="multiSelector" :value="multiIndex" :pickerList="multiArray"
            @onConfirm="handleValueChange" @columnChange="handleColumnChange"></ns-picker>
    </view>
</template>

<script>
    export default {
        props: {
            defaultRegions: {
                type: Array
            },
            selectArr: {
                type: String
            }
        },
        data() {
            return {
                pickerValueArray: [],
                cityArr: [],
                districtArr: [],
                multiIndex: [0, 0, 0],
                isInitMultiArray: false,
                // 是否加载完默认地区
                isLoadDefaultAreas: false
            };
        },
        watch: {
            defaultRegions: {
                handler(arr, oldArr = []) {
                    // 避免传的是字面量的时候重复触发
                    if (arr.length != this.selectArr || arr.join('') === oldArr.join('')) return;
                    this.handleDefaultRegions();
                },
                immediate: true
            }
        },
        computed: {
            multiArray() {
                if (!this.isLoadDefaultAreas) return;
                var arr = this.pickedArr.map(arr => arr.map(item => item.label));
                return arr;
            },
            pickedArr() {
                // 进行初始化
                if (this.isInitMultiArray) {
                    if (this.selectArr == '2') {
                        return [this.pickerValueArray[0], this.pickerValueArray[1]];
                    } else {
                        return [this.pickerValueArray[0], this.pickerValueArray[1], this.pickerValueArray[2]];
                    }
                }

                if (this.selectArr == '2') {
                    return [this.pickerValueArray[0], this.cityArr];
                } else {
                    return [this.pickerValueArray[0], this.cityArr, this.districtArr];
                }
            }
        },
        created() {
            this.getDefaultAreas(0, {
                type: 1
            });
        },
        methods: {
            open() {
                this.$refs.logistics.open()
            },
            async handleColumnChange(e) {
                this.isInitMultiArray = false;

                let col = e.column;
                let row = e.value;
                this.multiIndex[col] = row;
                switch (col) {
                    case 0:
                        //选择省,加载市、区县
                        this.cityArr = await this.getAreasAsync(this.pickerValueArray[0][this.multiIndex[col]]
                            .value);
                        this.districtArr = await this.getAreasAsync(this.cityArr[0].value);
                        break;
                    case 1:
                        //选择市,加载区县
                        this.districtArr = await this.getAreasAsync(this.cityArr[this.multiIndex[col]].value);
                        break;
                    case 2:
                        if (!this.cityArr.length) this.cityArr = await this.getAreasAsync(this.pickerValueArray[0][
                            0
                        ].value)
                        if (!this.districtArr.length) this.districtArr = await this.getAreasAsync(this.cityArr[0]
                            .value);
                        break;
                }
            },
            handleValueChange(e) {
                // 结构赋值
                let [index0, index1, index2] = e;
                let [arr0, arr1, arr2] = this.pickedArr;
                let address = '';
                if (this.selectArr == '2') {
                    address = [arr0[index0], arr1[index1]];
                } else {
                    address = [arr0[index0], arr1[index1], arr2[index2]];
                }
                this.$emit('getRegions', address);
            },
            handleDefaultRegions() {
                var time = setInterval(() => {
                    if (!this.isLoadDefaultAreas) return;
                    this.isInitMultiArray = false;
                    for (let i = 0; i < this.defaultRegions.length; i++) {
                        for (let j = 0; j < this.pickerValueArray[i].length; j++) {
                            // 匹配省
                            if ((this.defaultRegions[i] == this.pickerValueArray[i][j].value || this
                                    .defaultRegions[i] == this.pickerValueArray[i][j].label) && this
                                .pickerValueArray[i][j].level == 1) {
                                // 设置选中省
                                this.$set(this.multiIndex, i, j);

                                // 查询市
                                this.getAreas(this.pickerValueArray[i][j].value, data => {
                                    this.cityArr = data;

                                    for (let k = 0; k < this.cityArr.length; k++) {
                                        if (this.defaultRegions[1] == this.cityArr[k].value || this
                                            .defaultRegions[1] == this.cityArr[k].label) {
                                            // 设置选中市
                                            this.$set(this.multiIndex, 1, k);

                                            // 查询区县
                                            this.getAreas(this.cityArr[k].value, data => {
                                                this.districtArr = data;

                                                // 设置选中区县
                                                for (let u = 0; u < this.districtArr
                                                    .length; u++) {
                                                    if (this.defaultRegions[2] == this
                                                        .districtArr[u].value || this
                                                        .defaultRegions[2] == this.districtArr[
                                                            u].label) {
                                                        this.$set(this.multiIndex, 2, u);
                                                        this.handleValueChange({
                                                            detail: {
                                                                value: [j, k, u]
                                                            }
                                                        });
                                                        break;
                                                    }
                                                }
                                            });
                                            break;
                                        }
                                    }
                                });
                            }
                        }
                    }
                    if (this.isLoadDefaultAreas) clearInterval(time);
                }, 100);
            },
            getDefaultAreas(pid, obj) {
                this.$api.sendRequest({
                    url: 'xxx',
                    data: {
                        type: obj.type,
                        region_id: pid
                    },
                    success: res => {
                        if (200 === res.code) {
                            var data = [];
                            var selected = undefined;
                            res.data.forEach((item, index) => {
                                if (obj != undefined) {
                                    if (obj.type == 1 && obj.id != undefined) {
                                        selected = obj.id;
                                    } else if (obj.type == 2 && obj.city_id != undefined) {
                                        selected = obj.city_id;
                                    } else if (obj.type == 3 && obj.district_id != undefined) {
                                        selected = obj.district_id;
                                    }
                                }
                                if (selected == undefined && index == 0) {
                                    selected = item.id;
                                }
                                data.push({
                                    value: item.id,
                                    label: item.name,
                                    level: item.level
                                });
                            });
                            this.pickerValueArray[obj.type - 1] = data;
                            if (obj.type + 1 < 4) {
                                obj.type++;
                                this.getDefaultAreas(selected, obj);
                            } else {
                                this.isInitMultiArray = true;
                                this.isLoadDefaultAreas = true;
                            }
                        }
                    }
                });
            },
            // 同步获取地区
            async getAreasAsync(pid) {
                let res = await this.$api.sendRequest({
                    url: 'xxx',
                    data: {
                        type: 2,
                        region_id: pid
                    },
                    async: false
                });
                if (200 === res.code) {
                    var data = [];
                    res.data.forEach((item, index) => {
                        data.push({
                            value: item.id,
                            label: item.name,
                            level: item.level
                        });
                    });
                    return data;
                }
            },
            // 异步获取地区
            getAreas(pid, callback) {
                this.$api.sendRequest({
                    url: 'xxx',
                    data: {
                        type: 3,
                        region_id: pid
                    },
                    success: res => {
                        if (200 === res.code) {
                            var data = [];
                            res.data.forEach((item, index) => {
                                data.push({
                                    value: item.id,
                                    label: item.name,
                                    level: item.level
                                });
                            });
                            if (callback) callback(data);
                        }
                    }
                });
            },
        }
    };
</script>

使用:

<pick-regions ref="picker" :default-regions="defaultRegions" @getRegions="handleGetRegions"></pick-regions>

export default{
    data(){
        return {
            defaultRegions: [] // [province_id, city_id,district_id]
        }
    },
    methods:{
        // 获取选择的地区
            handleGetRegions(regions) {
            },
    }
}

效果图如下:

image.png