Activities.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import React from 'react';
  2. import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
  3. import { Button } from '@/client/components/ui/button';
  4. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
  5. import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
  6. import { DataTablePagination } from '../components/DataTablePagination';
  7. import { Plus, Edit, Trash2, Calendar } from 'lucide-react';
  8. import { useState } from 'react';
  9. import { activityClient } from '@/client/api';
  10. import type { InferResponseType } from 'hono/client';
  11. // 类型提取规范
  12. type ActivityResponse = InferResponseType<typeof activityClient.$get, 200>['data'][0];
  13. // 统一操作处理函数
  14. const handleOperation = async (operation: () => Promise<any>) => {
  15. try {
  16. await operation();
  17. // toast.success('操作成功');
  18. console.log('操作成功');
  19. } catch (error) {
  20. console.error('操作失败:', error);
  21. // toast.error('操作失败,请重试');
  22. throw error;
  23. }
  24. };
  25. export const ActivitiesPage: React.FC = () => {
  26. const queryClient = useQueryClient();
  27. const [page, setPage] = useState(1);
  28. const [pageSize, setPageSize] = useState(20);
  29. // 获取活动列表 - 使用RPC客户端
  30. const { data, isLoading, error } = useQuery({
  31. queryKey: ['activities', page, pageSize],
  32. queryFn: async () => {
  33. const res = await activityClient.$get({
  34. query: {
  35. page,
  36. pageSize
  37. }
  38. });
  39. if (res.status !== 200) throw new Error('获取活动列表失败');
  40. return await res.json();
  41. },
  42. staleTime: 5 * 60 * 1000, // 5分钟缓存
  43. });
  44. // 删除活动 - 使用RPC客户端
  45. const deleteMutation = useMutation({
  46. mutationFn: async (id: number) => {
  47. await handleOperation(async () => {
  48. const res = await activityClient[':id'].$delete({
  49. param: { id }
  50. });
  51. if (res.status !== 204) throw new Error('删除活动失败');
  52. });
  53. },
  54. onSuccess: () => {
  55. queryClient.invalidateQueries({ queryKey: ['activities'] });
  56. },
  57. });
  58. if (error) {
  59. return (
  60. <div className="p-6">
  61. <Card>
  62. <CardContent className="pt-6">
  63. <div className="text-center text-red-500">
  64. 加载活动数据失败: {error.message}
  65. </div>
  66. </CardContent>
  67. </Card>
  68. </div>
  69. );
  70. }
  71. return (
  72. <div className="p-6">
  73. <div className="flex items-center justify-between mb-6">
  74. <div>
  75. <h1 className="text-3xl font-bold tracking-tight">活动管理</h1>
  76. <p className="text-muted-foreground">
  77. 管理旅行活动,包括去程和返程活动
  78. </p>
  79. </div>
  80. <Button>
  81. <Plus className="h-4 w-4 mr-2" />
  82. 新建活动
  83. </Button>
  84. </div>
  85. <Card>
  86. <CardHeader>
  87. <CardTitle>活动列表</CardTitle>
  88. <CardDescription>
  89. 当前共有 {data?.pagination.total || 0} 个活动
  90. </CardDescription>
  91. </CardHeader>
  92. <CardContent>
  93. <div className="rounded-md border">
  94. <Table>
  95. <TableHeader>
  96. <TableRow>
  97. <TableHead>活动名称</TableHead>
  98. <TableHead>类型</TableHead>
  99. <TableHead>开始时间</TableHead>
  100. <TableHead>结束时间</TableHead>
  101. <TableHead>状态</TableHead>
  102. <TableHead className="text-right">操作</TableHead>
  103. </TableRow>
  104. </TableHeader>
  105. <TableBody>
  106. {isLoading ? (
  107. <TableRow>
  108. <TableCell colSpan={6} className="text-center py-4">
  109. 加载中...
  110. </TableCell>
  111. </TableRow>
  112. ) : data?.data && data.data.length > 0 ? (
  113. data.data.map((activity: ActivityResponse) => (
  114. <TableRow key={activity.id}>
  115. <TableCell>
  116. <div className="flex items-center gap-2">
  117. <Calendar className="h-4 w-4 text-blue-500" />
  118. <span>{activity.name}</span>
  119. </div>
  120. </TableCell>
  121. <TableCell>
  122. <span className={`px-2 py-1 rounded-full text-xs ${
  123. activity.type === 'departure'
  124. ? 'bg-blue-100 text-blue-800'
  125. : 'bg-green-100 text-green-800'
  126. }`}>
  127. {activity.type === 'departure' ? '去程' : '返程'}
  128. </span>
  129. </TableCell>
  130. <TableCell>
  131. {new Date(activity.startDate).toLocaleString('zh-CN')}
  132. </TableCell>
  133. <TableCell>
  134. {new Date(activity.endDate).toLocaleString('zh-CN')}
  135. </TableCell>
  136. <TableCell>
  137. <span className={`px-2 py-1 rounded-full text-xs ${
  138. activity.isDisabled === 0
  139. ? 'bg-green-100 text-green-800'
  140. : 'bg-red-100 text-red-800'
  141. }`}>
  142. {activity.isDisabled === 0 ? '启用' : '禁用'}
  143. </span>
  144. </TableCell>
  145. <TableCell className="text-right">
  146. <div className="flex justify-end gap-2">
  147. <Button
  148. variant="outline"
  149. size="sm"
  150. onClick={() => {
  151. // TODO: 编辑活动
  152. console.log('编辑活动:', activity.id);
  153. }}
  154. >
  155. <Edit className="h-4 w-4" />
  156. </Button>
  157. <Button
  158. variant="destructive"
  159. size="sm"
  160. onClick={() => {
  161. if (confirm('确定要删除这个活动吗?')) {
  162. deleteMutation.mutate(activity.id);
  163. }
  164. }}
  165. >
  166. <Trash2 className="h-4 w-4" />
  167. </Button>
  168. </div>
  169. </TableCell>
  170. </TableRow>
  171. ))
  172. ) : (
  173. <TableRow>
  174. <TableCell colSpan={6} className="text-center py-4">
  175. 暂无活动数据
  176. </TableCell>
  177. </TableRow>
  178. )}
  179. </TableBody>
  180. </Table>
  181. </div>
  182. {data && (
  183. <DataTablePagination
  184. currentPage={data.pagination.current}
  185. totalCount={data.pagination.total}
  186. pageSize={data.pagination.pageSize}
  187. onPageChange={(page, pageSize) => {
  188. setPage(page);
  189. setPageSize(pageSize);
  190. }}
  191. />
  192. )}
  193. </CardContent>
  194. </Card>
  195. </div>
  196. );
  197. };