|
|
@@ -0,0 +1,266 @@
|
|
|
+<template>
|
|
|
+ <view class="region-picker" @click="showPicker">
|
|
|
+ <view class="picker-display">
|
|
|
+ <text :class="['region-text', selectedText ? '' : 'placeholder']">
|
|
|
+ {{ selectedText || placeholder }}
|
|
|
+ </text>
|
|
|
+ <u-icon class="arrow" name='arrow-right' size="18"></u-icon>
|
|
|
+ </view>
|
|
|
+ <picker mode="multiSelector" :range="range" range-key="name" :value="indexes" @change="onPickerChange"
|
|
|
+ @columnchange="onColumnChange" :disabled="disabled">
|
|
|
+ <view class="picker-mask"></view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+ import {
|
|
|
+ ref,
|
|
|
+ computed,
|
|
|
+ watch,
|
|
|
+ nextTick,
|
|
|
+ onMounted
|
|
|
+ } from 'vue'
|
|
|
+ import areaData from '@/utils/area-data.js'
|
|
|
+
|
|
|
+ const props = defineProps({
|
|
|
+ province: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ city: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ district: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ placeholder: {
|
|
|
+ type: String,
|
|
|
+ default: '省市区'
|
|
|
+ },
|
|
|
+ disabled: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const emit = defineEmits(['change', 'update:province', 'update:city', 'update:district'])
|
|
|
+
|
|
|
+ // 省市区列表
|
|
|
+ const provinces = ref([])
|
|
|
+ const cities = ref([])
|
|
|
+ const districts = ref([])
|
|
|
+
|
|
|
+ // 选中索引
|
|
|
+ const provinceIndex = ref(0)
|
|
|
+ const cityIndex = ref(0)
|
|
|
+ const districtIndex = ref(0)
|
|
|
+
|
|
|
+ // picker range
|
|
|
+ const range = computed(() => [provinces.value, cities.value, districts.value])
|
|
|
+ const indexes = computed(() => [provinceIndex.value, cityIndex.value, districtIndex.value])
|
|
|
+
|
|
|
+ // 显示文本
|
|
|
+ const selectedText = computed(() => {
|
|
|
+ if (provinceIndex.value >= 0 && provinces.value[provinceIndex.value]) {
|
|
|
+ const p = provinces.value[provinceIndex.value]?.name || ''
|
|
|
+ const c = cities.value[cityIndex.value]?.name || ''
|
|
|
+ const d = districts.value[districtIndex.value]?.name || ''
|
|
|
+ return `${p} ${c} ${d}`.trim()
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+ })
|
|
|
+
|
|
|
+ // 初始化省份数据
|
|
|
+ function initProvinces() {
|
|
|
+ provinces.value = areaData.filter(item => item.type === 0).map(item => ({
|
|
|
+ code: item.code,
|
|
|
+ name: item.name
|
|
|
+ }))
|
|
|
+ if (provinces.value.length > 0) {
|
|
|
+ provinceIndex.value = 0
|
|
|
+ // 默认加载第一个省份的城市
|
|
|
+ updateCities(provinces.value[0].code)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据省份代码更新城市列表
|
|
|
+ function updateCities(provinceCode) {
|
|
|
+ cities.value = areaData.filter(item => item.parent_code === provinceCode && item.type === 1).map(item => ({
|
|
|
+ code: item.code,
|
|
|
+ name: item.name
|
|
|
+ }))
|
|
|
+ if (cities.value.length > 0) {
|
|
|
+ cityIndex.value = 0
|
|
|
+ updateDistricts(cities.value[0].code)
|
|
|
+ } else {
|
|
|
+ cities.value = []
|
|
|
+ districts.value = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据城市代码更新区县列表
|
|
|
+ function updateDistricts(cityCode) {
|
|
|
+ districts.value = areaData.filter(item => item.parent_code === cityCode && item.type === 2).map(item => ({
|
|
|
+ code: item.code,
|
|
|
+ name: item.name
|
|
|
+ }))
|
|
|
+ if (districts.value.length > 0) {
|
|
|
+ districtIndex.value = 0
|
|
|
+ } else {
|
|
|
+ districts.value = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据省市区名称设置索引(用于回显)
|
|
|
+ function setIndexesByNames(provinceName, cityName, districtName) {
|
|
|
+ if (!provinceName || provinces.value.length === 0) return
|
|
|
+
|
|
|
+ // 查找省份
|
|
|
+ const provinceItem = provinces.value.find(p => p.name === provinceName)
|
|
|
+ if (!provinceItem) return
|
|
|
+
|
|
|
+ provinceIndex.value = provinces.value.indexOf(provinceItem)
|
|
|
+
|
|
|
+ // 更新城市列表(同步执行)
|
|
|
+ updateCities(provinceItem.code)
|
|
|
+
|
|
|
+ // 等待城市列表更新后(由于 updateCities 是同步,但 cities 已更新),设置城市索引
|
|
|
+ if (cityName) {
|
|
|
+ const cityItem = cities.value.find(c => c.name === cityName)
|
|
|
+ if (cityItem) {
|
|
|
+ cityIndex.value = cities.value.indexOf(cityItem)
|
|
|
+ // 更新区县列表
|
|
|
+ updateDistricts(cityItem.code)
|
|
|
+ // 等待区县列表更新后设置区县索引
|
|
|
+ if (districtName) {
|
|
|
+ const districtItem = districts.value.find(d => d.name === districtName)
|
|
|
+ if (districtItem) {
|
|
|
+ districtIndex.value = districts.value.indexOf(districtItem)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 列变化
|
|
|
+ function onColumnChange(e) {
|
|
|
+ const {
|
|
|
+ column,
|
|
|
+ value
|
|
|
+ } = e.detail
|
|
|
+ if (column === 0) {
|
|
|
+ provinceIndex.value = value
|
|
|
+ const provinceCode = provinces.value[value].code
|
|
|
+ updateCities(provinceCode)
|
|
|
+ } else if (column === 1) {
|
|
|
+ cityIndex.value = value
|
|
|
+ const cityCode = cities.value[value].code
|
|
|
+ updateDistricts(cityCode)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认选择
|
|
|
+ function onPickerChange(e) {
|
|
|
+ const values = e.detail.value
|
|
|
+ provinceIndex.value = values[0]
|
|
|
+ cityIndex.value = values[1]
|
|
|
+ districtIndex.value = values[2]
|
|
|
+
|
|
|
+ const province = provinces.value[provinceIndex.value]
|
|
|
+ const city = cities.value[cityIndex.value]
|
|
|
+ const district = districts.value[districtIndex.value]
|
|
|
+
|
|
|
+ emit('update:province', province?.name || '')
|
|
|
+ emit('update:city', city?.name || '')
|
|
|
+ emit('update:district', district?.name || '')
|
|
|
+ emit('change', {
|
|
|
+ province: province?.name || '',
|
|
|
+ city: city?.name || '',
|
|
|
+ district: district?.name || '',
|
|
|
+ provinceCode: province?.code || '',
|
|
|
+ cityCode: city?.code || '',
|
|
|
+ districtCode: district?.code || ''
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ function showPicker() {}
|
|
|
+
|
|
|
+ // 监听外部传入的省市区名称变化(用于编辑回显)
|
|
|
+ watch(
|
|
|
+ () => [props.province, props.city, props.district],
|
|
|
+ ([newProvince, newCity, newDistrict]) => {
|
|
|
+ if (newProvince && provinces.value.length > 0) {
|
|
|
+ setIndexesByNames(newProvince, newCity, newDistrict)
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ immediate: true,
|
|
|
+ deep: true
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ initProvinces()
|
|
|
+ })
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+
|
|
|
+ .region-picker {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100rpx;
|
|
|
+ /* 与 input 高度一致 */
|
|
|
+ }
|
|
|
+
|
|
|
+ .picker-display {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: 100%;
|
|
|
+ /* 与输入框背景一致 */
|
|
|
+ border-radius: 8rpx;
|
|
|
+ /* 圆角一致 */
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+
|
|
|
+ .region-text {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 28rpx;
|
|
|
+ line-height: 100rpx;
|
|
|
+ color: #333;
|
|
|
+
|
|
|
+ &.placeholder {
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .arrow {
|
|
|
+ margin-left: 16rpx;
|
|
|
+ color: #999;
|
|
|
+ font-size: 28rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .region-text {
|
|
|
+ flex: 1;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+
|
|
|
+ .region-text.placeholder {
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .picker-mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+</style>
|