feat(uart): data frame break
This commit is contained in:
parent
a7758ac69a
commit
a2b7026f54
|
@ -14,6 +14,7 @@
|
|||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
"vue-draggable-plus": "^0.4.1",
|
||||
"vue-i18n": "^9.10.2",
|
||||
"vue-router": "^4.3.0",
|
||||
"vuetify": "^3.6.5"
|
||||
|
@ -1051,6 +1052,12 @@
|
|||
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/sortablejs": {
|
||||
"version": "1.15.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.8.tgz",
|
||||
"integrity": "sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||
|
@ -5479,6 +5486,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-draggable-plus": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-draggable-plus/-/vue-draggable-plus-0.4.1.tgz",
|
||||
"integrity": "sha512-KNi+c482OQUZTZ2kXIGc41fEwknkNF+LlngjBr5TVtBLNvpX2dmwRJJ3J7dy5dGcijXb7V1j+mhqce4iHOoi6Q==",
|
||||
"peerDependencies": {
|
||||
"@types/sortablejs": "^1.15.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-eslint-parser": {
|
||||
"version": "9.4.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
|
||||
|
|
|
@ -21,9 +21,10 @@
|
|||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
"vue-draggable-plus": "^0.4.1",
|
||||
"vue-i18n": "^9.10.2",
|
||||
"vuetify": "^3.6.5",
|
||||
"vue-router": "^4.3.0"
|
||||
"vue-router": "^4.3.0",
|
||||
"vuetify": "^3.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
|
|
|
@ -11,6 +11,7 @@ import {ControlEvent, ControlMsgType} from "@/api";
|
|||
import {routeCtrlMsg, routeModuleServerMsg} from "@/router/msgRouter";
|
||||
import {globalNotify} from "@/composables/notification";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
import {ElMessageBox} from "element-plus";
|
||||
|
||||
const wsState = useWsStore();
|
||||
|
||||
|
@ -47,6 +48,9 @@ onMounted(() => {
|
|||
websocketService = getWebsocketService();
|
||||
websocketService.init(host, onServerMsg, onClientCtrl);
|
||||
changeFavicon();
|
||||
ElMessageBox.alert('欢迎参与允斯无线串口助手固件内侧,有任何问题请在Q群踢群主:642246000', '2024-05-24', {
|
||||
confirmButtonText: '好的',
|
||||
})
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>
|
After Width: | Height: | Size: 668 B |
|
@ -2,7 +2,7 @@ import {ElMessage, ElNotification} from "element-plus";
|
|||
|
||||
type NotificationType = 'error' | 'warning' | 'info' | 'success' ;
|
||||
|
||||
export function globalNotify(msg: string, type: NotificationType) {
|
||||
export function globalNotify(msg: string, type: NotificationType = "info") {
|
||||
ElMessage({
|
||||
message: msg,
|
||||
grouping: true,
|
||||
|
@ -13,7 +13,7 @@ export function globalNotify(msg: string, type: NotificationType) {
|
|||
})
|
||||
}
|
||||
|
||||
export function globalNotifyRightSide(msg: string, type: NotificationType) {
|
||||
export function globalNotifyRightSide(msg: string, type: NotificationType = "info") {
|
||||
ElNotification({
|
||||
message: msg,
|
||||
type: type,
|
||||
|
|
|
@ -16,7 +16,7 @@ const router = createRouter({
|
|||
name: 'home',
|
||||
meta: {title: translate("page.home")},
|
||||
// component: Wifi
|
||||
redirect: () => '/wifi',
|
||||
redirect: () => '/uart',
|
||||
}, {
|
||||
path: '/home:ext(.*)',
|
||||
meta: {title: translate("page.home")},
|
||||
|
|
|
@ -5,11 +5,12 @@ import {
|
|||
type ShallowReactive,
|
||||
ref,
|
||||
shallowReactive,
|
||||
watch
|
||||
watch, reactive
|
||||
} from "vue";
|
||||
import {AnsiUp} from 'ansi_up'
|
||||
import {debouncedWatch} from "@vueuse/core";
|
||||
import {type IUartConfig, uart_send_msg} from "@/api/apiUart";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
|
||||
interface IDataArchive {
|
||||
time: number;
|
||||
|
@ -164,7 +165,7 @@ const baudArr = [
|
|||
}
|
||||
]
|
||||
|
||||
function generateBaudArr(results: { baud: number;}[]) {
|
||||
function generateBaudArr(results: { baud: number; }[]) {
|
||||
for (let i = 0; i < baudArr.length; ++i) {
|
||||
let start = baudArr[i].start;
|
||||
for (let j = 0; j < baudArr[i].count; ++j) {
|
||||
|
@ -210,9 +211,188 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
|
|||
const configPanelShow = ref(true);
|
||||
const quickAccessPanelShow = ref(true);
|
||||
const enableAnsiDecode = ref(true);
|
||||
|
||||
|
||||
/*
|
||||
* FRAME BREAK STUFS
|
||||
* */
|
||||
|
||||
let RxSegment: Uint8Array = new Uint8Array(0);
|
||||
const RxRemainHexdump = ref("");
|
||||
|
||||
const frameBreakSequence = ref("\\n");
|
||||
const frameBreakSequenceNormalized = ref(new Uint8Array(0));
|
||||
const frameBreakAfterSequence = ref(true);
|
||||
const frameBreakSequenceNormalized = computed(() => {
|
||||
const unescapedStr = unescapeString(frameBreakSequence.value);
|
||||
const encoder = new TextEncoder();
|
||||
return encoder.encode(unescapedStr);
|
||||
});
|
||||
const frameBreakDelay = ref(0);
|
||||
let frameBreakDelayTimeoutID: number = -1;
|
||||
|
||||
function frameBreakFlush() {
|
||||
if (RxSegment.length) {
|
||||
addItem(RxSegment, true);
|
||||
RxSegment = new Uint8Array();
|
||||
RxRemainHexdump.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
function frameBreakRefreshTimout() {
|
||||
if (frameBreakDelay.value > 0) {
|
||||
if (frameBreakDelayTimeoutID >= 0) {
|
||||
clearTimeout(frameBreakDelayTimeoutID);
|
||||
frameBreakDelayTimeoutID = -1;
|
||||
}
|
||||
frameBreakDelayTimeoutID = setTimeout(() => {
|
||||
frameBreakFlush()
|
||||
}, frameBreakDelay.value);
|
||||
} else {
|
||||
if (frameBreakDelayTimeoutID >= 0) {
|
||||
clearTimeout(frameBreakDelayTimeoutID);
|
||||
frameBreakDelayTimeoutID = -1;
|
||||
}
|
||||
if (frameBreakDelay.value === 0) {
|
||||
frameBreakFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debouncedWatch(() => frameBreakDelay.value, () => {
|
||||
frameBreakRefreshTimout();
|
||||
console.log("timeout called");
|
||||
}, {debounce: 300});
|
||||
|
||||
|
||||
|
||||
const frameBreakSize = ref(0);
|
||||
const frameBreakRules = reactive([{
|
||||
ref: frameBreakDelay,
|
||||
name: '超时(ms)',
|
||||
type: 'number',
|
||||
min: -1,
|
||||
draggable: false,
|
||||
transformData: () => {
|
||||
return {result: [] as Uint8Array[], remain: true};
|
||||
}
|
||||
}, {
|
||||
ref: frameBreakSequence,
|
||||
name: '匹配',
|
||||
type: 'text',
|
||||
draggable: true,
|
||||
transformData: (inputArray: Uint8Array[]) => {
|
||||
if (frameBreakSequenceNormalized.value.length <= 0) {
|
||||
return {result: inputArray, remain: true};
|
||||
}
|
||||
const result: Uint8Array[] = [];
|
||||
/* if split after, the matched array is appended to the previous */
|
||||
const appendedLength = frameBreakAfterSequence.value ? frameBreakSequenceNormalized.value.length : 0;
|
||||
/* else after the first match, skip the matchArr at the beginning of array in subsequent match */
|
||||
const skipLength = frameBreakAfterSequence.value ? 0 : frameBreakSequenceNormalized.value.length;
|
||||
let remain = false;
|
||||
let startIndex = 0;
|
||||
|
||||
inputArray.forEach(array => {
|
||||
startIndex = 0;
|
||||
let matchIndex = isArrayContained(frameBreakSequenceNormalized.value, array, 0, startIndex);
|
||||
|
||||
while (matchIndex !== -1) {
|
||||
const endIndex = matchIndex + appendedLength;
|
||||
if (startIndex !== endIndex) {
|
||||
result.push(array.subarray(startIndex, endIndex));
|
||||
}
|
||||
startIndex = endIndex;
|
||||
matchIndex = isArrayContained(frameBreakSequenceNormalized.value, array,
|
||||
0, startIndex + skipLength);
|
||||
}
|
||||
// Add the last segment if there's any remaining part of the array
|
||||
if (startIndex < array.length) {
|
||||
result.push(array.subarray(startIndex, array.length));
|
||||
}
|
||||
});
|
||||
remain = startIndex < inputArray[inputArray.length - 1].length;
|
||||
return {result, remain};
|
||||
}
|
||||
}, {
|
||||
ref: frameBreakSize,
|
||||
name: '字节(B)',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
draggable: true,
|
||||
transformData: (inputArray: Uint8Array[]) => {
|
||||
if (frameBreakSize.value <= 0) {
|
||||
return {result: inputArray, remain: true};
|
||||
}
|
||||
const result: Uint8Array[] = [];
|
||||
inputArray.forEach(item => {
|
||||
for (let start = 0; start < item.length; start += frameBreakSize.value) {
|
||||
const end = Math.min(start + frameBreakSize.value, item.length);
|
||||
result.push(item.subarray(start, end));
|
||||
}
|
||||
});
|
||||
const remain = result[result.length - 1].length < frameBreakSize.value;
|
||||
return {result, remain};
|
||||
}
|
||||
},])
|
||||
|
||||
function addStringMessage(input: string, isRX: boolean, doSend: boolean = false) {
|
||||
const encoder = new TextEncoder();
|
||||
input = unescapeString(input);
|
||||
const encodedStr = encoder.encode(input);
|
||||
addSegment(encodedStr, isRX);
|
||||
}
|
||||
|
||||
function addSegment(input: Uint8Array, isRX: boolean, doSend: boolean = false) {
|
||||
if (input.length <= 0) {
|
||||
if (isDevMode()) {
|
||||
console.log("input size =0");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let frames: Uint8Array[] = []
|
||||
const data= new Uint8Array(RxSegment.length + input.length);
|
||||
let remain = true;
|
||||
data.set(RxSegment);
|
||||
data.set(input, RxSegment.length);
|
||||
RxSegment = data;
|
||||
|
||||
frames.push(RxSegment);
|
||||
/* ready for adding new items */
|
||||
for (let i = 1; i < frameBreakRules.length; i++) {
|
||||
const ret: {result: Uint8Array[], remain: boolean} = frameBreakRules[i].transformData(frames);
|
||||
/* check if last item changed */
|
||||
if (!ret.remain || ret.result[ret.result.length - 1].length !== frames[frames.length - 1].length) {
|
||||
remain = ret.remain;
|
||||
}
|
||||
frames = ret.result;
|
||||
}
|
||||
|
||||
if (frameBreakDelay.value !== 0 && remain) {
|
||||
RxSegment = frames.pop() || new Uint8Array();
|
||||
if (frameBreakDelay.value > 0) {
|
||||
frameBreakRefreshTimout();
|
||||
}
|
||||
} else {
|
||||
RxSegment = new Uint8Array();
|
||||
}
|
||||
RxRemainHexdump.value = u8toHexdump(RxSegment);
|
||||
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
addItem(frames[i], isRX, doSend);
|
||||
}
|
||||
}
|
||||
|
||||
const frameBreakRet = {
|
||||
frameBreakSequence,
|
||||
frameBreakAfterSequence,
|
||||
frameBreakDelay,
|
||||
frameBreakSize,
|
||||
frameBreakRules,
|
||||
RxRemainHexdump,
|
||||
addStringMessage,
|
||||
addSegment,
|
||||
}
|
||||
|
||||
const showText = ref(true);
|
||||
const showHex = ref(false);
|
||||
|
@ -230,7 +410,6 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
|
|||
const TxByteCount = ref(0);
|
||||
|
||||
const enableFilter = ref(true);
|
||||
const enableMatch = ref(false);
|
||||
const forceToBottom = ref(true);
|
||||
const filterChanged = ref(false);
|
||||
|
||||
|
@ -343,7 +522,7 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
|
|||
return addItem(encodedStr, isRX, doSend, type);
|
||||
}
|
||||
|
||||
function addHexString(item: string, isRX: boolean = false, doSend: boolean = false, type: number = 0){
|
||||
function addHexString(item: string, isRX: boolean = false, doSend: boolean = false, type: number = 0) {
|
||||
if (item === "") {
|
||||
return addItem(new Uint8Array(0), isRX);
|
||||
}
|
||||
|
@ -623,10 +802,10 @@ export const useDataViewerStore = defineStore('text-viewer', () => {
|
|||
TxByteCount,
|
||||
TxTotalByteCount,
|
||||
forceToBottom,
|
||||
frameBreakSequence,
|
||||
frameBreakDelay,
|
||||
filterChanged,
|
||||
|
||||
...frameBreakRet,
|
||||
|
||||
/* UART */
|
||||
predefinedUartBaudFrequent,
|
||||
uartBaudList,
|
||||
|
|
|
@ -382,7 +382,7 @@ const onUartBinaryMsg = (msg: ApiBinaryMsg) => {
|
|||
}
|
||||
|
||||
/* UART_NUM_1 msg */
|
||||
store.addItem(new Uint8Array(msg.payload), true);
|
||||
store.addSegment(new Uint8Array(msg.payload), true);
|
||||
};
|
||||
|
||||
const onDataFlowJsonMsg = (msg: api.ApiJsonMsg) => {
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<p class="text-xs">实际波特率:{{store.uartBaudReal}}</p>
|
||||
<p class="text-xs">实际波特率:{{ store.uartBaudReal }}</p>
|
||||
|
||||
<el-form-item label="数据位" class="mb-2">
|
||||
<el-select v-model="store.uartConfig.data_bits" :teleported="false" placeholder="Select">
|
||||
|
@ -140,6 +140,74 @@
|
|||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="2">
|
||||
<template #title>
|
||||
断帧策略
|
||||
</template>
|
||||
<VueDraggable v-model="store.frameBreakRules" target="tbody" handle=".sort-target:not(:first-child)"
|
||||
:animation="150"
|
||||
:on-move="checkMove">
|
||||
<table class="w-full bg-white">
|
||||
<thead>
|
||||
<tr class="text-sm h-7">
|
||||
<th>优先级</th>
|
||||
<th>
|
||||
<div class="flex justify-center">
|
||||
规则
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>
|
||||
<p>超时=-1: 禁用超时断帧</p>
|
||||
<p>超时=0: 当机立断,收到任何数据都视为完整数据</p>
|
||||
<p>匹配断后:典型\n的场景</p>
|
||||
<p>匹配断前:用于有特殊帧头的场景</p>
|
||||
<p>固定字节断帧:传输大量数据,比如可以每隔1024字节断帧,方便查看数据</p>
|
||||
</template>
|
||||
<InlineSvg name="help" class="w-4 text-gray-500 cursor-help"></InlineSvg>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</th>
|
||||
<th>值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-xs text-center">
|
||||
<tr v-for="(item, index) in store.frameBreakRules" :key="index"
|
||||
:class="item.draggable ? '' : 'cursor-no-drop'">
|
||||
<td :class="item.draggable ? 'sort-target' : 'cursor-no-drop'">
|
||||
{{ item.draggable ? index : 'NaN' }}
|
||||
</td>
|
||||
<td :class="item.draggable ? 'sort-target' : 'cursor-no-drop'">{{ item.name }}</td>
|
||||
<td>
|
||||
<div v-if="item.type === 'number'">
|
||||
<el-input-number v-model="item.ref" :min="item.min || 0" size="small" style="width: 100px"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-input class="break-input" v-model="item.ref" placeholder="文本;支持\n\x" size="small"
|
||||
style="width: 100px">
|
||||
<template #prepend>
|
||||
<el-button size="small" @click="store.frameBreakAfterSequence = false">
|
||||
<span
|
||||
:class="store.frameBreakAfterSequence ? 'text-gray-400' : 'text-blue-400 font-bold'">
|
||||
断
|
||||
</span>
|
||||
</el-button>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button size="small" @click="store.frameBreakAfterSequence = true">
|
||||
<span
|
||||
:class="store.frameBreakAfterSequence ? 'text-blue-400 font-bold' : 'text-gray-300'">
|
||||
断
|
||||
</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</VueDraggable>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="3">
|
||||
<template #title>
|
||||
其他
|
||||
</template>
|
||||
|
@ -179,7 +247,8 @@
|
|||
<el-checkbox border v-model="store.dataFilterAutoUpdate">新数据自动刷新</el-checkbox>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="提高间隔可减少CPU资源的使用" placement="right" effect="light" :show-after="500">
|
||||
<el-tooltip content="提高间隔可减少CPU资源的使用" placement="right" effect="light"
|
||||
:show-after="500">
|
||||
<div class="flex gap-4 p-2">
|
||||
<el-text>数据显示刷新间隔(ms)</el-text>
|
||||
<el-input-number
|
||||
|
@ -255,17 +324,20 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {VueDraggable} from 'vue-draggable-plus'
|
||||
import {ref} from "vue";
|
||||
import {useDataViewerStore} from "@/stores/dataViewerStore";
|
||||
import {useWsStore} from "@/stores/websocket";
|
||||
import {globalNotify} from "@/composables/notification";
|
||||
import {ControlEvent} from "@/api";
|
||||
import type {MoveEvent} from "sortablejs";
|
||||
import InlineSvg from "@/components/InlineSvg.vue";
|
||||
|
||||
const store = useDataViewerStore()
|
||||
const wsStore = useWsStore()
|
||||
const collapseActiveName = ref(["1", "2"])
|
||||
const collapseActiveName = ref(["1", "2", "3"])
|
||||
|
||||
const uartCustomBaud = ref(9600)
|
||||
const uartCustomBaud = ref(114514)
|
||||
|
||||
const uartDataBitsOptions = [
|
||||
{
|
||||
|
@ -318,6 +390,12 @@ const onUseCustomUartBaud = () => {
|
|||
}
|
||||
}
|
||||
|
||||
function checkMove(event: MoveEvent) {
|
||||
// Find index of related element
|
||||
const toIndex: number = Array.from(event.to.children).indexOf(event.related);
|
||||
return !!store.frameBreakRules[toIndex].draggable;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -337,4 +415,21 @@ const onUseCustomUartBaud = () => {
|
|||
transition: all 0s; /* Customize the duration and easing */
|
||||
}
|
||||
|
||||
.sortable-chosen {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
}
|
||||
|
||||
.sort-target {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
tr td {
|
||||
@apply p-1;
|
||||
}
|
||||
|
||||
.break-input :deep(.el-input-group__prepend), .break-input :deep(.el-input-group__append) {
|
||||
background-color: unset;
|
||||
@apply p-0 min-w-6
|
||||
}
|
||||
|
||||
</style>
|
|
@ -18,7 +18,7 @@
|
|||
</el-popover>
|
||||
|
||||
<div class="flex">
|
||||
<el-checkbox size="small" v-model="store.forceToBottom" label="强制滚动至底部" border/>
|
||||
<el-checkbox size="small" v-model="store.forceToBottom" label="自动滚动至底部" border/>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
|
@ -60,19 +60,19 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div class="flex">
|
||||
<el-button size="small" @click="addItem(1)">add1</el-button>
|
||||
<el-button size="small" @click="addItem(10)">add10</el-button>
|
||||
<el-button size="small" @click="addItem(100)">add100</el-button>
|
||||
<el-button size="small" @click="addItem(1000)">add1000</el-button>
|
||||
<el-button size="small" @click="scrollToBottom">scrollToBottom</el-button>
|
||||
<!-- <div class="flex">-->
|
||||
<!-- <el-button size="small" @click="addItem(1)">add1</el-button>-->
|
||||
<!-- <el-button size="small" @click="addItem(10)">add10</el-button>-->
|
||||
<!-- <el-button size="small" @click="addItem(100)">add100</el-button>-->
|
||||
<!-- <el-button size="small" @click="addItem(1000)">add1000</el-button>-->
|
||||
<!-- <el-button size="small" @click="scrollToBottom">scrollToBottom</el-button>-->
|
||||
<!-- <el-button @click="toggleAutoBottom">autoBot: {{ forceToBottom }}</el-button>-->
|
||||
<!-- <el-checkbox size="small" v-model="store.forceToBottom" :label="'autoBot:' + store.forceToBottom" border></el-checkbox>-->
|
||||
<!-- <el-button>{{ count }}, {{ items.length }}, {{ vuetifyVirtualScrollBarRef.scrollTop }},-->
|
||||
<!-- {{ vuetifyVirtualScrollBarRef.clientHeight }}, {{ vuetifyVirtualScrollBarRef.scrollHeight }}-->
|
||||
<!-- </el-button>-->
|
||||
<!-- <el-button @click="updateScroll">{{ scrollTop }}, {{ clientHeight }}, {{ scrollHeight }}</el-button>-->
|
||||
</div>
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <div>-->
|
||||
<!-- <el-popover-->
|
||||
|
@ -93,7 +93,7 @@
|
|||
<!-- </div>-->
|
||||
</div>
|
||||
|
||||
<div class="flex flex-grow overflow-hidden border-2 scroll-m-2">
|
||||
<div class="flex flex-grow overflow-hidden border-2 rounded scroll-m-2">
|
||||
<v-virtual-scroll
|
||||
v-if="store.showVirtualScroll"
|
||||
:items="store.dataFiltered"
|
||||
|
@ -145,7 +145,6 @@
|
|||
<span>{{ item.time }}</span>TX-►|</p>
|
||||
<p class="text-nowrap text-sm text-amber-800" v-else type="primary" v-show="store.showTimestamp">
|
||||
<span>{{ item.time }}</span>未发送►|</p>
|
||||
|
||||
<p v-show="store.showText"
|
||||
v-html="item.str"></p>
|
||||
</div>
|
||||
|
@ -164,6 +163,18 @@
|
|||
</v-virtual-scroll>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 flex max-h-14 mt-0.5 text-xs">
|
||||
<div class="flex shrink-0">
|
||||
<el-tooltip content="未满足断帧规则的数据(如:未超时),暂时实时显示在此区域。" effect="light">
|
||||
<InlineSvg name="help" class="w-3.5 h-3.5 text-gray-500 cursor-help"></InlineSvg>
|
||||
</el-tooltip>
|
||||
<p>►</p>
|
||||
</div>
|
||||
<div class="p-0.5 border-2 rounded w-full overflow-auto font-mono text-nowrap">
|
||||
<p v-html="store.RxRemainHexdump"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 min-h-6 flex gap-2 justify-between overflow-y-scroll">
|
||||
<div class="flex gap-2">
|
||||
<el-link @click="clearSendInput">
|
||||
|
@ -196,14 +207,14 @@
|
|||
</el-link>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<el-link @click="showTxTotalByte = !showTxTotalByte">
|
||||
<el-link>
|
||||
<el-tag class="font-mono font-bold" size="small">
|
||||
{{ showTxTotalByte ? `TX统计:${store.TxTotalByteCount}B` : `上个TX帧:${store.TxByteCount}B` }}
|
||||
{{ `TX:${store.TxByteCount}B/${store.TxTotalByteCount}B` }}
|
||||
</el-tag>
|
||||
</el-link>
|
||||
<el-link type="success" @click="showRxTotalByte = !showRxTotalByte">
|
||||
<el-link type="success">
|
||||
<el-tag class="font-mono font-bold" size="small" type="success">
|
||||
{{ showRxTotalByte ? `RX统计:${store.RxTotalByteCount}B` : `上个RX帧:${store.RxByteCount}B` }}
|
||||
{{ `RX:${store.RxByteCount}B/${store.RxTotalByteCount}B` }}
|
||||
</el-tag>
|
||||
</el-link>
|
||||
<div class="flex align-center">
|
||||
|
@ -238,10 +249,9 @@ import {useDataViewerStore} from "@/stores/dataViewerStore";
|
|||
import InlineSvg from "@/components/InlineSvg.vue";
|
||||
import TextDataConfig from "@/views/text-data-viewer/textDataConfig.vue";
|
||||
import {debouncedWatch} from "@vueuse/core";
|
||||
import {globalNotify} from "@/composables/notification";
|
||||
|
||||
const count = ref(0);
|
||||
const showTxTotalByte = ref(false);
|
||||
const showRxTotalByte = ref(false);
|
||||
const vuetifyVirtualScrollBarRef = ref(document.body);
|
||||
const vuetifyVirtualScrollContainerRef = ref(document.body);
|
||||
|
||||
|
@ -423,6 +433,9 @@ const handleScroll = (ev: Event) => {
|
|||
if (vuetifyVirtualScrollBarRef.value.scrollTop - lastScrollHeight < 0) {
|
||||
store.forceToBottom = false;
|
||||
}
|
||||
} else if ((vuetifyVirtualScrollBarRef.value.scrollHeight -
|
||||
vuetifyVirtualScrollBarRef.value.scrollTop) <= vuetifyVirtualScrollBarRef.value.clientHeight) {
|
||||
store.forceToBottom = true;
|
||||
}
|
||||
lastScrollHeight = vuetifyVirtualScrollBarRef.value.scrollTop;
|
||||
};
|
||||
|
@ -444,6 +457,11 @@ function handleTextboxKeydown(ev: KeyboardEvent) {
|
|||
}
|
||||
|
||||
function onSendClick() {
|
||||
if (!uartInputTextBox.value) {
|
||||
globalNotify("发送框无数据发送")
|
||||
return;
|
||||
}
|
||||
|
||||
if (store.acceptIncomingData) {
|
||||
if (isSendTextFormat.value) {
|
||||
store.addString(uartInputTextBox.value, false, true);
|
||||
|
|
Loading…
Reference in New Issue