| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- import { Controller, useFormContext, Control, FieldValues, Path } from 'react-hook-form';
- import { useQuery } from '@tanstack/react-query';
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@d8d/shared-ui-components/components/ui/select';
- import { FormDescription, FormLabel } from '@d8d/shared-ui-components/components/ui/form';
- import { areaClient, areaClientManager } from '../api/areaClient';
- import type { InferResponseType } from 'hono/client';
- interface AreaSelectFormProps<T extends FieldValues = FieldValues> {
- provinceName: Path<T>;
- cityName: Path<T>;
- districtName?: Path<T>;
- label?: string;
- description?: string;
- required?: boolean;
- disabled?: boolean;
- className?: string;
- control?: Control<T>;
- }
- // 类型定义
- type AreaResponse = InferResponseType<typeof areaClient.index.$get, 200>['data'][0];
- export const AreaSelectForm = <T extends FieldValues = FieldValues>({
- provinceName,
- cityName,
- districtName,
- label = '地区选择',
- description,
- required = false,
- disabled = false,
- className,
- control: propControl
- }: AreaSelectFormProps<T>) => {
- // 使用传入的 control,或者从上下文中获取
- const formContext = useFormContext<T>();
- const control = propControl || formContext?.control;
- if (!control) {
- console.error('AreaSelectForm: 缺少 react-hook-form 上下文或 control prop');
- return null;
- }
- // 获取表单值
- const provinceValue = control._getWatch(provinceName);
- const cityValue = control._getWatch(cityName);
- // districtValue 变量未使用,移除
- // 查询省份列表
- const { data: provinces, isLoading: isLoadingProvinces } = useQuery({
- queryKey: ['areas', 'provinces'],
- queryFn: async () => {
- const res = await areaClientManager.get().index.$get({
- query: {
- page: 1,
- pageSize: 100,
- filters: JSON.stringify({
- level: 1,
- isDisabled: 0
- }),
- sortBy: 'id',
- sortOrder: 'ASC'
- }
- });
- if (res.status !== 200) throw new Error('获取省份列表失败');
- return await res.json();
- },
- staleTime: 10 * 60 * 1000,
- gcTime: 30 * 60 * 1000,
- });
- // 查询城市列表
- const { data: cities, isLoading: isLoadingCities } = useQuery({
- queryKey: ['areas', 'cities', provinceValue],
- queryFn: async () => {
- if (!provinceValue) return { data: [] };
- const res = await areaClientManager.get().index.$get({
- query: {
- page: 1,
- pageSize: 100,
- filters: JSON.stringify({
- level: 2,
- parentId: Number(provinceValue),
- isDisabled: 0
- }),
- sortBy: 'id',
- sortOrder: 'ASC'
- }
- });
- if (res.status !== 200) throw new Error('获取城市列表失败');
- return await res.json();
- },
- staleTime: 10 * 60 * 1000,
- gcTime: 30 * 60 * 1000,
- enabled: !!provinceValue,
- });
- // 查询区县列表(如果提供districtName)
- const { data: districts, isLoading: isLoadingDistricts } = useQuery({
- queryKey: ['areas', 'districts', cityValue],
- queryFn: async () => {
- if (!cityValue || !districtName) return { data: [] };
- const res = await areaClientManager.get().index.$get({
- query: {
- page: 1,
- pageSize: 100,
- filters: JSON.stringify({
- level: 3,
- parentId: Number(cityValue),
- isDisabled: 0
- }),
- sortBy: 'id',
- sortOrder: 'ASC'
- }
- });
- if (res.status !== 200) throw new Error('获取区县列表失败');
- return await res.json();
- },
- staleTime: 10 * 60 * 1000,
- gcTime: 30 * 60 * 1000,
- enabled: !!cityValue && !!districtName,
- });
- return (
- <div className={className}>
- {label && (
- <FormLabel>
- {label}{required && <span className="text-destructive">*</span>}
- </FormLabel>
- )}
- <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-2">
- {/* 省份选择 */}
- <Controller
- name={provinceName as any}
- control={control}
- render={({ field: provinceField, fieldState: provinceFieldState }) => (
- <div>
- <Select
- value={provinceField.value || ''}
- onValueChange={(value) => {
- provinceField.onChange(value && value !== 'none' ? value : '');
- // 清空城市和区县字段
- control.setValue(cityName as any, '');
- if (districtName) {
- control.setValue(districtName as any, '');
- }
- }}
- disabled={disabled || isLoadingProvinces}
- >
- <SelectTrigger>
- <SelectValue placeholder="选择省份" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="none">请选择省份</SelectItem>
- {provinces?.data.map((province: AreaResponse) => (
- <SelectItem key={province.id} value={province.id.toString()}>
- {province.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- {provinceFieldState.error && (
- <div className="text-sm font-medium text-destructive mt-1">
- {provinceFieldState.error.message}
- </div>
- )}
- </div>
- )}
- />
- {/* 城市选择 */}
- <Controller
- name={cityName as any}
- control={control}
- render={({ field: cityField, fieldState: cityFieldState }) => (
- <div>
- <Select
- value={cityField.value || ''}
- onValueChange={(value) => {
- cityField.onChange(value && value !== 'none' ? value : '');
- // 清空区县字段
- if (districtName) {
- control.setValue(districtName as any, '');
- }
- }}
- disabled={disabled || !provinceValue || isLoadingCities}
- >
- <SelectTrigger>
- <SelectValue placeholder="选择城市" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="none">请选择城市</SelectItem>
- {cities?.data.map((city: AreaResponse) => (
- <SelectItem key={city.id} value={city.id.toString()}>
- {city.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- {cityFieldState.error && (
- <div className="text-sm font-medium text-destructive mt-1">
- {cityFieldState.error.message}
- </div>
- )}
- </div>
- )}
- />
- {/* 区县选择(可选) */}
- {districtName && (
- <Controller
- name={districtName as any}
- control={control}
- render={({ field: districtField, fieldState: districtFieldState }) => (
- <div>
- <Select
- value={districtField.value || ''}
- onValueChange={(value) => {
- districtField.onChange(value && value !== 'none' ? value : '');
- }}
- disabled={disabled || !cityValue || isLoadingDistricts}
- >
- <SelectTrigger>
- <SelectValue placeholder="选择区县" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="none">请选区县</SelectItem>
- {districts?.data.map((district: AreaResponse) => (
- <SelectItem key={district.id} value={district.id.toString()}>
- {district.name}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- {districtFieldState.error && (
- <div className="text-sm font-medium text-destructive mt-1">
- {districtFieldState.error.message}
- </div>
- )}
- </div>
- )}
- />
- )}
- </div>
- {description && (
- <FormDescription className="mt-2">
- {description}
- </FormDescription>
- )}
- </div>
- );
- };
|