pages_xunlian_codes.tsx 8.5 KB

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