Files
yxe-mp/components/AreaPicker.vue
T
2026-09-29 15:17:13 +08:00

251 lines
5.8 KiB
Vue

<template>
<u-popup :show="show" mode="bottom" round="24rpx" close-on-click-overlay @close="handleClose">
<view class="area-picker">
<view class="area-picker-head">
<text class="area-picker-title">选择省市</text>
<view class="area-picker-actions">
<view class="area-picker-btn" @tap="handleClose">取消</view>
<view class="area-picker-btn primary" @tap="confirmSelection">确定</view>
</view>
</view>
<view class="area-picker-body">
<view v-if="loading" class="area-loading">加载中...</view>
<view v-else class="area-columns">
<view class="area-column">
<scroll-view scroll-y class="area-scroll">
<view
v-for="(item, index) in provinces"
:key="index"
:class="['area-item', index === provinceIndex ? 'active' : '']"
@tap="selectProvince(index)"
>
{{ item.name }}
</view>
</scroll-view>
</view>
<view class="area-column">
<scroll-view scroll-y class="area-scroll">
<view v-if="cities.length === 0" class="area-empty">暂无城市</view>
<view
v-for="(item, index) in cities"
:key="index"
:class="['area-item', index === cityIndex ? 'active' : '']"
@tap="selectCity(index)"
>
{{ item.name }}
</view>
</scroll-view>
</view>
</view>
</view>
</view>
</u-popup>
</template>
<script>
import {areaListReq, areaListByFidReq} from "@/api/common";
export default {
name: 'AreaPicker',
props: {
show: {
type: Boolean,
default: false
},
defaultProvince: {
type: String,
default: ''
},
defaultCity: {
type: String,
default: ''
}
},
data() {
return {
loading: false,
provinces: [],
cities: [],
provinceIndex: 0,
cityIndex: 0
}
},
watch: {
show(val) {
if (val) {
this.initAreas()
}
}
},
methods: {
handleClose() {
this.$emit('close')
},
normalizeAreaItem(item) {
return {
id: item.areaId || item.code || item.value || item.id || null,
name: item.name || item.title || item.areaName || item.label || ''
}
},
async fetchProvinces() {
const {data} = await areaListReq({level: 1})
const list = Array.isArray(data) ? data : (data && data.list ? data.list : [])
return list.map(this.normalizeAreaItem)
},
async fetchCities(fid) {
const {data} = await areaListByFidReq({fid})
const list = Array.isArray(data) ? data : (data && data.list ? data.list : [])
return list.map(this.normalizeAreaItem)
},
findIndexByName(list, name) {
if (!name) return -1
return list.findIndex(item => item.name === name)
},
async initAreas() {
this.loading = true
try {
if (!this.provinces.length) {
this.provinces = await this.fetchProvinces()
}
let provinceIndex = this.findIndexByName(this.provinces, this.defaultProvince)
if (provinceIndex < 0) provinceIndex = 0
this.provinceIndex = provinceIndex
const province = this.provinces[provinceIndex]
this.cities = province ? await this.fetchCities(province.id) : []
let cityIndex = this.findIndexByName(this.cities, this.defaultCity)
if (cityIndex < 0) cityIndex = 0
this.cityIndex = cityIndex
} catch (err) {
uni.showToast({
title: '地区加载失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
async selectProvince(index) {
if (index === this.provinceIndex) return
this.provinceIndex = index
this.cityIndex = 0
const province = this.provinces[index]
if (!province) {
this.cities = []
return
}
this.loading = true
try {
this.cities = await this.fetchCities(province.id)
this.cityIndex = 0
} finally {
this.loading = false
}
},
selectCity(index) {
this.cityIndex = index
},
confirmSelection() {
const province = this.provinces[this.provinceIndex]
const city = this.cities[this.cityIndex]
if (!province || !city) {
uni.showToast({
title: '请选择省市',
icon: 'none'
})
return
}
this.$emit('confirm', {
province,
city,
provinceId: province.id,
cityId: city.id,
provinceName: province.name,
cityName: city.name
})
}
}
}
</script>
<style lang="scss" scoped>
.area-picker {
background: #ffffff;
padding: 20rpx 20rpx 30rpx;
border-radius: 24rpx 24rpx 0 0;
}
.area-picker-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.area-picker-title {
font-size: 28rpx;
font-weight: 700;
color: #1f2937;
}
.area-picker-actions {
display: flex;
align-items: center;
gap: 16rpx;
}
.area-picker-btn {
font-size: 24rpx;
color: #6b7280;
}
.area-picker-btn.primary {
color: #2fc6a7;
font-weight: 700;
}
.area-picker-body {
margin-top: 16rpx;
}
.area-loading {
padding: 24rpx 0;
text-align: center;
font-size: 24rpx;
color: #9ca3af;
}
.area-columns {
display: flex;
gap: 16rpx;
}
.area-column {
flex: 1;
border-radius: 16rpx;
background: #f8fafc;
overflow: hidden;
}
.area-scroll {
height: 60vh;
}
.area-item {
padding: 16rpx 18rpx;
font-size: 26rpx;
color: #374151;
}
.area-item.active {
color: #2fc6a7;
font-weight: 700;
background: #e6fffa;
}
.area-empty {
padding: 18rpx;
font-size: 24rpx;
color: #9ca3af;
text-align: center;
}
</style>