AreaPicker.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import React, { useState, useEffect } from 'react'
  2. import { View, Text, Picker, Button } from '@tarojs/components'
  3. import { useQuery } from '@tanstack/react-query'
  4. import { areaClient } from '../api'
  5. interface AreaInfo {
  6. id: number
  7. name: string
  8. type: 'province' | 'city' | 'district'
  9. }
  10. interface AreaPickerProps {
  11. visible: boolean
  12. onClose: () => void
  13. onConfirm: (areaIds: number[], areaInfos: AreaInfo[]) => void
  14. value?: number[]
  15. title?: string
  16. }
  17. export const AreaPicker: React.FC<AreaPickerProps> = ({
  18. visible,
  19. onClose,
  20. onConfirm,
  21. value = [],
  22. title = '选择地区'
  23. }) => {
  24. const [selectedProvince, setSelectedProvince] = useState<number | undefined>(value[0])
  25. const [selectedCity, setSelectedCity] = useState<number | undefined>(value[1])
  26. const [selectedDistrict, setSelectedDistrict] = useState<number | undefined>(value[2])
  27. // 获取省份列表
  28. const { data: provincesResponse } = useQuery({
  29. queryKey: ['areas', 'provinces'],
  30. queryFn: async () => {
  31. const res = await areaClient.provinces.$get({
  32. query: { page: 1, pageSize: 50 }
  33. })
  34. if (res.status !== 200) throw new Error('获取省份列表失败')
  35. return await res.json()
  36. }
  37. })
  38. // 获取城市列表
  39. const { data: citiesResponse } = useQuery({
  40. queryKey: ['areas', 'cities', selectedProvince],
  41. queryFn: async () => {
  42. if (!selectedProvince) return { success: true, data: { cities: [] }, message: '' }
  43. const res = await areaClient.cities.$get({
  44. query: { provinceId: selectedProvince, page: 1, pageSize: 50 }
  45. })
  46. if (res.status !== 200) throw new Error('获取城市列表失败')
  47. return await res.json()
  48. },
  49. enabled: !!selectedProvince
  50. })
  51. // 获取区县列表
  52. const { data: districtsResponse } = useQuery({
  53. queryKey: ['areas', 'districts', selectedCity],
  54. queryFn: async () => {
  55. if (!selectedCity) return { success: true, data: { districts: [] }, message: '' }
  56. const res = await areaClient.districts.$get({
  57. query: { cityId: selectedCity, page: 1, pageSize: 50 }
  58. })
  59. if (res.status !== 200) throw new Error('获取区县列表失败')
  60. return await res.json()
  61. },
  62. enabled: !!selectedCity
  63. })
  64. // 提取数据
  65. const provinces = provincesResponse?.data?.provinces || []
  66. const cities = citiesResponse?.data?.cities || []
  67. const districts = districtsResponse?.data?.districts || []
  68. // 初始化选择值 - 只在组件首次显示时初始化
  69. const [hasInitialized, setHasInitialized] = useState(false)
  70. useEffect(() => {
  71. if (visible && !hasInitialized) {
  72. setSelectedProvince(value[0])
  73. setSelectedCity(value[1])
  74. setSelectedDistrict(value[2])
  75. setHasInitialized(true)
  76. } else if (!visible) {
  77. setHasInitialized(false)
  78. }
  79. }, [visible, value, hasInitialized])
  80. // 处理省份选择
  81. const handleProvinceChange = (e: any) => {
  82. const provinceIndex = Number(e.detail.value)
  83. const selectedProvinceObj = provinces[provinceIndex]
  84. if (selectedProvinceObj) {
  85. setSelectedProvince(selectedProvinceObj.id)
  86. setSelectedCity(undefined)
  87. setSelectedDistrict(undefined)
  88. }
  89. }
  90. // 处理城市选择
  91. const handleCityChange = (e: any) => {
  92. const cityIndex = Number(e.detail.value)
  93. const selectedCityObj = cities[cityIndex]
  94. if (selectedCityObj) {
  95. setSelectedCity(selectedCityObj.id)
  96. setSelectedDistrict(undefined)
  97. }
  98. }
  99. // 处理区县选择
  100. const handleDistrictChange = (e: any) => {
  101. const districtIndex = Number(e.detail.value)
  102. const selectedDistrictObj = districts[districtIndex]
  103. if (selectedDistrictObj) {
  104. setSelectedDistrict(selectedDistrictObj.id)
  105. }
  106. }
  107. // 确认选择
  108. const handleConfirm = () => {
  109. const areaIds: number[] = []
  110. const areaInfos: AreaInfo[] = []
  111. if (selectedProvince) {
  112. const province = provinces.find(p => p.id === selectedProvince)
  113. if (province) {
  114. areaIds.push(selectedProvince)
  115. areaInfos.push({ id: province.id, name: province.name, type: 'province' })
  116. }
  117. }
  118. if (selectedCity) {
  119. const city = cities.find(c => c.id === selectedCity)
  120. if (city) {
  121. areaIds.push(selectedCity)
  122. areaInfos.push({ id: city.id, name: city.name, type: 'city' })
  123. }
  124. }
  125. if (selectedDistrict) {
  126. const district = districts.find(d => d.id === selectedDistrict)
  127. if (district) {
  128. areaIds.push(selectedDistrict)
  129. areaInfos.push({ id: district.id, name: district.name, type: 'district' })
  130. }
  131. }
  132. onConfirm(areaIds, areaInfos)
  133. onClose()
  134. }
  135. // 取消选择
  136. const handleCancel = () => {
  137. onClose()
  138. }
  139. // 获取显示文本
  140. const getDisplayText = () => {
  141. if (!selectedProvince) return '请选择省市区'
  142. const province = provinces.find(p => p.id === selectedProvince)
  143. const city = cities.find(c => c.id === selectedCity)
  144. const district = districts.find(d => d.id === selectedDistrict)
  145. if (district && city && province) {
  146. return `${province.name} ${city.name} ${district.name}`
  147. } else if (city && province) {
  148. return `${province.name} ${city.name}`
  149. } else if (province) {
  150. return province.name
  151. }
  152. return '请选择省市区'
  153. }
  154. if (!visible) return null
  155. return (
  156. <View className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
  157. <View className="bg-white rounded-lg w-4/5 max-w-md">
  158. {/* 标题栏 */}
  159. <View className="flex justify-between items-center p-4 border-b border-gray-200">
  160. <Text className="text-lg font-bold text-gray-800">{title}</Text>
  161. </View>
  162. {/* 选择器区域 */}
  163. <View className="p-4">
  164. {/* 当前选择显示 */}
  165. <View className="mb-4 p-3 bg-gray-50 rounded-lg">
  166. <Text className="text-sm text-gray-600">已选择:</Text>
  167. <Text className="text-sm font-medium text-blue-600">{getDisplayText()}</Text>
  168. </View>
  169. {/* 三级选择器 */}
  170. <View className="flex space-x-2 mb-4">
  171. {/* 省份选择器 */}
  172. <View className="flex-1">
  173. <Text className="text-sm text-gray-600 mb-1 block">省份</Text>
  174. <Picker
  175. mode="selector"
  176. range={provinces}
  177. rangeKey="name"
  178. value={selectedProvince ? provinces.findIndex(p => p.id === selectedProvince) : -1}
  179. onChange={handleProvinceChange}
  180. >
  181. <View className="border border-gray-300 rounded px-3 py-2 text-sm bg-white">
  182. {selectedProvince ? provinces.find(p => p.id === selectedProvince)?.name : '请选择省份'}
  183. </View>
  184. </Picker>
  185. </View>
  186. {/* 城市选择器 */}
  187. <View className="flex-1">
  188. <Text className="text-sm text-gray-600 mb-1 block">城市</Text>
  189. <Picker
  190. mode="selector"
  191. range={cities}
  192. rangeKey="name"
  193. value={selectedCity ? cities.findIndex(c => c.id === selectedCity) : -1}
  194. onChange={handleCityChange}
  195. disabled={!selectedProvince}
  196. >
  197. <View className={`border border-gray-300 rounded px-3 py-2 text-sm ${
  198. !selectedProvince ? 'bg-gray-100 text-gray-400' : 'bg-white'
  199. }`}>
  200. {selectedCity ? cities.find(c => c.id === selectedCity)?.name : '请选择城市'}
  201. </View>
  202. </Picker>
  203. </View>
  204. {/* 区县选择器 */}
  205. <View className="flex-1">
  206. <Text className="text-sm text-gray-600 mb-1 block">区县</Text>
  207. <Picker
  208. mode="selector"
  209. range={districts}
  210. rangeKey="name"
  211. value={selectedDistrict ? districts.findIndex(d => d.id === selectedDistrict) : -1}
  212. onChange={handleDistrictChange}
  213. disabled={!selectedCity}
  214. >
  215. <View className={`border border-gray-300 rounded px-3 py-2 text-sm ${
  216. !selectedCity ? 'bg-gray-100 text-gray-400' : 'bg-white'
  217. }`}>
  218. {selectedDistrict ? districts.find(d => d.id === selectedDistrict)?.name : '请选择区县'}
  219. </View>
  220. </Picker>
  221. </View>
  222. </View>
  223. </View>
  224. {/* 按钮区域 */}
  225. <View className="flex border-t border-gray-200">
  226. <Button
  227. className="flex-1 py-3 text-gray-600 bg-white border-none rounded-none"
  228. onClick={handleCancel}
  229. >
  230. 取消
  231. </Button>
  232. <View className="w-px bg-gray-200"></View>
  233. <Button
  234. className="flex-1 py-3 text-blue-500 bg-white border-none rounded-none font-bold"
  235. onClick={handleConfirm}
  236. >
  237. 确定
  238. </Button>
  239. </View>
  240. </View>
  241. </View>
  242. )
  243. }