DisabilityManagement.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. import React, { useState } from 'react';
  2. import { useQuery, useMutation } from '@tanstack/react-query';
  3. import { Plus, Edit, Trash2, Search } from 'lucide-react';
  4. import { format } from 'date-fns';
  5. import { Input } from '@d8d/shared-ui-components/components/ui/input';
  6. import { Button } from '@d8d/shared-ui-components/components/ui/button';
  7. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@d8d/shared-ui-components/components/ui/card';
  8. import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
  9. import { Skeleton } from '@d8d/shared-ui-components/components/ui/skeleton';
  10. import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@d8d/shared-ui-components/components/ui/dialog';
  11. import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@d8d/shared-ui-components/components/ui/form';
  12. import { useForm } from 'react-hook-form';
  13. import { zodResolver } from '@hookform/resolvers/zod';
  14. import { toast } from 'sonner';
  15. import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
  16. import { AreaSelect } from '@d8d/area-management-ui';
  17. import { disabilityClientManager } from '../api/disabilityClient';
  18. import { CreateDisabledPersonSchema, UpdateDisabledPersonSchema } from '@d8d/allin-disability-module/schemas';
  19. import type { CreateDisabledPersonRequest, UpdateDisabledPersonRequest, DisabledPersonData } from '../api/types';
  20. interface DisabilitySearchParams {
  21. page: number;
  22. limit: number;
  23. search: string;
  24. }
  25. const DisabilityManagement: React.FC = () => {
  26. const [searchParams, setSearchParams] = useState<DisabilitySearchParams>({ page: 1, limit: 10, search: '' });
  27. const [isModalOpen, setIsModalOpen] = useState(false);
  28. const [isCreateForm, setIsCreateForm] = useState(true);
  29. const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
  30. const [personToDelete, setPersonToDelete] = useState<number | null>(null);
  31. // 表单实例 - 创建表单
  32. const createForm = useForm<CreateDisabledPersonRequest>({
  33. resolver: zodResolver(CreateDisabledPersonSchema),
  34. defaultValues: {
  35. name: '',
  36. gender: '男',
  37. idCard: '',
  38. disabilityId: '',
  39. disabilityType: '',
  40. disabilityLevel: '',
  41. idAddress: '',
  42. phone: '',
  43. province: '',
  44. city: '',
  45. }
  46. });
  47. // 表单实例 - 更新表单
  48. const updateForm = useForm<UpdateDisabledPersonRequest>({
  49. resolver: zodResolver(UpdateDisabledPersonSchema),
  50. defaultValues: {}
  51. });
  52. // 查询残疾人列表
  53. const { data: disabilityList, isLoading, refetch } = useQuery({
  54. queryKey: ['disabled-persons', searchParams],
  55. queryFn: async () => {
  56. const res = await disabilityClientManager.get().getAllDisabledPersons.$get({
  57. query: {
  58. skip: (searchParams.page - 1) * searchParams.limit,
  59. take: searchParams.limit
  60. }
  61. });
  62. if (res.status !== 200) throw new Error('获取残疾人列表失败');
  63. return await res.json();
  64. }
  65. });
  66. // 搜索残疾人
  67. const { data: searchResults } = useQuery({
  68. queryKey: ['search-disabled-persons', searchParams.search],
  69. queryFn: async () => {
  70. if (!searchParams.search.trim()) return null;
  71. const res = await disabilityClientManager.get().searchDisabledPersons.$get({
  72. query: {
  73. keyword: searchParams.search,
  74. skip: 0,
  75. take: searchParams.limit
  76. }
  77. });
  78. if (res.status !== 200) throw new Error('搜索残疾人失败');
  79. return await res.json();
  80. },
  81. enabled: !!searchParams.search.trim()
  82. });
  83. // 创建残疾人
  84. const createMutation = useMutation({
  85. mutationFn: async (data: CreateDisabledPersonRequest) => {
  86. const res = await disabilityClientManager.get().createDisabledPerson.$post({
  87. json: data
  88. });
  89. return res;
  90. },
  91. onSuccess: () => {
  92. toast.success('残疾人创建成功');
  93. createForm.reset();
  94. setIsModalOpen(false);
  95. refetch();
  96. },
  97. onError: (error: any) => {
  98. toast.error(error.message || '创建残疾人失败');
  99. }
  100. });
  101. // 更新残疾人
  102. const updateMutation = useMutation({
  103. mutationFn: async (data: UpdateDisabledPersonRequest) => {
  104. const res = await disabilityClientManager.get().updateDisabledPerson.$post({
  105. json: data
  106. });
  107. return res;
  108. },
  109. onSuccess: () => {
  110. toast.success('残疾人更新成功');
  111. updateForm.reset();
  112. setIsModalOpen(false);
  113. refetch();
  114. },
  115. onError: (error: any) => {
  116. toast.error(error.message || '更新残疾人失败');
  117. }
  118. });
  119. // 删除残疾人
  120. const deleteMutation = useMutation({
  121. mutationFn: async (id: number) => {
  122. const res = await disabilityClientManager.get().deleteDisabledPerson.$post({
  123. json: { id }
  124. });
  125. return res;
  126. },
  127. onSuccess: () => {
  128. toast.success('残疾人删除成功');
  129. setDeleteDialogOpen(false);
  130. setPersonToDelete(null);
  131. refetch();
  132. },
  133. onError: (error: any) => {
  134. toast.error(error.message || '删除残疾人失败');
  135. }
  136. });
  137. // 处理区域选择变化
  138. const handleAreaChange = (form: any, areaValue: { provinceId?: number; cityId?: number; districtId?: number }) => {
  139. // 这里需要将区域ID转换为区域名称
  140. // 实际实现中需要调用区域服务获取名称
  141. form.setValue('province', areaValue.provinceId?.toString() || '');
  142. form.setValue('city', areaValue.cityId?.toString() || '');
  143. if (areaValue.districtId) {
  144. form.setValue('district', areaValue.districtId.toString());
  145. }
  146. };
  147. // 打开创建模态框
  148. const handleOpenCreateModal = () => {
  149. setIsCreateForm(true);
  150. createForm.reset();
  151. setIsModalOpen(true);
  152. };
  153. // 打开更新模态框
  154. const handleOpenUpdateModal = (person: DisabledPersonData) => {
  155. setIsCreateForm(false);
  156. updateForm.reset({
  157. id: person.id,
  158. name: person.name,
  159. gender: person.gender,
  160. idCard: person.idCard,
  161. disabilityId: person.disabilityId,
  162. disabilityType: person.disabilityType,
  163. disabilityLevel: person.disabilityLevel,
  164. idAddress: person.idAddress,
  165. phone: person.phone,
  166. province: person.province,
  167. city: person.city,
  168. district: person.district || '',
  169. detailedAddress: person.detailedAddress || '',
  170. nation: person.nation || '',
  171. isMarried: person.isMarried || 0,
  172. canDirectContact: person.canDirectContact,
  173. jobStatus: person.jobStatus,
  174. });
  175. setIsModalOpen(true);
  176. };
  177. // 打开删除确认对话框
  178. const handleOpenDeleteDialog = (id: number) => {
  179. setPersonToDelete(id);
  180. setDeleteDialogOpen(true);
  181. };
  182. // 处理创建表单提交
  183. const handleCreateSubmit = createForm.handleSubmit((data) => {
  184. createMutation.mutate(data);
  185. }, (errors) => console.debug('创建表单验证错误:', errors));
  186. // 处理更新表单提交
  187. const handleUpdateSubmit = updateForm.handleSubmit((data) => {
  188. updateMutation.mutate(data);
  189. }, (errors) => console.debug('更新表单验证错误:', errors));
  190. // 处理删除
  191. const handleDelete = () => {
  192. if (personToDelete) {
  193. deleteMutation.mutate(personToDelete);
  194. }
  195. };
  196. // 处理搜索
  197. const handleSearch = (e: React.FormEvent) => {
  198. e.preventDefault();
  199. refetch();
  200. };
  201. const displayData = searchParams.search.trim() ? searchResults?.data : disabilityList?.data;
  202. const displayTotal = searchParams.search.trim() ? searchResults?.total : disabilityList?.total;
  203. return (
  204. <div className="space-y-6">
  205. <Card>
  206. <CardHeader>
  207. <CardTitle>残疾人管理</CardTitle>
  208. <CardDescription>管理残疾人信息,包括创建、更新、删除和查询功能</CardDescription>
  209. </CardHeader>
  210. <CardContent>
  211. <div className="flex justify-between items-center mb-6">
  212. <form onSubmit={handleSearch} className="flex items-center space-x-2">
  213. <Input
  214. placeholder="搜索姓名或身份证号..."
  215. value={searchParams.search}
  216. onChange={(e) => setSearchParams({ ...searchParams, search: e.target.value })}
  217. className="w-64"
  218. data-testid="search-input"
  219. />
  220. <Button type="submit" size="sm" data-testid="search-button">
  221. <Search className="h-4 w-4 mr-2" />
  222. 搜索
  223. </Button>
  224. </form>
  225. <Button onClick={handleOpenCreateModal} data-testid="create-button">
  226. <Plus className="h-4 w-4 mr-2" />
  227. 新增残疾人
  228. </Button>
  229. </div>
  230. {isLoading ? (
  231. <div className="space-y-2">
  232. {Array.from({ length: 5 }).map((_, i) => (
  233. <Skeleton key={i} className="h-12 w-full" />
  234. ))}
  235. </div>
  236. ) : (
  237. <>
  238. <Table>
  239. <TableHeader>
  240. <TableRow>
  241. <TableHead>姓名</TableHead>
  242. <TableHead>性别</TableHead>
  243. <TableHead>身份证号</TableHead>
  244. <TableHead>残疾证号</TableHead>
  245. <TableHead>残疾类型</TableHead>
  246. <TableHead>联系电话</TableHead>
  247. <TableHead>创建时间</TableHead>
  248. <TableHead>操作</TableHead>
  249. </TableRow>
  250. </TableHeader>
  251. <TableBody>
  252. {displayData?.map((person) => (
  253. <TableRow key={person.id} data-testid={`person-row-${person.id}`}>
  254. <TableCell>{person.name}</TableCell>
  255. <TableCell>{person.gender}</TableCell>
  256. <TableCell>{person.idCard}</TableCell>
  257. <TableCell>{person.disabilityId}</TableCell>
  258. <TableCell>{person.disabilityType}</TableCell>
  259. <TableCell>{person.phone}</TableCell>
  260. <TableCell>{format(new Date(person.createTime), 'yyyy-MM-dd HH:mm')}</TableCell>
  261. <TableCell>
  262. <div className="flex space-x-2">
  263. <Button
  264. variant="outline"
  265. size="sm"
  266. onClick={() => handleOpenUpdateModal(person)}
  267. data-testid={`edit-button-${person.id}`}
  268. >
  269. <Edit className="h-4 w-4" />
  270. </Button>
  271. <Button
  272. variant="outline"
  273. size="sm"
  274. onClick={() => handleOpenDeleteDialog(person.id)}
  275. data-testid={`delete-button-${person.id}`}
  276. >
  277. <Trash2 className="h-4 w-4" />
  278. </Button>
  279. </div>
  280. </TableCell>
  281. </TableRow>
  282. ))}
  283. </TableBody>
  284. </Table>
  285. {displayData?.length === 0 && (
  286. <div className="text-center py-8 text-muted-foreground">
  287. 暂无数据
  288. </div>
  289. )}
  290. {displayTotal && displayTotal > 0 && (
  291. <DataTablePagination
  292. totalCount={displayTotal}
  293. pageSize={searchParams.limit}
  294. currentPage={searchParams.page}
  295. onPageChange={(page, pageSize) => setSearchParams({ ...searchParams, page, limit: pageSize })}
  296. />
  297. )}
  298. </>
  299. )}
  300. </CardContent>
  301. </Card>
  302. {/* 创建/更新模态框 */}
  303. <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
  304. <DialogContent className="max-w-2xl">
  305. <DialogHeader>
  306. <DialogTitle>{isCreateForm ? '新增残疾人' : '编辑残疾人'}</DialogTitle>
  307. <DialogDescription>
  308. {isCreateForm ? '填写残疾人信息' : '修改残疾人信息'}
  309. </DialogDescription>
  310. </DialogHeader>
  311. {isCreateForm ? (
  312. <Form {...createForm}>
  313. <form onSubmit={handleCreateSubmit} className="space-y-4">
  314. <div className="grid grid-cols-2 gap-4">
  315. <FormField
  316. control={createForm.control}
  317. name="name"
  318. render={({ field }) => (
  319. <FormItem>
  320. <FormLabel>姓名 *</FormLabel>
  321. <FormControl>
  322. <Input placeholder="请输入姓名" {...field} data-testid="name-input" />
  323. </FormControl>
  324. <FormMessage />
  325. </FormItem>
  326. )}
  327. />
  328. <FormField
  329. control={createForm.control}
  330. name="gender"
  331. render={({ field }) => (
  332. <FormItem>
  333. <FormLabel>性别 *</FormLabel>
  334. <FormControl>
  335. <select
  336. className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
  337. {...field}
  338. data-testid="gender-select"
  339. >
  340. <option value="男">男</option>
  341. <option value="女">女</option>
  342. </select>
  343. </FormControl>
  344. <FormMessage />
  345. </FormItem>
  346. )}
  347. />
  348. <FormField
  349. control={createForm.control}
  350. name="idCard"
  351. render={({ field }) => (
  352. <FormItem>
  353. <FormLabel>身份证号 *</FormLabel>
  354. <FormControl>
  355. <Input placeholder="请输入身份证号" {...field} data-testid="id-card-input" />
  356. </FormControl>
  357. <FormMessage />
  358. </FormItem>
  359. )}
  360. />
  361. <FormField
  362. control={createForm.control}
  363. name="disabilityId"
  364. render={({ field }) => (
  365. <FormItem>
  366. <FormLabel>残疾证号 *</FormLabel>
  367. <FormControl>
  368. <Input placeholder="请输入残疾证号" {...field} data-testid="disability-id-input" />
  369. </FormControl>
  370. <FormMessage />
  371. </FormItem>
  372. )}
  373. />
  374. <FormField
  375. control={createForm.control}
  376. name="disabilityType"
  377. render={({ field }) => (
  378. <FormItem>
  379. <FormLabel>残疾类型 *</FormLabel>
  380. <FormControl>
  381. <Input placeholder="请输入残疾类型" {...field} data-testid="disability-type-input" />
  382. </FormControl>
  383. <FormMessage />
  384. </FormItem>
  385. )}
  386. />
  387. <FormField
  388. control={createForm.control}
  389. name="disabilityLevel"
  390. render={({ field }) => (
  391. <FormItem>
  392. <FormLabel>残疾等级 *</FormLabel>
  393. <FormControl>
  394. <Input placeholder="请输入残疾等级" {...field} data-testid="disability-level-input" />
  395. </FormControl>
  396. <FormMessage />
  397. </FormItem>
  398. )}
  399. />
  400. <FormField
  401. control={createForm.control}
  402. name="phone"
  403. render={({ field }) => (
  404. <FormItem>
  405. <FormLabel>联系电话 *</FormLabel>
  406. <FormControl>
  407. <Input placeholder="请输入联系电话" {...field} data-testid="phone-input" />
  408. </FormControl>
  409. <FormMessage />
  410. </FormItem>
  411. )}
  412. />
  413. <FormField
  414. control={createForm.control}
  415. name="idAddress"
  416. render={({ field }) => (
  417. <FormItem>
  418. <FormLabel>身份证地址 *</FormLabel>
  419. <FormControl>
  420. <Input placeholder="请输入身份证地址" {...field} data-testid="id-address-input" />
  421. </FormControl>
  422. <FormMessage />
  423. </FormItem>
  424. )}
  425. />
  426. <FormItem>
  427. <FormLabel>所在地区 *</FormLabel>
  428. <FormControl>
  429. <AreaSelect
  430. value={{}}
  431. onChange={(value) => handleAreaChange(createForm, value)}
  432. required
  433. data-testid="area-select"
  434. />
  435. </FormControl>
  436. <FormMessage />
  437. </FormItem>
  438. <FormField
  439. control={createForm.control}
  440. name="detailedAddress"
  441. render={({ field }) => (
  442. <FormItem>
  443. <FormLabel>详细地址</FormLabel>
  444. <FormControl>
  445. <Input placeholder="请输入详细地址" {...field} data-testid="detailed-address-input" />
  446. </FormControl>
  447. <FormMessage />
  448. </FormItem>
  449. )}
  450. />
  451. <FormField
  452. control={createForm.control}
  453. name="nation"
  454. render={({ field }) => (
  455. <FormItem>
  456. <FormLabel>民族</FormLabel>
  457. <FormControl>
  458. <Input placeholder="请输入民族" {...field} data-testid="nation-input" />
  459. </FormControl>
  460. <FormMessage />
  461. </FormItem>
  462. )}
  463. />
  464. </div>
  465. <DialogFooter>
  466. <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
  467. 取消
  468. </Button>
  469. <Button type="submit" disabled={createMutation.isPending} data-testid="create-submit-button">
  470. {createMutation.isPending ? '创建中...' : '创建'}
  471. </Button>
  472. </DialogFooter>
  473. </form>
  474. </Form>
  475. ) : (
  476. <Form {...updateForm}>
  477. <form onSubmit={handleUpdateSubmit} className="space-y-4">
  478. <div className="grid grid-cols-2 gap-4">
  479. <FormField
  480. control={updateForm.control}
  481. name="name"
  482. render={({ field }) => (
  483. <FormItem>
  484. <FormLabel>姓名 *</FormLabel>
  485. <FormControl>
  486. <Input placeholder="请输入姓名" {...field} data-testid="update-name-input" />
  487. </FormControl>
  488. <FormMessage />
  489. </FormItem>
  490. )}
  491. />
  492. <FormField
  493. control={updateForm.control}
  494. name="gender"
  495. render={({ field }) => (
  496. <FormItem>
  497. <FormLabel>性别 *</FormLabel>
  498. <FormControl>
  499. <select
  500. className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
  501. {...field}
  502. data-testid="update-gender-select"
  503. >
  504. <option value="男">男</option>
  505. <option value="女">女</option>
  506. </select>
  507. </FormControl>
  508. <FormMessage />
  509. </FormItem>
  510. )}
  511. />
  512. <FormField
  513. control={updateForm.control}
  514. name="idCard"
  515. render={({ field }) => (
  516. <FormItem>
  517. <FormLabel>身份证号 *</FormLabel>
  518. <FormControl>
  519. <Input placeholder="请输入身份证号" {...field} data-testid="update-id-card-input" />
  520. </FormControl>
  521. <FormMessage />
  522. </FormItem>
  523. )}
  524. />
  525. <FormField
  526. control={updateForm.control}
  527. name="disabilityId"
  528. render={({ field }) => (
  529. <FormItem>
  530. <FormLabel>残疾证号 *</FormLabel>
  531. <FormControl>
  532. <Input placeholder="请输入残疾证号" {...field} data-testid="update-disability-id-input" />
  533. </FormControl>
  534. <FormMessage />
  535. </FormItem>
  536. )}
  537. />
  538. <FormField
  539. control={updateForm.control}
  540. name="disabilityType"
  541. render={({ field }) => (
  542. <FormItem>
  543. <FormLabel>残疾类型 *</FormLabel>
  544. <FormControl>
  545. <Input placeholder="请输入残疾类型" {...field} data-testid="update-disability-type-input" />
  546. </FormControl>
  547. <FormMessage />
  548. </FormItem>
  549. )}
  550. />
  551. <FormField
  552. control={updateForm.control}
  553. name="disabilityLevel"
  554. render={({ field }) => (
  555. <FormItem>
  556. <FormLabel>残疾等级 *</FormLabel>
  557. <FormControl>
  558. <Input placeholder="请输入残疾等级" {...field} data-testid="update-disability-level-input" />
  559. </FormControl>
  560. <FormMessage />
  561. </FormItem>
  562. )}
  563. />
  564. <FormField
  565. control={updateForm.control}
  566. name="phone"
  567. render={({ field }) => (
  568. <FormItem>
  569. <FormLabel>联系电话 *</FormLabel>
  570. <FormControl>
  571. <Input placeholder="请输入联系电话" {...field} data-testid="update-phone-input" />
  572. </FormControl>
  573. <FormMessage />
  574. </FormItem>
  575. )}
  576. />
  577. <FormField
  578. control={updateForm.control}
  579. name="idAddress"
  580. render={({ field }) => (
  581. <FormItem>
  582. <FormLabel>身份证地址 *</FormLabel>
  583. <FormControl>
  584. <Input placeholder="请输入身份证地址" {...field} data-testid="update-id-address-input" />
  585. </FormControl>
  586. <FormMessage />
  587. </FormItem>
  588. )}
  589. />
  590. <FormItem>
  591. <FormLabel>所在地区 *</FormLabel>
  592. <FormControl>
  593. <AreaSelect
  594. value={{}}
  595. onChange={(value) => handleAreaChange(updateForm, value)}
  596. required
  597. data-testid="update-area-select"
  598. />
  599. </FormControl>
  600. <FormMessage />
  601. </FormItem>
  602. <FormField
  603. control={updateForm.control}
  604. name="detailedAddress"
  605. render={({ field }) => (
  606. <FormItem>
  607. <FormLabel>详细地址</FormLabel>
  608. <FormControl>
  609. <Input placeholder="请输入详细地址" {...field} data-testid="update-detailed-address-input" />
  610. </FormControl>
  611. <FormMessage />
  612. </FormItem>
  613. )}
  614. />
  615. <FormField
  616. control={updateForm.control}
  617. name="nation"
  618. render={({ field }) => (
  619. <FormItem>
  620. <FormLabel>民族</FormLabel>
  621. <FormControl>
  622. <Input placeholder="请输入民族" {...field} data-testid="update-nation-input" />
  623. </FormControl>
  624. <FormMessage />
  625. </FormItem>
  626. )}
  627. />
  628. </div>
  629. <DialogFooter>
  630. <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
  631. 取消
  632. </Button>
  633. <Button type="submit" disabled={updateMutation.isPending} data-testid="update-submit-button">
  634. {updateMutation.isPending ? '更新中...' : '更新'}
  635. </Button>
  636. </DialogFooter>
  637. </form>
  638. </Form>
  639. )}
  640. </DialogContent>
  641. </Dialog>
  642. {/* 删除确认对话框 */}
  643. <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
  644. <DialogContent>
  645. <DialogHeader>
  646. <DialogTitle>确认删除</DialogTitle>
  647. <DialogDescription>
  648. 确定要删除这个残疾人记录吗?此操作不可恢复。
  649. </DialogDescription>
  650. </DialogHeader>
  651. <DialogFooter>
  652. <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
  653. 取消
  654. </Button>
  655. <Button variant="destructive" onClick={handleDelete} disabled={deleteMutation.isPending} data-testid="confirm-delete-button">
  656. {deleteMutation.isPending ? '删除中...' : '确认删除'}
  657. </Button>
  658. </DialogFooter>
  659. </DialogContent>
  660. </Dialog>
  661. </div>
  662. );
  663. };
  664. export default DisabilityManagement;