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.
595 lines
17 KiB
595 lines
17 KiB
<!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">
|
|
|
|
<!-- fn -->
|
|
<div v-if="!item.isLineBreak" class="action-item">
|
|
|
|
<a-button type="primary" class="action-btn" @click="actionActionExecute(item)">
|
|
{{item.fnDispName}}</a-button>
|
|
|
|
<div v-for="param in item.paramsDescript">
|
|
<a-select v-if="item.paramInfoMap[param.name].isEnum" class="param-input" v-model:value="param.value"
|
|
:placeholder="param.name" :dropdownMatchSelectWidth="false">
|
|
|
|
<a-select-option v-for="enumValue in item.paramInfoMap[param.name].enumValues" :key="enumValue"
|
|
:value="enumValue">{{enumValue}}</a-select-option>
|
|
</a-select>
|
|
|
|
<a-input v-else v-model:value="param.value" :placeholder="param.name" class="param-input"
|
|
@click="showKeyboard(item.values, param.name)">
|
|
</a-input>
|
|
</div>
|
|
|
|
</div>
|
|
<!-- lineBreak -->
|
|
<a-divider v-else style="height: 1px; border-color: #7cb305"
|
|
orientation="left">{{item.lineBreakName}}</a-divider>
|
|
</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;
|
|
}
|
|
|
|
.line-break-divider {
|
|
margin: 1px 0;
|
|
}
|
|
|
|
.custom-divider {
|
|
border-color: var(--primary-color);
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.divider-text {
|
|
font-weight: 500;
|
|
font-size: 16px;
|
|
padding: 0 10px;
|
|
background: linear-gradient(90deg, transparent, var(--primary-hover), transparent);
|
|
}
|
|
|
|
/* 响应式调整 */
|
|
@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: [],
|
|
curmenu:"",
|
|
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.close();
|
|
this.ws.onopen = () => {
|
|
this.$message.success('连接成功');
|
|
{
|
|
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.wsCallSlient(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.wsCallSlient(request);
|
|
}
|
|
}
|
|
this.ws.onclose = () => this.ws = null;
|
|
|
|
// this.ws.open();
|
|
|
|
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);
|
|
}
|
|
else if ('Ack' === data.messageType && 'FNScheduler' === data.fromClass && 'geMenuList' === data.fromFn) {
|
|
this.menuListReload(data);
|
|
}
|
|
else if ('Ack' === data.messageType) {
|
|
this.logs.push(responseEntry);
|
|
// 完成TODO: 根据ackcode显示提示信息
|
|
if (data.ackcode !== 0) {
|
|
// 错误情况,显示错误提示
|
|
this.$message.error(data.message || '操作执行失败');
|
|
} else {
|
|
// 成功情况,显示成功提示
|
|
this.$message.success(data.message || '操作执行成功');
|
|
}
|
|
//TODO:
|
|
// 如果data.ackcode不为0,则弹出提示显示data.message
|
|
// 否则显示执行成功
|
|
}
|
|
if ('Report' === data.messageType) {
|
|
let reportuuid = data.fromClass + '.' + data.fromFn;
|
|
let reportId = this.getReportId(reportuuid);
|
|
// this.reports.unshift(reportId,responseEntry);
|
|
this.reports[reportId] = responseEntry;
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
// 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);
|
|
}
|
|
|
|
this.actionActiveGroup = this.actions.find(i => i.key === curmenu);
|
|
},
|
|
|
|
// action group menu item click
|
|
actionGroupMenuItemClick(event) {
|
|
//
|
|
|
|
{
|
|
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.wsCallSlient(request);
|
|
}
|
|
|
|
curmenu = 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.paramsDescript[i].value;
|
|
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));
|
|
},
|
|
wsCallSlient(request) {
|
|
if (null === this.ws) {
|
|
return;
|
|
}
|
|
console.log('wsCallSlient', request);
|
|
this.ws.send(JSON.stringify(request));
|
|
},
|
|
},
|
|
})
|
|
.use(antd)
|
|
.mount('#app');
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|