CNCEC_APP/uni_modules/uview-pro/components/u-city-select/u-city-select.vue

277 lines
9.1 KiB
Vue
Raw Normal View History

2026-03-25 14:54:15 +08:00
<template>
<u-popup
v-model="popupValue"
:class="customClass"
:style="$u.toStyle(customStyle)"
mode="bottom"
:popup="false"
:mask="true"
:closeable="true"
:safe-area-inset-bottom="true"
close-icon-color="var(--u-white-color)"
:z-index="uZIndex"
:maskCloseAble="props.maskCloseAble"
@close="close"
>
<u-tabs
v-if="popupValue"
:list="genTabsList"
:is-scroll="true"
:current="tabsIndex"
@change="tabsChange"
ref="tabs"
></u-tabs>
<view class="area-box">
<view class="u-flex" :class="{ change: isChange }">
<!-- -->
<view class="area-item">
<view class="u-padding-10 u-bg-gray" style="height: 100%">
<scroll-view :scroll-y="true" style="height: 100%">
<u-cell-group>
<u-cell-item
v-for="(item, index) in provincesList"
:title="item.label"
:arrow="false"
:index="index"
:key="index"
@click="provinceChange(index)"
>
<template v-if="isChooseP && province === index" #right-icon>
<u-icon size="34" name="checkbox-mark"></u-icon>
</template>
</u-cell-item>
</u-cell-group>
</scroll-view>
</view>
</view>
<!-- -->
<view class="area-item">
<view class="u-padding-10 u-bg-gray" style="height: 100%">
<scroll-view :scroll-y="true" style="height: 100%">
<u-cell-group v-if="isChooseP">
<u-cell-item
v-for="(item, index) in citys"
:title="item.label"
:arrow="false"
:index="index"
:key="index"
@click="cityChange(index)"
>
<template v-if="isChooseC && city === index" #right-icon>
<u-icon size="34" name="checkbox-mark"></u-icon>
</template>
</u-cell-item>
</u-cell-group>
</scroll-view>
</view>
</view>
<!-- -->
<view class="area-item">
<view class="u-padding-10 u-bg-gray" style="height: 100%">
<scroll-view :scroll-y="true" style="height: 100%">
<u-cell-group v-if="isChooseC">
<u-cell-item
v-for="(item, index) in areas"
:title="item.label"
:arrow="false"
:index="index"
:key="index"
@click="areaChange(index)"
>
<template v-if="isChooseA && area === index" #right-icon>
<u-icon size="34" name="checkbox-mark"></u-icon>
</template>
</u-cell-item>
</u-cell-group>
</scroll-view>
</view>
</view>
</view>
</view>
</u-popup>
</template>
<script lang="ts">
export default {
name: 'u-city-select',
options: {
addGlobalClass: true,
// #ifndef MP-TOUTIAO
virtualHost: true,
// #endif
styleIsolation: 'shared'
}
};
</script>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import provinces from '../../libs/util/province';
import citysData from '../../libs/util/city';
import areasData from '../../libs/util/area';
import { CitySelectProps } from './types';
import { $u } from '../..';
/**
* u-city-select 城市选择器
* @description 用于选择省区三级行政区域支持回显和自定义初始值
* @property {Boolean} modelValue 控制弹窗显示与隐藏v-model
* @property {Array} defaultRegion 默认选中的省市区名称数组 ['山东省', '青岛市', '崂山区']
* @property {Array} areaCode 默认选中的省市区编码数组 ['440000', '440100', '440106']
* @property {Boolean} maskCloseAble 是否允许点击遮罩关闭弹窗默认 true
* @property {String|Number} zIndex 弹窗层级默认 0自动适配
* @event update:modelValue v-model 绑定值变化时触发
* @event city-change 选择省市区后触发返回选中的省市区对象
* @example <u-city-select v-model="show" :defaultRegion="['山东省', '青岛市', '崂山区']" @city-change="onChange"></u-city-select>
*/
const props = defineProps(CitySelectProps);
// emits 定义
const emit = defineEmits(['update:modelValue', 'city-change']);
// 省市区选择相关响应式数据
const cityValue = ref('');
const isChooseP = ref(false); // 是否已选省
const province = ref(0); // 省下标
const provincesList = ref(provinces);
const isChooseC = ref(false); // 是否已选市
const city = ref(0); // 市下标
const citys = ref(citysData[0]);
const isChooseA = ref(false); // 是否已选区
const area = ref(0); // 区下标
const areas = ref(areasData[0][0]);
const tabsIndex = ref(0);
// v-model 双向绑定
const popupValue = computed({
get: () => props.modelValue,
set: val => emit('update:modelValue', val)
});
// 是否切换到区级
const isChange = computed(() => tabsIndex.value > 1);
// 顶部 tab 列表
const genTabsList = computed(() => {
const tabsList = [{ name: '请选择' }];
if (isChooseP.value) {
tabsList[0].name = provincesList.value[province.value].label;
tabsList[1] = { name: '请选择' };
}
if (isChooseC.value) {
tabsList[1].name = citys.value[city.value].label;
tabsList[2] = { name: '请选择' };
}
if (isChooseA.value) {
tabsList[2].name = areas.value[area.value].label;
}
return tabsList;
});
// z-index 计算
const uZIndex = computed(() => (props.zIndex ? props.zIndex : (uni as any).$u?.zIndex?.popup || 1075));
// 初始化选中项
function init() {
if (props.areaCode.length === 3) {
setProvince('', props.areaCode[0]);
setCity('', props.areaCode[1]);
setArea('', props.areaCode[2]);
} else if (props.defaultRegion.length === 3) {
setProvince(props.defaultRegion[0], '');
setCity(props.defaultRegion[1], '');
setArea(props.defaultRegion[2], '');
}
}
// 选中省
function setProvince(label = '', value = '') {
provincesList.value.forEach((v, k) => {
if (value ? v.value == value : v.label == label) {
provinceChange(k);
}
});
}
// 选中市
function setCity(label = '', value = '') {
citys.value.forEach((v, k) => {
if (value ? v.value == value : v.label == label) {
cityChange(k);
}
});
}
// 选中区
function setArea(label = '', value = '') {
areas.value.forEach((v, k) => {
if (value ? v.value == value : v.label == label) {
isChooseA.value = true;
area.value = k;
}
});
}
// 关闭弹窗
function close() {
emit('update:modelValue', false);
}
// tab 切换
function tabsChange(index: number) {
tabsIndex.value = index;
}
// 省点击
function provinceChange(index: number) {
isChooseP.value = true;
isChooseC.value = false;
isChooseA.value = false;
province.value = index;
citys.value = citysData[index];
tabsIndex.value = 1;
}
// 市点击
function cityChange(index: number) {
isChooseC.value = true;
isChooseA.value = false;
city.value = index;
areas.value = areasData[province.value][index];
tabsIndex.value = 2;
}
// 区点击
function areaChange(index: number) {
isChooseA.value = true;
area.value = index;
const result = {
province: provincesList.value[province.value],
city: citys.value[city.value],
area: areas.value[area.value]
};
emit('city-change', result);
close();
}
// 挂载时初始化
onMounted(init);
</script>
<style lang="scss">
.area-box {
width: 100%;
overflow: hidden;
height: 800rpx;
> view {
width: 150%;
transition: transform 0.3s ease-in-out 0s;
transform: translateX(0);
&.change {
transform: translateX(-33.3333333%);
}
}
.area-item {
width: 33.3333333%;
height: 800rpx;
}
}
</style>