|
|
@@ -0,0 +1,272 @@
|
|
|
+import React, { useState, useEffect } from 'react'
|
|
|
+import { View, Text, Picker, Button } from '@tarojs/components'
|
|
|
+import { useQuery } from '@tanstack/react-query'
|
|
|
+import { areaClient } from '../api'
|
|
|
+
|
|
|
+interface AreaInfo {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ type: 'province' | 'city' | 'district'
|
|
|
+}
|
|
|
+
|
|
|
+interface AreaPickerProps {
|
|
|
+ visible: boolean
|
|
|
+ onClose: () => void
|
|
|
+ onConfirm: (areaIds: number[], areaInfos: AreaInfo[]) => void
|
|
|
+ value?: number[]
|
|
|
+ title?: string
|
|
|
+}
|
|
|
+
|
|
|
+export const AreaPicker: React.FC<AreaPickerProps> = ({
|
|
|
+ visible,
|
|
|
+ onClose,
|
|
|
+ onConfirm,
|
|
|
+ value = [],
|
|
|
+ title = '选择地区'
|
|
|
+}) => {
|
|
|
+ const [selectedProvince, setSelectedProvince] = useState<number | undefined>(value[0])
|
|
|
+ const [selectedCity, setSelectedCity] = useState<number | undefined>(value[1])
|
|
|
+ const [selectedDistrict, setSelectedDistrict] = useState<number | undefined>(value[2])
|
|
|
+
|
|
|
+ // 获取省份列表
|
|
|
+ const { data: provincesResponse } = useQuery({
|
|
|
+ queryKey: ['areas', 'provinces'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const res = await areaClient.provinces.$get({
|
|
|
+ query: { page: 1, pageSize: 50 }
|
|
|
+ })
|
|
|
+ if (res.status !== 200) throw new Error('获取省份列表失败')
|
|
|
+ return await res.json()
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 获取城市列表
|
|
|
+ const { data: citiesResponse } = useQuery({
|
|
|
+ queryKey: ['areas', 'cities', selectedProvince],
|
|
|
+ queryFn: async () => {
|
|
|
+ if (!selectedProvince) return { success: true, data: { cities: [] }, message: '' }
|
|
|
+ const res = await areaClient.cities.$get({
|
|
|
+ query: { provinceId: selectedProvince, page: 1, pageSize: 50 }
|
|
|
+ })
|
|
|
+ if (res.status !== 200) throw new Error('获取城市列表失败')
|
|
|
+ return await res.json()
|
|
|
+ },
|
|
|
+ enabled: !!selectedProvince
|
|
|
+ })
|
|
|
+
|
|
|
+ // 获取区县列表
|
|
|
+ const { data: districtsResponse } = useQuery({
|
|
|
+ queryKey: ['areas', 'districts', selectedCity],
|
|
|
+ queryFn: async () => {
|
|
|
+ if (!selectedCity) return { success: true, data: { districts: [] }, message: '' }
|
|
|
+ const res = await areaClient.districts.$get({
|
|
|
+ query: { cityId: selectedCity, page: 1, pageSize: 50 }
|
|
|
+ })
|
|
|
+ if (res.status !== 200) throw new Error('获取区县列表失败')
|
|
|
+ return await res.json()
|
|
|
+ },
|
|
|
+ enabled: !!selectedCity
|
|
|
+ })
|
|
|
+
|
|
|
+ // 提取数据
|
|
|
+ const provinces = provincesResponse?.data?.provinces || []
|
|
|
+ const cities = citiesResponse?.data?.cities || []
|
|
|
+ const districts = districtsResponse?.data?.districts || []
|
|
|
+
|
|
|
+ // 初始化选择值 - 只在组件首次显示时初始化
|
|
|
+ const [hasInitialized, setHasInitialized] = useState(false)
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if (visible && !hasInitialized) {
|
|
|
+ setSelectedProvince(value[0])
|
|
|
+ setSelectedCity(value[1])
|
|
|
+ setSelectedDistrict(value[2])
|
|
|
+ setHasInitialized(true)
|
|
|
+ } else if (!visible) {
|
|
|
+ setHasInitialized(false)
|
|
|
+ }
|
|
|
+ }, [visible, value, hasInitialized])
|
|
|
+
|
|
|
+ // 处理省份选择
|
|
|
+ const handleProvinceChange = (e: any) => {
|
|
|
+ const provinceIndex = Number(e.detail.value)
|
|
|
+ const selectedProvinceObj = provinces[provinceIndex]
|
|
|
+ if (selectedProvinceObj) {
|
|
|
+ setSelectedProvince(selectedProvinceObj.id)
|
|
|
+ setSelectedCity(undefined)
|
|
|
+ setSelectedDistrict(undefined)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理城市选择
|
|
|
+ const handleCityChange = (e: any) => {
|
|
|
+ const cityIndex = Number(e.detail.value)
|
|
|
+ const selectedCityObj = cities[cityIndex]
|
|
|
+ if (selectedCityObj) {
|
|
|
+ setSelectedCity(selectedCityObj.id)
|
|
|
+ setSelectedDistrict(undefined)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理区县选择
|
|
|
+ const handleDistrictChange = (e: any) => {
|
|
|
+ const districtIndex = Number(e.detail.value)
|
|
|
+ const selectedDistrictObj = districts[districtIndex]
|
|
|
+ if (selectedDistrictObj) {
|
|
|
+ setSelectedDistrict(selectedDistrictObj.id)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确认选择
|
|
|
+ const handleConfirm = () => {
|
|
|
+ const areaIds: number[] = []
|
|
|
+ const areaInfos: AreaInfo[] = []
|
|
|
+
|
|
|
+ if (selectedProvince) {
|
|
|
+ const province = provinces.find(p => p.id === selectedProvince)
|
|
|
+ if (province) {
|
|
|
+ areaIds.push(selectedProvince)
|
|
|
+ areaInfos.push({ id: province.id, name: province.name, type: 'province' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedCity) {
|
|
|
+ const city = cities.find(c => c.id === selectedCity)
|
|
|
+ if (city) {
|
|
|
+ areaIds.push(selectedCity)
|
|
|
+ areaInfos.push({ id: city.id, name: city.name, type: 'city' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedDistrict) {
|
|
|
+ const district = districts.find(d => d.id === selectedDistrict)
|
|
|
+ if (district) {
|
|
|
+ areaIds.push(selectedDistrict)
|
|
|
+ areaInfos.push({ id: district.id, name: district.name, type: 'district' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onConfirm(areaIds, areaInfos)
|
|
|
+ onClose()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 取消选择
|
|
|
+ const handleCancel = () => {
|
|
|
+ onClose()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取显示文本
|
|
|
+ const getDisplayText = () => {
|
|
|
+ if (!selectedProvince) return '请选择省市区'
|
|
|
+
|
|
|
+ const province = provinces.find(p => p.id === selectedProvince)
|
|
|
+ const city = cities.find(c => c.id === selectedCity)
|
|
|
+ const district = districts.find(d => d.id === selectedDistrict)
|
|
|
+
|
|
|
+ if (district && city && province) {
|
|
|
+ return `${province.name} ${city.name} ${district.name}`
|
|
|
+ } else if (city && province) {
|
|
|
+ return `${province.name} ${city.name}`
|
|
|
+ } else if (province) {
|
|
|
+ return province.name
|
|
|
+ }
|
|
|
+
|
|
|
+ return '请选择省市区'
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!visible) return null
|
|
|
+
|
|
|
+ return (
|
|
|
+ <View className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
|
+ <View className="bg-white rounded-lg w-4/5 max-w-md">
|
|
|
+ {/* 标题栏 */}
|
|
|
+ <View className="flex justify-between items-center p-4 border-b border-gray-200">
|
|
|
+ <Text className="text-lg font-bold text-gray-800">{title}</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 选择器区域 */}
|
|
|
+ <View className="p-4">
|
|
|
+ {/* 当前选择显示 */}
|
|
|
+ <View className="mb-4 p-3 bg-gray-50 rounded-lg">
|
|
|
+ <Text className="text-sm text-gray-600">已选择:</Text>
|
|
|
+ <Text className="text-sm font-medium text-blue-600">{getDisplayText()}</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 三级选择器 */}
|
|
|
+ <View className="flex space-x-2 mb-4">
|
|
|
+ {/* 省份选择器 */}
|
|
|
+ <View className="flex-1">
|
|
|
+ <Text className="text-sm text-gray-600 mb-1 block">省份</Text>
|
|
|
+ <Picker
|
|
|
+ mode="selector"
|
|
|
+ range={provinces}
|
|
|
+ rangeKey="name"
|
|
|
+ value={selectedProvince ? provinces.findIndex(p => p.id === selectedProvince) : -1}
|
|
|
+ onChange={handleProvinceChange}
|
|
|
+ >
|
|
|
+ <View className="border border-gray-300 rounded px-3 py-2 text-sm bg-white">
|
|
|
+ {selectedProvince ? provinces.find(p => p.id === selectedProvince)?.name : '请选择省份'}
|
|
|
+ </View>
|
|
|
+ </Picker>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 城市选择器 */}
|
|
|
+ <View className="flex-1">
|
|
|
+ <Text className="text-sm text-gray-600 mb-1 block">城市</Text>
|
|
|
+ <Picker
|
|
|
+ mode="selector"
|
|
|
+ range={cities}
|
|
|
+ rangeKey="name"
|
|
|
+ value={selectedCity ? cities.findIndex(c => c.id === selectedCity) : -1}
|
|
|
+ onChange={handleCityChange}
|
|
|
+ disabled={!selectedProvince}
|
|
|
+ >
|
|
|
+ <View className={`border border-gray-300 rounded px-3 py-2 text-sm ${
|
|
|
+ !selectedProvince ? 'bg-gray-100 text-gray-400' : 'bg-white'
|
|
|
+ }`}>
|
|
|
+ {selectedCity ? cities.find(c => c.id === selectedCity)?.name : '请选择城市'}
|
|
|
+ </View>
|
|
|
+ </Picker>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 区县选择器 */}
|
|
|
+ <View className="flex-1">
|
|
|
+ <Text className="text-sm text-gray-600 mb-1 block">区县</Text>
|
|
|
+ <Picker
|
|
|
+ mode="selector"
|
|
|
+ range={districts}
|
|
|
+ rangeKey="name"
|
|
|
+ value={selectedDistrict ? districts.findIndex(d => d.id === selectedDistrict) : -1}
|
|
|
+ onChange={handleDistrictChange}
|
|
|
+ disabled={!selectedCity}
|
|
|
+ >
|
|
|
+ <View className={`border border-gray-300 rounded px-3 py-2 text-sm ${
|
|
|
+ !selectedCity ? 'bg-gray-100 text-gray-400' : 'bg-white'
|
|
|
+ }`}>
|
|
|
+ {selectedDistrict ? districts.find(d => d.id === selectedDistrict)?.name : '请选择区县'}
|
|
|
+ </View>
|
|
|
+ </Picker>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 按钮区域 */}
|
|
|
+ <View className="flex border-t border-gray-200">
|
|
|
+ <Button
|
|
|
+ className="flex-1 py-3 text-gray-600 bg-white border-none rounded-none"
|
|
|
+ onClick={handleCancel}
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <View className="w-px bg-gray-200"></View>
|
|
|
+ <Button
|
|
|
+ className="flex-1 py-3 text-blue-500 bg-white border-none rounded-none font-bold"
|
|
|
+ onClick={handleConfirm}
|
|
|
+ >
|
|
|
+ 确定
|
|
|
+ </Button>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ )
|
|
|
+}
|