pages_date_notes.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import React, { useState } from 'react';
  2. import { useQueryClient } from '@tanstack/react-query';
  3. import {
  4. Button, Table, Space,
  5. Form, Input, DatePicker, message, Modal,
  6. Card, Row, Col,
  7. Popconfirm, Tag
  8. } from 'antd';
  9. import {
  10. useQuery,
  11. } from '@tanstack/react-query';
  12. import dayjs from 'dayjs';
  13. import weekday from 'dayjs/plugin/weekday';
  14. import localeData from 'dayjs/plugin/localeData';
  15. import 'dayjs/locale/zh-cn';
  16. import type {
  17. DateNote
  18. } from '../share/types_stock.ts';
  19. import { DateNotesAPI } from './api/date_notes.ts';
  20. // 配置 dayjs 插件
  21. dayjs.extend(weekday);
  22. dayjs.extend(localeData);
  23. // 设置 dayjs 语言
  24. dayjs.locale('zh-cn');
  25. export const DateNotesPage = () => {
  26. const queryClient = useQueryClient();
  27. const [modalVisible, setModalVisible] = useState(false);
  28. const [formMode, setFormMode] = useState<'create' | 'edit'>('create');
  29. const [editingId, setEditingId] = useState<number | null>(null);
  30. const [form] = Form.useForm();
  31. const [searchForm] = Form.useForm();
  32. const [searchParams, setSearchParams] = useState({
  33. code: '',
  34. start_date: '',
  35. end_date: '',
  36. page: 1,
  37. limit: 10,
  38. });
  39. // 使用React Query获取日期备注列表
  40. const { data: notesData, isLoading: isListLoading, refetch } = useQuery({
  41. queryKey: ['dateNotes', searchParams],
  42. queryFn: () => DateNotesAPI.getDateNotes({
  43. page: searchParams.page,
  44. pageSize: searchParams.limit,
  45. code: searchParams.code,
  46. start_date: searchParams.start_date,
  47. end_date: searchParams.end_date
  48. }),
  49. placeholderData: {
  50. data: [],
  51. pagination: {
  52. current: 1,
  53. pageSize: 10,
  54. total: 0,
  55. totalPages: 1
  56. }
  57. }
  58. });
  59. const notes = React.useMemo(() => notesData?.data || [], [notesData]);
  60. const pagination = React.useMemo(() => ({
  61. current: notesData?.pagination?.current || 1,
  62. pageSize: notesData?.pagination?.pageSize || 10,
  63. total: notesData?.pagination?.total || 0,
  64. totalPages: notesData?.pagination?.totalPages || 1
  65. }), [notesData]);
  66. // 获取单个日期备注
  67. const fetchNote = async (id: number) => {
  68. try {
  69. const response = await DateNotesAPI.getDateNote(id);
  70. return response.data;
  71. } catch (error) {
  72. message.error('获取日期备注详情失败');
  73. return null;
  74. }
  75. };
  76. // 处理表单提交
  77. const handleSubmit = async (values: Partial<DateNote>) => {
  78. try {
  79. const response = formMode === 'create'
  80. ? await DateNotesAPI.createDateNote(values)
  81. : await DateNotesAPI.updateDateNote(editingId!, values);
  82. message.success(formMode === 'create' ? '创建日期备注成功' : '更新日期备注成功');
  83. setModalVisible(false);
  84. form.resetFields();
  85. refetch();
  86. } catch (error) {
  87. message.error((error as Error).message);
  88. }
  89. };
  90. // 处理编辑
  91. const handleEdit = async (id: number) => {
  92. const note = await fetchNote(id);
  93. if (note) {
  94. setFormMode('edit');
  95. setEditingId(id);
  96. form.setFieldsValue({
  97. ...note,
  98. note_date: note.note_date ? dayjs(note.note_date) : null
  99. });
  100. setModalVisible(true);
  101. }
  102. };
  103. // 处理删除
  104. const handleDelete = async (id: number) => {
  105. try {
  106. await DateNotesAPI.deleteDateNote(id);
  107. message.success('删除日期备注成功');
  108. refetch();
  109. } catch (error) {
  110. message.error((error as Error).message);
  111. }
  112. };
  113. // 处理搜索
  114. const handleSearch = async (values: any) => {
  115. try {
  116. queryClient.removeQueries({ queryKey: ['dateNotes'] });
  117. setSearchParams({
  118. code: values.code || '',
  119. start_date: values.dateRange?.[0]?.format('YYYY-MM-DD') || '',
  120. end_date: values.dateRange?.[1]?.format('YYYY-MM-DD') || '',
  121. page: 1,
  122. limit: searchParams.limit,
  123. });
  124. } catch (error) {
  125. message.error('搜索失败');
  126. }
  127. };
  128. // 处理分页
  129. const handlePageChange = (page: number, pageSize?: number) => {
  130. setSearchParams(prev => ({
  131. ...prev,
  132. page,
  133. limit: pageSize || prev.limit,
  134. }));
  135. };
  136. // 处理添加
  137. const handleAdd = () => {
  138. setFormMode('create');
  139. setEditingId(null);
  140. form.resetFields();
  141. setModalVisible(true);
  142. };
  143. // 表格列定义
  144. const columns = [
  145. {
  146. title: 'ID',
  147. dataIndex: 'id',
  148. key: 'id',
  149. width: 80,
  150. },
  151. {
  152. title: '股票代码',
  153. dataIndex: 'code',
  154. key: 'code',
  155. },
  156. {
  157. title: '日期',
  158. dataIndex: 'note_date',
  159. key: 'note_date',
  160. render: (date: string) => dayjs(date).format('YYYY-MM-DD'),
  161. },
  162. {
  163. title: '备注内容',
  164. dataIndex: 'note',
  165. key: 'note',
  166. ellipsis: true,
  167. },
  168. {
  169. title: '更新时间',
  170. dataIndex: 'updated_at',
  171. key: 'updated_at',
  172. render: (date: string) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'),
  173. },
  174. {
  175. title: '操作',
  176. key: 'action',
  177. render: (_: any, record: DateNote) => (
  178. <Space size="middle">
  179. <Button type="link" onClick={() => handleEdit(record.id)}>编辑</Button>
  180. <Popconfirm
  181. title="确定要删除这条备注吗?"
  182. onConfirm={() => handleDelete(record.id)}
  183. okText="确定"
  184. cancelText="取消"
  185. >
  186. <Button type="link" danger>删除</Button>
  187. </Popconfirm>
  188. </Space>
  189. ),
  190. },
  191. ];
  192. return (
  193. <div>
  194. <Card title="日期备注管理" className="mb-4">
  195. <Form
  196. form={searchForm}
  197. layout="inline"
  198. onFinish={handleSearch}
  199. style={{ marginBottom: '16px' }}
  200. >
  201. <Form.Item name="code" label="股票代码">
  202. <Input placeholder="要搜索的股票代码" />
  203. </Form.Item>
  204. <Form.Item name="dateRange" label="日期范围">
  205. <DatePicker.RangePicker />
  206. </Form.Item>
  207. <Form.Item>
  208. <Space>
  209. <Button type="primary" htmlType="submit">
  210. 搜索
  211. </Button>
  212. <Button htmlType="reset" onClick={() => {
  213. searchForm.resetFields();
  214. setSearchParams({
  215. code: '',
  216. start_date: '',
  217. end_date: '',
  218. page: 1,
  219. limit: 10,
  220. });
  221. }}>
  222. 重置
  223. </Button>
  224. <Button type="primary" onClick={handleAdd}>
  225. 添加备注
  226. </Button>
  227. </Space>
  228. </Form.Item>
  229. </Form>
  230. <Table
  231. columns={columns}
  232. dataSource={notes}
  233. rowKey="id"
  234. loading={{
  235. spinning: isListLoading,
  236. tip: '正在加载数据...',
  237. }}
  238. pagination={{
  239. current: pagination.current,
  240. pageSize: pagination.pageSize,
  241. total: pagination.total,
  242. onChange: handlePageChange,
  243. showSizeChanger: true,
  244. showTotal: (total) => `共 ${total} 条`,
  245. }}
  246. />
  247. </Card>
  248. <Modal
  249. title={formMode === 'create' ? '添加日期备注' : '编辑日期备注'}
  250. open={modalVisible}
  251. onOk={() => {
  252. form.validateFields()
  253. .then(values => {
  254. handleSubmit({
  255. ...values,
  256. note_date: values.note_date?.format('YYYY-MM-DD')
  257. });
  258. })
  259. .catch(info => {
  260. console.log('表单验证失败:', info);
  261. });
  262. }}
  263. onCancel={() => setModalVisible(false)}
  264. width={800}
  265. okText="确定"
  266. cancelText="取消"
  267. destroyOnClose
  268. >
  269. <Form
  270. form={form}
  271. layout="vertical"
  272. >
  273. <Row gutter={16}>
  274. <Col span={12}>
  275. <Form.Item
  276. name="code"
  277. label="股票代码"
  278. rules={[{ required: true, message: '请输入股票代码' }]}
  279. >
  280. <Input placeholder="请输入股票代码" />
  281. </Form.Item>
  282. </Col>
  283. <Col span={12}>
  284. <Form.Item
  285. name="note_date"
  286. label="日期"
  287. rules={[{ required: true, message: '请选择日期' }]}
  288. >
  289. <DatePicker style={{ width: '100%' }} />
  290. </Form.Item>
  291. </Col>
  292. </Row>
  293. <Form.Item
  294. name="note"
  295. label="备注内容"
  296. rules={[{ required: true, message: '请输入备注内容' }]}
  297. >
  298. <Input.TextArea rows={8} placeholder="请输入备注内容" />
  299. </Form.Item>
  300. </Form>
  301. </Modal>
  302. </div>
  303. );
  304. };