251 lines
5.8 KiB
Vue
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>
|