CityCascadeSelector.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import React, { useState, useEffect } from 'react';
  2. import { useQuery } from '@tanstack/react-query';
  3. import {
  4. Select,
  5. SelectContent,
  6. SelectItem,
  7. SelectTrigger,
  8. SelectValue,
  9. } from '@/client/components/ui/select';
  10. import { FormItem, FormLabel, FormControl, FormMessage } from '@/client/components/ui/form';
  11. import { cityClient } from '@/client/api';
  12. interface CityCascadeSelectorProps {
  13. provinceValue?: number;
  14. cityValue?: number;
  15. districtValue?: number;
  16. townValue?: number;
  17. onProvinceChange?: (value: number) => void;
  18. onCityChange?: (value: number) => void;
  19. onDistrictChange?: (value: number) => void;
  20. onTownChange?: (value: number) => void;
  21. disabled?: boolean;
  22. showLabels?: boolean;
  23. }
  24. interface City {
  25. id: number;
  26. name: string;
  27. level: number;
  28. parentId: number;
  29. }
  30. export const CityCascadeSelector: React.FC<CityCascadeSelectorProps> = ({
  31. provinceValue,
  32. cityValue,
  33. districtValue,
  34. townValue,
  35. onProvinceChange,
  36. onCityChange,
  37. onDistrictChange,
  38. onTownChange,
  39. disabled = false,
  40. showLabels = true,
  41. }) => {
  42. // 获取省份数据 (level=1)
  43. const { data: provinces } = useQuery({
  44. queryKey: ['cities', 'provinces'],
  45. queryFn: async () => {
  46. const res = await cityClient.$get({
  47. query: {
  48. page: 1,
  49. pageSize: 100,
  50. filters: JSON.stringify({ level: 1 }),
  51. },
  52. });
  53. if (res.status !== 200) throw new Error('获取省份数据失败');
  54. const data = await res.json();
  55. return data.data as City[];
  56. },
  57. });
  58. // 获取城市数据 (level=2)
  59. const { data: cities } = useQuery({
  60. queryKey: ['cities', 'cities', provinceValue],
  61. queryFn: async () => {
  62. if (!provinceValue) return [];
  63. const res = await cityClient.$get({
  64. query: {
  65. page: 1,
  66. pageSize: 100,
  67. filters: JSON.stringify({ level: 2, parentId: provinceValue }),
  68. },
  69. });
  70. if (res.status !== 200) throw new Error('获取城市数据失败');
  71. const data = await res.json();
  72. return data.data as City[];
  73. },
  74. enabled: !!provinceValue,
  75. });
  76. // 获取区县数据 (level=3)
  77. const { data: districts } = useQuery({
  78. queryKey: ['cities', 'districts', cityValue],
  79. queryFn: async () => {
  80. if (!cityValue) return [];
  81. const res = await cityClient.$get({
  82. query: {
  83. page: 1,
  84. pageSize: 100,
  85. filters: JSON.stringify({ level: 3, parentId: cityValue }),
  86. },
  87. });
  88. if (res.status !== 200) throw new Error('获取区县数据失败');
  89. const data = await res.json();
  90. return data.data as City[];
  91. },
  92. enabled: !!cityValue,
  93. });
  94. // 获取街道数据 (level=4)
  95. const { data: towns } = useQuery({
  96. queryKey: ['cities', 'towns', districtValue],
  97. queryFn: async () => {
  98. if (!districtValue) return [];
  99. const res = await cityClient.$get({
  100. query: {
  101. page: 1,
  102. pageSize: 100,
  103. filters: JSON.stringify({ level: 4, parentId: districtValue }),
  104. },
  105. });
  106. if (res.status !== 200) throw new Error('获取街道数据失败');
  107. const data = await res.json();
  108. return data.data as City[];
  109. },
  110. enabled: !!districtValue,
  111. });
  112. // 清除下级选择器
  113. useEffect(() => {
  114. if (!provinceValue && cityValue !== undefined) {
  115. onCityChange?.(0);
  116. }
  117. }, [provinceValue, cityValue]);
  118. useEffect(() => {
  119. if (!cityValue && districtValue !== undefined) {
  120. onDistrictChange?.(0);
  121. }
  122. }, [cityValue, districtValue]);
  123. useEffect(() => {
  124. if (!districtValue && townValue !== undefined) {
  125. onTownChange?.(0);
  126. }
  127. }, [districtValue, townValue]);
  128. const renderSelect = (
  129. label: string,
  130. value: number | undefined,
  131. onChange: ((value: number) => void) | undefined,
  132. options: City[] | undefined,
  133. placeholder: string
  134. ) => (
  135. <FormItem>
  136. {showLabels && <FormLabel>{label}</FormLabel>}
  137. <FormControl>
  138. <Select
  139. value={value?.toString() || ''}
  140. onValueChange={(val) => onChange?.(parseInt(val))}
  141. disabled={disabled || !options?.length}
  142. >
  143. <SelectTrigger>
  144. <SelectValue placeholder={placeholder} />
  145. </SelectTrigger>
  146. <SelectContent>
  147. {options?.map((option) => (
  148. <SelectItem key={option.id} value={option.id.toString()}>
  149. {option.name}
  150. </SelectItem>
  151. ))}
  152. </SelectContent>
  153. </Select>
  154. </FormControl>
  155. </FormItem>
  156. );
  157. return (
  158. <div className="grid grid-cols-4 gap-4">
  159. {renderSelect(
  160. '省份',
  161. provinceValue,
  162. onProvinceChange,
  163. provinces,
  164. '选择省份'
  165. )}
  166. {renderSelect(
  167. '城市',
  168. cityValue,
  169. onCityChange,
  170. cities,
  171. provinceValue ? '选择城市' : '请先选择省份'
  172. )}
  173. {renderSelect(
  174. '区县',
  175. districtValue,
  176. onDistrictChange,
  177. districts,
  178. cityValue ? '选择区县' : '请先选择城市'
  179. )}
  180. {renderSelect(
  181. '街道',
  182. townValue,
  183. onTownChange,
  184. towns,
  185. districtValue ? '选择街道' : '请先选择区县'
  186. )}
  187. </div>
  188. );
  189. };