AreaForm.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import React, { useState, useEffect } from 'react';
  2. import { useForm } from 'react-hook-form';
  3. import { zodResolver } from '@hookform/resolvers/zod';
  4. import { Button } from '@/client/components/ui/button';
  5. import { Input } from '@/client/components/ui/input';
  6. import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
  7. import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
  8. import { createAreaSchema, updateAreaSchema } from '@d8d/server/modules/areas/area.schema';
  9. import type { CreateAreaInput, UpdateAreaInput } from '@d8d/server/modules/areas/area.schema';
  10. import { AreaLevel } from '@d8d/server/modules/areas/area.entity';
  11. import { DisabledStatus } from '@/share/types';
  12. import { AreaSelect } from './AreaSelect';
  13. import { useQuery } from '@tanstack/react-query';
  14. import { areaClient } from '@/client/api';
  15. interface AreaFormProps {
  16. area?: UpdateAreaInput & { id?: number };
  17. onSubmit: (data: CreateAreaInput | UpdateAreaInput) => Promise<void>;
  18. onCancel: () => void;
  19. isLoading?: boolean;
  20. defaultLevel?: number;
  21. }
  22. export const AreaForm: React.FC<AreaFormProps> = ({
  23. area,
  24. onSubmit,
  25. onCancel,
  26. isLoading = false,
  27. defaultLevel
  28. }) => {
  29. const isEditing = !!area;
  30. const [parentAreaInfo, setParentAreaInfo] = useState<{
  31. provinceId?: number;
  32. cityId?: number;
  33. }>({});
  34. // 查询父级区域的完整层级信息
  35. const { data: parentAreaData } = useQuery({
  36. queryKey: ['area', 'parent', area?.parentId],
  37. queryFn: async () => {
  38. if (!area?.parentId || area.parentId === 0) return null;
  39. const res = await areaClient[':id'].$get({
  40. param: { id: area.parentId }
  41. });
  42. if (res.status !== 200) throw new Error('获取父级区域信息失败');
  43. return await res.json();
  44. },
  45. enabled: isEditing && !!area?.parentId && area.parentId > 0,
  46. staleTime: 5 * 60 * 1000,
  47. gcTime: 10 * 60 * 1000,
  48. });
  49. // 根据父级区域信息设置层级关系
  50. useEffect(() => {
  51. if (parentAreaData) {
  52. const parentArea = parentAreaData;
  53. if (parentArea.level === AreaLevel.PROVINCE) {
  54. // 父级是省份,当前区域是城市
  55. setParentAreaInfo({
  56. provinceId: parentArea.id
  57. });
  58. } else if (parentArea.level === AreaLevel.CITY) {
  59. // 父级是城市,当前区域是区县,需要查询城市的父级省份
  60. const fetchProvinceId = async () => {
  61. const res = await areaClient[':id'].$get({
  62. param: { id: parentArea.parentId! }
  63. });
  64. if (res.status === 200) {
  65. const provinceResult = await res.json();
  66. setParentAreaInfo({
  67. provinceId: provinceResult.id,
  68. cityId: parentArea.id
  69. });
  70. }
  71. };
  72. fetchProvinceId();
  73. }
  74. }
  75. }, [parentAreaData]);
  76. const form = useForm<CreateAreaInput | UpdateAreaInput>({
  77. resolver: zodResolver(isEditing ? updateAreaSchema : createAreaSchema),
  78. defaultValues: area ? {
  79. parentId: area.parentId,
  80. name: area.name,
  81. level: area.level,
  82. code: area.code,
  83. isDisabled: area.isDisabled,
  84. } : {
  85. parentId: 0,
  86. name: '',
  87. level: defaultLevel || AreaLevel.PROVINCE,
  88. code: '',
  89. isDisabled: DisabledStatus.ENABLED,
  90. },
  91. });
  92. const handleSubmit = async (data: CreateAreaInput | UpdateAreaInput) => {
  93. await onSubmit(data);
  94. };
  95. return (
  96. <Form {...form}>
  97. <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
  98. <div className="grid grid-cols-1 gap-6">
  99. {/* 层级选择 */}
  100. <FormField
  101. control={form.control}
  102. name="level"
  103. render={({ field }) => (
  104. <FormItem>
  105. <FormLabel>层级</FormLabel>
  106. <Select onValueChange={(value) => field.onChange(Number(value))} defaultValue={field.value?.toString()}>
  107. <FormControl>
  108. <SelectTrigger>
  109. <SelectValue placeholder="选择层级" />
  110. </SelectTrigger>
  111. </FormControl>
  112. <SelectContent>
  113. <SelectItem value={AreaLevel.PROVINCE.toString()}>
  114. 省/直辖市
  115. </SelectItem>
  116. <SelectItem value={AreaLevel.CITY.toString()}>
  117. </SelectItem>
  118. <SelectItem value={AreaLevel.DISTRICT.toString()}>
  119. 区/县
  120. </SelectItem>
  121. </SelectContent>
  122. </Select>
  123. <FormDescription>
  124. 选择省市区层级
  125. </FormDescription>
  126. <FormMessage />
  127. </FormItem>
  128. )}
  129. />
  130. {/* 父级区域选择 */}
  131. <FormField
  132. control={form.control}
  133. name="parentId"
  134. render={({ field }) => {
  135. const level = form.watch('level');
  136. const showParentSelect = level !== AreaLevel.PROVINCE;
  137. // 根据当前层级和编辑状态设置AreaSelect的值
  138. const getAreaSelectValue = () => {
  139. if (!isEditing || !area) {
  140. return {};
  141. }
  142. if (level === AreaLevel.CITY) {
  143. // 城市级别:需要省份ID
  144. return { provinceId: parentAreaInfo.provinceId };
  145. } else if (level === AreaLevel.DISTRICT) {
  146. // 区县级别:需要省份ID和城市ID
  147. return {
  148. provinceId: parentAreaInfo.provinceId,
  149. cityId: parentAreaInfo.cityId
  150. };
  151. }
  152. return {};
  153. };
  154. return (
  155. <FormItem>
  156. <FormLabel>父级区域</FormLabel>
  157. {showParentSelect ? (
  158. <>
  159. <FormControl>
  160. <AreaSelect
  161. value={getAreaSelectValue()}
  162. onChange={(value) => {
  163. // 根据层级设置父级ID
  164. if (level === AreaLevel.CITY) {
  165. field.onChange(value.provinceId || 0);
  166. } else if (level === AreaLevel.DISTRICT) {
  167. field.onChange(value.cityId || 0);
  168. }
  169. }}
  170. required
  171. />
  172. </FormControl>
  173. <FormDescription>
  174. {level === AreaLevel.CITY
  175. ? '选择所属省份'
  176. : '选择所属城市'}
  177. </FormDescription>
  178. </>
  179. ) : (
  180. <>
  181. <FormControl>
  182. <Input
  183. type="number"
  184. value={0}
  185. disabled
  186. className="bg-muted"
  187. />
  188. </FormControl>
  189. <FormDescription>
  190. 省/直辖市没有父级区域
  191. </FormDescription>
  192. </>
  193. )}
  194. <FormMessage />
  195. </FormItem>
  196. );
  197. }}
  198. />
  199. {/* 区域名称 */}
  200. <FormField
  201. control={form.control}
  202. name="name"
  203. render={({ field }) => (
  204. <FormItem>
  205. <FormLabel>区域名称</FormLabel>
  206. <FormControl>
  207. <Input
  208. placeholder="输入区域名称"
  209. {...field}
  210. />
  211. </FormControl>
  212. <FormDescription>
  213. 输入省市区名称,如:北京市、上海市、朝阳区等
  214. </FormDescription>
  215. <FormMessage />
  216. </FormItem>
  217. )}
  218. />
  219. {/* 行政区划代码 */}
  220. <FormField
  221. control={form.control}
  222. name="code"
  223. render={({ field }) => (
  224. <FormItem>
  225. <FormLabel>行政区划代码</FormLabel>
  226. <FormControl>
  227. <Input
  228. placeholder="输入行政区划代码"
  229. {...field}
  230. />
  231. </FormControl>
  232. <FormDescription>
  233. 输入标准的行政区划代码
  234. </FormDescription>
  235. <FormMessage />
  236. </FormItem>
  237. )}
  238. />
  239. {/* 状态选择 */}
  240. <FormField
  241. control={form.control}
  242. name="isDisabled"
  243. render={({ field }) => (
  244. <FormItem>
  245. <FormLabel>状态</FormLabel>
  246. <Select onValueChange={(value) => field.onChange(Number(value))} defaultValue={field.value?.toString()}>
  247. <FormControl>
  248. <SelectTrigger>
  249. <SelectValue placeholder="选择状态" />
  250. </SelectTrigger>
  251. </FormControl>
  252. <SelectContent>
  253. <SelectItem value={DisabledStatus.ENABLED.toString()}>
  254. 启用
  255. </SelectItem>
  256. <SelectItem value={DisabledStatus.DISABLED.toString()}>
  257. 禁用
  258. </SelectItem>
  259. </SelectContent>
  260. </Select>
  261. <FormDescription>
  262. 选择省市区状态
  263. </FormDescription>
  264. <FormMessage />
  265. </FormItem>
  266. )}
  267. />
  268. </div>
  269. {/* 表单操作按钮 */}
  270. <div className="flex justify-end gap-4">
  271. <Button
  272. type="button"
  273. variant="outline"
  274. onClick={onCancel}
  275. disabled={isLoading}
  276. >
  277. 取消
  278. </Button>
  279. <Button
  280. type="submit"
  281. disabled={isLoading}
  282. >
  283. {isLoading ? '提交中...' : isEditing ? '更新' : '创建'}
  284. </Button>
  285. </div>
  286. </form>
  287. </Form>
  288. );
  289. };