CustomQuery.tsx 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740
  1. import { useState, useCallback, useEffect, useRef } from 'react';
  2. import {
  3. Card, Form, InputNumber, Select, Button, Space,
  4. Table, Descriptions, Tag, Alert, Row, Col,
  5. Input, Typography, message, Modal, List, Collapse
  6. } from 'antd';
  7. import {
  8. PlayCircleOutlined, ReloadOutlined, PlusOutlined, DeleteOutlined,
  9. MedicineBoxOutlined, UserOutlined, ScissorOutlined, BankOutlined
  10. } from '@ant-design/icons';
  11. import { drgGroup, convertResultToLowerCamel, type DiagnosisInfoLowerCamel, type OperationInfoLowerCamel, type DRGGroupResultLowerCamel } from '@/api/drgGrouping';
  12. import { queryMedInsuIcdInfo, getProvinceData, getCityData, type MedInsuIcdItem, type ProvinceItem, type CityItem } from '@/api/basicData';
  13. import { queryHospitals, type HospitalItem } from '@/api/hospital';
  14. const { Option } = Select;
  15. const { Text } = Typography;
  16. const { useForm } = Form;
  17. /**
  18. * 自定义DRG分组查询页面
  19. * 功能:用户手动输入患者就诊信息,调用02010001接口获取分组结果
  20. */
  21. const DRGCustomQuery: React.FC = () => {
  22. const [form] = useForm();
  23. const [loading, setLoading] = useState(false);
  24. const [result, setResult] = useState<DRGGroupResultLowerCamel | null>(null);
  25. const [diagnoses, setDiagnoses] = useState<DiagnosisInfoLowerCamel[]>([
  26. { mainFlag: 1, diagSn: 1, diagCode: '', diagName: '' }
  27. ]);
  28. const [operations, setOperations] = useState<OperationInfoLowerCamel[]>([
  29. { mainFlag: '1', oprnSn: 1, oprnCode: '', oprnName: '' }
  30. ]);
  31. // 使用useRef存储最新的diagnoses和operations值,解决闭包问题
  32. const diagnosesRef = useRef(diagnoses);
  33. const operationsRef = useRef(operations);
  34. // 当diagnoses或operations更新时,同步更新ref
  35. useEffect(() => {
  36. diagnosesRef.current = diagnoses;
  37. }, [diagnoses]);
  38. useEffect(() => {
  39. operationsRef.current = operations;
  40. }, [operations]);
  41. // ICD查询相关状态
  42. const [icdModalOpen, setIcdModalOpen] = useState(false);
  43. const [icdQueryType, setIcdQueryType] = useState<'diagnosis' | 'operation'>('diagnosis');
  44. const [currentIndex, setCurrentIndex] = useState<number>(-1);
  45. const [icdSearchCode, setIcdSearchCode] = useState('');
  46. const [icdSearchName, setIcdSearchName] = useState('');
  47. const [icdLoading, setIcdLoading] = useState(false);
  48. const [icdResults, setIcdResults] = useState<MedInsuIcdItem[]>([]);
  49. // 医疗机构查询相关状态
  50. const [hospitals, setHospitals] = useState<HospitalItem[]>([
  51. { ID: '', hospitalID: '', code: '', descripts: '', hospTypeID: '', hospNatureID: '', provIDID: 0, cityIDID: 0, areaIDID: 0, active: 'Y', organizationCode: '', businesslicense: '', policyTypeID: '', policyTypeDesc: '' }
  52. ]);
  53. const [hospitalModalOpen, setHospitalModalOpen] = useState(false);
  54. const [hospitalLoading, setHospitalLoading] = useState(false);
  55. const [hospitalResults, setHospitalResults] = useState<HospitalItem[]>([]);
  56. const [hospitalCurrentIndex, setHospitalCurrentIndex] = useState<number>(-1);
  57. // 使用useRef存储最新的hospitals值
  58. const hospitalsRef = useRef(hospitals);
  59. useEffect(() => {
  60. hospitalsRef.current = hospitals;
  61. }, [hospitals]);
  62. // 省、市下拉数据状态
  63. const [provinceList, setProvinceList] = useState<ProvinceItem[]>([]);
  64. const [cityList, setCityList] = useState<CityItem[]>([]);
  65. const [provinceLoading, setProvinceLoading] = useState(false);
  66. const [cityLoading, setCityLoading] = useState(false);
  67. // 加载省数据
  68. const loadProvinces = useCallback(async () => {
  69. setProvinceLoading(true);
  70. try {
  71. const res = await getProvinceData();
  72. if (res.errorCode === '0' && res.result) {
  73. setProvinceList(res.result);
  74. }
  75. } catch (error) {
  76. message.error('加载省数据失败');
  77. } finally {
  78. setProvinceLoading(false);
  79. }
  80. }, []);
  81. // 加载市数据
  82. const loadCities = useCallback(async (provinceId: string) => {
  83. if (!provinceId) {
  84. setCityList([]);
  85. return;
  86. }
  87. setCityLoading(true);
  88. try {
  89. const res = await getCityData(provinceId);
  90. if (res.errorCode === '0' && res.result) {
  91. setCityList(res.result);
  92. }
  93. } catch (error) {
  94. message.error('加载市数据失败');
  95. } finally {
  96. setCityLoading(false);
  97. }
  98. }, []);
  99. // 初始化加载省数据
  100. useEffect(() => {
  101. loadProvinces();
  102. }, [loadProvinces]);
  103. // 省选择变化
  104. const handleProvinceChange = (value: string) => {
  105. form.setFieldsValue({ City: undefined }); // 清空市的选择
  106. setCityList([]);
  107. if (value) {
  108. loadCities(value);
  109. }
  110. };
  111. // 提交分组查询 - 使用小驼峰格式
  112. const handleSubmit = async (values: any) => {
  113. try {
  114. setLoading(true);
  115. setResult(null);
  116. // 先清理无效记录并排序
  117. cleanupAll();
  118. // 校验主诊断信息
  119. const mainDiag = diagnoses.find(d => d.mainFlag === 1);
  120. if (!mainDiag || !mainDiag.diagCode || !mainDiag.diagCode.trim()) {
  121. message.error('请填写主诊断信息(诊断代码不能为空)');
  122. setLoading(false);
  123. return;
  124. }
  125. // 校验患者有效年龄和新生儿天数,不能同时为0
  126. const age = values.Age || 0;
  127. const ageGroupDays = values.AgeGroupDays || 0;
  128. if (age === 0 && ageGroupDays === 0) {
  129. message.error('患者有效年龄和新生儿天数不能同时为0,请至少填写一项');
  130. setLoading(false);
  131. return;
  132. }
  133. // 校验新生儿天数大于0时,年龄不能大于1
  134. if (ageGroupDays > 0 && age > 1) {
  135. message.error('当新生儿天数大于0时,年龄不能大于1岁');
  136. setLoading(false);
  137. return;
  138. }
  139. // 校验当前医疗费用总额 - 如果不为空且不为0,则必须是有效金额
  140. const totalCost = values.TotalCost;
  141. if (totalCost !== undefined && totalCost !== null && totalCost !== '' && Number(totalCost) !== 0) {
  142. const totalCostNum = Number(totalCost);
  143. if (isNaN(totalCostNum)) {
  144. message.error('当前医疗费用总额必须是有效数字');
  145. setLoading(false);
  146. return;
  147. }
  148. if (totalCostNum <= 0) {
  149. message.error('当前医疗费用总额必须是大于0的金额');
  150. setLoading(false);
  151. return;
  152. }
  153. }
  154. // 获取主手术代码
  155. const mainOprn = operations.find(o => o.mainFlag === '1');
  156. // 根据新生儿天数自动设置新生儿标志
  157. const newbornFlag = values.AgeGroupDays && values.AgeGroupDays > 0 ? '1' : '0';
  158. form.setFieldValue('NewbornFlag', newbornFlag);
  159. // 获取市对应的行政区划代码(从 cityList 中查找)
  160. const selectedCityCode = values.City
  161. ? (cityList.find(c => c.id === values.City)?.code || '')
  162. : '';
  163. // 构建医疗机构信息数组,从已选中的医疗机构中提取code和descripts
  164. const hospInfo = hospitals
  165. .filter(h => h.code) // 只选择有code的医疗机构
  166. .map(h => ({
  167. code: h.code,
  168. name: h.descripts,
  169. }));
  170. // 校验:如果医疗机构信息不为空,则省市必选
  171. if (hospInfo.length > 0 && (!values.Province || !values.City)) {
  172. message.error('选择医疗机构时,参保省份、参保城市为必选项');
  173. setLoading(false);
  174. return;
  175. }
  176. // 构建小驼峰入参
  177. // 注意:diseInfo 和 oprnInfo 必须传递,即使是空数组
  178. // 主诊断和主手术信息已包含在数组对象内
  179. const lowerParams: any = {
  180. mainDiagnosisCode: mainDiag?.diagCode || '',
  181. mainOperationCode: mainOprn?.oprnCode || '',
  182. sex: values.Sex,
  183. age: values.Age,
  184. ageGroupDays: values.AgeGroupDays,
  185. newbornFlag: newbornFlag,
  186. respiratorTime: values.RespiratorTime,
  187. ecmoFlag: values.ECMOFlag,
  188. transplantFlag: values.TransplantFlag,
  189. marrowTransplantFlag: values.MarrowTransplantFlag,
  190. hivFlag: values.HIVFlag,
  191. traumaLevel: values.TraumaLevel,
  192. department: values.Department,
  193. dischargeType: values.DischargeType,
  194. hospitalDays: values.HospitalDays,
  195. totalCost: values.TotalCost,
  196. // 就医地行政区划代码(取自市下拉框的 code 值)
  197. insuranceAreaCode: selectedCityCode,
  198. // diseInfo 和 oprnInfo 必须传递,即使是空数组
  199. // 主诊断和主手术信息已包含在数组对象内
  200. diseInfo: diagnoses,
  201. oprnInfo: operations,
  202. // 医疗机构信息数组,即使为空也要传递
  203. hospInfo: hospInfo,
  204. };
  205. // 直接调用接口(内部会自动转换大小驼峰)
  206. const res = await drgGroup(lowerParams);
  207. if (res.errorCode === '0' || res.errorCode === '00') {
  208. // 转换为小驼峰格式返回
  209. setResult(convertResultToLowerCamel(res));
  210. message.success('分组成功');
  211. } else {
  212. message.error(res.errorMessage || '分组失败');
  213. }
  214. } catch (error: any) {
  215. console.error('分组失败:', error);
  216. message.error(error.message || '分组失败,请检查网络连接');
  217. } finally {
  218. setLoading(false);
  219. }
  220. };
  221. // 重置表单
  222. const handleReset = () => {
  223. form.resetFields();
  224. setDiagnoses([{ mainFlag: 1, diagSn: 1, diagCode: '', diagName: '' }]);
  225. setOperations([]);
  226. setHospitals([]);
  227. setResult(null);
  228. };
  229. // 清理并排序诊断信息
  230. const cleanupDiagnoses = () => {
  231. // 过滤掉无效记录(代码和名称为空的)
  232. let validDiagnoses = diagnoses.filter(d =>
  233. d.diagCode && d.diagCode.trim() !== '' && d.diagName && d.diagName.trim() !== ''
  234. );
  235. // 如果没有有效记录,保留一个空记录
  236. if (validDiagnoses.length === 0) {
  237. validDiagnoses = [{ mainFlag: 1, diagSn: 1, diagCode: '', diagName: '' }];
  238. }
  239. // 根据主诊断排序(主诊断放第一个)
  240. const mainDiagIndex = validDiagnoses.findIndex(d => d.mainFlag === 1);
  241. if (mainDiagIndex > 0) {
  242. const mainDiag = validDiagnoses[mainDiagIndex];
  243. validDiagnoses.splice(mainDiagIndex, 1);
  244. validDiagnoses.unshift(mainDiag);
  245. }
  246. // 重新排序号
  247. validDiagnoses = validDiagnoses.map((d, i) => ({ ...d, diagSn: i + 1 }));
  248. // 确保第一个为主诊断
  249. if (validDiagnoses.length > 0 && validDiagnoses[0].mainFlag !== 1) {
  250. validDiagnoses[0].mainFlag = 1;
  251. }
  252. setDiagnoses(validDiagnoses);
  253. };
  254. // 清理并排序手术信息
  255. const cleanupOperations = () => {
  256. // 过滤掉无效记录(代码和名称为空的)
  257. let validOperations = operations.filter(o =>
  258. o.oprnCode && o.oprnCode.trim() !== '' && o.oprnName && o.oprnName.trim() !== ''
  259. );
  260. // 根据主手术排序(主手术放第一个)
  261. const mainOprnIndex = validOperations.findIndex(o => o.mainFlag === '1');
  262. if (mainOprnIndex > 0) {
  263. const mainOprn = validOperations[mainOprnIndex];
  264. validOperations.splice(mainOprnIndex, 1);
  265. validOperations.unshift(mainOprn);
  266. }
  267. // 重新排序号
  268. validOperations = validOperations.map((o, i) => ({ ...o, oprnSn: i + 1 }));
  269. setOperations(validOperations);
  270. };
  271. // 统一清理函数
  272. const cleanupAll = () => {
  273. cleanupDiagnoses();
  274. cleanupOperations();
  275. };
  276. // 添加诊断
  277. const addDiagnosis = () => {
  278. // 检查是否已存在未填写的空记录
  279. const hasEmptyRecord = diagnoses.some(d => !d.diagCode || d.diagCode.trim() === '');
  280. if (hasEmptyRecord) {
  281. message.warning('已存在未填写的诊断记录,请先完善后再添加');
  282. return;
  283. }
  284. const newSn = diagnoses.length + 1;
  285. setDiagnoses([...diagnoses, { mainFlag: 0, diagSn: newSn, diagCode: '', diagName: '' }]);
  286. };
  287. // 删除诊断
  288. const removeDiagnosis = (index: number) => {
  289. if (diagnoses.length === 1) {
  290. message.warning('至少保留一个诊断');
  291. return;
  292. }
  293. const newDiagnoses = diagnoses.filter((_, i) => i !== index);
  294. // 重新编号
  295. setDiagnoses(newDiagnoses.map((d, i) => ({ ...d, diagSn: i + 1 })));
  296. };
  297. // 更新诊断
  298. const updateDiagnosis = (index: number, field: keyof DiagnosisInfoLowerCamel, value: any) => {
  299. let newDiagnoses = [...diagnoses];
  300. newDiagnoses[index] = { ...newDiagnoses[index], [field]: value };
  301. // 如果是设置为主诊断且当前不是第一个,调整顺序
  302. if (field === 'mainFlag' && value === 1) {
  303. form.setFieldValue('MainDiagnosisCode', newDiagnoses[index].diagCode);
  304. // 清除其他的主诊断标志
  305. newDiagnoses.forEach((d, i) => {
  306. if (i !== index && d.mainFlag === 1) {
  307. newDiagnoses[i] = { ...d, mainFlag: 0 };
  308. }
  309. });
  310. // 如果当前不是第一个,将其移到第一个位置
  311. if (index !== 0) {
  312. const selectedItem = newDiagnoses[index];
  313. // 移除当前项
  314. newDiagnoses.splice(index, 1);
  315. // 插入到第一个位置
  316. newDiagnoses.unshift(selectedItem);
  317. // 重新排序号
  318. newDiagnoses = newDiagnoses.map((d, i) => ({ ...d, diagSn: i + 1 }));
  319. }
  320. setDiagnoses(newDiagnoses);
  321. return;
  322. }
  323. setDiagnoses(newDiagnoses);
  324. };
  325. // 添加手术
  326. const addOperation = () => {
  327. // 检查是否已存在未填写的空记录
  328. const hasEmptyRecord = operations.some(o => !o.oprnCode || o.oprnCode.trim() === '');
  329. if (hasEmptyRecord) {
  330. message.warning('已存在未填写的手术记录,请先完善后再添加');
  331. return;
  332. }
  333. const newSn = operations.length + 1;
  334. // 如果是第一条手术记录,默认设置为主手术
  335. const isFirstOperation = operations.length === 0;
  336. const newOperation = {
  337. mainFlag: isFirstOperation ? '1' : '0',
  338. oprnSn: newSn,
  339. oprnCode: '',
  340. oprnName: ''
  341. };
  342. setOperations([...operations, newOperation]);
  343. };
  344. // 删除手术
  345. const removeOperation = (index: number) => {
  346. const newOperations = operations.filter((_, i) => i !== index);
  347. setOperations(newOperations.map((o, i) => ({ ...o, oprnSn: i + 1 })));
  348. };
  349. // 更新手术
  350. const updateOperation = (index: number, field: keyof OperationInfoLowerCamel, value: any) => {
  351. let newOperations = [...operations];
  352. newOperations[index] = { ...newOperations[index], [field]: value };
  353. // 如果是设置为主手术且当前不是第一个,调整顺序
  354. if (field === 'mainFlag' && value === '1') {
  355. form.setFieldValue('MainOperationCode', newOperations[index].oprnCode);
  356. // 清除其他的主手术标志
  357. newOperations.forEach((o, i) => {
  358. if (i !== index && o.mainFlag === '1') {
  359. newOperations[i] = { ...o, mainFlag: '0' };
  360. }
  361. });
  362. // 如果当前不是第一个,将其移到第一个位置
  363. if (index !== 0) {
  364. const selectedItem = newOperations[index];
  365. // 移除当前项
  366. newOperations.splice(index, 1);
  367. // 插入到第一个位置
  368. newOperations.unshift(selectedItem);
  369. // 重新排序号
  370. newOperations = newOperations.map((o, i) => ({ ...o, oprnSn: i + 1 }));
  371. }
  372. setOperations(newOperations);
  373. return;
  374. }
  375. setOperations(newOperations);
  376. };
  377. // 添加医疗机构
  378. const addHospital = () => {
  379. // 检查是否已存在未填写的空记录
  380. const hasEmptyRecord = hospitals.some(h => !h.code || h.code.trim() === '');
  381. if (hasEmptyRecord) {
  382. message.warning('已存在未填写的医疗机构记录,请先完善后再添加');
  383. return;
  384. }
  385. setHospitals([...hospitals, { ID: '', hospitalID: '', code: '', descripts: '', hospTypeID: '', hospNatureID: '', provIDID: 0, cityIDID: 0, areaIDID: 0, active: 'Y', organizationCode: '', businesslicense: '', policyTypeID: '', policyTypeDesc: '' }]);
  386. };
  387. // 删除医疗机构
  388. const removeHospital = (index: number) => {
  389. setHospitals(hospitals.filter((_, i) => i !== index));
  390. };
  391. // 更新医疗机构
  392. const updateHospital = (index: number, field: keyof HospitalItem, value: any) => {
  393. // 如果是更新医疗机构代码,校验唯一性
  394. if (field === 'code' && value && value.trim() !== '') {
  395. const isDuplicate = hospitals.some((h, i) => i !== index && h.code === value.trim());
  396. if (isDuplicate) {
  397. message.warning('该医疗机构代码已存在,请勿重复添加');
  398. return;
  399. }
  400. }
  401. const newHospitals = [...hospitals];
  402. newHospitals[index] = { ...newHospitals[index], [field]: value };
  403. setHospitals(newHospitals);
  404. };
  405. // 打开医疗机构查询弹窗并自动查询
  406. const openHospitalSearch = async (index: number) => {
  407. setHospitalCurrentIndex(index);
  408. // 获取当前行的医疗机构代码和名称
  409. const currentHospitals = hospitalsRef.current;
  410. const code = currentHospitals[index]?.code || '';
  411. const desc = currentHospitals[index]?.descripts || '';
  412. // 自动查询医疗机构,传入 code 和 desc
  413. setHospitalLoading(true);
  414. try {
  415. const res = await queryHospitals(
  416. {
  417. desc: desc || undefined,
  418. active: 'Y',
  419. },
  420. { pageSize: 20, currentPage: 1 }
  421. );
  422. if (String(res.errorCode) === '0' && res.result) {
  423. const rows = res.result.rows || [];
  424. if (rows.length === 1) {
  425. // 只有1条数据,直接填充
  426. const item = rows[0];
  427. const newCode = item.organizationCode || item.code;
  428. // 检查是否已存在相同的医疗机构代码
  429. const isDuplicate = hospitalsRef.current.some((h, i) => i !== index && h.code === newCode);
  430. if (isDuplicate) {
  431. message.warning(`医疗机构代码 ${newCode} 已存在,请勿重复添加`);
  432. setHospitalResults(rows);
  433. setHospitalModalOpen(true);
  434. return;
  435. }
  436. const newHospitals = [...hospitalsRef.current];
  437. newHospitals[index] = {
  438. ...newHospitals[index],
  439. ...item,
  440. code: newCode
  441. };
  442. setHospitals(newHospitals);
  443. message.success(`已选择:${newCode} ${item.descripts}`);
  444. } else if (rows.length > 1) {
  445. // 多条数据,弹出选择框
  446. setHospitalResults(rows);
  447. setHospitalModalOpen(true);
  448. } else {
  449. // 没有数据
  450. message.info('未找到匹配的医疗机构');
  451. setHospitalResults([]);
  452. setHospitalModalOpen(true);
  453. }
  454. } else {
  455. message.error(res.errorMessage || '查询失败');
  456. setHospitalModalOpen(true);
  457. }
  458. } catch (error) {
  459. message.error('查询医疗机构失败');
  460. setHospitalModalOpen(true);
  461. } finally {
  462. setHospitalLoading(false);
  463. }
  464. };
  465. // 选择医疗机构
  466. const selectHospital = (item: HospitalItem) => {
  467. if (hospitalCurrentIndex >= 0) {
  468. const newCode = item.organizationCode || item.code;
  469. // 检查是否已存在相同的医疗机构代码
  470. const isDuplicate = hospitals.some((h, i) => i !== hospitalCurrentIndex && h.code === newCode);
  471. if (isDuplicate) {
  472. message.warning(`医疗机构代码 ${newCode} 已存在,请勿重复添加`);
  473. setHospitalModalOpen(false);
  474. return;
  475. }
  476. const newHospitals = [...hospitals];
  477. // 使用 organizationCode 作为 code 字段的值
  478. newHospitals[hospitalCurrentIndex] = {
  479. ...newHospitals[hospitalCurrentIndex],
  480. ...item,
  481. code: newCode
  482. };
  483. setHospitals(newHospitals);
  484. }
  485. setHospitalModalOpen(false);
  486. message.success(`已选择:${item.organizationCode || item.code} ${item.descripts}`);
  487. };
  488. // 打开ICD查询弹窗 - 诊断
  489. const openIcdSearchForDiagnosis = async (index: number) => {
  490. setIcdQueryType('diagnosis');
  491. setCurrentIndex(index);
  492. // 使用ref获取最新的diagnoses值,解决闭包问题
  493. const currentDiagnoses = diagnosesRef.current;
  494. const code = currentDiagnoses[index]?.diagCode || '';
  495. const name = currentDiagnoses[index]?.diagName || '';
  496. setIcdSearchCode(code);
  497. setIcdSearchName(name);
  498. setIcdResults([]);
  499. // 如果有诊断代码或名称,自动查询
  500. if (code || name) {
  501. setIcdLoading(true);
  502. try {
  503. // 优先使用名称查询,如果没有名称则使用代码
  504. const queryParams: any = {
  505. versionNo: 'ICD-10',
  506. provinceId: '36',
  507. cityId: '371',
  508. };
  509. // 如果输入的是名称(包含中文),用desc查询
  510. // 如果输入的是代码(如I21.0),用code查询
  511. if (name && /[\u4e00-\u9fa5]/.test(name)) {
  512. // 包含中文,按名称查询
  513. queryParams.desc = name;
  514. } else if (code) {
  515. // 按代码查询
  516. queryParams.code = code;
  517. } else if (name) {
  518. // 其他情况也按名称查询
  519. queryParams.desc = name;
  520. }
  521. const res = await queryMedInsuIcdInfo(
  522. queryParams,
  523. { pageSize: 10, currentPage: 1 }
  524. );
  525. if (res.errorCode === '0' && res.result) {
  526. const rows = res.result.rows || [];
  527. if (rows.length === 1) {
  528. // 只有1条数据,直接赋值
  529. const item = rows[0];
  530. // 检查是否已存在相同的诊断代码
  531. const isDuplicate = diagnosesRef.current.some((d, i) => i !== index && d.diagCode === item.code);
  532. if (isDuplicate) {
  533. message.warning(`诊断代码 ${item.code} 已存在,请勿重复添加`);
  534. setIcdResults(rows);
  535. setIcdModalOpen(true);
  536. return;
  537. }
  538. // 同时更新代码和名称,避免setState异步问题
  539. const newDiagnoses = [...diagnosesRef.current];
  540. newDiagnoses[index] = {
  541. ...newDiagnoses[index],
  542. diagCode: item.code,
  543. diagName: item.desc
  544. };
  545. setDiagnoses(newDiagnoses);
  546. // 如果是主诊断,更新MainDiagnosisCode
  547. if (newDiagnoses[index]?.mainFlag === 1) {
  548. form.setFieldValue('MainDiagnosisCode', item.code);
  549. }
  550. message.success(`已选择:${item.code} ${item.desc}`);
  551. } else if (rows.length > 1) {
  552. // 多条数据,弹出选择框
  553. setIcdResults(rows);
  554. setIcdModalOpen(true);
  555. } else {
  556. // 没有数据
  557. message.info('未找到匹配的ICD编码');
  558. setIcdModalOpen(true);
  559. }
  560. } else {
  561. message.error(res.errorMessage || '查询失败');
  562. setIcdModalOpen(true);
  563. }
  564. } catch (error) {
  565. message.error('查询ICD编码失败');
  566. setIcdModalOpen(true);
  567. } finally {
  568. setIcdLoading(false);
  569. }
  570. } else {
  571. // 没有输入内容,直接打开弹窗
  572. setIcdModalOpen(true);
  573. }
  574. };
  575. // 打开ICD查询弹窗 - 手术
  576. const openIcdSearchForOperation = async (index: number) => {
  577. setIcdQueryType('operation');
  578. setCurrentIndex(index);
  579. // 使用ref获取最新的operations值,解决闭包问题
  580. const currentOperations = operationsRef.current;
  581. const code = currentOperations[index]?.oprnCode || '';
  582. const name = currentOperations[index]?.oprnName || '';
  583. setIcdSearchCode(code);
  584. setIcdSearchName(name);
  585. setIcdResults([]);
  586. // 如果有手术代码或名称,自动查询
  587. if (code || name) {
  588. setIcdLoading(true);
  589. try {
  590. // 优先使用名称查询,如果没有名称则使用代码
  591. const queryParams: any = {
  592. versionNo: 'ICD-9',
  593. provinceId: '36',
  594. cityId: '371',
  595. };
  596. // 如果输入的是名称(包含中文),用desc查询
  597. // 如果输入的是代码(如99.01),用code查询
  598. if (name && /[\u4e00-\u9fa5]/.test(name)) {
  599. // 包含中文,按名称查询
  600. queryParams.desc = name;
  601. } else if (code) {
  602. // 按代码查询
  603. queryParams.code = code;
  604. } else if (name) {
  605. // 其他情况也按名称查询
  606. queryParams.desc = name;
  607. }
  608. const res = await queryMedInsuIcdInfo(
  609. queryParams,
  610. { pageSize: 10, currentPage: 1 }
  611. );
  612. if (res.errorCode === '0' && res.result) {
  613. const rows = res.result.rows || [];
  614. if (rows.length === 1) {
  615. // 只有1条数据,直接赋值
  616. const item = rows[0];
  617. // 检查是否已存在相同的手术代码
  618. const isDuplicate = operationsRef.current.some((o, i) => i !== index && o.oprnCode === item.code);
  619. if (isDuplicate) {
  620. message.warning(`手术代码 ${item.code} 已存在,请勿重复添加`);
  621. setIcdResults(rows);
  622. setIcdModalOpen(true);
  623. return;
  624. }
  625. // 同时更新代码和名称,避免setState异步问题
  626. const newOperations = [...operationsRef.current];
  627. newOperations[index] = {
  628. ...newOperations[index],
  629. oprnCode: item.code,
  630. oprnName: item.desc
  631. };
  632. setOperations(newOperations);
  633. // 如果是主手术,更新MainOperationCode
  634. if (newOperations[index]?.mainFlag === '1') {
  635. form.setFieldValue('MainOperationCode', item.code);
  636. }
  637. message.success(`已选择:${item.code} ${item.desc}`);
  638. } else if (rows.length > 1) {
  639. // 多条数据,弹出选择框
  640. setIcdResults(rows);
  641. setIcdModalOpen(true);
  642. } else {
  643. // 没有数据
  644. message.info('未找到匹配的ICD编码');
  645. setIcdModalOpen(true);
  646. }
  647. } else {
  648. message.error(res.errorMessage || '查询失败');
  649. setIcdModalOpen(true);
  650. }
  651. } catch (error) {
  652. message.error('查询ICD编码失败');
  653. setIcdModalOpen(true);
  654. } finally {
  655. setIcdLoading(false);
  656. }
  657. } else {
  658. // 没有输入内容,直接打开弹窗
  659. setIcdModalOpen(true);
  660. }
  661. };
  662. // 查询ICD编码 - 诊断用ICD-10,手术用ICD-9
  663. const handleIcdSearch = useCallback(async () => {
  664. if (!icdSearchCode && !icdSearchName) {
  665. message.warning('请输入ICD编码或名称进行查询');
  666. return;
  667. }
  668. setIcdLoading(true);
  669. try {
  670. // ICD-10: 诊断, ICD-9: 手术
  671. const version = icdQueryType === 'diagnosis' ? 'ICD-10' : 'ICD-9';
  672. const res = await queryMedInsuIcdInfo(
  673. {
  674. versionNo: version,
  675. provinceId: '36',
  676. cityId: '371',
  677. code: icdSearchCode || undefined,
  678. desc: icdSearchName || undefined,
  679. },
  680. { pageSize: 10, currentPage: 1 }
  681. );
  682. if (res.errorCode === '0' && res.result) {
  683. setIcdResults(res.result.rows || []);
  684. if (res.result.rows?.length === 0) {
  685. message.info('未找到匹配的ICD编码');
  686. }
  687. } else {
  688. message.error(res.errorMessage || '查询失败');
  689. }
  690. } catch (error) {
  691. message.error('查询ICD编码失败');
  692. } finally {
  693. setIcdLoading(false);
  694. }
  695. }, [icdSearchCode, icdSearchName, icdQueryType]);
  696. // 选择ICD编码
  697. const selectIcd = (item: MedInsuIcdItem) => {
  698. if (icdQueryType === 'diagnosis' && currentIndex >= 0) {
  699. // 检查是否已存在相同的诊断代码
  700. const isDuplicate = diagnoses.some((d, i) => i !== currentIndex && d.diagCode === item.code);
  701. if (isDuplicate) {
  702. message.warning(`诊断代码 ${item.code} 已存在,请勿重复添加`);
  703. setIcdModalOpen(false);
  704. return;
  705. }
  706. // 诊断:直接更新状态,避免多次调用导致的异步问题
  707. const newDiagnoses = [...diagnoses];
  708. newDiagnoses[currentIndex] = {
  709. ...newDiagnoses[currentIndex],
  710. diagCode: item.code,
  711. diagName: item.desc
  712. };
  713. // 如果是主诊断,更新MainDiagnosisCode,并确保只有一个主诊断
  714. if (newDiagnoses[currentIndex]?.mainFlag === 1) {
  715. form.setFieldValue('MainDiagnosisCode', item.code);
  716. // 清除其他主诊断标志
  717. newDiagnoses.forEach((d, i) => {
  718. if (i !== currentIndex && d.mainFlag === 1) {
  719. newDiagnoses[i] = { ...d, mainFlag: 0 };
  720. }
  721. });
  722. }
  723. setDiagnoses(newDiagnoses);
  724. } else if (icdQueryType === 'operation' && currentIndex >= 0) {
  725. // 检查是否已存在相同的手术代码
  726. const isDuplicate = operations.some((o, i) => i !== currentIndex && o.oprnCode === item.code);
  727. if (isDuplicate) {
  728. message.warning(`手术代码 ${item.code} 已存在,请勿重复添加`);
  729. setIcdModalOpen(false);
  730. return;
  731. }
  732. // 手术:直接更新状态
  733. const newOperations = [...operations];
  734. newOperations[currentIndex] = {
  735. ...newOperations[currentIndex],
  736. oprnCode: item.code,
  737. oprnName: item.desc
  738. };
  739. // 如果是主手术,更新MainOperationCode,并确保只有一个主手术
  740. if (newOperations[currentIndex]?.mainFlag === '1') {
  741. form.setFieldValue('MainOperationCode', item.code);
  742. // 清除其他主手术标志
  743. newOperations.forEach((o, i) => {
  744. if (i !== currentIndex && o.mainFlag === '1') {
  745. newOperations[i] = { ...o, mainFlag: '0' };
  746. }
  747. });
  748. }
  749. setOperations(newOperations);
  750. }
  751. setIcdModalOpen(false);
  752. message.success(`已选择:${item.code} ${item.desc}`);
  753. };
  754. // 诊断列定义
  755. const diagnosisColumns = [
  756. {
  757. title: '主诊断',
  758. dataIndex: 'mainFlag',
  759. width: 80,
  760. render: (_: any, __: any, index: number) => (
  761. <Select
  762. value={diagnoses[index]?.mainFlag}
  763. onChange={(value) => updateDiagnosis(index, 'mainFlag', value)}
  764. style={{ width: '100%' }}
  765. options={[
  766. { value: 1, label: '是' },
  767. { value: 0, label: '否' }
  768. ]}
  769. />
  770. )
  771. },
  772. {
  773. title: '诊断序号',
  774. dataIndex: 'diagSn',
  775. width: 90,
  776. render: (_: any, __: any, index: number) => (
  777. <span>{diagnoses[index]?.diagSn}</span>
  778. )
  779. },
  780. {
  781. title: '诊断代码(ICD-10)',
  782. dataIndex: 'diagCode',
  783. render: (_: any, __: any, index: number) => (
  784. <Input
  785. placeholder="如:I21.0"
  786. value={diagnoses[index]?.diagCode}
  787. onChange={(e) => {
  788. const newValue = e.target.value;
  789. // 一次性更新:设置代码,清空名称
  790. let newDiagnoses = [...diagnoses];
  791. newDiagnoses[index] = { ...newDiagnoses[index], diagCode: newValue, diagName: '' };
  792. setDiagnoses(newDiagnoses);
  793. // 如果是主诊断,更新MainDiagnosisCode
  794. if (diagnoses[index]?.mainFlag === 1) {
  795. form.setFieldValue('MainDiagnosisCode', newValue);
  796. }
  797. }}
  798. />
  799. )
  800. },
  801. {
  802. title: '诊断名称',
  803. dataIndex: 'diagName',
  804. render: (_: any, __: any, index: number) => (
  805. <Input
  806. placeholder="如:急性心肌梗死"
  807. value={diagnoses[index]?.diagName}
  808. onChange={(e) => {
  809. const newValue = e.target.value;
  810. // 一次性更新:设置名称,清空代码
  811. let newDiagnoses = [...diagnoses];
  812. newDiagnoses[index] = { ...newDiagnoses[index], diagName: newValue, diagCode: '' };
  813. setDiagnoses(newDiagnoses);
  814. // 如果是主诊断,清空MainDiagnosisCode
  815. if (diagnoses[index]?.mainFlag === 1) {
  816. form.setFieldValue('MainDiagnosisCode', '');
  817. }
  818. }}
  819. />
  820. )
  821. },
  822. {
  823. title: '操作',
  824. width: 120,
  825. render: (_: any, record: any, index: number) => (
  826. <Space>
  827. <Button
  828. type="primary"
  829. size="small"
  830. onClick={() => openIcdSearchForDiagnosis(index)}
  831. >
  832. 查询
  833. </Button>
  834. <Button
  835. type="text"
  836. danger
  837. icon={<DeleteOutlined />}
  838. onClick={() => removeDiagnosis(index)}
  839. disabled={diagnoses.length === 1}
  840. />
  841. </Space>
  842. )
  843. }
  844. ];
  845. // 手术列定义
  846. const operationColumns = [
  847. {
  848. title: '主手术',
  849. dataIndex: 'mainFlag',
  850. width: 80,
  851. render: (_: any, __: any, index: number) => (
  852. <Select
  853. value={operations[index]?.mainFlag}
  854. onChange={(value) => updateOperation(index, 'mainFlag', value)}
  855. style={{ width: '100%' }}
  856. options={[
  857. { value: '1', label: '是' },
  858. { value: '0', label: '否' }
  859. ]}
  860. />
  861. )
  862. },
  863. {
  864. title: '手术序号',
  865. dataIndex: 'oprnSn',
  866. width: 90,
  867. render: (_: any, __: any, index: number) => (
  868. <span>{operations[index]?.oprnSn}</span>
  869. )
  870. },
  871. {
  872. title: '手术代码(ICD-9-CM-3)',
  873. dataIndex: 'oprnCode',
  874. render: (_: any, __: any, index: number) => (
  875. <Input
  876. placeholder="如:51.23"
  877. value={operations[index]?.oprnCode}
  878. onChange={(e) => {
  879. const newValue = e.target.value;
  880. // 一次性更新:设置代码,清空名称
  881. const newOperations = [...operations];
  882. newOperations[index] = { ...newOperations[index], oprnCode: newValue, oprnName: '' };
  883. setOperations(newOperations);
  884. // 如果是主手术,更新MainOperationCode
  885. if (operations[index]?.mainFlag === '1') {
  886. form.setFieldValue('MainOperationCode', newValue);
  887. }
  888. }}
  889. />
  890. )
  891. },
  892. {
  893. title: '手术名称',
  894. dataIndex: 'oprnName',
  895. render: (_: any, __: any, index: number) => (
  896. <Input
  897. placeholder="如:冠状动脉造影术"
  898. value={operations[index]?.oprnName}
  899. onChange={(e) => {
  900. const newValue = e.target.value;
  901. // 一次性更新:设置名称,清空代码
  902. const newOperations = [...operations];
  903. newOperations[index] = { ...newOperations[index], oprnName: newValue, oprnCode: '' };
  904. setOperations(newOperations);
  905. // 如果是主手术,清空MainOperationCode
  906. if (operations[index]?.mainFlag === '1') {
  907. form.setFieldValue('MainOperationCode', '');
  908. }
  909. }}
  910. />
  911. )
  912. },
  913. {
  914. title: '操作',
  915. width: 120,
  916. render: (_: any, __: any, index: number) => (
  917. <Space>
  918. <Button
  919. type="primary"
  920. size="small"
  921. onClick={() => openIcdSearchForOperation(index)}
  922. >
  923. 查询
  924. </Button>
  925. <Button
  926. type="text"
  927. danger
  928. icon={<DeleteOutlined />}
  929. onClick={() => removeOperation(index)}
  930. />
  931. </Space>
  932. )
  933. }
  934. ];
  935. // 医疗机构列定义
  936. const hospitalColumns = [
  937. {
  938. title: '医疗机构代码',
  939. dataIndex: 'code',
  940. render: (_: any, __: any, index: number) => (
  941. <Input
  942. placeholder="如:10001"
  943. value={hospitals[index]?.code}
  944. onChange={(e) => {
  945. const newValue = e.target.value;
  946. // 一次性更新:设置代码,清空名称
  947. const newHospitals = [...hospitals];
  948. newHospitals[index] = { ...newHospitals[index], code: newValue, descripts: '' };
  949. setHospitals(newHospitals);
  950. }}
  951. />
  952. )
  953. },
  954. {
  955. title: '医疗机构名称',
  956. dataIndex: 'descripts',
  957. render: (_: any, __: any, index: number) => (
  958. <Input
  959. placeholder="如:XX市人民医院"
  960. value={hospitals[index]?.descripts}
  961. onChange={(e) => {
  962. const newValue = e.target.value;
  963. // 一次性更新:设置名称,清空代码
  964. const newHospitals = [...hospitals];
  965. newHospitals[index] = { ...newHospitals[index], descripts: newValue, code: '' };
  966. setHospitals(newHospitals);
  967. }}
  968. />
  969. )
  970. },
  971. {
  972. title: '操作',
  973. width: 120,
  974. render: (_: any, __: any, index: number) => (
  975. <Space>
  976. <Button
  977. type="primary"
  978. size="small"
  979. onClick={() => openHospitalSearch(index)}
  980. >
  981. 查询
  982. </Button>
  983. <Button
  984. type="text"
  985. danger
  986. icon={<DeleteOutlined />}
  987. onClick={() => removeHospital(index)}
  988. />
  989. </Space>
  990. )
  991. }
  992. ];
  993. return (
  994. <div style={{ width: '100%', boxSizing: 'border-box', padding: 16 }}>
  995. <Row gutter={24}>
  996. {/* 左侧:输入表单 */}
  997. <Col span={14}>
  998. <Card
  999. title={
  1000. <Space>
  1001. <MedicineBoxOutlined />
  1002. <span>DRG分组器</span>
  1003. </Space>
  1004. }
  1005. style={{
  1006. width: '100%',
  1007. maxHeight: 'calc(100vh - 120px)',
  1008. overflow: 'auto'
  1009. }}
  1010. bodyStyle={{ padding: '16px 24px' }}
  1011. >
  1012. {/* 提示信息 */}
  1013. <Alert
  1014. description={
  1015. <div style={{ textAlign: 'left', lineHeight: 1.6, fontSize: 12 }}>
  1016. <Row gutter={8}>
  1017. <Col span={10}>
  1018. <div><Text strong>1.</Text> 主诊断必填且只有一个</div>
  1019. <div><Text strong>2.</Text> 手术或操作信息选填 </div>
  1020. </Col>
  1021. <Col span={13}>
  1022. <div><Text strong>3.</Text> ICD编码版本均为国家医保局发布的医保版 </div>
  1023. <div><Text strong>4.</Text> 医疗机构算法信息需要在菜单"DRG核心算法配置维护"中提前维护</div>
  1024. </Col>
  1025. </Row>
  1026. </div>
  1027. }
  1028. type="info"
  1029. showIcon
  1030. style={{ marginBottom: 12, padding: '8px 12px' }}
  1031. />
  1032. <Form
  1033. form={form}
  1034. layout="vertical"
  1035. onFinish={handleSubmit}
  1036. onKeyDown={(e) => {
  1037. if (e.key === 'Enter') {
  1038. e.preventDefault();
  1039. }
  1040. }}
  1041. initialValues={{
  1042. Sex: '1',
  1043. Age: 0,
  1044. AgeGroupDays: 0,
  1045. RespiratorTime: 0,
  1046. ECMOFlag: '0',
  1047. TransplantFlag: '0',
  1048. MarrowTransplantFlag: '0',
  1049. HIVFlag: '0',
  1050. TraumaLevel: 0,
  1051. HospitalDays: 1,
  1052. DischargeType: '1', // 默认值:1-医嘱离院
  1053. }}
  1054. >
  1055. {/* 患者基本信息 */}
  1056. <Card
  1057. title={
  1058. <Space>
  1059. <UserOutlined />
  1060. <span>患者基本信息</span>
  1061. </Space>
  1062. }
  1063. size="small"
  1064. style={{ marginBottom: 16 }}
  1065. >
  1066. {/* 第一行:基础信息 */}
  1067. <Row gutter={16}>
  1068. <Col span={6}>
  1069. <Form.Item name="Sex" label="性别" rules={[{ required: true }]}>
  1070. <Select>
  1071. <Option value="1">男</Option>
  1072. <Option value="2">女</Option>
  1073. </Select>
  1074. </Form.Item>
  1075. </Col>
  1076. <Col span={6}>
  1077. <Form.Item name="Age" label="年龄(岁)" rules={[{ required: true }]}>
  1078. <InputNumber min={0} max={150} style={{ width: '100%' }} />
  1079. </Form.Item>
  1080. </Col>
  1081. <Col span={6}>
  1082. <Form.Item name="AgeGroupDays" label="新生儿天数">
  1083. <InputNumber
  1084. min={0}
  1085. max={28}
  1086. placeholder="0-28天"
  1087. style={{ width: '100%' }}
  1088. onChange={(value) => {
  1089. if (value && value > 0) {
  1090. form.setFieldValue('NewbornFlag', '1');
  1091. } else {
  1092. form.setFieldValue('NewbornFlag', '0');
  1093. }
  1094. }}
  1095. />
  1096. </Form.Item>
  1097. </Col>
  1098. <Col span={6}>
  1099. <Form.Item
  1100. name="DischargeType"
  1101. label="离院方式"
  1102. rules={[{ required: true, message: '请选择离院方式' }]}
  1103. >
  1104. <Select style={{ width: '100%' }}>
  1105. <Option value="1">1-医嘱离院</Option>
  1106. <Option value="2">2-医嘱转院</Option>
  1107. <Option value="3">3-医嘱转社区卫生服务机构/乡镇卫生院</Option>
  1108. <Option value="4">4-非医嘱离院</Option>
  1109. <Option value="5">5-死亡</Option>
  1110. <Option value="9">9-其他</Option>
  1111. </Select>
  1112. </Form.Item>
  1113. </Col>
  1114. </Row>
  1115. {/* 第二行:科室和住院信息 */}
  1116. <Row gutter={16}>
  1117. <Col span={6}>
  1118. <Form.Item name="TotalCost" label="当前医疗费用总额(元)">
  1119. <Input
  1120. placeholder="请输入费用总额"
  1121. />
  1122. </Form.Item>
  1123. </Col>
  1124. <Col span={6}>
  1125. <Form.Item name="HospitalDays" label="住院天数">
  1126. <InputNumber
  1127. min={1}
  1128. style={{ width: '100%' }}
  1129. />
  1130. </Form.Item>
  1131. </Col>
  1132. <Col span={6}>
  1133. <Form.Item name="RespiratorTime" label="呼吸机时长(小时)">
  1134. <InputNumber
  1135. min={0}
  1136. max={9999}
  1137. style={{ width: '100%' }}
  1138. />
  1139. </Form.Item>
  1140. </Col>
  1141. <Col span={6}>
  1142. <Form.Item name="TraumaLevel" label="创伤等级">
  1143. <InputNumber
  1144. min={0}
  1145. max={10}
  1146. placeholder="≥2为严重创伤"
  1147. style={{ width: '100%' }}
  1148. />
  1149. </Form.Item>
  1150. </Col>
  1151. </Row>
  1152. {/* 第三行:特殊治疗标志 */}
  1153. <Row gutter={16}>
  1154. <Col span={6}>
  1155. <Form.Item name="ECMOFlag" label="ECMO标志">
  1156. <Select>
  1157. <Option value="0">否</Option>
  1158. <Option value="1">是</Option>
  1159. </Select>
  1160. </Form.Item>
  1161. </Col>
  1162. <Col span={6}>
  1163. <Form.Item name="TransplantFlag" label="器官移植标志">
  1164. <Select>
  1165. <Option value="0">否</Option>
  1166. <Option value="1">是</Option>
  1167. </Select>
  1168. </Form.Item>
  1169. </Col>
  1170. <Col span={6}>
  1171. <Form.Item name="MarrowTransplantFlag" label="骨髓移植标志">
  1172. <Select>
  1173. <Option value="0">否</Option>
  1174. <Option value="1">是</Option>
  1175. </Select>
  1176. </Form.Item>
  1177. </Col>
  1178. <Col span={6}>
  1179. <Form.Item name="HIVFlag" label="HIV感染标志">
  1180. <Select>
  1181. <Option value="0">否</Option>
  1182. <Option value="1">是</Option>
  1183. </Select>
  1184. </Form.Item>
  1185. </Col>
  1186. </Row>
  1187. {/* 第四行:参保省份、参保城市下拉框 */}
  1188. <Row gutter={16}>
  1189. <Col span={6}>
  1190. <Form.Item name="Province" label="参保省份">
  1191. <Select
  1192. placeholder="请选择参保省份"
  1193. onChange={handleProvinceChange}
  1194. allowClear
  1195. loading={provinceLoading}
  1196. showSearch
  1197. optionFilterProp="children"
  1198. style={{ width: '100%' }}
  1199. >
  1200. {provinceList.map(item => (
  1201. <Option key={item.id} value={item.id}>{item.descripts}</Option>
  1202. ))}
  1203. </Select>
  1204. </Form.Item>
  1205. </Col>
  1206. <Col span={6}>
  1207. <Form.Item name="City" label="参保城市">
  1208. <Select
  1209. placeholder="请选择参保城市"
  1210. allowClear
  1211. loading={cityLoading}
  1212. showSearch
  1213. optionFilterProp="children"
  1214. disabled={!form.getFieldValue('Province')}
  1215. style={{ width: '100%' }}
  1216. >
  1217. {cityList.map(item => (
  1218. <Option key={item.id} value={item.id}>{item.descripts}</Option>
  1219. ))}
  1220. </Select>
  1221. </Form.Item>
  1222. </Col>
  1223. </Row>
  1224. </Card>
  1225. {/* 诊断信息 */}
  1226. <Card
  1227. title={
  1228. <Space>
  1229. <MedicineBoxOutlined />
  1230. <span>诊断信息</span>
  1231. <Text type="secondary">(主诊断必填,请使用ICD-10编码)</Text>
  1232. </Space>
  1233. }
  1234. size="small"
  1235. style={{ marginBottom: 16 }}
  1236. >
  1237. <Form.Item name="MainDiagnosisCode" hidden>
  1238. <Input />
  1239. </Form.Item>
  1240. <Table
  1241. dataSource={diagnoses}
  1242. columns={diagnosisColumns}
  1243. pagination={false}
  1244. rowKey={(_, index) => `diag-${index}`}
  1245. size="small"
  1246. />
  1247. <Button
  1248. type="dashed"
  1249. onClick={addDiagnosis}
  1250. icon={<PlusOutlined />}
  1251. style={{ marginTop: 8, width: '100%' }}
  1252. >
  1253. 添加诊断
  1254. </Button>
  1255. </Card>
  1256. {/* 手术信息 */}
  1257. <Card
  1258. title={
  1259. <Space>
  1260. <ScissorOutlined />
  1261. <span>手术/操作信息</span>
  1262. <Text type="secondary">(可选,请使用ICD-9-CM-3编码)</Text>
  1263. </Space>
  1264. }
  1265. size="small"
  1266. style={{ marginBottom: 16 }}
  1267. >
  1268. <Form.Item name="MainOperationCode" hidden>
  1269. <Input />
  1270. </Form.Item>
  1271. <Table
  1272. dataSource={operations}
  1273. columns={operationColumns}
  1274. pagination={false}
  1275. rowKey={(_, index) => `oprn-${index}`}
  1276. size="small"
  1277. />
  1278. <Button
  1279. type="dashed"
  1280. onClick={addOperation}
  1281. icon={<PlusOutlined />}
  1282. style={{ marginTop: 8, width: '100%' }}
  1283. >
  1284. 添加手术
  1285. </Button>
  1286. </Card>
  1287. {/* 医疗机构信息 */}
  1288. <Card
  1289. title={
  1290. <Space>
  1291. <BankOutlined />
  1292. <span>医疗机构信息</span>
  1293. <Text type="secondary">(可选,点击查询选择医疗机构)</Text>
  1294. </Space>
  1295. }
  1296. size="small"
  1297. style={{ marginBottom: 16 }}
  1298. >
  1299. <Table
  1300. dataSource={hospitals}
  1301. columns={hospitalColumns}
  1302. pagination={false}
  1303. rowKey={(_, index) => `hospital-${index}`}
  1304. size="small"
  1305. />
  1306. <Button
  1307. type="dashed"
  1308. onClick={addHospital}
  1309. icon={<PlusOutlined />}
  1310. style={{ marginTop: 8, width: '100%' }}
  1311. >
  1312. 添加医疗机构
  1313. </Button>
  1314. </Card>
  1315. {/* 提交按钮 */}
  1316. <Form.Item>
  1317. <Space>
  1318. <Button
  1319. type="primary"
  1320. htmlType="submit"
  1321. icon={<PlayCircleOutlined />}
  1322. loading={loading}
  1323. size="large"
  1324. >
  1325. 执行分组查询
  1326. </Button>
  1327. <Button
  1328. icon={<ReloadOutlined />}
  1329. onClick={handleReset}
  1330. size="large"
  1331. >
  1332. 重置
  1333. </Button>
  1334. </Space>
  1335. </Form.Item>
  1336. </Form>
  1337. </Card>
  1338. </Col>
  1339. {/* 右侧:分组结果 */}
  1340. <Col span={10} style={{ position: 'sticky', top: 16, alignSelf: 'flex-start' }}>
  1341. <Card
  1342. title="分组结果"
  1343. style={{ height: 'fit-content', minHeight: 200 }}
  1344. >
  1345. {result ? (
  1346. <>
  1347. {/* 分组状态总览 */}
  1348. <Card
  1349. size="small"
  1350. style={{
  1351. marginBottom: 16,
  1352. backgroundColor: '#fafafa',
  1353. borderLeft: '4px solid #52c41a'
  1354. }}
  1355. >
  1356. <Row gutter={16}>
  1357. <Col span={8}>
  1358. <div>
  1359. <Text type="secondary" style={{ display: 'block', marginBottom: 4, fontSize: 12 }}>分组状态</Text>
  1360. <Tag color={result.drg ? "success" : "warning"} style={{ fontSize: 14 }}>
  1361. {result.drg ? "分组成功" : "未分组"}
  1362. </Tag>
  1363. </div>
  1364. </Col>
  1365. <Col span={8}>
  1366. <div>
  1367. <Text type="secondary" style={{ display: 'block', marginBottom: 4, fontSize: 12 }}>合并症状态</Text>
  1368. <Tag color={result.ccFlag ? "red" : "green"} style={{ fontSize: 14 }}>
  1369. {result.ccFlag ? "有合并症" : "无合并症"}
  1370. </Tag>
  1371. </div>
  1372. </Col>
  1373. <Col span={8}>
  1374. <div>
  1375. <Text type="secondary" style={{ display: 'block', marginBottom: 4, fontSize: 12 }}>MDC编码</Text>
  1376. <Tag color="purple" style={{ fontSize: 12, whiteSpace: 'normal', height: 'auto' }}>
  1377. {result.mdc || '-'}{result.mdcDesc ? ` ${result.mdcDesc}` : ''}
  1378. </Tag>
  1379. </div>
  1380. </Col>
  1381. </Row>
  1382. </Card>
  1383. {/* 折叠面板展示详细信息 */}
  1384. <Collapse
  1385. defaultActiveKey={['drgInfo']}
  1386. size="small"
  1387. style={{ backgroundColor: '#fff' }}
  1388. >
  1389. {/* DRG分组信息 */}
  1390. <Collapse.Panel
  1391. header={<Text strong>DRG分组信息 (共{result.drgInfo?.length || 0}个)</Text>}
  1392. key="drgInfo"
  1393. >
  1394. {result.drgInfo && result.drgInfo.length > 0 ? (
  1395. <div style={{
  1396. display: 'grid',
  1397. gridTemplateColumns: result.drgInfo.length === 1 ? '1fr' : 'repeat(auto-fill, minmax(260px, 1fr))',
  1398. gap: 12
  1399. }}>
  1400. {result.drgInfo.map((item, index) => (
  1401. <Card
  1402. key={index}
  1403. size="small"
  1404. style={{
  1405. backgroundColor: '#f0f9ff',
  1406. border: '1px solid #bae0ff',
  1407. width: '100%'
  1408. }}
  1409. bodyStyle={{ padding: '12px' }}
  1410. >
  1411. <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
  1412. <Text strong style={{ fontSize: 18, color: '#1890ff' }}>{item.code}</Text>
  1413. <Text strong style={{ fontSize: 14 }}>{item.desc}</Text>
  1414. </div>
  1415. </Card>
  1416. ))}
  1417. </div>
  1418. ) : (
  1419. <Alert message="未获得DRG分组信息" type="info" showIcon style={{ backgroundColor: '#fafafa' }} />
  1420. )}
  1421. </Collapse.Panel>
  1422. {/* 合并症信息 */}
  1423. {result.complicationInfo && result.complicationInfo.length > 0 && (
  1424. <Collapse.Panel
  1425. header={<Text strong>合并症信息 (共{result.complicationInfo.length}个)</Text>}
  1426. key="complicationInfo"
  1427. >
  1428. <div style={{
  1429. display: 'grid',
  1430. gridTemplateColumns: result.complicationInfo.length === 1 ? '1fr' : 'repeat(auto-fill, minmax(260px, 1fr))',
  1431. gap: 10
  1432. }}>
  1433. {result.complicationInfo.map((item, index) => {
  1434. const isMCC = item.complication === 'MCC';
  1435. const isSingle = result.complicationInfo.length === 1;
  1436. return (
  1437. <Card
  1438. key={index}
  1439. size="small"
  1440. style={{
  1441. backgroundColor: isMCC ? '#fff2f0' : '#fff1f0',
  1442. border: isMCC ? '2px solid #ff4d4f' : '1px dashed #ffa39e',
  1443. width: '100%'
  1444. }}
  1445. bodyStyle={{ padding: '12px' }}
  1446. >
  1447. {isSingle ? (
  1448. // 只有一个时:一行显示所有信息
  1449. <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
  1450. <Text strong style={{ fontSize: 16, color: isMCC ? '#ff4d4f' : '#fa8c16' }}>
  1451. {item.complication || '合并症'}
  1452. </Text>
  1453. <Text strong style={{ fontSize: 14, color: isMCC ? '#ff4d4f' : '#fa8c16' }}>
  1454. {item.complicationDesc}
  1455. </Text>
  1456. <Text type="secondary" style={{ fontSize: 12 }}>|</Text>
  1457. <Text style={{ fontSize: 12 }}>{item.diagCode}</Text>
  1458. <Text style={{ fontSize: 12 }}>{item.diagName}</Text>
  1459. </div>
  1460. ) : (
  1461. // 多个时:两行显示
  1462. <>
  1463. <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
  1464. <Text strong style={{ fontSize: 16, color: isMCC ? '#ff4d4f' : '#fa8c16' }}>
  1465. {item.complication || '合并症'}
  1466. </Text>
  1467. <Text strong style={{ fontSize: 14, color: isMCC ? '#ff4d4f' : '#fa8c16' }}>
  1468. {item.complicationDesc}
  1469. </Text>
  1470. </div>
  1471. <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
  1472. <Text type="secondary" style={{ fontSize: 12 }}>诊断:</Text>
  1473. <Text style={{ fontSize: 12 }}>{item.diagCode}</Text>
  1474. <Text style={{ fontSize: 12 }}>{item.diagName}</Text>
  1475. </div>
  1476. </>
  1477. )}
  1478. </Card>
  1479. );
  1480. })}
  1481. </div>
  1482. </Collapse.Panel>
  1483. )}
  1484. {/* 医疗机构算法信息 */}
  1485. {result.hospAlgorithmInfo && result.hospAlgorithmInfo.length > 0 && (
  1486. <Collapse.Panel
  1487. header={<Text strong>医疗机构算法信息 (共{result.hospAlgorithmInfo.length}家)</Text>}
  1488. key="hospAlgorithmInfo"
  1489. >
  1490. <div style={{
  1491. display: 'grid',
  1492. gridTemplateColumns: result.hospAlgorithmInfo.length === 1 ? '1fr' : 'repeat(auto-fill, minmax(280px, 1fr))',
  1493. justifyItems: result.hospAlgorithmInfo.length === 1 ? 'center' : 'stretch',
  1494. gap: 10
  1495. }}>
  1496. {result.hospAlgorithmInfo.map((item, index) => (
  1497. <Card
  1498. key={index}
  1499. size="small"
  1500. style={{
  1501. backgroundColor: '#f6ffed',
  1502. border: '1px solid #b7eb8f'
  1503. }}
  1504. bodyStyle={{ padding: '12px' }}
  1505. >
  1506. <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
  1507. <Tag color="green" style={{ fontSize: 13 }}>{item.drg}</Tag>
  1508. <Text type="secondary" style={{ fontSize: 11 }}>{item.hospName}</Text>
  1509. </div>
  1510. <Row gutter={[8, 4]}>
  1511. <Col span={12}>
  1512. <Text type="secondary" style={{ fontSize: 11 }}>基准点数: </Text>
  1513. <Text style={{ fontSize: 12 }}>{item.points || '-'}</Text>
  1514. </Col>
  1515. <Col span={12}>
  1516. <Text type="secondary" style={{ fontSize: 11 }}>预估标准: </Text>
  1517. <Text style={{ fontSize: 12 }}>{item.payStandard || '-'}</Text>
  1518. </Col>
  1519. <Col span={12}>
  1520. <Text type="secondary" style={{ fontSize: 11 }}>基准点值: </Text>
  1521. <Text style={{ fontSize: 12 }}>{item.pipValue || '-'}</Text>
  1522. </Col>
  1523. <Col span={12}>
  1524. <Text type="secondary" style={{ fontSize: 11 }}>差异系数: </Text>
  1525. <Text style={{ fontSize: 12 }}>{item.dgdov || '-'}</Text>
  1526. </Col>
  1527. <Col span={12}>
  1528. <Text type="secondary" style={{ fontSize: 11 }}>当前费用总额: </Text>
  1529. <Text style={{ fontSize: 12 }}>{item.totalCost !== undefined ? `¥${item.totalCost}` : '-'}</Text>
  1530. </Col>
  1531. <Col span={12}>
  1532. <Text type="secondary" style={{ fontSize: 11 }}>预估盈利: </Text>
  1533. <Text style={{ fontSize: 12, color: '#52c41a' }}>{item.preProfit !== undefined ? `¥${item.preProfit}` : '-'}</Text>
  1534. </Col>
  1535. <Col span={12}>
  1536. <Text type="secondary" style={{ fontSize: 11 }}>预估亏损: </Text>
  1537. <Text style={{ fontSize: 12, color: '#ff4d4f' }}>{item.preLoss !== undefined ? `¥${item.preLoss}` : '-'}</Text>
  1538. </Col>
  1539. <Col span={12}>
  1540. <Text type="secondary" style={{ fontSize: 11 }}>差异率: </Text>
  1541. <Text style={{ fontSize: 12 }}>{item.discrepancyRate !== undefined ? `${item.discrepancyRate}%` : '-'}</Text>
  1542. </Col>
  1543. </Row>
  1544. </Card>
  1545. ))}
  1546. </div>
  1547. </Collapse.Panel>
  1548. )}
  1549. </Collapse>
  1550. </>
  1551. ) : (
  1552. <div style={{
  1553. display: 'flex',
  1554. flexDirection: 'column',
  1555. alignItems: 'center',
  1556. justifyContent: 'center',
  1557. padding: '60px 20px',
  1558. color: '#999'
  1559. }}>
  1560. <MedicineBoxOutlined style={{ fontSize: 48, marginBottom: 16, color: '#d9d9d9' }} />
  1561. <Text style={{ fontSize: 14 }}>暂无分组结果</Text>
  1562. <Text type="secondary" style={{ fontSize: 12, marginTop: 8 }}>请在左侧填写信息后点击"执行分组查询"</Text>
  1563. </div>
  1564. )}
  1565. </Card>
  1566. </Col>
  1567. </Row>
  1568. {/* ICD编码查询弹窗 */}
  1569. <Modal
  1570. title={icdQueryType === 'diagnosis' ? '查询ICD-10诊断编码 (02010022)' : '查询ICD-9手术编码 (02010022)'}
  1571. open={icdModalOpen}
  1572. onCancel={() => setIcdModalOpen(false)}
  1573. footer={null}
  1574. width={700}
  1575. >
  1576. <List
  1577. bordered
  1578. loading={icdLoading}
  1579. dataSource={icdResults}
  1580. renderItem={(item) => (
  1581. <List.Item
  1582. onDoubleClick={() => selectIcd(item)}
  1583. style={{ cursor: 'pointer' }}
  1584. >
  1585. <List.Item.Meta
  1586. title={<Space><Tag color="blue">{item.code}</Tag><span>{item.desc}</span></Space>}
  1587. description={`省: ${item.provinceDesc || '-'} | 市: ${item.cityDesc || '-'} | 状态: ${item.statusDesc || '-'}`}
  1588. />
  1589. </List.Item>
  1590. )}
  1591. locale={{ emptyText: '双击选中查询结果' }}
  1592. />
  1593. </Modal>
  1594. {/* 医疗机构查询弹窗 */}
  1595. <Modal
  1596. title="查询医疗机构 (01050103)"
  1597. open={hospitalModalOpen}
  1598. onCancel={() => setHospitalModalOpen(false)}
  1599. footer={null}
  1600. width={700}
  1601. >
  1602. <List
  1603. bordered
  1604. loading={hospitalLoading}
  1605. dataSource={hospitalResults}
  1606. renderItem={(item: HospitalItem) => (
  1607. <List.Item
  1608. onDoubleClick={() => selectHospital(item)}
  1609. style={{ cursor: 'pointer' }}
  1610. >
  1611. <List.Item.Meta
  1612. title={<Space><Tag color="green">{item.organizationCode || item.code}</Tag><span>{item.descripts}</span></Space>}
  1613. description={
  1614. <span>
  1615. 类型: {item.typeDesc || '-'} |
  1616. 等级: {item.gradeDesc || '-'} |
  1617. 性质: {item.natureDesc || '-'} |
  1618. 地区: {item.proDesc || ''}{item.cityDesc || ''}{item.areaDesc || ''} |
  1619. 状态: {item.active === 'Y' ? '启用' : '停用'}
  1620. </span>
  1621. }
  1622. />
  1623. </List.Item>
  1624. )}
  1625. locale={{ emptyText: hospitalLoading ? '加载中...' : '暂无数据' }}
  1626. />
  1627. </Modal>
  1628. </div>
  1629. );
  1630. };
  1631. export default DRGCustomQuery;