workinjury_test_interface.html 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>江苏工伤联网结算接口测试平台</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: 'Microsoft YaHei', Arial, sans-serif;
  15. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  16. min-height: 100vh;
  17. padding: 20px;
  18. }
  19. .container {
  20. max-width: 1200px;
  21. margin: 0 auto;
  22. background: white;
  23. border-radius: 12px;
  24. box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  25. overflow: hidden;
  26. }
  27. .header {
  28. background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
  29. color: white;
  30. padding: 20px;
  31. text-align: center;
  32. }
  33. .header h1 {
  34. font-size: 24px;
  35. margin-bottom: 10px;
  36. }
  37. .header p {
  38. opacity: 0.9;
  39. font-size: 14px;
  40. }
  41. .main-content {
  42. display: grid;
  43. grid-template-columns: 300px 1fr;
  44. min-height: 600px;
  45. }
  46. .sidebar {
  47. background: #f8f9fa;
  48. border-right: 1px solid #dee2e6;
  49. padding: 20px;
  50. }
  51. .function-group {
  52. margin-bottom: 20px;
  53. }
  54. .function-group h3 {
  55. font-size: 14px;
  56. color: #666;
  57. margin-bottom: 10px;
  58. padding-bottom: 5px;
  59. border-bottom: 1px solid #e0e0e0;
  60. }
  61. .function-btn {
  62. display: block;
  63. width: 100%;
  64. padding: 10px 15px;
  65. margin: 5px 0;
  66. background: white;
  67. border: 1px solid #ddd;
  68. border-radius: 6px;
  69. color: #333;
  70. text-decoration: none;
  71. font-size: 13px;
  72. cursor: pointer;
  73. transition: all 0.2s;
  74. }
  75. .function-btn:hover {
  76. background: #f0f0f0;
  77. border-color: #999;
  78. }
  79. .function-btn.active {
  80. background: #4CAF50;
  81. color: white;
  82. border-color: #4CAF50;
  83. }
  84. .content-area {
  85. padding: 20px;
  86. }
  87. .action-bar {
  88. display: flex;
  89. gap: 10px;
  90. margin-bottom: 20px;
  91. align-items: center;
  92. }
  93. .execute-btn {
  94. background: #4CAF50;
  95. color: white;
  96. border: none;
  97. padding: 10px 20px;
  98. border-radius: 6px;
  99. cursor: pointer;
  100. font-weight: bold;
  101. transition: background 0.2s;
  102. }
  103. .execute-btn:hover {
  104. background: #45a049;
  105. }
  106. .execute-btn:disabled {
  107. background: #ccc;
  108. cursor: not-allowed;
  109. }
  110. .clear-btn {
  111. background: #f44336;
  112. color: white;
  113. border: none;
  114. padding: 10px 15px;
  115. border-radius: 6px;
  116. cursor: pointer;
  117. }
  118. .clear-btn:hover {
  119. background: #da190b;
  120. }
  121. .status-indicator {
  122. display: flex;
  123. align-items: center;
  124. margin-left: auto;
  125. font-size: 14px;
  126. }
  127. .status-dot {
  128. width: 10px;
  129. height: 10px;
  130. border-radius: 50%;
  131. margin-right: 8px;
  132. }
  133. .status-dot.success { background: #4CAF50; }
  134. .status-dot.error { background: #f44336; }
  135. .status-dot.warning { background: #ff9800; }
  136. .status-dot.info { background: #2196f3; }
  137. .params-section {
  138. display: grid;
  139. grid-template-columns: 1fr 1fr;
  140. gap: 20px;
  141. margin-bottom: 20px;
  142. }
  143. .params-panel {
  144. background: #f8f9fa;
  145. border: 1px solid #dee2e6;
  146. border-radius: 8px;
  147. overflow: hidden;
  148. }
  149. .params-panel-header {
  150. background: #e9ecef;
  151. padding: 15px;
  152. font-weight: bold;
  153. color: #495057;
  154. border-bottom: 1px solid #dee2e6;
  155. }
  156. .params-panel-content {
  157. padding: 15px;
  158. }
  159. .params-textarea {
  160. width: 100%;
  161. height: 300px;
  162. border: 1px solid #ddd;
  163. border-radius: 4px;
  164. padding: 10px;
  165. font-family: 'Courier New', monospace;
  166. font-size: 12px;
  167. resize: vertical;
  168. background: white;
  169. }
  170. .params-textarea:focus {
  171. outline: none;
  172. border-color: #4CAF50;
  173. }
  174. .result-textarea {
  175. background: #f5f5f5;
  176. color: #333;
  177. }
  178. .log-section {
  179. background: #f8f9fa;
  180. border: 1px solid #dee2e6;
  181. border-radius: 8px;
  182. overflow: hidden;
  183. }
  184. .log-header {
  185. background: #e9ecef;
  186. padding: 15px;
  187. font-weight: bold;
  188. color: #495057;
  189. border-bottom: 1px solid #dee2e6;
  190. display: flex;
  191. justify-content: space-between;
  192. align-items: center;
  193. }
  194. .log-content {
  195. padding: 15px;
  196. max-height: 200px;
  197. overflow-y: auto;
  198. background: #1e1e1e;
  199. color: #f8f8f2;
  200. font-family: 'Courier New', monospace;
  201. font-size: 12px;
  202. }
  203. .log-entry {
  204. margin: 2px 0;
  205. padding: 2px 0;
  206. }
  207. .log-entry.info { color: #a6e22e; }
  208. .log-entry.error { color: #f92672; }
  209. .log-entry.warning { color: #fd971f; }
  210. .log-entry.success { color: #66d9ef; }
  211. .help-panel {
  212. background: #e7f3ff;
  213. border: 1px solid #b3d9ff;
  214. border-radius: 6px;
  215. padding: 15px;
  216. margin-top: 20px;
  217. }
  218. .help-panel h4 {
  219. color: #0066cc;
  220. margin-bottom: 10px;
  221. }
  222. .help-panel ul {
  223. margin-left: 20px;
  224. }
  225. .help-panel li {
  226. margin: 5px 0;
  227. font-size: 13px;
  228. color: #333;
  229. }
  230. .config-modal {
  231. display: none;
  232. position: fixed;
  233. top: 0;
  234. left: 0;
  235. width: 100%;
  236. height: 100%;
  237. background: rgba(0,0,0,0.5);
  238. z-index: 1000;
  239. }
  240. .modal-content {
  241. position: absolute;
  242. top: 50%;
  243. left: 50%;
  244. transform: translate(-50%, -50%);
  245. background: white;
  246. border-radius: 8px;
  247. padding: 20px;
  248. width: 90%;
  249. max-width: 500px;
  250. }
  251. .modal-header {
  252. display: flex;
  253. justify-content: space-between;
  254. align-items: center;
  255. margin-bottom: 20px;
  256. padding-bottom: 10px;
  257. border-bottom: 1px solid #eee;
  258. }
  259. .close-btn {
  260. background: none;
  261. border: none;
  262. font-size: 20px;
  263. cursor: pointer;
  264. color: #999;
  265. }
  266. .form-group {
  267. margin-bottom: 15px;
  268. }
  269. .form-group label {
  270. display: block;
  271. margin-bottom: 5px;
  272. font-weight: bold;
  273. color: #333;
  274. }
  275. .form-group input {
  276. width: 100%;
  277. padding: 8px 12px;
  278. border: 1px solid #ddd;
  279. border-radius: 4px;
  280. font-size: 14px;
  281. }
  282. .form-group input:focus {
  283. outline: none;
  284. border-color: #4CAF50;
  285. }
  286. .modal-footer {
  287. display: flex;
  288. gap: 10px;
  289. justify-content: flex-end;
  290. margin-top: 20px;
  291. }
  292. .btn-primary {
  293. background: #4CAF50;
  294. color: white;
  295. border: none;
  296. padding: 8px 16px;
  297. border-radius: 4px;
  298. cursor: pointer;
  299. }
  300. .btn-secondary {
  301. background: #6c757d;
  302. color: white;
  303. border: none;
  304. padding: 8px 16px;
  305. border-radius: 4px;
  306. cursor: pointer;
  307. }
  308. </style>
  309. </head>
  310. <body>
  311. <div class="container">
  312. <div class="header">
  313. <h1>🏥 江苏工伤联网结算接口测试平台</h1>
  314. <p>基于JSSiInterface.dll的标准工伤联网接口 | 版本 V2.1 | 医院编号:SQ201348</p>
  315. </div>
  316. <div class="main-content">
  317. <div class="sidebar">
  318. <div class="function-group">
  319. <h3>🔧 系统管理</h3>
  320. <button class="function-btn" onclick="loadFunction('init', event)">系统初始化</button>
  321. <button class="function-btn" onclick="loadFunction('health', event)">健康检查</button>
  322. <button class="function-btn" onclick="loadFunction('stats', event)">统计信息</button>
  323. <button class="function-btn" onclick="showConfigModal()">配置设置</button>
  324. <button class="function-btn" onclick="runDiagnostics()" style="background: #ff9800; color: white;">🔍 系统诊断</button>
  325. </div>
  326. <div class="function-group">
  327. <h3>👤 认证类</h3>
  328. <button class="function-btn" onclick="loadFunction('SignIn', event)">操作员签到</button>
  329. <button class="function-btn" onclick="loadFunction('signout', event)">操作员签退</button>
  330. <button class="function-btn" onclick="loadFunction('signin_status', event)">签到状态</button>
  331. </div>
  332. <div class="function-group">
  333. <h3>🏥 业务类</h3>
  334. <button class="function-btn" onclick="loadFunction('readcard', event)">工伤读卡</button>
  335. <button class="function-btn" onclick="loadFunction('register', event)">患者登记</button>
  336. <button class="function-btn" onclick="loadFunction('presettle', event)">费用预结算</button>
  337. <button class="function-btn" onclick="loadFunction('settle', event)">费用结算</button>
  338. <button class="function-btn" onclick="loadFunction('cancel_settle', event)">结算撤销</button>
  339. </div>
  340. <div class="function-group">
  341. <h3>📄 处方类</h3>
  342. <button class="function-btn" onclick="loadFunction('upload_prescription', event)">处方上报</button>
  343. <button class="function-btn" onclick="loadFunction('cancel_prescription', event)">处方撤销</button>
  344. <button class="function-btn" onclick="loadFunction('batch_upload', event)">批量上传</button>
  345. </div>
  346. <div class="function-group">
  347. <h3>🔄 其他</h3>
  348. <button class="function-btn" onclick="loadFunction('complete_process', event)">完整流程</button>
  349. <button class="function-btn" onclick="loadFunction('retry', event)">智能重试</button>
  350. <button class="function-btn" onclick="loadFunction('reverse', event)">交易冲正</button>
  351. </div>
  352. </div>
  353. <div class="content-area">
  354. <div class="action-bar">
  355. <button class="execute-btn" onclick="executeAPI()" id="executeBtn">🚀 执行接口</button>
  356. <button class="clear-btn" onclick="clearAll()">🗑️ 清空</button>
  357. <div class="status-indicator">
  358. <div class="status-dot info" id="statusDot"></div>
  359. <span id="statusText">就绪</span>
  360. </div>
  361. </div>
  362. <div class="params-section">
  363. <div class="params-panel">
  364. <div class="params-panel-header">📝 请求参数</div>
  365. <div class="params-panel-content">
  366. <textarea id="inputParams" class="params-textarea" placeholder="请选择左侧功能按钮加载默认参数..."></textarea>
  367. </div>
  368. </div>
  369. <div class="params-panel">
  370. <div class="params-panel-header">📊 响应结果</div>
  371. <div class="params-panel-content">
  372. <textarea id="outputParams" class="params-textarea result-textarea" readonly placeholder="执行接口后,返回结果将显示在这里..."></textarea>
  373. </div>
  374. </div>
  375. </div>
  376. <div class="log-section">
  377. <div class="log-header">
  378. 📋 执行日志
  379. <button class="clear-btn" onclick="clearLogs()" style="font-size: 12px; padding: 5px 10px;">清空日志</button>
  380. </div>
  381. <div class="log-content" id="logContent">
  382. <div class="log-entry info">[INFO] 江苏工伤联网测试平台已就绪</div>
  383. </div>
  384. </div>
  385. <div class="help-panel" id="helpPanel">
  386. <h4>📚 使用说明</h4>
  387. <ul>
  388. <li>首次使用请先点击"系统初始化"</li>
  389. <li>业务交易前需要先"操作员签到"</li>
  390. <li>完整的工伤就医流程:读卡 → 登记 → 预结算 → 结算</li>
  391. <li>可以使用"健康检查"监控系统状态</li>
  392. <li>支持快捷键:Ctrl+Enter 执行接口,Ctrl+D 清空</li>
  393. </ul>
  394. </div>
  395. </div>
  396. </div>
  397. </div>
  398. <!-- 配置模态框 -->
  399. <div class="config-modal" id="configModal">
  400. <div class="modal-content">
  401. <div class="modal-header">
  402. <h3>⚙️ 系统配置</h3>
  403. <button class="close-btn" onclick="closeConfigModal()">&times;</button>
  404. </div>
  405. <div class="modal-body">
  406. <div class="form-group">
  407. <label>医院编号 (fixmedinsCode)</label>
  408. <input type="text" id="fixmedinsCode" value="SQ201348">
  409. </div>
  410. <div class="form-group">
  411. <label>医院名称 (fixmedinsName)</label>
  412. <input type="text" id="fixmedinsName" value="沭阳铭和医院">
  413. </div>
  414. <div class="form-group">
  415. <label>接口版本 (interfaceVersion)</label>
  416. <input type="text" id="interfaceVersion" value="V2.1">
  417. </div>
  418. <div class="form-group">
  419. <label>操作员编号 (operatorId)</label>
  420. <input type="text" id="operatorId" value="001">
  421. </div>
  422. <div class="form-group">
  423. <label>操作员姓名 (operatorName)</label>
  424. <input type="text" id="operatorName" value="收费员张三">
  425. </div>
  426. <div class="form-group">
  427. <label>服务地址 (baseUrl)</label>
  428. <input type="text" id="baseUrl" value="http://localhost:8321">
  429. </div>
  430. </div>
  431. <div class="modal-footer">
  432. <button class="btn-primary" onclick="saveConfig()">保存</button>
  433. <button class="btn-secondary" onclick="closeConfigModal()">取消</button>
  434. </div>
  435. </div>
  436. </div>
  437. <script>
  438. // 全局配置
  439. let config = {
  440. fixmedinsCode: "SQ201348",
  441. fixmedinsName: "沭阳铭和医院",
  442. interfaceVersion: "V2.1",
  443. operatorId: "001",
  444. operatorName: "收费员张三",
  445. baseUrl: "http://localhost:8321"
  446. };
  447. let currentFunction = null;
  448. // DOM 元素
  449. const inputParams = document.getElementById('inputParams');
  450. const outputParams = document.getElementById('outputParams');
  451. const executeBtn = document.getElementById('executeBtn');
  452. const statusDot = document.getElementById('statusDot');
  453. const statusText = document.getElementById('statusText');
  454. const logContent = document.getElementById('logContent');
  455. // 参数模板
  456. const paramTemplates = {
  457. init: {
  458. action: "init",
  459. config: {
  460. fixmedinsCode: "SQ201348",
  461. fixmedinsName: "沭阳铭和医院",
  462. receiverSysCode: "JSYTH",
  463. interfaceVersion: "V2.1",
  464. operatorType: "1",
  465. defaultOperator: "001",
  466. defaultOperatorName: "收费员张三",
  467. logPath: "logs/workinjury/"
  468. }
  469. },
  470. SignIn: {
  471. action: "transaction",
  472. transactionName: "SignIn",
  473. identifyMode: "1",
  474. operatorId: "001",
  475. operatorName: "收费员张三",
  476. businessParams: {}
  477. },
  478. signout: {
  479. action: "transaction",
  480. transactionName: "SignOut",
  481. identifyMode: "1",
  482. operatorId: "001",
  483. operatorName: "收费员张三",
  484. businessParams: {}
  485. },
  486. readcard: {
  487. action: "transaction",
  488. transactionName: "ReadCard",
  489. identifyMode: "1",
  490. operatorId: "001",
  491. operatorName: "收费员张三",
  492. businessParams: {}
  493. },
  494. register: {
  495. action: "transaction",
  496. transactionName: "RegisterPatient",
  497. identifyMode: "1",
  498. operatorId: "001",
  499. operatorName: "收费员张三",
  500. businessParams: {
  501. psn_no: "32010219800101001X",
  502. visit_type: "1",
  503. dept_code: "001",
  504. dept_name: "骨科",
  505. doctor_code: "DOC001",
  506. doctor_name: "张主任",
  507. diag_code: "M79.900",
  508. diag_name: "骨折",
  509. visit_time: "2024-12-19 10:30:00"
  510. }
  511. },
  512. presettle: {
  513. action: "transaction",
  514. transactionName: "PreSettle",
  515. identifyMode: "1",
  516. operatorId: "001",
  517. operatorName: "收费员张三",
  518. businessParams: {
  519. visit_no: "V20241219001",
  520. fee_details: [
  521. {
  522. fee_ocur_time: "2024-12-19 10:30:00",
  523. med_list_codg: "001",
  524. med_list_name: "X光片检查",
  525. med_chrgitm_type: "4",
  526. fee_amt: 120.00,
  527. cnt: 1,
  528. pric: 120.00,
  529. spec: "胸部X光",
  530. dept_code: "001",
  531. dept_name: "骨科"
  532. }
  533. ]
  534. }
  535. },
  536. settle: {
  537. action: "transaction",
  538. transactionName: "Settle",
  539. identifyMode: "1",
  540. operatorId: "001",
  541. operatorName: "收费员张三",
  542. businessParams: {
  543. visit_no: "V20241219001",
  544. pre_settle_id: "PS20241219001"
  545. }
  546. },
  547. cancel_settle: {
  548. action: "transaction",
  549. transactionName: "CancelSettle",
  550. identifyMode: "1",
  551. operatorId: "001",
  552. operatorName: "收费员张三",
  553. businessParams: {
  554. original_settle_id: "ST20241219001",
  555. cancel_reason: "系统撤销"
  556. }
  557. },
  558. upload_prescription: {
  559. action: "transaction",
  560. transactionName: "UploadPrescription",
  561. identifyMode: "1",
  562. operatorId: "001",
  563. operatorName: "收费员张三",
  564. businessParams: {
  565. visit_no: "V20241219001",
  566. prescriptions: [
  567. {
  568. med_list_codg: "001",
  569. med_list_name: "阿莫西林胶囊",
  570. med_chrgitm_type: "1",
  571. fee_amt: 15.60,
  572. cnt: 1,
  573. pric: 15.60,
  574. spec: "0.25g*24粒",
  575. usage: "口服",
  576. dosage: "一次2粒,一日3次"
  577. }
  578. ]
  579. }
  580. },
  581. cancel_prescription: {
  582. action: "transaction",
  583. transactionName: "CancelPrescription",
  584. identifyMode: "1",
  585. operatorId: "001",
  586. operatorName: "收费员张三",
  587. businessParams: {
  588. original_prescription_id: "RX20241219001",
  589. cancel_reason: "处方错误"
  590. }
  591. },
  592. health: {
  593. action: "health"
  594. },
  595. stats: {
  596. action: "stats"
  597. },
  598. signin_status: {
  599. action: "signin_status"
  600. },
  601. batch_upload: {
  602. action: "batch_upload",
  603. patientId: "32010219800101001X",
  604. visitNo: "V20241219001",
  605. prescriptions: [
  606. {
  607. med_list_codg: "001",
  608. med_list_name: "阿莫西林胶囊",
  609. med_chrgitm_type: "1",
  610. fee_amt: 15.60,
  611. cnt: 1,
  612. pric: 15.60
  613. },
  614. {
  615. med_list_codg: "002",
  616. med_list_name: "布洛芬片",
  617. med_chrgitm_type: "1",
  618. fee_amt: 12.50,
  619. cnt: 1,
  620. pric: 12.50
  621. }
  622. ]
  623. },
  624. complete_process: {
  625. action: "complete_process",
  626. patientInfo: {
  627. visit_type: "1",
  628. dept_code: "001",
  629. dept_name: "骨科",
  630. doctor_code: "DOC001",
  631. doctor_name: "张主任"
  632. },
  633. feeDetails: [
  634. {
  635. med_list_codg: "001",
  636. med_list_name: "X光检查",
  637. med_chrgitm_type: "4",
  638. fee_amt: 120.00,
  639. cnt: 1,
  640. pric: 120.00
  641. }
  642. ]
  643. },
  644. retry: {
  645. action: "retry",
  646. transactionName: "ReadCard",
  647. maxRetries: 3,
  648. baseDelayMs: 1000,
  649. businessParams: {}
  650. },
  651. reverse: {
  652. action: "reverse",
  653. originalMessageId: "SQ201320241219103012340001"
  654. }
  655. };
  656. // 加载功能参数
  657. function loadFunction(functionName, sourceEvent) {
  658. // 移除之前的活动状态
  659. document.querySelectorAll('.function-btn').forEach(btn => {
  660. btn.classList.remove('active');
  661. });
  662. // 设置当前按钮为活动状态(如果有事件源)
  663. if (sourceEvent && sourceEvent.target) {
  664. sourceEvent.target.classList.add('active');
  665. } else {
  666. // 如果没有事件源,根据functionName查找对应按钮
  667. const targetBtn = Array.from(document.querySelectorAll('.function-btn')).find(btn =>
  668. btn.textContent.includes(getFunctionDisplayName(functionName)) ||
  669. btn.getAttribute('onclick') === `loadFunction('${functionName}')`
  670. );
  671. if (targetBtn) {
  672. targetBtn.classList.add('active');
  673. }
  674. }
  675. currentFunction = functionName;
  676. if (paramTemplates[functionName]) {
  677. // 使用当前配置更新模板
  678. let template = JSON.parse(JSON.stringify(paramTemplates[functionName]));
  679. updateTemplateWithConfig(template);
  680. inputParams.value = JSON.stringify(template, null, 2);
  681. addLog(`已加载 ${getFunctionDisplayName(functionName)} 参数模板`, 'info');
  682. updateStatus('参数已加载', 'info');
  683. } else {
  684. addLog(`未找到 ${functionName} 的参数模板`, 'error');
  685. updateStatus('参数加载失败', 'error');
  686. }
  687. }
  688. // 使用当前配置更新模板
  689. function updateTemplateWithConfig(template) {
  690. if (template.config) {
  691. template.config.fixmedinsCode = config.fixmedinsCode;
  692. template.config.fixmedinsName = config.fixmedinsName;
  693. template.config.interfaceVersion = config.interfaceVersion;
  694. template.config.defaultOperator = config.operatorId;
  695. template.config.defaultOperatorName = config.operatorName;
  696. }
  697. if (template.operatorId !== undefined) {
  698. template.operatorId = config.operatorId;
  699. }
  700. if (template.operatorName !== undefined) {
  701. template.operatorName = config.operatorName;
  702. }
  703. }
  704. // 获取功能显示名称
  705. function getFunctionDisplayName(functionName) {
  706. const displayNames = {
  707. init: "系统初始化",
  708. SignIn: "操作员签到",
  709. signout: "操作员签退",
  710. readcard: "工伤读卡",
  711. register: "患者登记",
  712. presettle: "费用预结算",
  713. settle: "费用结算",
  714. cancel_settle: "结算撤销",
  715. upload_prescription: "处方上报",
  716. cancel_prescription: "处方撤销",
  717. health: "健康检查",
  718. stats: "统计信息",
  719. signin_status: "签到状态",
  720. batch_upload: "批量上传",
  721. complete_process: "完整流程",
  722. retry: "智能重试",
  723. reverse: "交易冲正"
  724. };
  725. return displayNames[functionName] || functionName;
  726. }
  727. // 执行API调用
  728. async function executeAPI() {
  729. const startTime = new Date();
  730. try {
  731. const inputText = inputParams.value.trim();
  732. if (!inputText) {
  733. addLog('请先选择功能并加载参数', 'error');
  734. updateStatus('参数为空', 'error');
  735. return;
  736. }
  737. let params;
  738. try {
  739. params = JSON.parse(inputText);
  740. } catch (e) {
  741. addLog('参数JSON格式错误: ' + e.message, 'error');
  742. updateStatus('JSON格式错误', 'error');
  743. return;
  744. }
  745. executeBtn.disabled = true;
  746. executeBtn.textContent = '🔄 执行中...';
  747. updateStatus('正在调用接口...', 'warning');
  748. addLog(`开始执行 ${getFunctionDisplayName(currentFunction)} 接口`, 'info');
  749. addLog(`请求地址: ${config.baseUrl}/api/entry/workinjury`, 'info');
  750. addLog(`请求参数: ${JSON.stringify(params)}`, 'info');
  751. // 创建 AbortController 来处理超时
  752. const controller = new AbortController();
  753. const timeoutId = setTimeout(() => {
  754. controller.abort();
  755. addLog('请求超时(30秒),已取消', 'error');
  756. }, 30000);
  757. try {
  758. const response = await fetch(`${config.baseUrl}/api/entry/workinjury`, {
  759. method: 'POST',
  760. headers: {
  761. 'Content-Type': 'application/json',
  762. 'Accept': 'application/json',
  763. 'Cache-Control': 'no-cache',
  764. 'Pragma': 'no-cache'
  765. },
  766. body: JSON.stringify(params),
  767. signal: controller.signal
  768. });
  769. clearTimeout(timeoutId);
  770. const elapsed = new Date() - startTime;
  771. addLog(`HTTP状态码: ${response.status} (耗时: ${elapsed}ms)`, response.ok ? 'success' : 'error');
  772. if (!response.ok) {
  773. throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  774. }
  775. const contentType = response.headers.get('content-type');
  776. if (!contentType || !contentType.includes('application/json')) {
  777. const text = await response.text();
  778. addLog(`服务端返回非JSON数据: ${text.substring(0, 200)}...`, 'error');
  779. throw new Error('服务端返回的不是JSON格式数据');
  780. }
  781. const result = await response.json();
  782. outputParams.value = JSON.stringify(result, null, 2);
  783. if (result.success === true) {
  784. addLog(`接口调用成功: ${result.message}`, 'success');
  785. updateStatus('接口调用成功', 'success');
  786. } else {
  787. addLog(`接口调用失败: ${result.message} (代码: ${result.code})`, 'error');
  788. updateStatus('接口调用失败', 'error');
  789. // 如果是DLL相关错误,提供额外建议
  790. if (result.message && result.message.includes('JSSiInterface.dll')) {
  791. addLog('建议检查: 1) DLL文件是否存在 2) DLL版本是否正确 3) 运行环境配置', 'warning');
  792. }
  793. }
  794. } catch (fetchError) {
  795. clearTimeout(timeoutId);
  796. if (fetchError.name === 'AbortError') {
  797. throw new Error('请求超时');
  798. } else {
  799. throw fetchError;
  800. }
  801. }
  802. } catch (error) {
  803. const elapsed = new Date() - startTime;
  804. addLog(`网络错误 (${elapsed}ms): ${error.message}`, 'error');
  805. updateStatus('网络连接失败', 'error');
  806. // 提供更详细的错误信息和建议
  807. let errorAdvice = '';
  808. if (error.message.includes('Failed to fetch')) {
  809. errorAdvice = '建议检查: 1) 服务是否启动 2) 端口8321是否被占用 3) 防火墙设置';
  810. } else if (error.message.includes('超时')) {
  811. errorAdvice = '建议检查: 1) 服务响应速度 2) DLL是否正常加载 3) 网络连接';
  812. } else if (error.message.includes('CORS')) {
  813. errorAdvice = '建议检查: 1) 服务端CORS配置 2) 请求头设置';
  814. }
  815. if (errorAdvice) {
  816. addLog(errorAdvice, 'warning');
  817. }
  818. outputParams.value = JSON.stringify({
  819. success: false,
  820. error: error.message,
  821. timestamp: new Date().toISOString(),
  822. elapsed: elapsed + 'ms',
  823. function: currentFunction,
  824. advice: errorAdvice
  825. }, null, 2);
  826. } finally {
  827. executeBtn.disabled = false;
  828. executeBtn.textContent = '🚀 执行接口';
  829. }
  830. }
  831. // 更新状态显示
  832. function updateStatus(text, type = 'info') {
  833. statusText.textContent = text;
  834. statusDot.className = `status-dot ${type}`;
  835. }
  836. // 添加日志
  837. function addLog(message, type = 'info') {
  838. const timestamp = new Date().toLocaleTimeString();
  839. const logEntry = document.createElement('div');
  840. logEntry.className = `log-entry ${type}`;
  841. logEntry.textContent = `[${timestamp}] ${message}`;
  842. logContent.appendChild(logEntry);
  843. logContent.scrollTop = logContent.scrollHeight;
  844. // 限制日志条数
  845. const logs = logContent.querySelectorAll('.log-entry');
  846. if (logs.length > 100) {
  847. logs[0].remove();
  848. }
  849. }
  850. // 清空所有内容
  851. function clearAll() {
  852. inputParams.value = '';
  853. outputParams.value = '';
  854. updateStatus('已清空', 'info');
  855. addLog('已清空所有内容', 'info');
  856. // 移除所有按钮的活动状态
  857. document.querySelectorAll('.function-btn').forEach(btn => {
  858. btn.classList.remove('active');
  859. });
  860. currentFunction = null;
  861. }
  862. // 清空日志
  863. function clearLogs() {
  864. logContent.innerHTML = '<div class="log-entry info">[INFO] 日志已清空</div>';
  865. }
  866. // 显示配置模态框
  867. function showConfigModal() {
  868. document.getElementById('fixmedinsCode').value = config.fixmedinsCode;
  869. document.getElementById('fixmedinsName').value = config.fixmedinsName;
  870. document.getElementById('interfaceVersion').value = config.interfaceVersion;
  871. document.getElementById('operatorId').value = config.operatorId;
  872. document.getElementById('operatorName').value = config.operatorName;
  873. document.getElementById('baseUrl').value = config.baseUrl;
  874. document.getElementById('configModal').style.display = 'block';
  875. }
  876. // 关闭配置模态框
  877. function closeConfigModal() {
  878. document.getElementById('configModal').style.display = 'none';
  879. }
  880. // 保存配置
  881. function saveConfig() {
  882. config.fixmedinsCode = document.getElementById('fixmedinsCode').value;
  883. config.fixmedinsName = document.getElementById('fixmedinsName').value;
  884. config.interfaceVersion = document.getElementById('interfaceVersion').value;
  885. config.operatorId = document.getElementById('operatorId').value;
  886. config.operatorName = document.getElementById('operatorName').value;
  887. config.baseUrl = document.getElementById('baseUrl').value;
  888. // 保存到localStorage
  889. localStorage.setItem('workinjury_config', JSON.stringify(config));
  890. addLog('配置已保存', 'success');
  891. closeConfigModal();
  892. }
  893. // 加载保存的配置
  894. function loadSavedConfig() {
  895. const savedConfig = localStorage.getItem('workinjury_config');
  896. if (savedConfig) {
  897. try {
  898. config = { ...config, ...JSON.parse(savedConfig) };
  899. addLog('已加载保存的配置', 'info');
  900. } catch (e) {
  901. addLog('配置加载失败,使用默认配置', 'warning');
  902. }
  903. }
  904. }
  905. // 键盘快捷键
  906. document.addEventListener('keydown', function(e) {
  907. if (e.ctrlKey) {
  908. switch(e.key) {
  909. case 'Enter':
  910. e.preventDefault();
  911. executeAPI();
  912. break;
  913. case 'd':
  914. e.preventDefault();
  915. clearAll();
  916. break;
  917. }
  918. }
  919. });
  920. // 点击模态框外部关闭
  921. document.getElementById('configModal').addEventListener('click', function(e) {
  922. if (e.target === this) {
  923. closeConfigModal();
  924. }
  925. });
  926. // 运行系统诊断
  927. async function runDiagnostics() {
  928. addLog('开始运行系统诊断...', 'info');
  929. updateStatus('正在诊断...', 'warning');
  930. const diagnostics = {
  931. timestamp: new Date().toISOString(),
  932. tests: {}
  933. };
  934. // 1. 检查服务连通性
  935. addLog('1. 检查服务连通性...', 'info');
  936. try {
  937. // 使用健康检查接口测试连通性,避免OPTIONS请求问题
  938. const response = await fetch(`${config.baseUrl}/api/entry/workinjury`, {
  939. method: 'POST',
  940. headers: {
  941. 'Content-Type': 'application/json',
  942. 'Accept': 'application/json'
  943. },
  944. body: JSON.stringify({ action: 'health' })
  945. });
  946. if (response.ok) {
  947. diagnostics.tests.connectivity = {
  948. status: 'success',
  949. message: `服务可访问 (HTTP ${response.status})`,
  950. url: `${config.baseUrl}/api/entry/workinjury`
  951. };
  952. addLog('✓ 服务连通性检查通过', 'success');
  953. } else {
  954. diagnostics.tests.connectivity = {
  955. status: 'error',
  956. message: `服务响应错误: HTTP ${response.status}`,
  957. url: `${config.baseUrl}/api/entry/workinjury`
  958. };
  959. addLog('✗ 服务连通性检查失败: HTTP ' + response.status, 'error');
  960. }
  961. } catch (error) {
  962. diagnostics.tests.connectivity = {
  963. status: 'error',
  964. message: `服务无法访问: ${error.message}`,
  965. url: `${config.baseUrl}/api/entry/workinjury`
  966. };
  967. addLog('✗ 服务连通性检查失败: ' + error.message, 'error');
  968. }
  969. // 2. 解析健康状态(从连通性检查的结果中获取)
  970. addLog('2. 解析系统健康状态...', 'info');
  971. if (diagnostics.tests.connectivity.status === 'success') {
  972. try {
  973. // 从连通性检查的响应中获取健康检查结果
  974. const healthResponse = await fetch(`${config.baseUrl}/api/entry/workinjury`, {
  975. method: 'POST',
  976. headers: { 'Content-Type': 'application/json' },
  977. body: JSON.stringify({ action: 'health' })
  978. });
  979. if (healthResponse.ok) {
  980. const healthResult = await healthResponse.json();
  981. diagnostics.tests.health = healthResult;
  982. addLog('✓ 健康检查完成', 'success');
  983. } else {
  984. diagnostics.tests.health = {
  985. success: false,
  986. message: `健康检查失败: HTTP ${healthResponse.status}`
  987. };
  988. addLog('✗ 健康检查失败', 'error');
  989. }
  990. } catch (error) {
  991. diagnostics.tests.health = {
  992. success: false,
  993. message: `健康检查异常: ${error.message}`
  994. };
  995. addLog('✗ 健康检查异常: ' + error.message, 'error');
  996. }
  997. } else {
  998. diagnostics.tests.health = {
  999. success: false,
  1000. message: '跳过健康检查(服务连通性失败)'
  1001. };
  1002. addLog('⚠ 跳过健康检查(服务不可达)', 'warning');
  1003. }
  1004. // 3. 测试初始化
  1005. addLog('3. 测试系统初始化...', 'info');
  1006. if (diagnostics.tests.connectivity.status === 'success') {
  1007. try {
  1008. const initResponse = await fetch(`${config.baseUrl}/api/entry/workinjury`, {
  1009. method: 'POST',
  1010. headers: { 'Content-Type': 'application/json' },
  1011. body: JSON.stringify({
  1012. action: 'init',
  1013. config: {
  1014. fixmedinsCode: config.fixmedinsCode,
  1015. fixmedinsName: config.fixmedinsName,
  1016. receiverSysCode: 'JSYTH',
  1017. interfaceVersion: config.interfaceVersion,
  1018. operatorType: '1',
  1019. defaultOperator: config.operatorId,
  1020. defaultOperatorName: config.operatorName,
  1021. logPath: 'logs/workinjury/'
  1022. }
  1023. })
  1024. });
  1025. if (initResponse.ok) {
  1026. const initResult = await initResponse.json();
  1027. diagnostics.tests.initialization = initResult;
  1028. if (initResult.success) {
  1029. addLog('✓ 初始化测试成功', 'success');
  1030. } else {
  1031. addLog('✗ 初始化失败: ' + initResult.message, 'error');
  1032. }
  1033. } else {
  1034. diagnostics.tests.initialization = {
  1035. success: false,
  1036. message: `初始化请求失败: HTTP ${initResponse.status}`
  1037. };
  1038. addLog('✗ 初始化请求失败: HTTP ' + initResponse.status, 'error');
  1039. }
  1040. } catch (error) {
  1041. diagnostics.tests.initialization = {
  1042. success: false,
  1043. message: `初始化测试异常: ${error.message}`
  1044. };
  1045. addLog('✗ 初始化测试异常: ' + error.message, 'error');
  1046. }
  1047. } else {
  1048. diagnostics.tests.initialization = {
  1049. success: false,
  1050. message: '跳过初始化测试(服务连通性失败)'
  1051. };
  1052. addLog('⚠ 跳过初始化测试(服务不可达)', 'warning');
  1053. }
  1054. // 4. 环境信息收集
  1055. addLog('4. 收集环境信息...', 'info');
  1056. diagnostics.environment = {
  1057. userAgent: navigator.userAgent,
  1058. currentUrl: window.location.href,
  1059. timestamp: new Date().toLocaleString(),
  1060. config: config
  1061. };
  1062. addLog('✓ 环境信息收集完成', 'success');
  1063. // 5. 生成诊断报告
  1064. const report = generateDiagnosticReport(diagnostics);
  1065. outputParams.value = JSON.stringify(diagnostics, null, 2);
  1066. addLog('系统诊断完成', 'success');
  1067. addLog(report, 'info');
  1068. updateStatus('诊断完成', 'success');
  1069. }
  1070. // 生成诊断报告
  1071. function generateDiagnosticReport(diagnostics) {
  1072. let report = '=== 诊断报告 ===\n';
  1073. if (diagnostics.tests.connectivity?.status === 'success') {
  1074. report += '✓ 服务连通性: 正常\n';
  1075. } else {
  1076. report += '✗ 服务连通性: 异常 - ' + (diagnostics.tests.connectivity?.message || '未知错误') + '\n';
  1077. }
  1078. if (diagnostics.tests.health?.success) {
  1079. report += '✓ 系统健康: 正常\n';
  1080. } else {
  1081. report += '✗ 系统健康: 异常 - ' + (diagnostics.tests.health?.message || '未知错误') + '\n';
  1082. }
  1083. if (diagnostics.tests.initialization?.success) {
  1084. report += '✓ 系统初始化: 正常\n';
  1085. } else {
  1086. report += '✗ 系统初始化: 异常 - ' + (diagnostics.tests.initialization?.message || '未知错误') + '\n';
  1087. }
  1088. // 提供解决建议
  1089. const hasErrors = Object.values(diagnostics.tests).some(test =>
  1090. test.status === 'error' || test.success === false
  1091. );
  1092. if (hasErrors) {
  1093. report += '\n=== 解决建议 ===\n';
  1094. report += '1. 确认 ThCardReader.exe 服务已启动\n';
  1095. report += '2. 检查 JSSiInterface.dll 文件是否存在\n';
  1096. report += '3. 确认端口 8321 未被占用\n';
  1097. report += '4. 检查防火墙和杀毒软件设置\n';
  1098. report += '5. 确认医院编号和配置信息正确\n';
  1099. } else {
  1100. report += '\n✓ 所有检查都通过,系统状态正常';
  1101. }
  1102. return report;
  1103. }
  1104. // 页面加载完成
  1105. window.addEventListener('load', function() {
  1106. loadSavedConfig();
  1107. addLog('江苏工伤联网测试平台已启动', 'success');
  1108. updateStatus('系统就绪', 'success');
  1109. // 默认加载初始化功能
  1110. setTimeout(() => {
  1111. loadFunction('init', null);
  1112. }, 500);
  1113. });
  1114. </script>
  1115. </body>
  1116. </html>