Merchants.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. import { useState } from 'react'
  2. import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
  3. import { useForm } from 'react-hook-form'
  4. import { zodResolver } from '@hookform/resolvers/zod'
  5. import { format } from 'date-fns'
  6. import { zhCN } from 'date-fns/locale'
  7. import { toast } from 'sonner'
  8. import type { InferRequestType, InferResponseType } from 'hono/client'
  9. import { Plus, Search, Edit, Trash2, Eye } from 'lucide-react'
  10. import { Button } from '@/client/components/ui/button'
  11. import { Input } from '@/client/components/ui/input'
  12. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card'
  13. import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table'
  14. import { Badge } from '@/client/components/ui/badge'
  15. import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog'
  16. import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form'
  17. import { Separator } from '@/client/components/ui/separator'
  18. import { Switch } from '@/client/components/ui/switch'
  19. import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select'
  20. import { DataTablePagination } from '@/client/admin/components/DataTablePagination'
  21. import { merchantClient } from '@/client/api'
  22. import { AdminCreateMerchantDto, AdminUpdateMerchantDto } from '@d8d/merchant-module/schemas'
  23. import { Skeleton } from '@/client/components/ui/skeleton'
  24. type CreateRequest = InferRequestType<typeof merchantClient.$post>['json']
  25. type UpdateRequest = InferRequestType<typeof merchantClient[':id']['$put']>['json']
  26. type MerchantResponse = InferResponseType<typeof merchantClient.$get, 200>['data'][0]
  27. const createFormSchema = AdminCreateMerchantDto
  28. const updateFormSchema = AdminUpdateMerchantDto
  29. export const MerchantsPage = () => {
  30. const queryClient = useQueryClient()
  31. const [searchParams, setSearchParams] = useState({
  32. page: 1,
  33. limit: 10,
  34. search: '',
  35. })
  36. const [isModalOpen, setIsModalOpen] = useState(false)
  37. const [editingMerchant, setEditingMerchant] = useState<MerchantResponse | null>(null)
  38. const [isCreateForm, setIsCreateForm] = useState(true)
  39. const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
  40. const [merchantToDelete, setMerchantToDelete] = useState<number | null>(null)
  41. const [detailDialogOpen, setDetailDialogOpen] = useState(false)
  42. const [detailMerchant, setDetailMerchant] = useState<MerchantResponse | null>(null)
  43. // 创建表单
  44. const createForm = useForm<CreateRequest>({
  45. resolver: zodResolver(createFormSchema),
  46. defaultValues: {
  47. name: '',
  48. username: '',
  49. password: '',
  50. phone: '',
  51. realname: '',
  52. state: 2,
  53. rsaPublicKey: '',
  54. aesKey: '',
  55. },
  56. })
  57. // 更新表单
  58. const updateForm = useForm<UpdateRequest>({
  59. resolver: zodResolver(updateFormSchema),
  60. })
  61. // 获取商户列表
  62. const { data, isLoading, refetch } = useQuery({
  63. queryKey: ['merchants', searchParams],
  64. queryFn: async () => {
  65. const res = await merchantClient.$get({
  66. query: {
  67. page: searchParams.page,
  68. pageSize: searchParams.limit,
  69. keyword: searchParams.search,
  70. }
  71. })
  72. if (res.status !== 200) throw new Error('获取商户列表失败')
  73. return await res.json()
  74. }
  75. })
  76. // 创建商户
  77. const createMutation = useMutation({
  78. mutationFn: async (data: CreateRequest) => {
  79. const res = await merchantClient.$post({ json: data })
  80. if (res.status !== 201) throw new Error('创建商户失败')
  81. return await res.json()
  82. },
  83. onSuccess: () => {
  84. toast.success('商户创建成功')
  85. setIsModalOpen(false)
  86. createForm.reset()
  87. refetch()
  88. },
  89. onError: (error: Error) => {
  90. toast.error(error.message || '创建失败')
  91. }
  92. })
  93. // 更新商户
  94. const updateMutation = useMutation({
  95. mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
  96. const res = await merchantClient[':id']['$put']({
  97. param: { id: id.toString() },
  98. json: data
  99. })
  100. if (res.status !== 200) throw new Error('更新商户失败')
  101. return await res.json()
  102. },
  103. onSuccess: () => {
  104. toast.success('商户更新成功')
  105. setIsModalOpen(false)
  106. setEditingMerchant(null)
  107. refetch()
  108. },
  109. onError: (error: Error) => {
  110. toast.error(error.message || '更新失败')
  111. }
  112. })
  113. // 删除商户
  114. const deleteMutation = useMutation({
  115. mutationFn: async (id: number) => {
  116. const res = await merchantClient[':id']['$delete']({
  117. param: { id: id.toString() }
  118. })
  119. if (res.status !== 204) throw new Error('删除商户失败')
  120. return res
  121. },
  122. onSuccess: () => {
  123. toast.success('商户删除成功')
  124. setDeleteDialogOpen(false)
  125. setMerchantToDelete(null)
  126. refetch()
  127. },
  128. onError: (error: Error) => {
  129. toast.error(error.message || '删除失败')
  130. }
  131. })
  132. // 搜索处理
  133. const handleSearch = (e?: React.FormEvent) => {
  134. e?.preventDefault()
  135. setSearchParams(prev => ({ ...prev, page: 1 }))
  136. }
  137. // 创建商户
  138. const handleCreateMerchant = () => {
  139. setIsCreateForm(true)
  140. setEditingMerchant(null)
  141. createForm.reset()
  142. setIsModalOpen(true)
  143. }
  144. // 编辑商户
  145. const handleEditMerchant = (merchant: MerchantResponse) => {
  146. setIsCreateForm(false)
  147. setEditingMerchant(merchant)
  148. updateForm.reset({
  149. name: merchant.name || '',
  150. username: merchant.username,
  151. phone: merchant.phone || '',
  152. realname: merchant.realname || '',
  153. state: merchant.state,
  154. rsaPublicKey: merchant.rsaPublicKey || '',
  155. aesKey: merchant.aesKey || '',
  156. })
  157. setIsModalOpen(true)
  158. }
  159. // 查看详情
  160. const handleViewDetail = (merchant: MerchantResponse) => {
  161. setDetailMerchant(merchant)
  162. setDetailDialogOpen(true)
  163. }
  164. // 删除商户
  165. const handleDeleteMerchant = (id: number) => {
  166. setMerchantToDelete(id)
  167. setDeleteDialogOpen(true)
  168. }
  169. // 确认删除
  170. const confirmDelete = () => {
  171. if (merchantToDelete) {
  172. deleteMutation.mutate(merchantToDelete)
  173. }
  174. }
  175. // 提交表单
  176. const handleSubmit = (data: CreateRequest | UpdateRequest) => {
  177. if (isCreateForm) {
  178. createMutation.mutate(data as CreateRequest)
  179. } else if (editingMerchant) {
  180. updateMutation.mutate({ id: editingMerchant.id, data: data as UpdateRequest })
  181. }
  182. }
  183. // 状态文本
  184. const getStateText = (state: number) => {
  185. return state === 1 ? '启用' : '禁用'
  186. }
  187. const getStateBadgeVariant = (state: number) => {
  188. return state === 1 ? 'default' : 'secondary'
  189. }
  190. // 渲染加载骨架
  191. if (isLoading) {
  192. return (
  193. <div className="space-y-4">
  194. <div className="flex justify-between items-center">
  195. <Skeleton className="h-8 w-48" />
  196. <Skeleton className="h-10 w-32" />
  197. </div>
  198. <Card>
  199. <CardContent className="pt-6">
  200. <div className="space-y-3">
  201. {[...Array(5)].map((_, i) => (
  202. <div key={i} className="flex gap-4">
  203. <Skeleton className="h-10 flex-1" />
  204. <Skeleton className="h-10 flex-1" />
  205. <Skeleton className="h-10 flex-1" />
  206. <Skeleton className="h-10 w-20" />
  207. </div>
  208. ))}
  209. </div>
  210. </CardContent>
  211. </Card>
  212. </div>
  213. )
  214. }
  215. return (
  216. <div className="space-y-4">
  217. {/* 页面标题 */}
  218. <div className="flex justify-between items-center">
  219. <h1 className="text-2xl font-bold">商户管理</h1>
  220. <Button onClick={handleCreateMerchant}>
  221. <Plus className="mr-2 h-4 w-4" />
  222. 创建商户
  223. </Button>
  224. </div>
  225. {/* 搜索区域 */}
  226. <Card>
  227. <CardHeader>
  228. <CardTitle>商户列表</CardTitle>
  229. <CardDescription>管理所有商户账户信息</CardDescription>
  230. </CardHeader>
  231. <CardContent>
  232. <form onSubmit={handleSearch} className="flex gap-2 mb-4">
  233. <div className="relative flex-1 max-w-sm">
  234. <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
  235. <Input
  236. placeholder="搜索商户名称、用户名、手机号..."
  237. value={searchParams.search}
  238. onChange={(e) => setSearchParams(prev => ({ ...prev, search: e.target.value }))}
  239. className="pl-8"
  240. />
  241. </div>
  242. <Button type="submit" variant="outline">
  243. 搜索
  244. </Button>
  245. </form>
  246. {/* 数据表格 */}
  247. <div className="rounded-md border">
  248. <Table>
  249. <TableHeader>
  250. <TableRow>
  251. <TableHead>商户名称</TableHead>
  252. <TableHead>用户名</TableHead>
  253. <TableHead>姓名</TableHead>
  254. <TableHead>手机号</TableHead>
  255. <TableHead>状态</TableHead>
  256. <TableHead>登录次数</TableHead>
  257. <TableHead>创建时间</TableHead>
  258. <TableHead className="text-right">操作</TableHead>
  259. </TableRow>
  260. </TableHeader>
  261. <TableBody>
  262. {data?.data.map((merchant) => (
  263. <TableRow key={merchant.id}>
  264. <TableCell>{merchant.name || '-'}</TableCell>
  265. <TableCell>{merchant.username}</TableCell>
  266. <TableCell>{merchant.realname || '-'}</TableCell>
  267. <TableCell>{merchant.phone || '-'}</TableCell>
  268. <TableCell>
  269. <Badge variant={getStateBadgeVariant(merchant.state)}>
  270. {getStateText(merchant.state)}
  271. </Badge>
  272. </TableCell>
  273. <TableCell>{merchant.loginNum}</TableCell>
  274. <TableCell>
  275. {format(new Date(merchant.createdAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })}
  276. </TableCell>
  277. <TableCell className="text-right">
  278. <div className="flex justify-end gap-2">
  279. <Button
  280. variant="ghost"
  281. size="icon"
  282. onClick={() => handleViewDetail(merchant)}
  283. title="查看详情"
  284. >
  285. <Eye className="h-4 w-4" />
  286. </Button>
  287. <Button
  288. variant="ghost"
  289. size="icon"
  290. onClick={() => handleEditMerchant(merchant)}
  291. title="编辑"
  292. >
  293. <Edit className="h-4 w-4" />
  294. </Button>
  295. <Button
  296. variant="ghost"
  297. size="icon"
  298. onClick={() => handleDeleteMerchant(merchant.id)}
  299. title="删除"
  300. className="text-destructive hover:text-destructive"
  301. >
  302. <Trash2 className="h-4 w-4" />
  303. </Button>
  304. </div>
  305. </TableCell>
  306. </TableRow>
  307. ))}
  308. </TableBody>
  309. </Table>
  310. {data?.data.length === 0 && !isLoading && (
  311. <div className="text-center py-8">
  312. <p className="text-muted-foreground">暂无数据</p>
  313. </div>
  314. )}
  315. </div>
  316. <DataTablePagination
  317. currentPage={searchParams.page}
  318. pageSize={searchParams.limit}
  319. totalCount={data?.pagination.total || 0}
  320. onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
  321. />
  322. </CardContent>
  323. </Card>
  324. {/* 创建/编辑对话框 */}
  325. <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
  326. <DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
  327. <DialogHeader>
  328. <DialogTitle>{isCreateForm ? '创建商户' : '编辑商户'}</DialogTitle>
  329. <DialogDescription>
  330. {isCreateForm ? '创建一个新的商户账户' : '编辑现有商户信息'}
  331. </DialogDescription>
  332. </DialogHeader>
  333. {isCreateForm ? (
  334. <Form {...createForm}>
  335. <form onSubmit={createForm.handleSubmit(handleSubmit)} className="space-y-4">
  336. <FormField
  337. control={createForm.control}
  338. name="name"
  339. render={({ field }) => (
  340. <FormItem>
  341. <FormLabel>商户名称</FormLabel>
  342. <FormControl>
  343. <Input placeholder="请输入商户名称" {...field} />
  344. </FormControl>
  345. <FormMessage />
  346. </FormItem>
  347. )}
  348. />
  349. <FormField
  350. control={createForm.control}
  351. name="username"
  352. render={({ field }) => (
  353. <FormItem>
  354. <FormLabel>用户名 <span className="text-red-500">*</span></FormLabel>
  355. <FormControl>
  356. <Input placeholder="请输入用户名" {...field} />
  357. </FormControl>
  358. <FormMessage />
  359. </FormItem>
  360. )}
  361. />
  362. <FormField
  363. control={createForm.control}
  364. name="password"
  365. render={({ field }) => (
  366. <FormItem>
  367. <FormLabel>密码 <span className="text-red-500">*</span></FormLabel>
  368. <FormControl>
  369. <Input type="password" placeholder="请输入密码" {...field} />
  370. </FormControl>
  371. <FormMessage />
  372. </FormItem>
  373. )}
  374. />
  375. <FormField
  376. control={createForm.control}
  377. name="phone"
  378. render={({ field }) => (
  379. <FormItem>
  380. <FormLabel>手机号</FormLabel>
  381. <FormControl>
  382. <Input placeholder="请输入手机号" {...field} />
  383. </FormControl>
  384. <FormMessage />
  385. </FormItem>
  386. )}
  387. />
  388. <FormField
  389. control={createForm.control}
  390. name="realname"
  391. render={({ field }) => (
  392. <FormItem>
  393. <FormLabel>姓名</FormLabel>
  394. <FormControl>
  395. <Input placeholder="请输入姓名" {...field} />
  396. </FormControl>
  397. <FormMessage />
  398. </FormItem>
  399. )}
  400. />
  401. <FormField
  402. control={createForm.control}
  403. name="state"
  404. render={({ field }) => (
  405. <FormItem>
  406. <FormLabel>状态</FormLabel>
  407. <Select onValueChange={(value) => field.onChange(parseInt(value))} defaultValue={field.value?.toString()}>
  408. <FormControl>
  409. <SelectTrigger>
  410. <SelectValue placeholder="请选择状态" />
  411. </SelectTrigger>
  412. </FormControl>
  413. <SelectContent>
  414. <SelectItem value="1">启用</SelectItem>
  415. <SelectItem value="2">禁用</SelectItem>
  416. </SelectContent>
  417. </Select>
  418. <FormMessage />
  419. </FormItem>
  420. )}
  421. />
  422. <DialogFooter>
  423. <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
  424. 取消
  425. </Button>
  426. <Button type="submit" disabled={createMutation.isPending}>
  427. 创建
  428. </Button>
  429. </DialogFooter>
  430. </form>
  431. </Form>
  432. ) : (
  433. <Form {...updateForm}>
  434. <form onSubmit={updateForm.handleSubmit(handleSubmit)} className="space-y-4">
  435. <FormField
  436. control={updateForm.control}
  437. name="name"
  438. render={({ field }) => (
  439. <FormItem>
  440. <FormLabel>商户名称</FormLabel>
  441. <FormControl>
  442. <Input placeholder="请输入商户名称" {...field} />
  443. </FormControl>
  444. <FormMessage />
  445. </FormItem>
  446. )}
  447. />
  448. <FormField
  449. control={updateForm.control}
  450. name="username"
  451. render={({ field }) => (
  452. <FormItem>
  453. <FormLabel>用户名</FormLabel>
  454. <FormControl>
  455. <Input placeholder="请输入用户名" {...field} />
  456. </FormControl>
  457. <FormMessage />
  458. </FormItem>
  459. )}
  460. />
  461. <FormField
  462. control={updateForm.control}
  463. name="phone"
  464. render={({ field }) => (
  465. <FormItem>
  466. <FormLabel>手机号</FormLabel>
  467. <FormControl>
  468. <Input placeholder="请输入手机号" {...field} />
  469. </FormControl>
  470. <FormMessage />
  471. </FormItem>
  472. )}
  473. />
  474. <FormField
  475. control={updateForm.control}
  476. name="realname"
  477. render={({ field }) => (
  478. <FormItem>
  479. <FormLabel>姓名</FormLabel>
  480. <FormControl>
  481. <Input placeholder="请输入姓名" {...field} />
  482. </FormControl>
  483. <FormMessage />
  484. </FormItem>
  485. )}
  486. />
  487. <FormField
  488. control={updateForm.control}
  489. name="password"
  490. render={({ field }) => (
  491. <FormItem>
  492. <FormLabel>密码(留空则不修改)</FormLabel>
  493. <FormControl>
  494. <Input type="password" placeholder="请输入新密码" {...field} />
  495. </FormControl>
  496. <FormDescription>如果不修改密码,请留空</FormDescription>
  497. <FormMessage />
  498. </FormItem>
  499. )}
  500. />
  501. <FormField
  502. control={updateForm.control}
  503. name="state"
  504. render={({ field }) => (
  505. <FormItem>
  506. <FormLabel>状态</FormLabel>
  507. <Select onValueChange={(value) => field.onChange(parseInt(value))} value={field.value?.toString()}>
  508. <FormControl>
  509. <SelectTrigger>
  510. <SelectValue placeholder="请选择状态" />
  511. </SelectTrigger>
  512. </FormControl>
  513. <SelectContent>
  514. <SelectItem value="1">启用</SelectItem>
  515. <SelectItem value="2">禁用</SelectItem>
  516. </SelectContent>
  517. </Select>
  518. <FormMessage />
  519. </FormItem>
  520. )}
  521. />
  522. <DialogFooter>
  523. <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
  524. 取消
  525. </Button>
  526. <Button type="submit" disabled={updateMutation.isPending}>
  527. 更新
  528. </Button>
  529. </DialogFooter>
  530. </form>
  531. </Form>
  532. )}
  533. </DialogContent>
  534. </Dialog>
  535. {/* 详情对话框 */}
  536. <Dialog open={detailDialogOpen} onOpenChange={setDetailDialogOpen}>
  537. <DialogContent className="sm:max-w-[500px]">
  538. <DialogHeader>
  539. <DialogTitle>商户详情</DialogTitle>
  540. <DialogDescription>查看商户详细信息</DialogDescription>
  541. </DialogHeader>
  542. {detailMerchant && (
  543. <div className="space-y-4">
  544. <div className="grid grid-cols-2 gap-4">
  545. <div>
  546. <label className="text-sm font-medium">商户名称</label>
  547. <p className="text-sm text-muted-foreground">{detailMerchant.name || '-'}</p>
  548. </div>
  549. <div>
  550. <label className="text-sm font-medium">用户名</label>
  551. <p className="text-sm text-muted-foreground">{detailMerchant.username}</p>
  552. </div>
  553. <div>
  554. <label className="text-sm font-medium">姓名</label>
  555. <p className="text-sm text-muted-foreground">{detailMerchant.realname || '-'}</p>
  556. </div>
  557. <div>
  558. <label className="text-sm font-medium">手机号</label>
  559. <p className="text-sm text-muted-foreground">{detailMerchant.phone || '-'}</p>
  560. </div>
  561. <div>
  562. <label className="text-sm font-medium">状态</label>
  563. <p className="text-sm">
  564. <Badge variant={getStateBadgeVariant(detailMerchant.state)}>
  565. {getStateText(detailMerchant.state)}
  566. </Badge>
  567. </p>
  568. </div>
  569. <div>
  570. <label className="text-sm font-medium">登录次数</label>
  571. <p className="text-sm text-muted-foreground">{detailMerchant.loginNum}</p>
  572. </div>
  573. <div>
  574. <label className="text-sm font-medium">创建时间</label>
  575. <p className="text-sm text-muted-foreground">
  576. {format(new Date(detailMerchant.createdAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })}
  577. </p>
  578. </div>
  579. <div>
  580. <label className="text-sm font-medium">更新时间</label>
  581. <p className="text-sm text-muted-foreground">
  582. {format(new Date(detailMerchant.updatedAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })}
  583. </p>
  584. </div>
  585. </div>
  586. {detailMerchant.lastLoginTime > 0 && (
  587. <div>
  588. <label className="text-sm font-medium">最后登录时间</label>
  589. <p className="text-sm text-muted-foreground">
  590. {format(new Date(detailMerchant.lastLoginTime * 1000), 'yyyy-MM-dd HH:mm', { locale: zhCN })}
  591. </p>
  592. </div>
  593. )}
  594. {detailMerchant.lastLoginIp && (
  595. <div>
  596. <label className="text-sm font-medium">最后登录IP</label>
  597. <p className="text-sm text-muted-foreground">{detailMerchant.lastLoginIp}</p>
  598. </div>
  599. )}
  600. </div>
  601. )}
  602. <DialogFooter>
  603. <Button onClick={() => setDetailDialogOpen(false)}>关闭</Button>
  604. </DialogFooter>
  605. </DialogContent>
  606. </Dialog>
  607. {/* 删除确认对话框 */}
  608. <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
  609. <DialogContent>
  610. <DialogHeader>
  611. <DialogTitle>确认删除</DialogTitle>
  612. <DialogDescription>
  613. 确定要删除这个商户吗?此操作无法撤销。
  614. </DialogDescription>
  615. </DialogHeader>
  616. <DialogFooter>
  617. <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
  618. 取消
  619. </Button>
  620. <Button
  621. variant="destructive"
  622. onClick={confirmDelete}
  623. disabled={deleteMutation.isPending}
  624. >
  625. 删除
  626. </Button>
  627. </DialogFooter>
  628. </DialogContent>
  629. </Dialog>
  630. </div>
  631. )
  632. }