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.

594 lines
17 KiB

12 months ago
12 months ago
12 months ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
12 months ago
4 weeks ago
12 months ago
4 weeks ago
4 weeks ago
12 months ago
4 weeks ago
12 months ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
12 months ago
4 weeks ago
12 months ago
3 weeks ago
3 weeks ago
12 months ago
11 months ago
3 weeks 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. curmenu:"",
  292. menuList: [],
  293. actionActiveGroup: null,
  294. rawRequestContent: '',
  295. logs: [],
  296. reports: [],
  297. wsUrl: "ws://" + window.location.hostname + ":19001",
  298. ws: null,
  299. wsMessageIndex: 0,
  300. requests: {},
  301. reportId: 0,
  302. map: new Map(),
  303. counter: 0
  304. }
  305. },
  306. computed: {
  307. actionMenuItems() {
  308. return this.menuList.map(i => ({ key: i.className, label: i.classDispName }));
  309. }
  310. },
  311. mounted() {
  312. },
  313. methods: {
  314. // connect
  315. getReportId(str) {
  316. // 如果字符串已存在,返回已存储的id
  317. if (this.map.has(str)) {
  318. return this.map.get(str);
  319. }
  320. // 否则,生成一个新的ID
  321. const id = this.counter++;
  322. this.map.set(str, id);
  323. return id;
  324. },
  325. actionConnect() {
  326. this.ws = new WebSocket(this.wsUrl);
  327. // this.ws.close();
  328. this.ws.onopen = () => {
  329. this.$message.success('连接成功');
  330. {
  331. this.wsMessageIndex++;
  332. let request = {};
  333. request.messageId = this.wsMessageIndex;
  334. request.timeStamp = Math.floor(Date.now() / 1000);
  335. request.messageType = 'Command';
  336. request.className = 'FNScheduler';
  337. request.fnName = 'geFnList';
  338. request.params = {};
  339. this.wsCallSlient(request);
  340. }
  341. {
  342. this.wsMessageIndex++;
  343. let request = {};
  344. request.messageId = this.wsMessageIndex;
  345. request.timeStamp = Math.floor(Date.now() / 1000);
  346. request.messageType = 'Command';
  347. request.className = 'FNScheduler';
  348. request.fnName = 'geMenuList';
  349. request.params = {};
  350. this.wsCallSlient(request);
  351. }
  352. }
  353. this.ws.onclose = () => this.ws = null;
  354. // this.ws.open();
  355. this.ws.onmessage = event => {
  356. let data = JSON.parse(event.data);
  357. let responseEntry = {};
  358. responseEntry.title = `${data.fromClass}.${data.fromFn}`;
  359. responseEntry.content = JSON.stringify(data, null, 2);
  360. if ('Ack' === data.messageType && 'FNScheduler' === data.fromClass && 'geFnList' === data.fromFn) {
  361. this.actionListReload(data);
  362. }
  363. else if ('Ack' === data.messageType && 'FNScheduler' === data.fromClass && 'geMenuList' === data.fromFn) {
  364. this.menuListReload(data);
  365. }
  366. else if ('Ack' === data.messageType) {
  367. this.logs.push(responseEntry);
  368. // 完成TODO: 根据ackcode显示提示信息
  369. if (data.ackcode !== 0) {
  370. // 错误情况,显示错误提示
  371. this.$message.error(data.message || '操作执行失败');
  372. } else {
  373. // 成功情况,显示成功提示
  374. this.$message.success(data.message || '操作执行成功');
  375. }
  376. //TODO:
  377. // 如果data.ackcode不为0,则弹出提示显示data.message
  378. // 否则显示执行成功
  379. }
  380. if ('Report' === data.messageType) {
  381. let reportuuid = data.fromClass + '.' + data.fromFn;
  382. let reportId = this.getReportId(reportuuid);
  383. // this.reports.unshift(reportId,responseEntry);
  384. this.reports[reportId] = responseEntry;
  385. }
  386. };
  387. },
  388. // disconnect
  389. actionDisconnect() {
  390. this.ws.close();
  391. },
  392. // clear logs
  393. actionClearLogs() {
  394. this.logs = [];
  395. },
  396. showKeyboard(values, param) {
  397. // const inputElement = document.querySelector(`input[placeholder="${param}"]`);
  398. // if (inputElement) {
  399. // VirtualKeyboard.show(inputElement);
  400. // this.updateDisplayContainer(inputElement);
  401. // }
  402. },
  403. updateDisplayContainer(inputElement) {
  404. const displayContainer = document.querySelector('.virtual-keyboard-display');
  405. if (displayContainer) {
  406. inputElement.addEventListener('input', () => {
  407. displayContainer.textContent = inputElement.value;
  408. });
  409. }
  410. },
  411. menuListReload(response) {
  412. let data = response.rely;
  413. if (undefined === data) {
  414. return;
  415. }
  416. this.menuList = data;
  417. },
  418. // action list reload
  419. actionListReload(response) {
  420. let data = response.rely;
  421. if (undefined === data) {
  422. return;
  423. }
  424. this.actions = [];
  425. for (let action of data.fnlist) {
  426. let group = this.actions.find(i => i.key === action.className);
  427. if (undefined === group) {
  428. group = { key: action.className, items: [] };
  429. this.actions.push(group);
  430. }
  431. item = JSON.parse(JSON.stringify(action)); // Replace structuredClone with JSON.parse(JSON.stringify())
  432. item.values = {};
  433. item.paramInfoMap = {};
  434. for (let i = 0; i < item.paramsTypeInfo.length; i++) {
  435. let typeName = item.paramsTypeInfo[i];
  436. let info = data.typeInfoList.find(p => p.typeName === typeName);
  437. let name = item.params[i];
  438. item.paramInfoMap[name] = info;
  439. }
  440. group.items.push(item);
  441. }
  442. this.actionActiveGroup = this.actions.find(i => i.key === curmenu);
  443. },
  444. // action group menu item click
  445. actionGroupMenuItemClick(event) {
  446. //
  447. {
  448. this.wsMessageIndex++;
  449. let request = {};
  450. request.messageId = this.wsMessageIndex;
  451. request.timeStamp = Math.floor(Date.now() / 1000);
  452. request.messageType = 'Command';
  453. request.className = 'FNScheduler';
  454. request.fnName = 'geFnList';
  455. request.params = {};
  456. this.wsCallSlient(request);
  457. }
  458. curmenu = event.key;
  459. },
  460. // action execute
  461. actionActionExecute(item) {
  462. this.wsMessageIndex++;
  463. let request = {};
  464. request.messageId = this.wsMessageIndex;
  465. request.timeStamp = Math.floor(Date.now() / 1000);
  466. request.messageType = 'Command';
  467. request.className = item.className;
  468. request.fnName = item.fnName;
  469. request.params = {};
  470. for (let i = 0; i < item.params.length; i++) {
  471. let param = item.params[i];
  472. let type = item.paramsTypeInfo[i];
  473. let value = item.paramsDescript[i].value;
  474. switch (type) {
  475. case 'json':
  476. case 'vector<bool>':
  477. case 'vector<json>':
  478. case 'vector<double>':
  479. case 'vector<int32_t>':
  480. case 'vector<string>': value = JSON.parse(value); break;
  481. case 'bool': value = '1' === value ? true : false; break;
  482. case 'float':
  483. case 'double':
  484. case 'int': value *= 1; break;
  485. case 'string': /* nothing to do here */ break;
  486. default: /* throw an exception is required. */
  487. }
  488. request.params[param] = value;
  489. }
  490. this.logs = [];
  491. this.wsCall(request);
  492. },
  493. // raw request send
  494. actionSendRawRequest() {
  495. let request = JSON.parse(this.rawRequestContent);
  496. this.wsCall(request);
  497. },
  498. // ws call
  499. wsCall(request) {
  500. if (null === this.ws) {
  501. return;
  502. }
  503. let requestEntry = {};
  504. requestEntry.title = `${request.messageType} : ${request.className}.${request.fnName}`;
  505. requestEntry.content = JSON.stringify(request, null, 2);
  506. this.logs.push(requestEntry);
  507. this.ws.send(JSON.stringify(request));
  508. },
  509. wsCallSlient(request) {
  510. if (null === this.ws) {
  511. return;
  512. }
  513. console.log('wsCallSlient', request);
  514. this.ws.send(JSON.stringify(request));
  515. },
  516. },
  517. })
  518. .use(antd)
  519. .mount('#app');
  520. </script>
  521. </body>
  522. </html>