|
@@ -0,0 +1,492 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="page">
|
|
|
|
|
+ <aside class="side">
|
|
|
|
|
+ <div class="panel-head">
|
|
|
|
|
+ <span class="label">命令列表</span>
|
|
|
|
|
+ <span v-if="commands.length" class="count">{{ commands.length }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="command-list">
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="(item, index) in commands"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="command-btn"
|
|
|
|
|
+ :class="{ active: activeIndex === index }"
|
|
|
|
|
+ @click="selectCommand(item, index)"
|
|
|
|
|
+ v-log="{ describe: { action: '点击任一参数命令', name: item.name } }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="command-mark">#</span>
|
|
|
|
|
+ <span class="command-name">{{ item.name }}</span>
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <div v-if="loaded && !commands.length" class="empty">暂无命令</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </aside>
|
|
|
|
|
+
|
|
|
|
|
+ <section class="editor">
|
|
|
|
|
+ <div class="panel">
|
|
|
|
|
+ <div class="panel-head">
|
|
|
|
|
+ <span class="label">JSON内容</span>
|
|
|
|
|
+ <span v-if="activeName" class="hint">#{{ activeName }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ v-model="jsonText"
|
|
|
|
|
+ class="json-box"
|
|
|
|
|
+ spellcheck="false"
|
|
|
|
|
+ placeholder="选择左侧命令,或直接填写 JSON / 十六进制命令"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="panel">
|
|
|
|
|
+ <div class="panel-head">
|
|
|
|
|
+ <span class="label">数据返回</span>
|
|
|
|
|
+ <span v-if="returnLines.length" class="count">{{ returnLines.length }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div ref="returnBox" class="return-box">
|
|
|
|
|
+ <div v-if="!returnLines.length" class="empty return-empty">下发后,设备返回会显示在这里</div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(line, index) in returnLines"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ class="return-line"
|
|
|
|
|
+ :class="{ 'is-error': isErrorMessage(line.message) }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span v-if="line.time" class="return-time">{{ line.time }}</span>
|
|
|
|
|
+ <span class="return-msg">{{ line.message }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="action-row">
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="action-btn action-btn--ghost"
|
|
|
|
|
+ @click="clearReturn"
|
|
|
|
|
+ v-log="{ describe: { action: '点击清空任一参数返回' } }"
|
|
|
|
|
+ >
|
|
|
|
|
+ 清空
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="action-btn action-btn--primary"
|
|
|
|
|
+ @click="sendJson"
|
|
|
|
|
+ v-log="{ describe: { action: '点击下发JSON' } }"
|
|
|
|
|
+ >
|
|
|
|
|
+ 下发JSON
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
+import client from '@/stores/modules/client'
|
|
|
|
|
+import socket from '@/stores/modules/socket'
|
|
|
|
|
+import icpList from '@/utils/ipc'
|
|
|
|
|
+
|
|
|
|
|
+type CommandItem = {
|
|
|
|
|
+ name: string
|
|
|
|
|
+ command: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const clientStore = client()
|
|
|
|
|
+const socketStore = socket()
|
|
|
|
|
+
|
|
|
|
|
+type ReturnLine = {
|
|
|
|
|
+ time: string
|
|
|
|
|
+ message: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const commands = ref<CommandItem[]>([])
|
|
|
|
|
+const activeIndex = ref(-1)
|
|
|
|
|
+const jsonText = ref('')
|
|
|
|
|
+const returnLines = ref<ReturnLine[]>([])
|
|
|
|
|
+const returnBox = ref<HTMLElement | null>(null)
|
|
|
|
|
+const loaded = ref(false)
|
|
|
|
|
+const activeName = computed(() => commands.value[activeIndex.value]?.name || '')
|
|
|
|
|
+
|
|
|
|
|
+const listChannel = icpList.socket.message + '_get_one_data_set'
|
|
|
|
|
+const sendChannel = icpList.socket.message + '_send_get_one_data_set'
|
|
|
|
|
+const printChannel = icpList.mcu.errorLog
|
|
|
|
|
+
|
|
|
|
|
+function selectCommand(item: CommandItem, index: number) {
|
|
|
|
|
+ activeIndex.value = index
|
|
|
|
|
+ jsonText.value = item.command || ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function clearReturn() {
|
|
|
|
|
+ returnLines.value = []
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isErrorMessage(message: string) {
|
|
|
|
|
+ const text = String(message || '').trimStart()
|
|
|
|
|
+ return text.startsWith('!') || text.startsWith('!')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function appendReturn(message: string, time = '') {
|
|
|
|
|
+ if (!message && !time) return
|
|
|
|
|
+ returnLines.value.push({ time, message })
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ if (returnBox.value) {
|
|
|
|
|
+ returnBox.value.scrollTop = returnBox.value.scrollHeight
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onCommandList(_event: unknown, result: any) {
|
|
|
|
|
+ console.log('[任一参数下发] get_one_data_set 回包', result)
|
|
|
|
|
+ loaded.value = true
|
|
|
|
|
+ const list = result?.data?.commands
|
|
|
|
|
+ if (result?.code === 0 && Array.isArray(list)) {
|
|
|
|
|
+ commands.value = list
|
|
|
|
|
+ console.log('[任一参数下发] 命令条数', list.length, list)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ commands.value = []
|
|
|
|
|
+ console.log('[任一参数下发] 未解析出命令列表', {
|
|
|
|
|
+ code: result?.code,
|
|
|
|
|
+ msg: result?.msg,
|
|
|
|
|
+ data: result?.data
|
|
|
|
|
+ })
|
|
|
|
|
+ if (result?.msg) ElMessage.error(result.msg)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onSendResult(_event: unknown, result: any) {
|
|
|
|
|
+ console.log('[任一参数下发] send_get_one_data_set 回包', result)
|
|
|
|
|
+ if (result?.code === 0) return
|
|
|
|
|
+ const text = result?.msg || '下发失败'
|
|
|
|
|
+ appendReturn(text)
|
|
|
|
|
+ ElMessage.error(text)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onPrint(_event: unknown, result: any) {
|
|
|
|
|
+ console.log('[任一参数下发] print_mcu_error_data 回包', result)
|
|
|
|
|
+ const time = result?.data?.current_time || ''
|
|
|
|
|
+ const message = result?.data?.message || ''
|
|
|
|
|
+ appendReturn(message, time)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function bindListeners() {
|
|
|
|
|
+ if (!clientStore.isClient) return
|
|
|
|
|
+ clientStore.ipc.removeListener(listChannel, onCommandList)
|
|
|
|
|
+ clientStore.ipc.removeListener(sendChannel, onSendResult)
|
|
|
|
|
+ clientStore.ipc.removeListener(printChannel, onPrint)
|
|
|
|
|
+ clientStore.ipc.on(listChannel, onCommandList)
|
|
|
|
|
+ clientStore.ipc.on(sendChannel, onSendResult)
|
|
|
|
|
+ clientStore.ipc.on(printChannel, onPrint)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function unbindListeners() {
|
|
|
|
|
+ if (!clientStore.isClient) return
|
|
|
|
|
+ clientStore.ipc.removeListener(listChannel, onCommandList)
|
|
|
|
|
+ clientStore.ipc.removeListener(sendChannel, onSendResult)
|
|
|
|
|
+ clientStore.ipc.removeListener(printChannel, onPrint)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function loadCommands() {
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ type: 'get_one_data_set'
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log('[任一参数下发] 发送', payload, '监听', listChannel)
|
|
|
|
|
+ socketStore.sendMessage(payload)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function sendJson() {
|
|
|
|
|
+ if (!jsonText.value.trim()) {
|
|
|
|
|
+ ElMessage.warning('请先选择或填写要下发的命令')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ type: 'send_get_one_data_set',
|
|
|
|
|
+ data: {
|
|
|
|
|
+ command: jsonText.value
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log('[任一参数下发] 发送', payload)
|
|
|
|
|
+ socketStore.sendMessage(payload)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ bindListeners()
|
|
|
|
|
+ if (clientStore.isClient) loadCommands()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ unbindListeners()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.page {
|
|
|
|
|
+ height: 100vh;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ padding: 14px;
|
|
|
|
|
+ background: #f3f5f9;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.side,
|
|
|
|
|
+.panel {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1px solid #e6ebf2;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ box-shadow: 0 8px 24px rgba(31, 45, 80, 0.05);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.side {
|
|
|
|
|
+ width: 228px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ padding: 12px 10px 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.editor {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.panel {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ padding: 12px 14px 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.panel-head {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.label {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.count {
|
|
|
|
|
+ min-width: 20px;
|
|
|
|
|
+ height: 20px;
|
|
|
|
|
+ padding: 0 6px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ background: #eef3fb;
|
|
|
|
|
+ color: #4b628a;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 20px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.hint {
|
|
|
|
|
+ margin-left: auto;
|
|
|
|
|
+ max-width: 50%;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ color: #6b7c93;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-list,
|
|
|
|
|
+.return-box {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-list::-webkit-scrollbar,
|
|
|
|
|
+.return-box::-webkit-scrollbar,
|
|
|
|
|
+.json-box::-webkit-scrollbar {
|
|
|
|
|
+ width: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-list::-webkit-scrollbar-thumb,
|
|
|
|
|
+.return-box::-webkit-scrollbar-thumb,
|
|
|
|
|
+.json-box::-webkit-scrollbar-thumb {
|
|
|
|
|
+ background: #d5dce8;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-btn {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin: 0 0 6px;
|
|
|
|
|
+ padding: 8px 10px;
|
|
|
|
|
+ border: 1px solid transparent;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #f6f8fb;
|
|
|
|
|
+ color: #334155;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-mark {
|
|
|
|
|
+ color: #8aa0bd;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-name {
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-btn:hover,
|
|
|
|
|
+.command-btn:focus {
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ border-color: #d5e4fb;
|
|
|
|
|
+ background: #f3f8ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-btn.active {
|
|
|
|
|
+ border-color: transparent;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: linear-gradient(135deg, #2fb0ff 0%, #b863fb 100%);
|
|
|
|
|
+ box-shadow: 0 6px 14px rgba(90, 120, 230, 0.28);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.command-btn.active .command-mark {
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.85);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.empty {
|
|
|
|
|
+ color: #94a3b8;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 28px 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-box,
|
|
|
|
|
+.return-box {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ border: 1px solid #e6ebf2;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #fbfcfe;
|
|
|
|
|
+ color: #1f2937;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.55;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-box {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ resize: none;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ font-family: Consolas, Monaco, monospace;
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-box:focus {
|
|
|
|
|
+ border-color: #8eb7ff;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ box-shadow: 0 0 0 3px rgba(47, 176, 255, 0.12);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.json-box::placeholder {
|
|
|
|
|
+ color: #b0bac8;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-box {
|
|
|
|
|
+ padding: 8px;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-empty {
|
|
|
|
|
+ padding: 24px 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-line {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ padding: 8px 10px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-line + .return-line {
|
|
|
|
|
+ margin-top: 6px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-line.is-error {
|
|
|
|
|
+ background: #fff6f5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-time {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ color: #7b8ba3;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-msg {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ white-space: pre-wrap;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.return-line.is-error .return-msg {
|
|
|
|
|
+ color: #c2410c;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 42px;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0 12px;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn:hover,
|
|
|
|
|
+.action-btn:focus {
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn--ghost {
|
|
|
|
|
+ border: 1px solid #d7deea;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #475569;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn--ghost:hover,
|
|
|
|
|
+.action-btn--ghost:focus {
|
|
|
|
|
+ border-color: #b9c6da;
|
|
|
|
|
+ background: #f8fafc;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn--primary {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: linear-gradient(135deg, #2fb0ff 0%, #b863fb 100%);
|
|
|
|
|
+ box-shadow: 0 8px 16px rgba(90, 120, 230, 0.28);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.action-btn--primary:hover,
|
|
|
|
|
+.action-btn--primary:focus {
|
|
|
|
|
+ filter: brightness(1.04);
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|