MemberList.jsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import React, { useState, useEffect, useCallback, useRef } from 'react';
  2. import { Table, Button, message, Modal, Space, Image, Input, Tag } from 'antd';
  3. import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
  4. import { deviceApi } from '../api/deviceApi';
  5. import ExcelImportExport from './ExcelImportExport';
  6. import MemberForm from './MemberForm';
  7. import Highlighter from 'react-highlight-words';
  8. const MemberList = () => {
  9. const [members, setMembers] = useState([]);
  10. const [loading, setLoading] = useState(false);
  11. const [isModalVisible, setIsModalVisible] = useState(false);
  12. const [editingMember, setEditingMember] = useState(null);
  13. const [searchText, setSearchText] = useState('');
  14. const [searchedColumn, setSearchedColumn] = useState('');
  15. const searchInput = useRef(null);
  16. const fetchMembers = useCallback(async () => {
  17. setLoading(true);
  18. try {
  19. const data = await deviceApi.getAllPersons();
  20. console.log('获取到的会友列表数据:', data);
  21. setMembers(data);
  22. message.success('会友列表获取成功');
  23. } catch (error) {
  24. console.error('获取会友列表失败', error);
  25. message.error(`获取会友列表失败: ${error.message}`);
  26. } finally {
  27. setLoading(false);
  28. }
  29. }, []);
  30. useEffect(() => {
  31. fetchMembers();
  32. }, [fetchMembers]);
  33. const handleSearch = (selectedKeys, confirm, dataIndex) => {
  34. confirm();
  35. setSearchText(selectedKeys[0]);
  36. setSearchedColumn(dataIndex);
  37. };
  38. const handleReset = (clearFilters) => {
  39. clearFilters();
  40. setSearchText('');
  41. };
  42. const getColumnSearchProps = (dataIndex) => ({
  43. filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
  44. <div style={{ padding: 8 }}>
  45. <Input
  46. ref={searchInput}
  47. placeholder={`搜索 ${dataIndex}`}
  48. value={selectedKeys[0]}
  49. onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])}
  50. onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
  51. style={{ width: 188, marginBottom: 8, display: 'block' }}
  52. />
  53. <Space>
  54. <Button
  55. type="primary"
  56. onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
  57. icon={<SearchOutlined />}
  58. size="small"
  59. style={{ width: 90 }}
  60. >
  61. 搜索
  62. </Button>
  63. <Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
  64. 重置
  65. </Button>
  66. </Space>
  67. </div>
  68. ),
  69. filterIcon: (filtered) => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
  70. onFilter: (value, record) =>
  71. record[dataIndex] ? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()) : '',
  72. onFilterDropdownVisibleChange: (visible) => {
  73. if (visible) {
  74. setTimeout(() => searchInput.current?.select(), 100);
  75. }
  76. },
  77. render: (text) =>
  78. searchedColumn === dataIndex ? (
  79. <Highlighter
  80. highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
  81. searchWords={[searchText]}
  82. autoEscape
  83. textToHighlight={text ? text.toString() : ''}
  84. />
  85. ) : (
  86. text
  87. ),
  88. });
  89. const handleAdd = () => {
  90. setEditingMember(null);
  91. setIsModalVisible(true);
  92. };
  93. const handleEdit = (record) => {
  94. setEditingMember(record);
  95. setIsModalVisible(true);
  96. };
  97. const handleDelete = (id) => {
  98. Modal.confirm({
  99. title: '确认删除',
  100. content: '您确定要删除这个会友吗?此操作不可逆。',
  101. onOk: async () => {
  102. try {
  103. await deviceApi.deletePerson(id);
  104. message.success('会友删除成功');
  105. fetchMembers();
  106. } catch (error) {
  107. console.error('删除会友失败', error);
  108. message.error(`删除会友失败: ${error.message}`);
  109. }
  110. },
  111. });
  112. };
  113. const handleSubmit = async (values) => {
  114. try {
  115. if (editingMember) {
  116. console.log('更新会员信息:', values);
  117. await deviceApi.updatePerson(editingMember.personId, values);
  118. message.success('会友信息更新成功');
  119. } else {
  120. console.log('添加新会员:', values);
  121. await deviceApi.addPerson(values);
  122. message.success('会友添加成功');
  123. }
  124. setIsModalVisible(false);
  125. fetchMembers();
  126. } catch (error) {
  127. console.error('操作失败', error);
  128. message.error(`操作失败: ${error.message}`);
  129. }
  130. };
  131. const columns = [
  132. { title: 'ID', dataIndex: 'personId', key: 'personId' },
  133. {
  134. title: '姓名',
  135. dataIndex: 'name',
  136. key: 'name',
  137. ...getColumnSearchProps('name'),
  138. },
  139. { title: '性别', dataIndex: 'gender', key: 'gender' },
  140. { title: '年龄', dataIndex: 'age', key: 'age' },
  141. { title: '出生日期', dataIndex: 'birthDate', key: 'birthDate' },
  142. { title: '婚姻状况', dataIndex: 'maritalStatus', key: 'maritalStatus' },
  143. {
  144. title: '是否受洗',
  145. dataIndex: 'isBaptized',
  146. key: 'isBaptized',
  147. render: (isBaptized) => isBaptized ? '是' : '否'
  148. },
  149. { title: '受洗日期', dataIndex: 'baptismDate', key: 'baptismDate' },
  150. {
  151. title: '联系方式',
  152. dataIndex: 'contact',
  153. key: 'contact',
  154. ...getColumnSearchProps('contact'),
  155. },
  156. {
  157. title: '服侍岗位',
  158. dataIndex: 'servicePosition',
  159. key: 'servicePosition',
  160. ...getColumnSearchProps('servicePosition'),
  161. render: (positions) => (
  162. <>
  163. {positions && positions.map((position) => (
  164. <Tag color="blue" key={position}>
  165. {position}
  166. </Tag>
  167. ))}
  168. </>
  169. ),
  170. },
  171. {
  172. title: '照片',
  173. dataIndex: 'photo',
  174. key: 'photo',
  175. render: (photo) => photo ? <Image src={photo} width={50} /> : '无照片'
  176. },
  177. {
  178. title: '操作',
  179. key: 'action',
  180. render: (_, record) => (
  181. <Space size="middle">
  182. <Button onClick={() => handleEdit(record)}>编辑</Button>
  183. <Button onClick={() => handleDelete(record.personId)} danger>删除</Button>
  184. </Space>
  185. ),
  186. },
  187. ];
  188. console.log('当前会友列表数据:', members);
  189. return (
  190. <div>
  191. <h2>会友列表</h2>
  192. <div style={{ marginBottom: 16 }}>
  193. <Button onClick={fetchMembers} type="primary" style={{ marginRight: 8 }}>
  194. 刷新列表
  195. </Button>
  196. <Button onClick={handleAdd} type="primary" icon={<PlusOutlined />}>
  197. 添加会友
  198. </Button>
  199. </div>
  200. <ExcelImportExport members={members} onImport={fetchMembers} />
  201. <Table
  202. columns={columns}
  203. dataSource={members}
  204. rowKey="personId"
  205. loading={loading}
  206. />
  207. <Modal
  208. title={editingMember ? "编辑会友" : "添加会友"}
  209. visible={isModalVisible}
  210. onCancel={() => setIsModalVisible(false)}
  211. footer={null}
  212. width={600}
  213. >
  214. <MemberForm
  215. initialValues={editingMember}
  216. onSubmit={handleSubmit}
  217. />
  218. </Modal>
  219. </div>
  220. );
  221. };
  222. export default MemberList;