You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

590 lines
17 KiB

12 months ago
12 months ago
12 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
12 months ago
1 month ago
12 months ago
1 month ago
1 month ago
12 months ago
1 month ago
12 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
4 weeks ago
4 weeks ago
1 month ago
1 month ago
12 months ago
1 month ago
12 months ago
12 months ago
11 months ago
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>消毒API测试页面</title>
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  7. <script src="./vue.global.js"></script>
  8. <script src="./dayjs/dayjs.min.js"></script>
  9. <script src="./dayjs/plugin/customParseFormat.js"></script>
  10. <script src="./dayjs/plugin/weekday.js"></script>
  11. <script src="./dayjs/plugin/localeData.js"></script>
  12. <script src="./dayjs/plugin/weekOfYear.js"></script>
  13. <script src="./dayjs/plugin/weekYear.js"></script>
  14. <script src="./dayjs/plugin/advancedFormat.js"></script>
  15. <script src="./dayjs/plugin/quarterOfYear.js"></script>
  16. <script src="./ant-design-vue/antd.min.js"></script>
  17. <link href="./ant-design-vue/reset.min.css" rel="stylesheet">
  18. <script src="./virtual-keyboard.js"></script>
  19. </head>
  20. <body>
  21. <div id="app" class="h-full">
  22. <a-row class="h-full">
  23. <a-col :span="3" class="sidebar">
  24. <div style="display:flex;padding:5px;">
  25. <a-input v-model:value="wsUrl" style="margin-right:5px;"></a-input>
  26. <a-button v-if="null === ws" @click="actionConnect" type="primary" class="connect-btn">连接</a-button>
  27. <a-button v-else @click="actionDisconnect" type="primary" danger class="disconnect-btn">断开</a-button>
  28. </div>
  29. <a-menu mode="inline" :items="actionMenuItems" @click="actionGroupMenuItemClick" class="menu"></a-menu>
  30. </a-col>
  31. <a-col :span="15" style="display:flex;flex-direction: column;" class="main-content">
  32. <div v-if="null !== actionActiveGroup" class="action-container">
  33. <div v-for="item in actionActiveGroup.items">
  34. <!-- fn -->
  35. <div v-if="!item.isLineBreak" class="action-item">
  36. <a-button type="primary" class="action-btn" @click="actionActionExecute(item)">
  37. {{item.fnDispName}}</a-button>
  38. <div v-for="param in item.paramsDescript">
  39. <a-select v-if="item.paramInfoMap[param.name].isEnum" class="param-input" v-model:value="param.value"
  40. :placeholder="param.name" :dropdownMatchSelectWidth="false">
  41. <a-select-option v-for="enumValue in item.paramInfoMap[param.name].enumValues" :key="enumValue"
  42. :value="enumValue">{{enumValue}}</a-select-option>
  43. </a-select>
  44. <a-input v-else v-model:value="param.value" :placeholder="param.name" class="param-input"
  45. @click="showKeyboard(item.values, param.name)">
  46. </a-input>
  47. </div>
  48. </div>
  49. <!-- lineBreak -->
  50. <a-divider v-else style="height: 1px; border-color: #7cb305"
  51. orientation="left">{{item.lineBreakName}}</a-divider>
  52. </div>
  53. </div>
  54. </a-col>
  55. <a-col :span="6" class="log-section">
  56. <div style="text-align: right; padding:10px;">
  57. <a-button @click="actionClearLogs" class="clear-btn">清空日志</a-button>
  58. </div>
  59. <div class="log-container">
  60. <a-collapse>
  61. <a-collapse-panel v-for="(entry,index) in logs" :key="index" :header="entry.title">
  62. <div class="log-content">
  63. {{entry.content}}
  64. </div>
  65. </a-collapse-panel>
  66. </a-collapse>
  67. </div>
  68. <div class="report-container">
  69. <a-collapse>
  70. <a-collapse-panel v-for="(entry,index) in reports" :key="index" :header="entry.title">
  71. <div class="report-content">
  72. {{entry.content}}
  73. </div>
  74. </a-collapse-panel>
  75. </a-collapse>
  76. </div>
  77. </a-col>
  78. </a-row>
  79. </div>
  80. <style>
  81. :root {
  82. --primary-color: #1890ff;
  83. /* 主蓝色 */
  84. --primary-hover: #40a9ff2a;
  85. /* 悬停蓝色 */
  86. --success-color: #52c41a;
  87. /* 成功绿色 */
  88. --warning-color: #faad14;
  89. /* 警告黄色 */
  90. --error-color: #f5222d;
  91. /* 错误红色 */
  92. --text-color: rgba(0, 0, 0, 0.85);
  93. /* 主文本颜色 */
  94. --text-color-secondary: rgba(0, 0, 0, 0.45);
  95. /* 次文本颜色 */
  96. --border-color: #d9d9d9;
  97. /* 边框颜色 */
  98. --background-color: #f5f5f5;
  99. /* 背景色 */
  100. --sidebar-bg: #f0f2f5;
  101. /* 侧边栏背景 */
  102. --card-bg: #ffffff;
  103. /* 卡片背景 */
  104. --action-btn-bg: #1890ff;
  105. /* 操作按钮背景 */
  106. --action-btn-hover: #40a9ff;
  107. /* 操作按钮悬停 */
  108. --disinfect-color: #1890ff;
  109. /* 消毒主题色 - */
  110. }
  111. body {
  112. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  113. color: var(--text-color);
  114. background-color: var(--background-color);
  115. margin: 0;
  116. padding: 0;
  117. height: 100vh;
  118. }
  119. .h-full {
  120. height: 100%;
  121. }
  122. /* 侧边栏样式 */
  123. .sidebar {
  124. background-color: var(--sidebar-bg);
  125. border-right: 1px solid var(--border-color);
  126. height: 100%;
  127. padding: 10px;
  128. }
  129. .menu {
  130. border-right: none;
  131. }
  132. .menu .ant-menu-item {
  133. margin: 4px 0;
  134. border-radius: 4px;
  135. }
  136. .menu .ant-menu-item:hover {
  137. background-color: #e6f7ff;
  138. }
  139. .menu .ant-menu-item-selected {
  140. background-color: var(--primary-color);
  141. color: white;
  142. }
  143. /* 主内容区样式 */
  144. .main-content {
  145. padding: 15px;
  146. background-color: #f0f2f5;
  147. }
  148. .action-container {
  149. height: 0;
  150. flex-grow: 1;
  151. overflow-y: auto;
  152. margin-bottom: 10px;
  153. padding: 10px;
  154. background-color: #f0f2f5;
  155. border-radius: 4px;
  156. box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
  157. }
  158. .action-item {
  159. display: flex;
  160. flex-direction: row;
  161. margin-bottom: 10px;
  162. padding: 10px;
  163. background-color: var(--primary-hover);
  164. border-radius: 4px;
  165. border-left: 3px solid var(--primary-color);
  166. transition: all 0.3s;
  167. }
  168. .action-item:hover {
  169. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
  170. }
  171. .action-btn {
  172. margin-right: 10px;
  173. min-width: 230px;
  174. font-weight: bold;
  175. text-align: left;
  176. background-color: var(--disinfect-color);
  177. border-color: var(--disinfect-color);
  178. }
  179. .action-btn:hover {
  180. background-color: #36cfc9;
  181. border-color: #36cfc9;
  182. }
  183. .param-input {
  184. margin-right: 10px;
  185. width: 150px;
  186. }
  187. /* 日志区域样式 */
  188. .log-section {
  189. height: 100%;
  190. display: flex;
  191. flex-direction: column;
  192. background-color: white;
  193. border-left: 1px solid var(--border-color);
  194. padding: 0 10px;
  195. }
  196. .clear-btn {
  197. color: var(--text-color-secondary);
  198. border-color: var(--border-color);
  199. }
  200. .clear-btn:hover {
  201. color: var(--primary-color);
  202. border-color: var(--primary-color);
  203. }
  204. .log-container,
  205. .report-container {
  206. height: 600px;
  207. flex-grow: 1;
  208. overflow-y: auto;
  209. margin-bottom: 10px;
  210. background-color: white;
  211. border-radius: 4px;
  212. }
  213. .report-container {
  214. height: 600px;
  215. }
  216. .ant-collapse {
  217. background-color: white;
  218. border: none;
  219. }
  220. .ant-collapse-item {
  221. border-bottom: 1px solid var(--border-color);
  222. }
  223. .ant-collapse-header {
  224. font-weight: 500;
  225. color: var(--text-color);
  226. }
  227. .log-content,
  228. .report-content {
  229. white-space: pre-wrap;
  230. font-family: 'Courier New', Courier, monospace;
  231. font-size: 13px;
  232. color: var(--text-color);
  233. background-color: #fafafa;
  234. padding: 10px;
  235. border-radius: 2px;
  236. }
  237. /* 连接按钮样式 */
  238. .connect-btn {
  239. background-color: var(--success-color);
  240. border-color: var(--success-color);
  241. }
  242. .connect-btn:hover {
  243. background-color: #73d13d;
  244. border-color: #73d13d;
  245. }
  246. .disconnect-btn {
  247. background-color: var(--error-color);
  248. border-color: var(--error-color);
  249. }
  250. .disconnect-btn:hover {
  251. background-color: #ff4d4f;
  252. border-color: #ff4d4f;
  253. }
  254. .line-break-divider {
  255. margin: 1px 0;
  256. }
  257. .custom-divider {
  258. border-color: var(--primary-color);
  259. color: var(--primary-color);
  260. }
  261. .divider-text {
  262. font-weight: 500;
  263. font-size: 16px;
  264. padding: 0 10px;
  265. background: linear-gradient(90deg, transparent, var(--primary-hover), transparent);
  266. }
  267. /* 响应式调整 */
  268. @media (max-width: 1200px) {
  269. .action-item {
  270. flex-direction: column;
  271. }
  272. .action-btn {
  273. margin-bottom: 8px;
  274. width: 100%;
  275. }
  276. .param-input {
  277. width: 100%;
  278. margin-right: 0;
  279. margin-bottom: 8px;
  280. }
  281. }
  282. </style>
  283. <!-- 保留原有的script内容 -->
  284. <script>
  285. // 原有的JavaScript代码保持不变
  286. const { createApp } = Vue
  287. createApp({
  288. data() {
  289. return {
  290. actions: [],
  291. menuList: [],
  292. actionActiveGroup: null,
  293. rawRequestContent: '',
  294. logs: [],
  295. reports: [],
  296. wsUrl: "ws://" + window.location.hostname + ":19001",
  297. ws: null,
  298. wsMessageIndex: 0,
  299. requests: {},
  300. reportId: 0,
  301. map: new Map(),
  302. counter: 0
  303. }
  304. },
  305. computed: {
  306. actionMenuItems() {
  307. return this.menuList.map(i => ({ key: i.className, label: i.classDispName }));
  308. }
  309. },
  310. mounted() {
  311. },
  312. methods: {
  313. // connect
  314. getReportId(str) {
  315. // 如果字符串已存在,返回已存储的id
  316. if (this.map.has(str)) {
  317. return this.map.get(str);
  318. }
  319. // 否则,生成一个新的ID
  320. const id = this.counter++;
  321. this.map.set(str, id);
  322. return id;
  323. },
  324. actionConnect() {
  325. this.ws = new WebSocket(this.wsUrl);
  326. // this.ws.close();
  327. this.ws.onopen = () => {
  328. this.$message.success('连接成功');
  329. {
  330. this.wsMessageIndex++;
  331. let request = {};
  332. request.messageId = this.wsMessageIndex;
  333. request.timeStamp = Math.floor(Date.now() / 1000);
  334. request.messageType = 'Command';
  335. request.className = 'FNScheduler';
  336. request.fnName = 'geFnList';
  337. request.params = {};
  338. this.wsCallSlient(request);
  339. }
  340. {
  341. this.wsMessageIndex++;
  342. let request = {};
  343. request.messageId = this.wsMessageIndex;
  344. request.timeStamp = Math.floor(Date.now() / 1000);
  345. request.messageType = 'Command';
  346. request.className = 'FNScheduler';
  347. request.fnName = 'geMenuList';
  348. request.params = {};
  349. this.wsCallSlient(request);
  350. }
  351. }
  352. this.ws.onclose = () => this.ws = null;
  353. // this.ws.open();
  354. this.ws.onmessage = event => {
  355. let data = JSON.parse(event.data);
  356. let responseEntry = {};
  357. responseEntry.title = `${data.fromClass}.${data.fromFn}`;
  358. responseEntry.content = JSON.stringify(data, null, 2);
  359. if ('Ack' === data.messageType && 'FNScheduler' === data.fromClass && 'geFnList' === data.fromFn) {
  360. this.actionListReload(data);
  361. }
  362. else if ('Ack' === data.messageType && 'FNScheduler' === data.fromClass && 'geMenuList' === data.fromFn) {
  363. this.menuListReload(data);
  364. }
  365. else if ('Ack' === data.messageType) {
  366. this.logs.push(responseEntry);
  367. // 完成TODO: 根据ackcode显示提示信息
  368. if (data.ackcode !== 0) {
  369. // 错误情况,显示错误提示
  370. this.$message.error(data.message || '操作执行失败');
  371. } else {
  372. // 成功情况,显示成功提示
  373. this.$message.success(data.message || '操作执行成功');
  374. }
  375. //TODO:
  376. // 如果data.ackcode不为0,则弹出提示显示data.message
  377. // 否则显示执行成功
  378. }
  379. if ('Report' === data.messageType) {
  380. let reportuuid = data.fromClass + '.' + data.fromFn;
  381. let reportId = this.getReportId(reportuuid);
  382. // this.reports.unshift(reportId,responseEntry);
  383. this.reports[reportId] = responseEntry;
  384. }
  385. };
  386. },
  387. // disconnect
  388. actionDisconnect() {
  389. this.ws.close();
  390. },
  391. // clear logs
  392. actionClearLogs() {
  393. this.logs = [];
  394. },
  395. showKeyboard(values, param) {
  396. // const inputElement = document.querySelector(`input[placeholder="${param}"]`);
  397. // if (inputElement) {
  398. // VirtualKeyboard.show(inputElement);
  399. // this.updateDisplayContainer(inputElement);
  400. // }
  401. },
  402. updateDisplayContainer(inputElement) {
  403. const displayContainer = document.querySelector('.virtual-keyboard-display');
  404. if (displayContainer) {
  405. inputElement.addEventListener('input', () => {
  406. displayContainer.textContent = inputElement.value;
  407. });
  408. }
  409. },
  410. menuListReload(response) {
  411. let data = response.rely;
  412. if (undefined === data) {
  413. return;
  414. }
  415. this.menuList = data;
  416. },
  417. // action list reload
  418. actionListReload(response) {
  419. let data = response.rely;
  420. if (undefined === data) {
  421. return;
  422. }
  423. this.actions = [];
  424. for (let action of data.fnlist) {
  425. let group = this.actions.find(i => i.key === action.className);
  426. if (undefined === group) {
  427. group = { key: action.className, items: [] };
  428. this.actions.push(group);
  429. }
  430. item = JSON.parse(JSON.stringify(action)); // Replace structuredClone with JSON.parse(JSON.stringify())
  431. item.values = {};
  432. item.paramInfoMap = {};
  433. for (let i = 0; i < item.paramsTypeInfo.length; i++) {
  434. let typeName = item.paramsTypeInfo[i];
  435. let info = data.typeInfoList.find(p => p.typeName === typeName);
  436. let name = item.params[i];
  437. item.paramInfoMap[name] = info;
  438. }
  439. group.items.push(item);
  440. }
  441. },
  442. // action group menu item click
  443. actionGroupMenuItemClick(event) {
  444. //
  445. {
  446. this.wsMessageIndex++;
  447. let request = {};
  448. request.messageId = this.wsMessageIndex;
  449. request.timeStamp = Math.floor(Date.now() / 1000);
  450. request.messageType = 'Command';
  451. request.className = 'FNScheduler';
  452. request.fnName = 'geFnList';
  453. request.params = {};
  454. this.wsCallSlient(request);
  455. }
  456. this.actionActiveGroup = this.actions.find(i => i.key === event.key);
  457. },
  458. // action execute
  459. actionActionExecute(item) {
  460. this.wsMessageIndex++;
  461. let request = {};
  462. request.messageId = this.wsMessageIndex;
  463. request.timeStamp = Math.floor(Date.now() / 1000);
  464. request.messageType = 'Command';
  465. request.className = item.className;
  466. request.fnName = item.fnName;
  467. request.params = {};
  468. for (let i = 0; i < item.params.length; i++) {
  469. let param = item.params[i];
  470. let type = item.paramsTypeInfo[i];
  471. let value = item.paramsDescript[i].value;
  472. switch (type) {
  473. case 'json':
  474. case 'vector<bool>':
  475. case 'vector<json>':
  476. case 'vector<double>':
  477. case 'vector<int32_t>':
  478. case 'vector<string>': value = JSON.parse(value); break;
  479. case 'bool': value = '1' === value ? true : false; break;
  480. case 'float':
  481. case 'double':
  482. case 'int': value *= 1; break;
  483. case 'string': /* nothing to do here */ break;
  484. default: /* throw an exception is required. */
  485. }
  486. request.params[param] = value;
  487. }
  488. this.logs = [];
  489. this.wsCall(request);
  490. },
  491. // raw request send
  492. actionSendRawRequest() {
  493. let request = JSON.parse(this.rawRequestContent);
  494. this.wsCall(request);
  495. },
  496. // ws call
  497. wsCall(request) {
  498. if (null === this.ws) {
  499. return;
  500. }
  501. let requestEntry = {};
  502. requestEntry.title = `${request.messageType} : ${request.className}.${request.fnName}`;
  503. requestEntry.content = JSON.stringify(request, null, 2);
  504. this.logs.push(requestEntry);
  505. this.ws.send(JSON.stringify(request));
  506. },
  507. wsCallSlient(request) {
  508. if (null === this.ws) {
  509. return;
  510. }
  511. this.ws.send(JSON.stringify(request));
  512. },
  513. },
  514. })
  515. .use(antd)
  516. .mount('#app');
  517. </script>
  518. </body>
  519. </html>