test_jiangsu_ec_decode_ui.html 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671
  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>江科HIS - 江苏医保电子凭证解码测试</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: 'Microsoft YaHei', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  15. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  16. min-height: 100vh;
  17. padding: 20px;
  18. }
  19. .container {
  20. max-width: 1400px;
  21. margin: 0 auto;
  22. background: white;
  23. border-radius: 15px;
  24. box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
  25. overflow: hidden;
  26. }
  27. .header {
  28. background: linear-gradient(135deg, #2980b9 0%, #3498db 100%);
  29. color: white;
  30. padding: 30px;
  31. text-align: center;
  32. position: relative;
  33. }
  34. .header::before {
  35. content: '';
  36. position: absolute;
  37. top: 0;
  38. left: 0;
  39. right: 0;
  40. bottom: 0;
  41. background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="medical" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23medical)"/></svg>');
  42. opacity: 0.3;
  43. }
  44. .header h1 {
  45. font-size: 2.5em;
  46. margin-bottom: 10px;
  47. position: relative;
  48. z-index: 2;
  49. }
  50. .header p {
  51. font-size: 1.2em;
  52. opacity: 0.9;
  53. position: relative;
  54. z-index: 2;
  55. }
  56. .content {
  57. padding: 40px;
  58. }
  59. .card-grid {
  60. display: grid;
  61. grid-template-columns: 1fr 1fr;
  62. gap: 30px;
  63. margin-bottom: 30px;
  64. }
  65. .card {
  66. background: #f8f9fa;
  67. border-radius: 12px;
  68. padding: 25px;
  69. border: 1px solid #e9ecef;
  70. transition: all 0.3s ease;
  71. }
  72. .card:hover {
  73. transform: translateY(-5px);
  74. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  75. }
  76. .card h3 {
  77. color: #2c3e50;
  78. margin-bottom: 20px;
  79. font-size: 1.3em;
  80. border-bottom: 2px solid #3498db;
  81. padding-bottom: 10px;
  82. }
  83. .business-types {
  84. display: grid;
  85. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  86. gap: 15px;
  87. margin-bottom: 20px;
  88. }
  89. .business-btn {
  90. background: linear-gradient(135deg, #3498db, #2980b9);
  91. color: white;
  92. border: none;
  93. padding: 15px 20px;
  94. border-radius: 8px;
  95. cursor: pointer;
  96. font-size: 14px;
  97. font-weight: 600;
  98. transition: all 0.3s ease;
  99. position: relative;
  100. overflow: hidden;
  101. }
  102. .business-btn:hover {
  103. transform: translateY(-2px);
  104. box-shadow: 0 8px 25px rgba(52, 152, 219, 0.3);
  105. }
  106. .business-btn:active {
  107. transform: translateY(0);
  108. }
  109. .business-btn.outpatient { background: linear-gradient(135deg, #e74c3c, #c0392b); }
  110. .business-btn.inpatient { background: linear-gradient(135deg, #f39c12, #e67e22); }
  111. .business-btn.pharmacy { background: linear-gradient(135deg, #27ae60, #229954); }
  112. .test-controls {
  113. display: flex;
  114. gap: 15px;
  115. margin-bottom: 20px;
  116. flex-wrap: wrap;
  117. }
  118. .control-btn {
  119. padding: 12px 24px;
  120. border: none;
  121. border-radius: 6px;
  122. cursor: pointer;
  123. font-weight: 600;
  124. font-size: 14px;
  125. transition: all 0.3s ease;
  126. }
  127. .primary-btn {
  128. background: linear-gradient(135deg, #2ecc71, #27ae60);
  129. color: white;
  130. }
  131. .secondary-btn {
  132. background: linear-gradient(135deg, #95a5a6, #7f8c8d);
  133. color: white;
  134. }
  135. .control-btn:hover {
  136. transform: translateY(-2px);
  137. box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
  138. }
  139. .result-area {
  140. background: #f8f9fa;
  141. border-radius: 12px;
  142. padding: 25px;
  143. margin-top: 20px;
  144. border: 1px solid #e9ecef;
  145. }
  146. .result-tabs {
  147. display: flex;
  148. gap: 10px;
  149. margin-bottom: 20px;
  150. }
  151. .tab-btn {
  152. padding: 10px 20px;
  153. border: none;
  154. background: #e9ecef;
  155. border-radius: 6px 6px 0 0;
  156. cursor: pointer;
  157. font-weight: 600;
  158. transition: all 0.3s ease;
  159. }
  160. .tab-btn.active {
  161. background: #3498db;
  162. color: white;
  163. }
  164. .result-content {
  165. background: white;
  166. border-radius: 6px;
  167. padding: 20px;
  168. border: 1px solid #dee2e6;
  169. min-height: 300px;
  170. font-family: 'Consolas', 'Monaco', monospace;
  171. white-space: pre-wrap;
  172. overflow-x: auto;
  173. }
  174. .status-indicator {
  175. display: inline-block;
  176. width: 12px;
  177. height: 12px;
  178. border-radius: 50%;
  179. margin-right: 8px;
  180. }
  181. .status-success { background: #27ae60; }
  182. .status-error { background: #e74c3c; }
  183. .status-warning { background: #f39c12; }
  184. .status-info { background: #3498db; }
  185. .feature-list {
  186. list-style: none;
  187. padding: 0;
  188. }
  189. .feature-list li {
  190. padding: 8px 0;
  191. border-bottom: 1px solid #ecf0f1;
  192. display: flex;
  193. align-items: center;
  194. }
  195. .feature-list li:last-child {
  196. border-bottom: none;
  197. }
  198. .feature-icon {
  199. width: 20px;
  200. height: 20px;
  201. background: #27ae60;
  202. border-radius: 50%;
  203. margin-right: 10px;
  204. display: flex;
  205. align-items: center;
  206. justify-content: center;
  207. color: white;
  208. font-size: 12px;
  209. font-weight: bold;
  210. }
  211. .api-info {
  212. background: linear-gradient(135deg, #ecf0f1, #bdc3c7);
  213. padding: 20px;
  214. border-radius: 8px;
  215. margin: 15px 0;
  216. }
  217. .api-info h4 {
  218. color: #2c3e50;
  219. margin-bottom: 10px;
  220. font-size: 1.1em;
  221. }
  222. .api-url {
  223. background: white;
  224. padding: 10px;
  225. border-radius: 4px;
  226. font-family: 'Consolas', 'Monaco', monospace;
  227. border-left: 4px solid #3498db;
  228. margin: 5px 0;
  229. word-break: break-all;
  230. }
  231. @media (max-width: 768px) {
  232. .card-grid {
  233. grid-template-columns: 1fr;
  234. }
  235. .header h1 {
  236. font-size: 2em;
  237. }
  238. .content {
  239. padding: 20px;
  240. }
  241. }
  242. </style>
  243. </head>
  244. <body>
  245. <div class="container">
  246. <div class="header">
  247. <h1>🏥 江科HIS</h1>
  248. <p>医疗信息系统 - 江苏医保电子凭证解码测试平台</p>
  249. </div>
  250. <div class="content">
  251. <div class="card-grid">
  252. <div class="card">
  253. <h3>🎯 业务类型测试</h3>
  254. <h4 style="color: #e74c3c; margin: 15px 0 10px 0;">门诊业务</h4>
  255. <div class="business-types">
  256. <button class="business-btn outpatient" onclick="testBusiness('01101')">01101 - 挂号</button>
  257. <button class="business-btn outpatient" onclick="testBusiness('01201')">01201 - 问诊</button>
  258. <button class="business-btn outpatient" onclick="testBusiness('01202')">01202 - 预约检查</button>
  259. <button class="business-btn outpatient" onclick="testBusiness('01203')">01203 - 检查</button>
  260. <button class="business-btn outpatient" onclick="testBusiness('01204')">01204 - 治疗</button>
  261. <button class="business-btn outpatient" onclick="testBusiness('01301')">01301 - 结算</button>
  262. <button class="business-btn outpatient" onclick="testBusiness('01302')">01302 - 取药</button>
  263. </div>
  264. <h4 style="color: #f39c12; margin: 15px 0 10px 0;">住院业务</h4>
  265. <div class="business-types">
  266. <button class="business-btn inpatient" onclick="testBusiness('01102')">01102 - 建档</button>
  267. <button class="business-btn inpatient" onclick="testBusiness('01103')">01103 - 入院登记</button>
  268. <button class="business-btn inpatient" onclick="testBusiness('01104')">01104 - 缴纳预缴金</button>
  269. </div>
  270. <h4 style="color: #27ae60; margin: 15px 0 10px 0;">药店业务</h4>
  271. <div class="business-types">
  272. <button class="business-btn pharmacy" onclick="testBusiness('02121')">02121 - 购药</button>
  273. <button class="business-btn pharmacy" onclick="testBusiness('02122')">02122 - 下载外购处方</button>
  274. </div>
  275. </div>
  276. <div class="card">
  277. <h3>⚙️ 系统功能特性</h3>
  278. <ul class="feature-list">
  279. <li>
  280. <span class="feature-icon">✓</span>
  281. <span>符合江苏医保接口规范 v0.9.9.15</span>
  282. </li>
  283. <li>
  284. <span class="feature-icon">✓</span>
  285. <span>支持1.14.6电子凭证解码功能</span>
  286. </li>
  287. <li>
  288. <span class="feature-icon">✓</span>
  289. <span>12种标准业务类型支持</span>
  290. </li>
  291. <li>
  292. <span class="feature-icon">✓</span>
  293. <span>自动初始化和配置管理</span>
  294. </li>
  295. <li>
  296. <span class="feature-icon">✓</span>
  297. <span>完整的错误处理机制</span>
  298. </li>
  299. <li>
  300. <span class="feature-icon">✓</span>
  301. <span>实时状态监控和反馈</span>
  302. </li>
  303. <li>
  304. <span class="feature-icon">✓</span>
  305. <span>兼容现有华视读卡器</span>
  306. </li>
  307. <li>
  308. <span class="feature-icon">✓</span>
  309. <span>RESTful API接口调用</span>
  310. </li>
  311. </ul>
  312. <div class="api-info">
  313. <h4>📡 API接口地址</h4>
  314. <div class="api-url">http://localhost:8321/api/entry?param=jiangsu_qrcode_01101</div>
  315. <div class="api-url">http://localhost:8321/api/entry?param=jiangsu_ec_01301</div>
  316. <p style="margin: 10px 0; color: #7f8c8d; font-size: 0.9em;">
  317. 格式: jiangsu_qrcode_[业务类型] 或 jiangsu_ec_[业务类型]
  318. </p>
  319. </div>
  320. </div>
  321. </div>
  322. <div class="card">
  323. <h3>🎛️ 测试控制台</h3>
  324. <div class="test-controls">
  325. <button class="control-btn primary-btn" onclick="initializeSystem()">
  326. 🔧 初始化系统
  327. </button>
  328. <button class="control-btn secondary-btn" onclick="checkStatus()">
  329. 📊 检查状态
  330. </button>
  331. <button class="control-btn secondary-btn" onclick="clearResults()">
  332. 🗑️ 清空结果
  333. </button>
  334. <button class="control-btn secondary-btn" onclick="exportResults()">
  335. 📥 导出结果
  336. </button>
  337. </div>
  338. <div class="result-area">
  339. <div class="result-tabs">
  340. <button class="tab-btn active" onclick="switchTab('response')">
  341. <span class="status-indicator status-info"></span>响应结果
  342. </button>
  343. <button class="tab-btn" onclick="switchTab('request')">
  344. <span class="status-indicator status-warning"></span>请求参数
  345. </button>
  346. <button class="tab-btn" onclick="switchTab('error')">
  347. <span class="status-indicator status-error"></span>错误信息
  348. </button>
  349. <button class="tab-btn" onclick="switchTab('logs')">
  350. <span class="status-indicator status-success"></span>系统日志
  351. </button>
  352. </div>
  353. <div id="result-content" class="result-content">
  354. 欢迎使用江科HIS江苏医保电子凭证解码测试平台!
  355. 请选择业务类型开始测试...
  356. 系统信息:
  357. - 接口版本: 江苏医保接口规范 v0.9.9.15
  358. - 解码功能: 1.14.6 电子凭证解码
  359. - 支持业务: 12种标准业务类型
  360. - 服务地址: http://localhost:8321
  361. - 状态: 等待测试
  362. </div>
  363. </div>
  364. </div>
  365. </div>
  366. </div>
  367. <script>
  368. let currentTab = 'response';
  369. let currentBusiness = '';
  370. let testResults = {};
  371. // 切换选项卡
  372. function switchTab(tab) {
  373. currentTab = tab;
  374. // 更新选项卡样式
  375. document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
  376. event.target.classList.add('active');
  377. // 显示对应内容
  378. updateResultContent();
  379. }
  380. // 测试业务功能
  381. async function testBusiness(businessType) {
  382. currentBusiness = businessType;
  383. // 显示加载状态
  384. document.getElementById('result-content').textContent = `正在测试业务类型: ${businessType}...\n\n请等待响应...`;
  385. try {
  386. // 构造请求URL
  387. const url = `http://localhost:8321/api/entry?param=jiangsu_qrcode_${businessType}`;
  388. const startTime = new Date();
  389. // 发送请求
  390. const response = await fetch(url, {
  391. method: 'GET',
  392. headers: {
  393. 'Content-Type': 'application/json',
  394. }
  395. });
  396. const endTime = new Date();
  397. const duration = endTime - startTime;
  398. const result = await response.text();
  399. // 保存测试结果
  400. testResults[businessType] = {
  401. request: {
  402. url: url,
  403. method: 'GET',
  404. businessType: businessType,
  405. timestamp: startTime.toISOString(),
  406. duration: duration + 'ms'
  407. },
  408. response: {
  409. status: response.status,
  410. statusText: response.statusText,
  411. data: result,
  412. timestamp: endTime.toISOString()
  413. }
  414. };
  415. updateResultContent();
  416. } catch (error) {
  417. testResults[businessType] = {
  418. request: {
  419. url: `http://localhost:8321/api/entry?param=jiangsu_qrcode_${businessType}`,
  420. method: 'GET',
  421. businessType: businessType,
  422. timestamp: new Date().toISOString()
  423. },
  424. error: error.message,
  425. timestamp: new Date().toISOString()
  426. };
  427. updateResultContent();
  428. }
  429. }
  430. // 更新结果显示
  431. function updateResultContent() {
  432. const content = document.getElementById('result-content');
  433. if (!currentBusiness || !testResults[currentBusiness]) {
  434. content.textContent = `当前选项卡: ${currentTab}\n\n暂无测试数据,请先选择业务类型进行测试。`;
  435. return;
  436. }
  437. const result = testResults[currentBusiness];
  438. switch (currentTab) {
  439. case 'response':
  440. if (result.response) {
  441. content.textContent = `业务类型: ${currentBusiness}\n` +
  442. `响应状态: ${result.response.status} ${result.response.statusText}\n` +
  443. `响应时间: ${result.request.duration}\n` +
  444. `时间戳: ${result.response.timestamp}\n\n` +
  445. `响应数据:\n${result.response.data}`;
  446. } else if (result.error) {
  447. content.textContent = `业务类型: ${currentBusiness}\n` +
  448. `错误信息: ${result.error}\n` +
  449. `时间戳: ${result.timestamp}`;
  450. }
  451. break;
  452. case 'request':
  453. content.textContent = `业务类型: ${currentBusiness}\n` +
  454. `请求URL: ${result.request.url}\n` +
  455. `请求方法: ${result.request.method}\n` +
  456. `业务类型: ${result.request.businessType}\n` +
  457. `发送时间: ${result.request.timestamp}\n\n` +
  458. `实际DLL调用参数 (EcQuery函数):\n` +
  459. `{\n` +
  460. ` "data": {\n` +
  461. ` "orgId": "H32132200561",\n` +
  462. ` "businessType": "${result.request.businessType}",\n` +
  463. ` "operatorId": "system001",\n` +
  464. ` "operatorName": "系统管理员",\n` +
  465. ` "officeId": "32760",\n` +
  466. ` "officeName": "医保科"\n` +
  467. ` },\n` +
  468. ` "transType": "ec.query",\n` +
  469. ` "orgId": "H32132200561"\n` +
  470. `}\n\n` +
  471. `说明:\n` +
  472. `- orgId: 定点编号(医院组织机构代码)\n` +
  473. `- businessType: 用码业务类型\n` +
  474. `- operatorId: 收款员编号\n` +
  475. `- transType: 固定值 "ec.query"`;
  476. break;
  477. case 'error':
  478. if (result.error) {
  479. content.textContent = `业务类型: ${currentBusiness}\n` +
  480. `错误类型: 网络连接错误\n` +
  481. `错误信息: ${result.error}\n` +
  482. `时间戳: ${result.timestamp}\n\n` +
  483. `故障排查建议:\n` +
  484. `1. 检查ThCardReader服务是否运行\n` +
  485. `2. 确认端口8321是否被占用\n` +
  486. `3. 验证江苏医保DLL是否正确部署\n` +
  487. `4. 检查网络配置参数`;
  488. } else if (result.response && result.response.status !== 200) {
  489. content.textContent = `业务类型: ${currentBusiness}\n` +
  490. `HTTP状态: ${result.response.status} ${result.response.statusText}\n` +
  491. `时间戳: ${result.response.timestamp}\n\n` +
  492. `错误详情:\n${result.response.data}`;
  493. } else {
  494. content.textContent = `业务类型: ${currentBusiness}\n\n暂无错误信息。`;
  495. }
  496. break;
  497. case 'logs':
  498. content.textContent = `业务类型: ${currentBusiness}\n` +
  499. `测试日志记录:\n\n` +
  500. `[${result.request.timestamp}] 开始测试业务类型: ${currentBusiness}\n` +
  501. `[${result.request.timestamp}] 构造请求: ${result.request.url}\n` +
  502. `[${result.request.timestamp}] 发送HTTP请求...\n`;
  503. if (result.response) {
  504. content.textContent += `[${result.response.timestamp}] 收到响应: ${result.response.status}\n` +
  505. `[${result.response.timestamp}] 响应耗时: ${result.request.duration}\n` +
  506. `[${result.response.timestamp}] 测试完成\n`;
  507. } else if (result.error) {
  508. content.textContent += `[${result.timestamp}] 请求失败: ${result.error}\n` +
  509. `[${result.timestamp}] 测试中止\n`;
  510. }
  511. break;
  512. }
  513. }
  514. // 初始化系统
  515. async function initializeSystem() {
  516. document.getElementById('result-content').textContent = '正在初始化江苏医保系统...\n\n';
  517. try {
  518. const response = await fetch('http://localhost:8321/readcard/jiangsu/init', {
  519. method: 'GET'
  520. });
  521. const result = await response.text();
  522. document.getElementById('result-content').textContent =
  523. `系统初始化完成!\n\n` +
  524. `响应状态: ${response.status} ${response.statusText}\n` +
  525. `初始化结果:\n${result}`;
  526. } catch (error) {
  527. document.getElementById('result-content').textContent =
  528. `系统初始化失败!\n\n` +
  529. `错误信息: ${error.message}\n\n` +
  530. `请检查:\n` +
  531. `1. ThCardReader服务是否运行\n` +
  532. `2. 端口8321是否可用\n` +
  533. `3. 江苏医保DLL是否正确安装`;
  534. }
  535. }
  536. // 检查状态
  537. async function checkStatus() {
  538. document.getElementById('result-content').textContent = '正在检查系统状态...\n\n';
  539. try {
  540. const response = await fetch('http://localhost:8321/readcard/jiangsu/status', {
  541. method: 'GET'
  542. });
  543. const result = await response.text();
  544. document.getElementById('result-content').textContent =
  545. `系统状态检查完成!\n\n` +
  546. `响应状态: ${response.status} ${response.statusText}\n` +
  547. `系统状态:\n${result}`;
  548. } catch (error) {
  549. document.getElementById('result-content').textContent =
  550. `系统状态检查失败!\n\n` +
  551. `错误信息: ${error.message}`;
  552. }
  553. }
  554. // 清空结果
  555. function clearResults() {
  556. testResults = {};
  557. currentBusiness = '';
  558. document.getElementById('result-content').textContent =
  559. '结果已清空。\n\n请选择业务类型开始新的测试。';
  560. }
  561. // 导出结果
  562. function exportResults() {
  563. if (Object.keys(testResults).length === 0) {
  564. alert('暂无测试结果可导出!');
  565. return;
  566. }
  567. const exportData = {
  568. timestamp: new Date().toISOString(),
  569. system: '江科HIS - 江苏医保电子凭证解码测试',
  570. version: 'v0.9.9.15',
  571. results: testResults
  572. };
  573. const dataStr = JSON.stringify(exportData, null, 2);
  574. const dataBlob = new Blob([dataStr], {type: 'application/json'});
  575. const link = document.createElement('a');
  576. link.href = URL.createObjectURL(dataBlob);
  577. link.download = `江科HIS测试结果_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
  578. link.click();
  579. }
  580. // 页面加载完成后的初始化
  581. document.addEventListener('DOMContentLoaded', function() {
  582. console.log('江科HIS - 江苏医保电子凭证解码测试平台已加载');
  583. });
  584. </script>
  585. </body>
  586. </html>