|
|
<!DOCTYPE html> <html>
<head> <title>消毒API测试页面</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script src="./vue.global.js"></script> <script src="./dayjs/dayjs.min.js"></script> <script src="./dayjs/plugin/customParseFormat.js"></script> <script src="./dayjs/plugin/weekday.js"></script> <script src="./dayjs/plugin/localeData.js"></script> <script src="./dayjs/plugin/weekOfYear.js"></script> <script src="./dayjs/plugin/weekYear.js"></script> <script src="./dayjs/plugin/advancedFormat.js"></script> <script src="./dayjs/plugin/quarterOfYear.js"></script> <script src="./ant-design-vue/antd.min.js"></script> <link href="./ant-design-vue/reset.min.css" rel="stylesheet"> <script src="./virtual-keyboard.js"></script> </head>
<body> <div id="app" class="h-full"> <a-row class="h-full">
<a-col :span="3" class="sidebar"> <div style="display:flex;padding:5px;"> <a-input v-model:value="wsUrl" style="margin-right:5px;"></a-input> <a-button v-if="null === ws" @click="actionConnect" type="primary" class="connect-btn">连接</a-button> <a-button v-else @click="actionDisconnect" type="primary" danger class="disconnect-btn">断开</a-button> </div> <a-menu mode="inline" :items="actionMenuItems" @click="actionGroupMenuItemClick" class="menu"></a-menu> </a-col>
<a-col :span="15" style="display:flex;flex-direction: column;" class="main-content"> <div v-if="null !== actionActiveGroup" class="action-container"> <div v-for="item in actionActiveGroup.items" class="action-item"> <a-button type="primary" class="action-btn" @click="actionActionExecute(item)"> {{item.fnDispName}}</a-button>
<div v-for="param in item.params" > <a-select v-if="item.paramInfoMap[param].isEnum" class="param-input" v-model:value="item.values[param]" :placeholder="param" :dropdownMatchSelectWidth="false"> <a-select-option v-for="enumValue in item.paramInfoMap[param].enumValues" :key="enumValue" :value="enumValue">{{enumValue}}</a-select-option> </a-select>
<a-input v-else v-model:value="item.values[param]" :placeholder="param" class="param-input" @click="showKeyboard(item.values, param)"></a-input> </div> </div> </div> </a-col>
<a-col :span="6" class="log-section"> <div style="text-align: right; padding:10px;"> <a-button @click="actionClearLogs" class="clear-btn">清空日志</a-button> </div>
<div class="log-container"> <a-collapse> <a-collapse-panel v-for="(entry,index) in logs" :key="index" :header="entry.title"> <div class="log-content"> {{entry.content}} </div> </a-collapse-panel> </a-collapse> </div>
<div class="report-container"> <a-collapse> <a-collapse-panel v-for="(entry,index) in reports" :key="index" :header="entry.title"> <div class="report-content"> {{entry.content}} </div> </a-collapse-panel> </a-collapse> </div>
</a-col> </a-row> </div>
<style> :root { --primary-color: #1890ff; /* 主蓝色 */ --primary-hover: #40a9ff2a; /* 悬停蓝色 */ --success-color: #52c41a; /* 成功绿色 */ --warning-color: #faad14; /* 警告黄色 */ --error-color: #f5222d; /* 错误红色 */ --text-color: rgba(0, 0, 0, 0.85); /* 主文本颜色 */ --text-color-secondary: rgba(0, 0, 0, 0.45); /* 次文本颜色 */ --border-color: #d9d9d9; /* 边框颜色 */ --background-color: #f5f5f5; /* 背景色 */ --sidebar-bg: #f0f2f5; /* 侧边栏背景 */ --card-bg: #ffffff; /* 卡片背景 */ --action-btn-bg: #1890ff; /* 操作按钮背景 */ --action-btn-hover: #40a9ff; /* 操作按钮悬停 */ --disinfect-color: #1890ff; /* 消毒主题色 - */ }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; color: var(--text-color); background-color: var(--background-color); margin: 0; padding: 0; height: 100vh; }
.h-full { height: 100%; }
/* 侧边栏样式 */ .sidebar { background-color: var(--sidebar-bg); border-right: 1px solid var(--border-color); height: 100%; padding: 10px; }
.menu { border-right: none; }
.menu .ant-menu-item { margin: 4px 0; border-radius: 4px; }
.menu .ant-menu-item:hover { background-color: #e6f7ff; }
.menu .ant-menu-item-selected { background-color: var(--primary-color); color: white; }
/* 主内容区样式 */ .main-content { padding: 15px; background-color: #f0f2f5; }
.action-container { height: 0; flex-grow: 1; overflow-y: auto; margin-bottom: 10px; padding: 10px; background-color:#f0f2f5; border-radius: 4px; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03); }
.action-item { display: flex; flex-direction: row; margin-bottom: 10px; padding: 10px; background-color: var(--primary-hover); border-radius: 4px; border-left: 3px solid var(--primary-color); transition: all 0.3s; }
.action-item:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09); }
.action-btn { margin-right: 10px; min-width: 230px; font-weight: bold; text-align: left; background-color: var(--disinfect-color); border-color: var(--disinfect-color); }
.action-btn:hover { background-color: #36cfc9; border-color: #36cfc9; }
.param-input { margin-right: 10px; width: 150px; }
/* 日志区域样式 */ .log-section { height: 100%; display: flex; flex-direction: column; background-color: white; border-left: 1px solid var(--border-color); padding: 0 10px; }
.clear-btn { color: var(--text-color-secondary); border-color: var(--border-color); }
.clear-btn:hover { color: var(--primary-color); border-color: var(--primary-color); }
.log-container, .report-container { height: 600px; flex-grow: 1; overflow-y: auto; margin-bottom: 10px; background-color: white; border-radius: 4px; }
.report-container { height: 600px; }
.ant-collapse { background-color: white; border: none; }
.ant-collapse-item { border-bottom: 1px solid var(--border-color); }
.ant-collapse-header { font-weight: 500; color: var(--text-color); }
.log-content, .report-content { white-space: pre-wrap; font-family: 'Courier New', Courier, monospace; font-size: 13px; color: var(--text-color); background-color: #fafafa; padding: 10px; border-radius: 2px; }
/* 连接按钮样式 */ .connect-btn { background-color: var(--success-color); border-color: var(--success-color); }
.connect-btn:hover { background-color: #73d13d; border-color: #73d13d; }
.disconnect-btn { background-color: var(--error-color); border-color: var(--error-color); }
.disconnect-btn:hover { background-color: #ff4d4f; border-color: #ff4d4f; }
/* 响应式调整 */ @media (max-width: 1200px) { .action-item { flex-direction: column; } .action-btn { margin-bottom: 8px; width: 100%; } .param-input { width: 100%; margin-right: 0; margin-bottom: 8px; } } </style>
<!-- 保留原有的script内容 --> <script> // 原有的JavaScript代码保持不变 const { createApp } = Vue createApp({ data() { return { actions: [], menuList: [], actionActiveGroup: null, rawRequestContent: '', logs: [], reports: [], wsUrl: "ws://" + window.location.hostname + ":19001", ws: null, wsMessageIndex: 0, requests: {}, reportId: 0, map: new Map(), counter: 0 } }, computed: { actionMenuItems() { return this.menuList.map(i => ({ key: i.className, label: i.classDispName })); } }, mounted() { }, methods: { // connect getReportId(str) { // 如果字符串已存在,返回已存储的id if (this.map.has(str)) { return this.map.get(str); }
// 否则,生成一个新的ID const id = this.counter++; this.map.set(str, id); return id; }, actionConnect() { this.ws = new WebSocket(this.wsUrl); this.ws.onclose = () => this.ws = null; this.ws.onmessage = event => { let data = JSON.parse(event.data); let responseEntry = {}; responseEntry.title = `${data.fromClass}.${data.fromFn}`; responseEntry.content = JSON.stringify(data, null, 2); if ('Ack' === data.messageType && 'FNScheduler' === data.fromClass && 'geFnList' === data.fromFn) { this.actionListReload(data); } if ('Ack' === data.messageType && 'FNScheduler' === data.fromClass && 'geMenuList' === data.fromFn) { this.menuListReload(data); }
if ('Ack' === data.messageType) { this.logs.push(responseEntry); } if ('Report' === data.messageType) { let reportuuid = data.fromClass + '.' + data.fromFn; let reportId = this.getReportId(reportuuid); // this.reports.unshift(reportId,responseEntry); this.reports[reportId] = responseEntry; }
}; this.ws.onopen = () => { { this.wsMessageIndex++; let request = {}; request.messageId = this.wsMessageIndex; request.timeStamp = Math.floor(Date.now() / 1000); request.messageType = 'Command'; request.className = 'FNScheduler'; request.fnName = 'geFnList'; request.params = {}; this.wsCall(request); }
{ this.wsMessageIndex++; let request = {}; request.messageId = this.wsMessageIndex; request.timeStamp = Math.floor(Date.now() / 1000); request.messageType = 'Command'; request.className = 'FNScheduler'; request.fnName = 'geMenuList'; request.params = {}; this.wsCall(request); }
} },
// disconnect actionDisconnect() { this.ws.close(); },
// clear logs actionClearLogs() { this.logs = []; },
showKeyboard(values, param) { // const inputElement = document.querySelector(`input[placeholder="${param}"]`); // if (inputElement) { // VirtualKeyboard.show(inputElement); // this.updateDisplayContainer(inputElement); // } },
updateDisplayContainer(inputElement) { const displayContainer = document.querySelector('.virtual-keyboard-display'); if (displayContainer) { inputElement.addEventListener('input', () => { displayContainer.textContent = inputElement.value; }); } },
menuListReload(response) { let data = response.rely; if (undefined === data) { return; } this.menuList = data; },
// action list reload actionListReload(response) { let data = response.rely; if (undefined === data) { return; }
this.actions = []; for (let action of data.fnlist) { let group = this.actions.find(i => i.key === action.className); if (undefined === group) { group = { key: action.className, items: [] }; this.actions.push(group); } item = JSON.parse(JSON.stringify(action)); // Replace structuredClone with JSON.parse(JSON.stringify()) item.values = {}; item.paramInfoMap = {}; for (let i = 0; i < item.paramsTypeInfo.length; i++) { let typeName = item.paramsTypeInfo[i]; let info = data.typeInfoList.find(p => p.typeName === typeName); let name = item.params[i]; item.paramInfoMap[name] = info; } group.items.push(item); } },
// action group menu item click actionGroupMenuItemClick(event) { this.actionActiveGroup = this.actions.find(i => i.key === event.key); },
// action execute actionActionExecute(item) { this.wsMessageIndex++;
let request = {}; request.messageId = this.wsMessageIndex; request.timeStamp = Math.floor(Date.now() / 1000); request.messageType = 'Command'; request.className = item.className; request.fnName = item.fnName; request.params = {}; for (let i = 0; i < item.params.length; i++) { let param = item.params[i]; let type = item.paramsTypeInfo[i]; let value = item.values[param]; switch (type) { case 'json': case 'vector<bool>': case 'vector<json>': case 'vector<double>': case 'vector<int32_t>': case 'vector<string>': value = JSON.parse(value); break; case 'bool': value = '1' === value ? true : false; break; case 'float': case 'double': case 'int': value *= 1; break; case 'string': /* nothing to do here */ break; default: /* throw an exception is required. */ } request.params[param] = value; } this.logs = []; this.wsCall(request); },
// raw request send actionSendRawRequest() { let request = JSON.parse(this.rawRequestContent); this.wsCall(request); },
// ws call wsCall(request) { if (null === this.ws) { return; } let requestEntry = {}; requestEntry.title = `${request.messageType} : ${request.className}.${request.fnName}`; requestEntry.content = JSON.stringify(request, null, 2); this.logs.push(requestEntry); this.ws.send(JSON.stringify(request)); }, }, }) .use(antd) .mount('#app'); </script> </body>
</html>
|