BasicDataMaintenance.tsx 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. import React, { useState, useEffect, useCallback } from 'react';
  2. import {
  3. Card, Table, Button, Input, Select, Space, Modal, Form, Row, Col,
  4. Tag, message, Popconfirm, DatePicker, ConfigProvider
  5. } from 'antd';
  6. import zhCN from 'antd/locale/zh_CN';
  7. import dayjs, { type Dayjs } from 'dayjs';
  8. import {
  9. PlusOutlined, SearchOutlined, ReloadOutlined, DeleteOutlined, EditOutlined,
  10. DatabaseOutlined
  11. } from '@ant-design/icons';
  12. import type { ColumnsType } from 'antd/es/table';
  13. import {
  14. queryBasicData, saveBasicData, deleteBasicData,
  15. queryBasicDataSub, saveBasicDataSub, deleteBasicDataSub,
  16. getProvinceData, getCityData,
  17. type BasicDataItem, type BasicDataSubItem,
  18. type SaveBasicDataParams, type SaveBasicDataSubParams,
  19. type ProvinceItem, type CityItem
  20. } from '../../api/basicData';
  21. const { Option } = Select;
  22. const BasicDataMaintenance: React.FC = () => {
  23. const [data, setData] = useState<BasicDataItem[]>([]);
  24. const [total, setTotal] = useState(0);
  25. const [loading, setLoading] = useState(false);
  26. const [currentPage, setCurrentPage] = useState(1);
  27. const [pageSize, setPageSize] = useState(15);
  28. // 选中主表行
  29. const [selectedRow, setSelectedRow] = useState<BasicDataItem | null>(null);
  30. const [subData, setSubData] = useState<BasicDataSubItem[]>([]);
  31. const [subTotal, setSubTotal] = useState(0);
  32. const [subLoading, setSubLoading] = useState(false);
  33. const [subCurrentPage, setSubCurrentPage] = useState(1);
  34. const [subPageSize, setSubPageSize] = useState(15);
  35. // 查询条件
  36. const [mainSearch, setMainSearch] = useState({ insuDesc: '', status: '' });
  37. const [subSearch, setSubSearch] = useState({ desc: '', status: '' });
  38. // 主表弹窗
  39. const [modalOpen, setModalOpen] = useState(false);
  40. const [editRecord, setEditRecord] = useState<BasicDataItem | null>(null);
  41. const [saving, setSaving] = useState(false);
  42. const [form] = Form.useForm<SaveBasicDataParams>();
  43. // 明细弹窗
  44. const [subModalOpen, setSubModalOpen] = useState(false);
  45. const [subEditRecord, setSubEditRecord] = useState<BasicDataSubItem | null>(null);
  46. const [subSaving, setSubSaving] = useState(false);
  47. const [subForm] = Form.useForm<SaveBasicDataSubParams>();
  48. // 弹框省、市下拉数据
  49. const [provinceList, setProvinceList] = useState<ProvinceItem[]>([]);
  50. const [cityList, setCityList] = useState<CityItem[]>([]);
  51. const [provinceLoading, setProvinceLoading] = useState(false);
  52. const [cityLoading, setCityLoading] = useState(false);
  53. // 加载主表数据
  54. const fetchData = useCallback(async (page = currentPage, size = pageSize) => {
  55. setLoading(true);
  56. try {
  57. const res = await queryBasicData(
  58. {
  59. insuDesc: mainSearch.insuDesc || undefined,
  60. status: mainSearch.status || undefined
  61. },
  62. { pageSize: size, currentPage: page }
  63. );
  64. if (res.errorCode === '0' && res.result) {
  65. setData(res.result.rows || []);
  66. setTotal(res.result.total || 0);
  67. } else {
  68. message.error(res.errorMessage || '查询失败');
  69. }
  70. } catch {
  71. message.error('网络异常');
  72. } finally {
  73. setLoading(false);
  74. }
  75. }, [mainSearch, currentPage, pageSize]);
  76. useEffect(() => {
  77. fetchData(1, pageSize);
  78. setCurrentPage(1);
  79. }, [mainSearch]);
  80. // 加载明细数据(支持分页)
  81. const fetchSubData = useCallback(async (page = subCurrentPage, size = subPageSize) => {
  82. if (!selectedRow) return;
  83. setSubLoading(true);
  84. try {
  85. // 使用主表返回的 dictID 作为查询明细的入参
  86. const res = await queryBasicDataSub(
  87. {
  88. dictID: selectedRow.dictID || selectedRow.id,
  89. desc: subSearch.desc || undefined,
  90. status: subSearch.status || undefined
  91. },
  92. { pageSize: size, currentPage: page }
  93. );
  94. if (res.errorCode === '0' && res.result) {
  95. setSubData(res.result.rows || []);
  96. setSubTotal(res.result.total || 0);
  97. } else {
  98. message.error(res.errorMessage || '查询明细失败');
  99. }
  100. } catch {
  101. message.error('网络异常');
  102. } finally {
  103. setSubLoading(false);
  104. }
  105. }, [selectedRow, subSearch, subCurrentPage, subPageSize]);
  106. // 选中主表行时,自动查询明细数据
  107. useEffect(() => {
  108. if (selectedRow && selectedRow.dictID) {
  109. setSubCurrentPage(1);
  110. fetchSubData(1, subPageSize);
  111. }
  112. }, [selectedRow?.dictID, fetchSubData]);
  113. const handleMainSearch = () => {
  114. setCurrentPage(1);
  115. fetchData(1, pageSize);
  116. };
  117. const handleMainReset = () => {
  118. setMainSearch({ insuDesc: '', status: '' });
  119. };
  120. const handleSubSearch = () => {
  121. fetchSubData();
  122. };
  123. const handleSubReset = () => {
  124. setSubSearch({ desc: '', status: '' });
  125. };
  126. // 主表操作
  127. const handleAdd = async () => {
  128. setEditRecord(null);
  129. form.resetFields();
  130. setCityList([]);
  131. setModalOpen(true);
  132. // 获取省下拉数据
  133. setProvinceLoading(true);
  134. try {
  135. const res = await getProvinceData();
  136. if (res.errorCode === '0' && res.result) {
  137. setProvinceList(res.result);
  138. }
  139. } finally {
  140. setProvinceLoading(false);
  141. }
  142. };
  143. const handleEdit = async (record: BasicDataItem) => {
  144. setEditRecord(record);
  145. setModalOpen(true);
  146. form.resetFields();
  147. setCityList([]);
  148. // 先获取下拉数据
  149. setProvinceLoading(true);
  150. let currentProvinceList: ProvinceItem[] = [];
  151. let currentCityList: CityItem[] = [];
  152. try {
  153. const res = await getProvinceData();
  154. if (res.errorCode === '0' && res.result) {
  155. currentProvinceList = res.result;
  156. setProvinceList(currentProvinceList);
  157. }
  158. } finally {
  159. setProvinceLoading(false);
  160. }
  161. // 找到省对应的ID(优先用ID匹配,如果没有则用名称匹配)
  162. let provinceValue = record.provinceID || record.provinceDr;
  163. let matchedProvinceId = '';
  164. if (currentProvinceList.length > 0) {
  165. // 先用ID匹配
  166. let matched = currentProvinceList.find(p =>
  167. String(p.id) === String(record.provinceID || record.provinceDr) ||
  168. String(p.code) === String(record.provinceID || record.provinceDr)
  169. );
  170. // 如果ID没匹配到,再用名称匹配
  171. if (!matched && record.provinceDesc) {
  172. matched = currentProvinceList.find(p =>
  173. p.descripts === record.provinceDesc ||
  174. p.descripts.includes(record.provinceDesc) ||
  175. record.provinceDesc.includes(p.descripts)
  176. );
  177. }
  178. if (matched) {
  179. provinceValue = matched.id;
  180. matchedProvinceId = matched.id;
  181. }
  182. }
  183. // 根据匹配到的省ID获取市数据
  184. if (matchedProvinceId) {
  185. setCityLoading(true);
  186. try {
  187. const cityRes = await getCityData(matchedProvinceId);
  188. if (cityRes.errorCode === '0' && cityRes.result) {
  189. currentCityList = cityRes.result;
  190. setCityList(currentCityList);
  191. }
  192. } finally {
  193. setCityLoading(false);
  194. }
  195. }
  196. // 找到市对应的ID(优先用ID匹配,如果没有则用名称匹配)
  197. let cityValue = record.cityID || record.cityDr;
  198. if (currentCityList.length > 0) {
  199. // 先用ID匹配
  200. let matched = currentCityList.find(c =>
  201. String(c.id) === String(record.cityID || record.cityDr) ||
  202. String(c.code) === String(record.cityID || record.cityDr)
  203. );
  204. // 如果ID没匹配到,再用名称匹配
  205. if (!matched && record.cityDesc) {
  206. matched = currentCityList.find(c =>
  207. c.descripts === record.cityDesc ||
  208. c.descripts.includes(record.cityDesc) ||
  209. record.cityDesc.includes(c.descripts)
  210. );
  211. }
  212. if (matched) {
  213. cityValue = matched.id;
  214. }
  215. }
  216. // 设置表单值(使用省市的ID,Select会根据ID匹配显示对应的中文)
  217. form.setFieldsValue({
  218. insuCode: record.insuCode,
  219. insuDesc: record.insuDesc,
  220. provinceID: provinceValue,
  221. cityID: cityValue,
  222. areaID: record.areaID || record.areaDr,
  223. startDate: record.startDate ? dayjs(record.startDate.split(' ')[0]) as unknown as string : null,
  224. stopDate: record.stopDate ? dayjs(record.stopDate.split(' ')[0]) as unknown as string : null,
  225. identification: record.identification,
  226. remark: record.remark,
  227. });
  228. };
  229. const handleDelete = async (id: string) => {
  230. try {
  231. const res = await deleteBasicData(id);
  232. if (res.errorCode === '0') {
  233. message.success('删除成功');
  234. fetchData();
  235. if (selectedRow?.id === id) {
  236. setSelectedRow(null);
  237. setSubData([]);
  238. }
  239. } else {
  240. message.error(res.errorMessage || '删除失败');
  241. }
  242. } catch {
  243. message.error('网络异常');
  244. }
  245. };
  246. const handleSave = async () => {
  247. try {
  248. const values = await form.validateFields();
  249. setSaving(true);
  250. // dictID 取查询接口02010011返回的dictID,为空表示新增,非空表示更新
  251. const dictID = editRecord?.dictID || '';
  252. const params: SaveBasicDataParams = {
  253. ...values,
  254. dictID,
  255. startDate: (values.startDate as unknown as Dayjs)?.format('YYYY-MM-DD') || '',
  256. stopDate: (values.stopDate as unknown as Dayjs)?.format('YYYY-MM-DD') || '',
  257. };
  258. const res = await saveBasicData(params);
  259. if (res.errorCode === '0') {
  260. message.success(editRecord ? '修改成功' : '新增成功');
  261. setModalOpen(false);
  262. fetchData();
  263. } else {
  264. message.error(res.errorMessage || '保存失败');
  265. }
  266. } catch {
  267. // form validation error
  268. } finally {
  269. setSaving(false);
  270. }
  271. };
  272. // 明细操作
  273. const handleSubAdd = () => {
  274. if (!selectedRow) {
  275. message.warning('请先选择主表记录');
  276. return;
  277. }
  278. setSubEditRecord(null);
  279. subForm.resetFields();
  280. // dictID 取自 02010011 查询主表返回的 dictID
  281. const dictID = selectedRow?.dictID || selectedRow?.id || '';
  282. subForm.setFieldsValue({ dictID });
  283. setSubModalOpen(true);
  284. };
  285. const handleSubEdit = (record: BasicDataSubItem) => {
  286. setSubEditRecord(record);
  287. subForm.setFieldsValue({
  288. code: record.code,
  289. desc: record.desc,
  290. identification: record.identification,
  291. startDate: record.startDate ? dayjs(record.startDate.split(' ')[0]) as unknown as string : null,
  292. stopDate: record.stopDate ? dayjs(record.stopDate.split(' ')[0]) as unknown as string : null,
  293. });
  294. setSubModalOpen(true);
  295. };
  296. const handleSubDelete = async (id: string) => {
  297. try {
  298. const res = await deleteBasicDataSub(id);
  299. if (res.errorCode === '0') {
  300. message.success('删除成功');
  301. fetchSubData();
  302. } else {
  303. message.error(res.errorMessage || '删除失败');
  304. }
  305. } catch {
  306. message.error('网络异常');
  307. }
  308. };
  309. const handleSubSave = async () => {
  310. try {
  311. const values = await subForm.validateFields();
  312. setSubSaving(true);
  313. // dictID 取自 02010011 查询主表返回的 dictID(selectedRow)
  314. const dictID = selectedRow?.dictID || selectedRow?.id || '';
  315. const params: SaveBasicDataSubParams = {
  316. ...values,
  317. id: subEditRecord?.id || '',
  318. dictID,
  319. startDate: (values.startDate as unknown as Dayjs)?.format('YYYY-MM-DD') || '',
  320. stopDate: (values.stopDate as unknown as Dayjs)?.format('YYYY-MM-DD') || '',
  321. };
  322. const res = await saveBasicDataSub(params);
  323. if (res.errorCode === '0') {
  324. message.success(subEditRecord ? '修改成功' : '新增成功');
  325. setSubModalOpen(false);
  326. fetchSubData();
  327. } else {
  328. message.error(res.errorMessage || '保存失败');
  329. }
  330. } catch {
  331. // form validation error
  332. } finally {
  333. setSubSaving(false);
  334. }
  335. };
  336. // 省选择变化时获取市数据
  337. const handleProvinceChange = async (value: string) => {
  338. form.setFieldsValue({ cityID: undefined, areaID: undefined });
  339. if (!value) {
  340. setCityList([]);
  341. return;
  342. }
  343. setCityLoading(true);
  344. try {
  345. const res = await getCityData(value);
  346. if (res.errorCode === '0' && res.result) {
  347. setCityList(res.result);
  348. }
  349. } finally {
  350. setCityLoading(false);
  351. }
  352. };
  353. // 市选择变化时填充区代码
  354. const handleCityChange = (value: string) => {
  355. if (!value) {
  356. form.setFieldsValue({ areaID: undefined });
  357. return;
  358. }
  359. const selectedCity = cityList.find(item => item.id === value);
  360. if (selectedCity) {
  361. form.setFieldsValue({ areaID: selectedCity.code });
  362. }
  363. };
  364. const columns: ColumnsType<BasicDataItem> = [
  365. { title: '代码', dataIndex: 'insuCode', width: 180, ellipsis: true },
  366. { title: '描述', dataIndex: 'insuDesc', width: 280, ellipsis: true },
  367. { title: '省', dataIndex: 'provinceDesc', width: 120, ellipsis: true },
  368. { title: '市', dataIndex: 'cityDesc', width: 120, ellipsis: true },
  369. { title: '标识码', dataIndex: 'identification', width: 120, ellipsis: true },
  370. {
  371. title: '操作',
  372. key: 'action',
  373. width: 120,
  374. fixed: 'right',
  375. render: (_, record) => (
  376. <Space size="middle">
  377. <Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
  378. <Popconfirm
  379. title="确认删除"
  380. description="确定要删除该基础数据吗?(将同时删除明细)"
  381. onConfirm={() => handleDelete(record.id)}
  382. okText="确定"
  383. cancelText="取消"
  384. >
  385. <Button type="link" size="small" danger icon={<DeleteOutlined />}>删除</Button>
  386. </Popconfirm>
  387. </Space>
  388. ),
  389. },
  390. ];
  391. const subColumns: ColumnsType<BasicDataSubItem> = [
  392. { title: '代码', dataIndex: 'code', width: 150, ellipsis: true },
  393. { title: '描述', dataIndex: 'desc', width: 250, ellipsis: true },
  394. { title: '标识码', dataIndex: 'identification', width: 100, ellipsis: true },
  395. { title: '生效日期', dataIndex: 'startDate', width: 100 },
  396. { title: '失效日期', dataIndex: 'stopDate', width: 100 },
  397. { title: '状态', dataIndex: 'statusDesc', width: 80 },
  398. {
  399. title: '操作',
  400. key: 'action',
  401. width: 120,
  402. fixed: 'right',
  403. render: (_, record) => {
  404. return (
  405. <Space size="middle">
  406. <Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleSubEdit(record)}>编辑</Button>
  407. <Popconfirm
  408. title="确认删除"
  409. description="确定要删除该明细吗?"
  410. onConfirm={() => handleSubDelete(record.id)}
  411. okText="确定"
  412. cancelText="取消"
  413. >
  414. <Button type="link" size="small" danger icon={<DeleteOutlined />}>删除</Button>
  415. </Popconfirm>
  416. </Space>
  417. );
  418. },
  419. },
  420. ];
  421. return (
  422. <ConfigProvider locale={zhCN}>
  423. <div style={{ padding: 16, width: '100%', height: '100%', boxSizing: 'border-box', display: 'flex', flexDirection: 'column' }}>
  424. {/* 左右两栏布局 */}
  425. <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'row', gap: 16 }}>
  426. {/* 左侧:主表数据 */}
  427. <Card
  428. size="small"
  429. title={
  430. <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
  431. <span>DRG基础数据</span>
  432. <Space size={4}>
  433. <Button type="primary" size="small" icon={<PlusOutlined />} onClick={handleAdd}>添加</Button>
  434. </Space>
  435. </div>
  436. }
  437. styles={{ body: { padding: 8, flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
  438. style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}
  439. >
  440. <Row gutter={8} style={{ marginBottom: 8 }} align="middle">
  441. <Col>
  442. <span>描述:</span>
  443. </Col>
  444. <Col>
  445. <Input
  446. placeholder="输入名称查询"
  447. value={mainSearch.insuDesc}
  448. onChange={e => setMainSearch(prev => ({ ...prev, insuDesc: e.target.value }))}
  449. allowClear
  450. style={{ width: 200 }}
  451. />
  452. </Col>
  453. <Col>
  454. <span>状态:</span>
  455. </Col>
  456. <Col>
  457. <Select
  458. placeholder="请选择"
  459. value={mainSearch.status || undefined}
  460. onChange={v => setMainSearch(prev => ({ ...prev, status: v || '' }))}
  461. style={{ width: 100 }}
  462. allowClear
  463. >
  464. <Option value="Y">有效</Option>
  465. <Option value="N">无效</Option>
  466. </Select>
  467. </Col>
  468. <Col style={{ marginLeft: 'auto' }}>
  469. <Space>
  470. <Button type="primary" icon={<SearchOutlined />} onClick={handleMainSearch}>查询</Button>
  471. <Button icon={<ReloadOutlined />} onClick={handleMainReset}>重置</Button>
  472. </Space>
  473. </Col>
  474. </Row>
  475. {/* 工具栏 + 表格 */}
  476. <div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
  477. <div style={{ flex: 1, overflow: 'auto' }}>
  478. <Table
  479. columns={columns}
  480. dataSource={data}
  481. rowKey="id"
  482. loading={loading}
  483. size="small"
  484. scroll={{ x: 'max-content' }}
  485. pagination={false}
  486. onRow={(record) => ({
  487. onClick: () => setSelectedRow(record),
  488. style: {
  489. cursor: 'pointer',
  490. backgroundColor: selectedRow?.id === record.id ? '#e6f7ff' : undefined
  491. },
  492. })}
  493. />
  494. </div>
  495. {/* 底部独立分页控件 */}
  496. <div style={{ marginTop: 8, display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
  497. <Space size="small">
  498. <Button
  499. size="small"
  500. disabled={currentPage === 1}
  501. onClick={() => {
  502. const newPage = 1;
  503. setCurrentPage(newPage);
  504. fetchData(newPage, pageSize);
  505. }}
  506. >
  507. 首页
  508. </Button>
  509. <Button
  510. size="small"
  511. disabled={currentPage === 1}
  512. onClick={() => {
  513. const newPage = currentPage - 1;
  514. setCurrentPage(newPage);
  515. fetchData(newPage, pageSize);
  516. }}
  517. >
  518. 上一页
  519. </Button>
  520. <span style={{ fontSize: 12, padding: '0 8px' }}>
  521. 第 {currentPage} / {Math.ceil(total / pageSize) || 1} 页
  522. </span>
  523. <Button
  524. size="small"
  525. disabled={currentPage >= Math.ceil(total / pageSize)}
  526. onClick={() => {
  527. const newPage = currentPage + 1;
  528. setCurrentPage(newPage);
  529. fetchData(newPage, pageSize);
  530. }}
  531. >
  532. 下一页
  533. </Button>
  534. <Button
  535. size="small"
  536. disabled={currentPage >= Math.ceil(total / pageSize)}
  537. onClick={() => {
  538. const newPage = Math.ceil(total / pageSize) || 1;
  539. setCurrentPage(newPage);
  540. fetchData(newPage, pageSize);
  541. }}
  542. >
  543. 末页
  544. </Button>
  545. <Select
  546. size="small"
  547. value={pageSize}
  548. style={{ width: 80 }}
  549. onChange={(value: number) => {
  550. setPageSize(value);
  551. setCurrentPage(1);
  552. fetchData(1, value);
  553. }}
  554. >
  555. <Option value={15}>15条/页</Option>
  556. <Option value={20}>20条/页</Option>
  557. <Option value={50}>50条/页</Option>
  558. <Option value={100}>100条/页</Option>
  559. </Select>
  560. </Space>
  561. </div>
  562. </div>
  563. </Card>
  564. {/* 右侧:明细数据 */}
  565. <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
  566. {selectedRow ? (
  567. <Card
  568. size="small"
  569. title={
  570. <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
  571. <span>明细数据({selectedRow.insuDesc})</span>
  572. <Space size={4}>
  573. <Button type="primary" size="small" icon={<PlusOutlined />} onClick={handleSubAdd}>添加</Button>
  574. </Space>
  575. </div>
  576. }
  577. styles={{ body: { padding: 8, flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
  578. style={{ flex: 1, display: 'flex', flexDirection: 'column' }}
  579. >
  580. <Row gutter={8} align="middle">
  581. <Col>
  582. <span>描述:</span>
  583. </Col>
  584. <Col>
  585. <Input
  586. placeholder="输入描述查询"
  587. value={subSearch.desc}
  588. onChange={e => setSubSearch(prev => ({ ...prev, desc: e.target.value }))}
  589. allowClear
  590. style={{ width: 200 }}
  591. />
  592. </Col>
  593. <Col>
  594. <span>状态:</span>
  595. </Col>
  596. <Col>
  597. <Select
  598. placeholder="请选择"
  599. value={subSearch.status || undefined}
  600. onChange={v => setSubSearch(prev => ({ ...prev, status: v || '' }))}
  601. style={{ width: 100 }}
  602. allowClear
  603. >
  604. <Option value="Y">有效</Option>
  605. <Option value="N">无效</Option>
  606. </Select>
  607. </Col>
  608. <Col style={{ marginLeft: 'auto' }}>
  609. <Space>
  610. <Button type="primary" icon={<SearchOutlined />} onClick={handleSubSearch}>查询</Button>
  611. <Button icon={<ReloadOutlined />} onClick={handleSubReset}>重置</Button>
  612. </Space>
  613. </Col>
  614. </Row>
  615. {/* 工具栏 + 表格 */}
  616. <div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
  617. <div style={{ flex: 1, overflow: 'auto' }}>
  618. <Table
  619. columns={subColumns}
  620. dataSource={subData}
  621. rowKey="id"
  622. loading={subLoading}
  623. size="small"
  624. scroll={{ x: 'max-content' }}
  625. pagination={false}
  626. />
  627. </div>
  628. {/* 底部独立分页控件 */}
  629. <div style={{ marginTop: 8, display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
  630. <Space size="small">
  631. <Button
  632. size="small"
  633. disabled={subCurrentPage === 1}
  634. onClick={() => {
  635. setSubCurrentPage(1);
  636. fetchSubData(1, subPageSize);
  637. }}
  638. >
  639. 首页
  640. </Button>
  641. <Button
  642. size="small"
  643. disabled={subCurrentPage === 1}
  644. onClick={() => {
  645. const newPage = subCurrentPage - 1;
  646. setSubCurrentPage(newPage);
  647. fetchSubData(newPage, subPageSize);
  648. }}
  649. >
  650. 上一页
  651. </Button>
  652. <span style={{ fontSize: 12, padding: '0 8px' }}>
  653. 第 {subCurrentPage} / {Math.ceil(subTotal / subPageSize) || 1} 页
  654. </span>
  655. <Button
  656. size="small"
  657. disabled={subCurrentPage >= Math.ceil(subTotal / subPageSize)}
  658. onClick={() => {
  659. const newPage = subCurrentPage + 1;
  660. setSubCurrentPage(newPage);
  661. fetchSubData(newPage, subPageSize);
  662. }}
  663. >
  664. 下一页
  665. </Button>
  666. <Button
  667. size="small"
  668. disabled={subCurrentPage >= Math.ceil(subTotal / subPageSize)}
  669. onClick={() => {
  670. const newPage = Math.ceil(subTotal / subPageSize) || 1;
  671. setSubCurrentPage(newPage);
  672. fetchSubData(newPage, subPageSize);
  673. }}
  674. >
  675. 末页
  676. </Button>
  677. <Select
  678. size="small"
  679. value={subPageSize}
  680. style={{ width: 80 }}
  681. onChange={(value: number) => {
  682. setSubPageSize(value);
  683. setSubCurrentPage(1);
  684. fetchSubData(1, value);
  685. }}
  686. >
  687. <Option value={15}>15条/页</Option>
  688. <Option value={20}>20条/页</Option>
  689. <Option value={50}>50条/页</Option>
  690. <Option value={100}>100条/页</Option>
  691. </Select>
  692. </Space>
  693. </div>
  694. </div>
  695. </Card>
  696. ) : (
  697. <Card size="small" title="明细数据" style={{ flex: 1, display: 'flex', flexDirection: 'column' }}
  698. styles={{ body: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' } }}
  699. >
  700. <div style={{ textAlign: 'center', color: '#bbb', fontSize: 14 }}>
  701. <DatabaseOutlined style={{ fontSize: 32, marginBottom: 8, display: 'block' }} />
  702. 请点击左侧主表数据查看明细
  703. </div>
  704. </Card>
  705. )}
  706. </div>
  707. </div>
  708. {/* 主表新增/编辑弹窗 */}
  709. <Modal
  710. title={editRecord ? '编辑DRG基础数据' : '新增DRG基础数据'}
  711. open={modalOpen}
  712. onOk={handleSave}
  713. onCancel={() => setModalOpen(false)}
  714. confirmLoading={saving}
  715. width={600}
  716. destroyOnClose
  717. >
  718. <Form form={form} layout="vertical">
  719. <Row gutter={16}>
  720. <Col span={12}>
  721. <Form.Item name="insuCode" label="代码" rules={[{ required: true, message: '请输入代码' }]}>
  722. <Input placeholder="如 DRG_VERSION_2024" />
  723. </Form.Item>
  724. </Col>
  725. <Col span={12}>
  726. <Form.Item name="insuDesc" label="描述" rules={[{ required: true, message: '请输入描述' }]}>
  727. <Input placeholder="如 CHS-DRG 2.0版本" />
  728. </Form.Item>
  729. </Col>
  730. <Col span={8}>
  731. <Form.Item name="provinceID" label="省" rules={[{ required: true, message: '请选择省' }]}>
  732. <Select
  733. placeholder="请选择省"
  734. loading={provinceLoading}
  735. allowClear
  736. showSearch
  737. optionFilterProp="children"
  738. onChange={handleProvinceChange}
  739. >
  740. {provinceList.map(item => (
  741. <Option key={item.id} value={item.id}>
  742. {item.descripts}
  743. </Option>
  744. ))}
  745. </Select>
  746. </Form.Item>
  747. </Col>
  748. <Col span={8}>
  749. <Form.Item name="cityID" label="市" rules={[{ required: true, message: '请选择市' }]}>
  750. <Select
  751. placeholder="请选择市"
  752. loading={cityLoading}
  753. allowClear
  754. showSearch
  755. optionFilterProp="children"
  756. onChange={handleCityChange}
  757. >
  758. {cityList.map(item => (
  759. <Option key={item.id} value={item.id}>
  760. {item.descripts}
  761. </Option>
  762. ))}
  763. </Select>
  764. </Form.Item>
  765. </Col>
  766. <Col span={8}>
  767. <Form.Item name="areaID" label="区代码">
  768. <Input placeholder="如 310101" />
  769. </Form.Item>
  770. </Col>
  771. <Col span={8}>
  772. <Form.Item name="startDate" label="生效日期" rules={[{ required: true, message: '请选择生效日期' }]}>
  773. <DatePicker style={{ width: '100%' }} format="YYYY-MM-DD" />
  774. </Form.Item>
  775. </Col>
  776. <Col span={8}>
  777. <Form.Item name="stopDate" label="失效日期">
  778. <DatePicker style={{ width: '100%' }} format="YYYY-MM-DD" />
  779. </Form.Item>
  780. </Col>
  781. <Col span={8}>
  782. <Form.Item name="identification" label="标识码">
  783. <Input placeholder="标识码" />
  784. </Form.Item>
  785. </Col>
  786. <Col span={24}>
  787. <Form.Item name="remark" label="备注">
  788. <Input.TextArea rows={2} placeholder="备注信息" />
  789. </Form.Item>
  790. </Col>
  791. </Row>
  792. </Form>
  793. </Modal>
  794. {/* 明细新增/编辑弹窗 */}
  795. <Modal
  796. title={subEditRecord ? '编辑明细' : '新增明细'}
  797. open={subModalOpen}
  798. onOk={handleSubSave}
  799. onCancel={() => setSubModalOpen(false)}
  800. confirmLoading={subSaving}
  801. width={500}
  802. destroyOnClose
  803. >
  804. <Form form={subForm} layout="vertical">
  805. <Form.Item name="dictID" hidden>
  806. <Input />
  807. </Form.Item>
  808. <Row gutter={16}>
  809. <Col span={12}>
  810. <Form.Item name="code" label="代码" rules={[{ required: true, message: '请输入代码' }]}>
  811. <Input placeholder="如 MDCA" />
  812. </Form.Item>
  813. </Col>
  814. <Col span={12}>
  815. <Form.Item name="desc" label="描述" rules={[{ required: true, message: '请输入描述' }]}>
  816. <Input placeholder="如 先期分组-器官移植" />
  817. </Form.Item>
  818. </Col>
  819. <Col span={12}>
  820. <Form.Item name="identification" label="标识码">
  821. <Input placeholder="标识码" />
  822. </Form.Item>
  823. </Col>
  824. <Col span={12}>
  825. <Form.Item name="startDate" label="生效日期" rules={[{ required: true, message: '请选择生效日期' }]}>
  826. <DatePicker style={{ width: '100%' }} format="YYYY-MM-DD" />
  827. </Form.Item>
  828. </Col>
  829. <Col span={12}>
  830. <Form.Item name="stopDate" label="失效日期">
  831. <DatePicker style={{ width: '100%' }} format="YYYY-MM-DD" />
  832. </Form.Item>
  833. </Col>
  834. </Row>
  835. </Form>
  836. </Modal>
  837. </div>
  838. </ConfigProvider>
  839. );
  840. };
  841. export default BasicDataMaintenance;