|
|
@@ -0,0 +1,352 @@
|
|
|
+import React, { useState } from 'react';
|
|
|
+import {
|
|
|
+ Button, Table, Space, Form, Input, Select, DatePicker,
|
|
|
+ message, Modal, Card, Typography, Tag, Popconfirm
|
|
|
+} from 'antd';
|
|
|
+import { useQuery } from '@tanstack/react-query';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+import { believerClient } from '@/client/api';
|
|
|
+import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
+import { BelieverStatus } from '@/server/modules/believers/believer.entity';
|
|
|
+
|
|
|
+type BelieverListResponse = InferResponseType<typeof believerClient.$get, 200>;
|
|
|
+type BelieverDetailResponse = InferResponseType<typeof believerClient[':id']['$get'], 200>;
|
|
|
+type CreateBelieverRequest = InferRequestType<typeof believerClient.$post>['json'];
|
|
|
+type UpdateBelieverRequest = InferRequestType<typeof believerClient[':id']['$put']>['json'];
|
|
|
+
|
|
|
+const { Title } = Typography;
|
|
|
+const { Option } = Select;
|
|
|
+
|
|
|
+// 信徒管理页面
|
|
|
+export const BelieversPage = () => {
|
|
|
+ const [searchParams, setSearchParams] = useState({
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword: ''
|
|
|
+ });
|
|
|
+ const [modalVisible, setModalVisible] = useState(false);
|
|
|
+ const [modalTitle, setModalTitle] = useState('');
|
|
|
+ const [editingBeliever, setEditingBeliever] = useState<any>(null);
|
|
|
+ const [form] = Form.useForm();
|
|
|
+
|
|
|
+ const { data: believersData, isLoading, refetch } = useQuery({
|
|
|
+ queryKey: ['believers', searchParams],
|
|
|
+ queryFn: async () => {
|
|
|
+ const res = await believerClient.$get({
|
|
|
+ query: {
|
|
|
+ page: searchParams.page,
|
|
|
+ pageSize: searchParams.pageSize,
|
|
|
+ keyword: searchParams.keyword
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (res.status !== 200) {
|
|
|
+ throw new Error('获取信徒列表失败');
|
|
|
+ }
|
|
|
+ return await res.json();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const believers = believersData?.data || [];
|
|
|
+ const pagination = {
|
|
|
+ current: searchParams.page,
|
|
|
+ pageSize: searchParams.pageSize,
|
|
|
+ total: believersData?.pagination?.total || 0
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理搜索
|
|
|
+ const handleSearch = (values: any) => {
|
|
|
+ setSearchParams(prev => ({
|
|
|
+ ...prev,
|
|
|
+ search: values.search || '',
|
|
|
+ page: 1
|
|
|
+ }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理分页变化
|
|
|
+ const handleTableChange = (newPagination: any) => {
|
|
|
+ setSearchParams(prev => ({
|
|
|
+ ...prev,
|
|
|
+ page: newPagination.current,
|
|
|
+ pageSize: newPagination.pageSize
|
|
|
+ }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 打开创建信徒模态框
|
|
|
+ const showCreateModal = () => {
|
|
|
+ setModalTitle('创建信徒');
|
|
|
+ setEditingBeliever(null);
|
|
|
+ form.resetFields();
|
|
|
+ setModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 打开编辑信徒模态框
|
|
|
+ const showEditModal = (believer: any) => {
|
|
|
+ setModalTitle('编辑信徒');
|
|
|
+ setEditingBeliever(believer);
|
|
|
+ form.setFieldsValue({
|
|
|
+ ...believer,
|
|
|
+ birthDate: believer.birthDate ? dayjs(believer.birthDate) : null,
|
|
|
+ baptismDate: believer.baptismDate ? dayjs(believer.baptismDate) : null
|
|
|
+ });
|
|
|
+ setModalVisible(true);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理模态框确认
|
|
|
+ const handleModalOk = async () => {
|
|
|
+ try {
|
|
|
+ const values = await form.validateFields();
|
|
|
+
|
|
|
+ // 转换日期格式
|
|
|
+ const formattedValues = {
|
|
|
+ ...values,
|
|
|
+ birthDate: values.birthDate ? values.birthDate.format('YYYY-MM-DD') : null,
|
|
|
+ baptismDate: values.baptismDate ? values.baptismDate.format('YYYY-MM-DD') : null
|
|
|
+ };
|
|
|
+
|
|
|
+ if (editingBeliever) {
|
|
|
+ // 编辑信徒
|
|
|
+ const res = await believerClient[':id']['$put']({
|
|
|
+ param: { id: editingBeliever.id },
|
|
|
+ json: formattedValues
|
|
|
+ });
|
|
|
+ if (res.status !== 200) {
|
|
|
+ throw new Error('更新信徒失败');
|
|
|
+ }
|
|
|
+ message.success('信徒更新成功');
|
|
|
+ } else {
|
|
|
+ // 创建信徒
|
|
|
+ const res = await believerClient.$post({
|
|
|
+ json: formattedValues
|
|
|
+ });
|
|
|
+ if (res.status !== 201) {
|
|
|
+ throw new Error('创建信徒失败');
|
|
|
+ }
|
|
|
+ message.success('信徒创建成功');
|
|
|
+ }
|
|
|
+
|
|
|
+ setModalVisible(false);
|
|
|
+ form.resetFields();
|
|
|
+ refetch(); // 刷新信徒列表
|
|
|
+ } catch (error) {
|
|
|
+ console.error('表单提交失败:', error);
|
|
|
+ message.error('操作失败,请重试');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理删除信徒
|
|
|
+ const handleDelete = async (id: number) => {
|
|
|
+ try {
|
|
|
+ const res = await believerClient[':id']['$delete']({
|
|
|
+ param: { id }
|
|
|
+ });
|
|
|
+ if (res.status !== 204) {
|
|
|
+ throw new Error('删除信徒失败');
|
|
|
+ }
|
|
|
+ message.success('信徒删除成功');
|
|
|
+ refetch(); // 刷新信徒列表
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除信徒失败:', error);
|
|
|
+ message.error('删除失败,请重试');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 状态标签格式化
|
|
|
+ const formatStatusTag = (status: string) => {
|
|
|
+ const statusMap: Record<string, { label: string; color: string }> = {
|
|
|
+ [BelieverStatus.ACTIVE]: { label: '活跃', color: 'green' },
|
|
|
+ [BelieverStatus.INACTIVE]: { label: '不活跃', color: 'orange' },
|
|
|
+ [BelieverStatus.TRANSFERRED]: { label: '已转会', color: 'purple' },
|
|
|
+ [BelieverStatus.DECEASED]: { label: '已故', color: 'red' }
|
|
|
+ };
|
|
|
+
|
|
|
+ const statusInfo = statusMap[status] || { label: status, color: 'default' };
|
|
|
+ return <Tag color={statusInfo.color}>{statusInfo.label}</Tag>;
|
|
|
+ };
|
|
|
+
|
|
|
+ const columns = [
|
|
|
+ {
|
|
|
+ title: '姓名',
|
|
|
+ dataIndex: 'name',
|
|
|
+ key: 'name',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '性别',
|
|
|
+ dataIndex: 'gender',
|
|
|
+ key: 'gender',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '出生日期',
|
|
|
+ dataIndex: 'birthDate',
|
|
|
+ key: 'birthDate',
|
|
|
+ render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '联系方式',
|
|
|
+ dataIndex: 'contactInfo',
|
|
|
+ key: 'contactInfo',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '受洗日期',
|
|
|
+ dataIndex: 'baptismDate',
|
|
|
+ key: 'baptismDate',
|
|
|
+ render: (date: string) => date ? dayjs(date).format('YYYY-MM-DD') : '-',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '状态',
|
|
|
+ dataIndex: 'status',
|
|
|
+ key: 'status',
|
|
|
+ render: (status: string) => formatStatusTag(status),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '创建时间',
|
|
|
+ dataIndex: 'createdAt',
|
|
|
+ key: 'createdAt',
|
|
|
+ render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'action',
|
|
|
+ render: (_: any, record: any) => (
|
|
|
+ <Space size="middle">
|
|
|
+ <Button type="link" onClick={() => showEditModal(record)}>
|
|
|
+ 编辑
|
|
|
+ </Button>
|
|
|
+ <Popconfirm
|
|
|
+ title="确定要删除此信徒记录吗?"
|
|
|
+ onConfirm={() => handleDelete(record.id)}
|
|
|
+ okText="确定"
|
|
|
+ cancelText="取消"
|
|
|
+ >
|
|
|
+ <Button type="link" danger>
|
|
|
+ 删除
|
|
|
+ </Button>
|
|
|
+ </Popconfirm>
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <Title level={2}>信徒管理</Title>
|
|
|
+ <Card>
|
|
|
+ <Form layout="inline" onFinish={handleSearch} style={{ marginBottom: 16 }}>
|
|
|
+ <Form.Item name="search" label="搜索">
|
|
|
+ <Input placeholder="姓名/联系方式" allowClear />
|
|
|
+ </Form.Item>
|
|
|
+ <Form.Item>
|
|
|
+ <Space>
|
|
|
+ <Button type="primary" htmlType="submit">
|
|
|
+ 搜索
|
|
|
+ </Button>
|
|
|
+ <Button type="primary" onClick={showCreateModal}>
|
|
|
+ 创建信徒
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+
|
|
|
+ <Table
|
|
|
+ columns={columns}
|
|
|
+ dataSource={believers}
|
|
|
+ loading={isLoading}
|
|
|
+ rowKey="id"
|
|
|
+ pagination={{
|
|
|
+ ...pagination,
|
|
|
+ showSizeChanger: true,
|
|
|
+ showQuickJumper: true,
|
|
|
+ showTotal: (total) => `共 ${total} 条记录`
|
|
|
+ }}
|
|
|
+ onChange={handleTableChange}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+
|
|
|
+ {/* 创建/编辑信徒模态框 */}
|
|
|
+ <Modal
|
|
|
+ title={modalTitle}
|
|
|
+ open={modalVisible}
|
|
|
+ onOk={handleModalOk}
|
|
|
+ onCancel={() => {
|
|
|
+ setModalVisible(false);
|
|
|
+ form.resetFields();
|
|
|
+ }}
|
|
|
+ width={600}
|
|
|
+ >
|
|
|
+ <Form
|
|
|
+ form={form}
|
|
|
+ layout="vertical"
|
|
|
+ >
|
|
|
+ <Form.Item
|
|
|
+ name="name"
|
|
|
+ label="姓名"
|
|
|
+ rules={[
|
|
|
+ { required: true, message: '请输入姓名' },
|
|
|
+ { max: 100, message: '姓名不能超过100个字符' }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <Input placeholder="请输入姓名" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="gender"
|
|
|
+ label="性别"
|
|
|
+ rules={[{ required: true, message: '请选择性别' }]}
|
|
|
+ >
|
|
|
+ <Select placeholder="请选择性别">
|
|
|
+ <Option value="男">男</Option>
|
|
|
+ <Option value="女">女</Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="birthDate"
|
|
|
+ label="出生日期"
|
|
|
+ >
|
|
|
+ <DatePicker format="YYYY-MM-DD" placeholder="请选择出生日期" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="contactInfo"
|
|
|
+ label="联系方式"
|
|
|
+ rules={[
|
|
|
+ { required: true, message: '请输入联系方式' },
|
|
|
+ { max: 200, message: '联系方式不能超过200个字符' }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <Input placeholder="请输入联系方式" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="address"
|
|
|
+ label="住址"
|
|
|
+ rules={[
|
|
|
+ { max: 255, message: '住址不能超过255个字符' }
|
|
|
+ ]}
|
|
|
+ >
|
|
|
+ <Input placeholder="请输入住址" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="baptismDate"
|
|
|
+ label="受洗日期"
|
|
|
+ >
|
|
|
+ <DatePicker format="YYYY-MM-DD" placeholder="请选择受洗日期" />
|
|
|
+ </Form.Item>
|
|
|
+
|
|
|
+ <Form.Item
|
|
|
+ name="status"
|
|
|
+ label="状态"
|
|
|
+ rules={[{ required: true, message: '请选择状态' }]}
|
|
|
+ >
|
|
|
+ <Select placeholder="请选择状态">
|
|
|
+ <Option value={BelieverStatus.ACTIVE}>活跃</Option>
|
|
|
+ <Option value={BelieverStatus.INACTIVE}>不活跃</Option>
|
|
|
+ <Option value={BelieverStatus.TRANSFERRED}>已转会</Option>
|
|
|
+ <Option value={BelieverStatus.DECEASED}>已故</Option>
|
|
|
+ </Select>
|
|
|
+ </Form.Item>
|
|
|
+ </Form>
|
|
|
+ </Modal>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|