Merge branch 'wireless-proxy'
# Conflicts: # package-lock.json # package.json # set_env.sh # src/App.vue # src/api/index.ts # src/views/About.vue # src/views/Wifi.vue # src/views/navigation/NavBar.vue
This commit is contained in:
commit
dccf5feaa8
.eslintignorepackage-lock.jsonpackage.json
src
App.vue
vite.config.tsapi
assets/icon
composables
broadcastChannelDef.tsbuildMode.tsnotification.tsuseDataFlowModule.tsuseSystemModule.tsuseUpdateModule.ts
i18n.tswebsocket
locales
main.tsrouter
stores
views
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
dist
|
|
@ -10,7 +10,7 @@
|
|||
"dependencies": {
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"ansi_up": "^6.0.2",
|
||||
"element-plus": "^2.7.3",
|
||||
"element-plus": "^2.8.1",
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
|
@ -979,31 +979,29 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.1.4.tgz",
|
||||
"integrity": "sha512-ROfPepDxZ5Eq+Unbx3M9QcHT7MoE9tYdbkuzLTtxG5rfkEi5RwsDPncjANMOq/gHhIIDlWgqWwS2nXWMGsuj4w==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0.tgz",
|
||||
"integrity": "sha512-FTla+khE+sYK0qJP+6hwPAAUwiNHVMph4RUXpxf/FIPKUP61NFrVZorml4mjFShnueR2y9/j8/vnh09YwVdH7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/source-map": "2.1.4"
|
||||
"@volar/source-map": "2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/source-map": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.1.4.tgz",
|
||||
"integrity": "sha512-mCg8IiPZmHZVzqL4Owg+BzQ5ZTG1cVwATxrkrFPZpcAin97Xa3MbchxVhHtHTWTT8ER8bJh5xVjeVxsSN++FUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"muggle-string": "^0.4.0"
|
||||
}
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0.tgz",
|
||||
"integrity": "sha512-2ceY8/NEZvN6F44TXw2qRP6AQsvCYhV2bxaBPWxV9HqIfkbRydSksTFObCF1DBDNBfKiZTS8G/4vqV6cvjdOIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@volar/typescript": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.1.4.tgz",
|
||||
"integrity": "sha512-Mt7wOLPkomFnUfVpb5IHlPhSpD7FJAn+FHSsovePmqFNQzFLz16wrpHjAkorPiAnP0847w71NL5fIJyWbAsR8Q==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0.tgz",
|
||||
"integrity": "sha512-9zx3lQWgHmVd+JRRAHUSRiEhe4TlzL7U7e6ulWXOxHH/WNYxzKwCvZD7WYWEZFdw4dHfTD9vUR0yPQO6GilCaQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.1.4",
|
||||
"path-browserify": "^1.0.1"
|
||||
"@volar/language-core": "2.4.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
|
@ -1052,6 +1050,16 @@
|
|||
"@vue/shared": "3.4.21"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-vue2": {
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
|
||||
"integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
|
||||
|
@ -1096,18 +1104,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.7.tgz",
|
||||
"integrity": "sha512-Vh1yZX3XmYjn9yYLkjU8DN6L0ceBtEcapqiyclHne8guG84IaTzqtvizZB1Yfxm3h6m7EIvjerLO5fvOZO6IIQ==",
|
||||
"version": "2.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.29.tgz",
|
||||
"integrity": "sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/language-core": "~2.1.3",
|
||||
"@volar/language-core": "~2.4.0-alpha.18",
|
||||
"@vue/compiler-dom": "^3.4.0",
|
||||
"@vue/compiler-vue2": "^2.7.16",
|
||||
"@vue/shared": "^3.4.0",
|
||||
"computeds": "^0.0.1",
|
||||
"minimatch": "^9.0.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"vue-template-compiler": "^2.7.14"
|
||||
"muggle-string": "^0.4.1",
|
||||
"path-browserify": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
|
@ -1533,9 +1542,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001599",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz",
|
||||
"integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==",
|
||||
"version": "1.0.30001667",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz",
|
||||
"integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1793,12 +1802,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
|
@ -1977,9 +1986,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/element-plus": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.7.3.tgz",
|
||||
"integrity": "sha512-OaqY1kQ2xzNyRFyge3fzM7jqMwux+464RBEqd+ybRV9xPiGxtgnj/sVK4iEbnKnzQIa9XK03DOIFzoToUhu1DA==",
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.8.1.tgz",
|
||||
"integrity": "sha512-p11/6w/O0+hGvPhiN3jrcgh+XG+eg5jZlLdQVYvcPHZYhhCh3J3YeZWW1JO/REPES1vevkboT6VAi+9wHA8Dsg==",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^3.4.1",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
|
@ -3249,9 +3258,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
|
||||
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
|
@ -3303,9 +3312,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
|
@ -3709,9 +3718,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
|
@ -3817,9 +3826,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.39",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
|
||||
"integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==",
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -3836,8 +3845,8 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.1",
|
||||
"source-map-js": "^1.2.0"
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
|
@ -4263,9 +4272,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -5042,6 +5051,12 @@
|
|||
"vue": ">=3.2.13"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
|
||||
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
|
||||
|
@ -5132,31 +5147,21 @@
|
|||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-template-compiler": {
|
||||
"version": "2.7.16",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
|
||||
"integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.7.tgz",
|
||||
"integrity": "sha512-LYa0nInkfcDBB7y8jQ9FQ4riJTRNTdh98zK/hzt4gEpBZQmf30dPhP+odzCa+cedGz6B/guvJEd0BavZaRptjg==",
|
||||
"version": "2.0.29",
|
||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.29.tgz",
|
||||
"integrity": "sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@volar/typescript": "~2.1.3",
|
||||
"@vue/language-core": "2.0.7",
|
||||
"@volar/typescript": "~2.4.0-alpha.18",
|
||||
"@vue/language-core": "2.0.29",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vuetify": {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
"dependencies": {
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"ansi_up": "^6.0.2",
|
||||
"element-plus": "^2.7.3",
|
||||
"element-plus": "^2.8.1",
|
||||
"mitt": "^3.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.21",
|
||||
|
|
30
src/App.vue
30
src/App.vue
|
@ -10,7 +10,11 @@ import type {ControlMsg, ServerMsg} from "@/api";
|
|||
import {ControlEvent, ControlMsgType} from "@/api";
|
||||
import {routeCtrlMsg, routeModuleServerMsg} from "@/router/msgRouter";
|
||||
import {globalNotify} from "@/composables/notification";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
import {getTrialDate, getTrialMsg, isDevMode, isOTAEnabled, isTrialMode} from "@/composables/buildMode";
|
||||
import {useSystemModule} from "@/composables/useSystemModule";
|
||||
import {useDataFlowModule} from "@/composables/useDataFlowModule";
|
||||
import {useUpdateModule} from "@/composables/useUpdateModule";
|
||||
import {ElMessageBox} from "element-plus";
|
||||
|
||||
const wsState = useWsStore();
|
||||
|
||||
|
@ -38,7 +42,7 @@ let websocketService: IWebsocketService;
|
|||
onMounted(() => {
|
||||
|
||||
logHelloMessage();
|
||||
let host = "";
|
||||
let host: string;
|
||||
if (isDevMode()) {
|
||||
host = import.meta.env.VITE_DEVICE_HOST_NAME || "dap.local";
|
||||
} else {
|
||||
|
@ -46,7 +50,21 @@ onMounted(() => {
|
|||
}
|
||||
websocketService = getWebsocketService();
|
||||
websocketService.init(host, onServerMsg, onClientCtrl);
|
||||
websocketService.getSocketStatus();
|
||||
changeFavicon();
|
||||
|
||||
useSystemModule();
|
||||
useDataFlowModule();
|
||||
|
||||
if (isOTAEnabled()) {
|
||||
useUpdateModule();
|
||||
}
|
||||
|
||||
if (isTrialMode()) {
|
||||
ElMessageBox.alert(getTrialMsg(), getTrialDate(), {
|
||||
confirmButtonText: '好的',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
@ -55,10 +73,16 @@ onUnmounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="flex flex-col wt-h-100">
|
||||
<header>
|
||||
<nav-bar/>
|
||||
</header>
|
||||
<RouterView/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.wt-h-100 {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
import {type ApiJsonMsg} from '@/api'
|
||||
import * as api from "@/api/index";
|
||||
|
||||
export enum WtDataFlowType {
|
||||
NONE = 0,
|
||||
SOCKET = 0x10,
|
||||
WS_SERVER = 0x11,
|
||||
WS_CLIENT,
|
||||
WSS_SERVER,
|
||||
WSS_CLIENT,
|
||||
TCP_SERVER,
|
||||
TCP_CLIENT,
|
||||
TCP_TLS_SERVER,
|
||||
TCP_TLS_CLIENT,
|
||||
UDP_SERVER,
|
||||
UDP_CLIENT,
|
||||
PERIPHERAL = 0x80,
|
||||
GPIO = 0x81,
|
||||
UART = 0x82,
|
||||
I2C,
|
||||
I3C,
|
||||
SPI,
|
||||
I2S,
|
||||
CAN,
|
||||
RMT,
|
||||
USB,
|
||||
}
|
||||
|
||||
export enum WtDataFlowCmd {
|
||||
UNKNOWN = 0,
|
||||
GET_INS_LIST = 1,
|
||||
GET_CUR_INS = 2,
|
||||
GET_CUR_ATTACH_LIST = 3,
|
||||
GET_ATTACH_LIST = 4,
|
||||
ATTACH = 5,
|
||||
ATTACH_CUR_TO_RECVER = 6,
|
||||
ATTACH_CUR_TO_SENDER = 7,
|
||||
DETACH_SINGLE = 8,
|
||||
DETACH_CUR_FROM = 9,
|
||||
SET_DATA_TYPE = 10,
|
||||
}
|
||||
|
||||
export interface IWtDataFlowJsonMsg extends ApiJsonMsg {
|
||||
data_type?: 3 | 4,
|
||||
ins_idx?: number,
|
||||
}
|
||||
|
||||
export interface IPeriphInfo {
|
||||
periph_num: number;
|
||||
}
|
||||
|
||||
export interface ISocketInfo {
|
||||
foreign_port: number;
|
||||
foreign_ip: string;
|
||||
local_port: number;
|
||||
}
|
||||
|
||||
export interface InstanceInfo {
|
||||
ins_idx: number,
|
||||
mod_idx: number,
|
||||
mod_type: number,
|
||||
port_info: ISocketInfo | IPeriphInfo;
|
||||
}
|
||||
|
||||
export interface IInstanceList extends ApiJsonMsg {
|
||||
instances: InstanceInfo[],
|
||||
}
|
||||
|
||||
export interface AttachInfo {
|
||||
attach_idx: number,
|
||||
s_ins_idx: number,
|
||||
r_ins_idx: number,
|
||||
data_type: 3 | 4,
|
||||
}
|
||||
|
||||
export interface IAttachList extends ApiJsonMsg {
|
||||
attaches: AttachInfo[],
|
||||
}
|
||||
|
||||
export function wt_data_flow_get_instance_list() {
|
||||
const jsonMsg: IWtDataFlowJsonMsg = {
|
||||
cmd: WtDataFlowCmd.GET_INS_LIST,
|
||||
module: api.WtModuleID.DATA_FLOW,
|
||||
}
|
||||
api.sendJsonMsg(jsonMsg);
|
||||
}
|
||||
|
||||
export function wt_data_flow_attach_cur_to_sender(instance_index: number) {
|
||||
const jsonMsg: IWtDataFlowJsonMsg = {
|
||||
cmd: WtDataFlowCmd.ATTACH_CUR_TO_SENDER,
|
||||
module: api.WtModuleID.DATA_FLOW,
|
||||
data_type: 3,
|
||||
ins_idx: instance_index,
|
||||
}
|
||||
api.sendJsonMsg(jsonMsg);
|
||||
}
|
||||
|
||||
export function wt_data_flow_get_attach_list() {
|
||||
const jsonMsg: IWtDataFlowJsonMsg = {
|
||||
cmd: WtDataFlowCmd.GET_ATTACH_LIST,
|
||||
module: api.WtModuleID.DATA_FLOW,
|
||||
}
|
||||
api.sendJsonMsg(jsonMsg);
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import {type ApiJsonMsg, sendJsonMsg, WtModuleID} from '@/api'
|
||||
|
||||
export enum WtOTACmd {
|
||||
WT_OTA_GET_UPDATE_INFO = 1, /* total_size, ver */
|
||||
WT_OTA_DO_UPDATE = 2, /* returns OK, chunk of remaining bytes and total length -> wt_event_manager */
|
||||
WT_OTA_GET_PROGRESS = 3, /* returns chunk of remaining bytes and total length */
|
||||
WT_OTA_DO_URL_UPDATE = 4, /* force update { url: "https://" } */
|
||||
}
|
||||
|
||||
export enum WtOTAProgressStatus {
|
||||
OK = "OK",
|
||||
IDLE = "IDLE",
|
||||
IN_PROGRESS = "IN_PROGRESS",
|
||||
FAILED = "FAILED",
|
||||
}
|
||||
|
||||
export interface IOTAProgress extends ApiJsonMsg {
|
||||
progress: number;
|
||||
total_size: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface IOTAFmInfo extends ApiJsonMsg {
|
||||
fm_size: number;
|
||||
fm_ver: string;
|
||||
upd_date: string;
|
||||
upd_note: string;
|
||||
}
|
||||
|
||||
export function wt_ota_get_update_info() {
|
||||
const msg: ApiJsonMsg = {
|
||||
module: WtModuleID.OTA,
|
||||
cmd: WtOTACmd.WT_OTA_GET_UPDATE_INFO,
|
||||
};
|
||||
sendJsonMsg(msg);
|
||||
}
|
||||
|
||||
export function wt_ota_do_update() {
|
||||
const msg: ApiJsonMsg = {
|
||||
module: WtModuleID.OTA,
|
||||
cmd: WtOTACmd.WT_OTA_DO_UPDATE,
|
||||
};
|
||||
sendJsonMsg(msg);
|
||||
}
|
||||
|
||||
export function wt_ota_get_progress() {
|
||||
const msg: ApiJsonMsg = {
|
||||
module: WtModuleID.OTA,
|
||||
cmd: WtOTACmd.WT_OTA_GET_PROGRESS,
|
||||
};
|
||||
sendJsonMsg(msg);
|
||||
}
|
||||
|
||||
export function wt_ota_do_url_update(url: string) {
|
||||
const msg: ApiJsonMsg & {url: string} = {
|
||||
module: WtModuleID.OTA,
|
||||
cmd: WtOTACmd.WT_OTA_DO_URL_UPDATE,
|
||||
url: url,
|
||||
};
|
||||
sendJsonMsg(msg);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import {type ApiJsonMsg, sendJsonMsg, WtModuleID} from '@/api'
|
||||
|
||||
export enum WtSytemCmd {
|
||||
WT_SYS_GET_FM_INFO = 1,
|
||||
WT_SYS_REBOOT = 2,
|
||||
WT_SYS_GET_SYS_INFO = 3,
|
||||
}
|
||||
|
||||
export interface ISysFmInfo extends ApiJsonMsg {
|
||||
fm_ver: string;
|
||||
upd_date: string;
|
||||
}
|
||||
|
||||
export interface ISysHwInfo extends ApiJsonMsg {
|
||||
hw_ver: string;
|
||||
mf_date: string;
|
||||
}
|
||||
|
||||
export interface ISysInfo {
|
||||
sn: string;
|
||||
}
|
||||
|
||||
export function wt_sys_get_fm_info() {
|
||||
const msg: ApiJsonMsg = {
|
||||
module: WtModuleID.SYSTEM,
|
||||
cmd: WtSytemCmd.WT_SYS_GET_FM_INFO,
|
||||
};
|
||||
sendJsonMsg(msg);
|
||||
}
|
||||
|
||||
export function wt_sys_reboot() {
|
||||
const msg: ApiJsonMsg = {
|
||||
module: WtModuleID.SYSTEM,
|
||||
cmd: WtSytemCmd.WT_SYS_REBOOT,
|
||||
};
|
||||
sendJsonMsg(msg);
|
||||
}
|
||||
|
||||
export function wt_sys_get_sys_info() {
|
||||
const msg: ApiJsonMsg = {
|
||||
module: WtModuleID.SYSTEM,
|
||||
cmd: WtSytemCmd.WT_SYS_GET_SYS_INFO,
|
||||
};
|
||||
sendJsonMsg(msg);
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
import type {ApiBinaryMsg} from "@/api/binDataDef";
|
||||
import {WtDataType} from "@/api/binDataDef";
|
||||
import {type ApiJsonMsg, sendBinMsg, sendJsonMsg, WtModuleID} from "@/api/index";
|
||||
|
||||
export enum WtUartCmd {
|
||||
UNKNOWN = 0,
|
||||
|
||||
/* UART PERIPHERAL */
|
||||
GET_AVAILABLE_NUMS = 1,
|
||||
GET_BAUD = 4,
|
||||
SET_BAUD = 5,
|
||||
GET_CONFIG = 6, /* data bits, parity and stop bits */
|
||||
SET_CONFIG = 7,
|
||||
GET_FLOW_CTRL, /* flow control function RTS/CTS*/
|
||||
SET_FLOW_CTRL,
|
||||
GET_PINS_NUM, /* not implemented change pinout function */
|
||||
SET_PINS_NUM, /* not implemented */
|
||||
GET_MODE, /* not implemented UART/RS485/IrDA */
|
||||
SET_MODE, /* not implemented UART/RS485/IrDA */
|
||||
|
||||
GET_STATUS = 20, /* is uart enabled and other information */
|
||||
SET_STATUS, /* set specific uart port disable */
|
||||
GET_DATA_TYPE = 22, // 0x03 or 0x04
|
||||
SET_DATA_TYPE = 23, // 0x03 or 0x04
|
||||
|
||||
GET_DEFAULT_NUM = 24,
|
||||
}
|
||||
|
||||
enum ANSI_ESCAPE_CODE {
|
||||
REFRESH_WINDOW = '\x1b[7t',
|
||||
CLEAR_WINDOW = '\x1b[2J'
|
||||
}
|
||||
|
||||
export interface IUartConfig {
|
||||
data_bits: 5 | 6 | 7 | 8;
|
||||
parity : 0 | 1 | 2;
|
||||
stop_bits: 1 | 15 | 2;
|
||||
}
|
||||
|
||||
export interface IUartMsgConfig extends ApiJsonMsg, IUartConfig {
|
||||
sub_mod: number;
|
||||
}
|
||||
|
||||
export interface IUartMsgBaud extends ApiJsonMsg {
|
||||
sub_mod: number;
|
||||
baud: number;
|
||||
}
|
||||
|
||||
export interface IUartMsgNum extends ApiJsonMsg {
|
||||
num: number;
|
||||
}
|
||||
|
||||
export function uart_send_msg(payload: Uint8Array, sub_mod: number) {
|
||||
/* hard code uart num for now */
|
||||
const msg: ApiBinaryMsg = {
|
||||
sub_mod: sub_mod,
|
||||
data_type: WtDataType.RAW,
|
||||
module: WtModuleID.UART,
|
||||
payload: payload,
|
||||
}
|
||||
sendBinMsg(msg);
|
||||
}
|
||||
|
||||
export function uart_get_baud(uart_num: number) {
|
||||
const cmd = {
|
||||
cmd: WtUartCmd.GET_BAUD,
|
||||
module: WtModuleID.UART,
|
||||
sub_mod: uart_num,
|
||||
}
|
||||
sendJsonMsg(cmd);
|
||||
}
|
||||
|
||||
export function uart_set_baud(baud: number, uart_num: number) {
|
||||
const cmd: IUartMsgBaud = {
|
||||
cmd: WtUartCmd.SET_BAUD,
|
||||
module: WtModuleID.UART,
|
||||
baud: baud,
|
||||
sub_mod: uart_num,
|
||||
}
|
||||
sendJsonMsg(cmd);
|
||||
}
|
||||
|
||||
export function uart_get_config(uart_num: number) {
|
||||
const cmd = {
|
||||
cmd: WtUartCmd.GET_CONFIG,
|
||||
module: WtModuleID.UART,
|
||||
sub_mod: uart_num,
|
||||
}
|
||||
sendJsonMsg(cmd);
|
||||
}
|
||||
|
||||
export function uart_set_config(uart_config: IUartConfig, uart_num: number) {
|
||||
const cmd: IUartMsgConfig = {
|
||||
cmd: WtUartCmd.SET_CONFIG,
|
||||
module: WtModuleID.UART,
|
||||
sub_mod: uart_num,
|
||||
data_bits: uart_config.data_bits,
|
||||
parity: uart_config.parity,
|
||||
stop_bits: uart_config.stop_bits,
|
||||
}
|
||||
sendJsonMsg(cmd);
|
||||
}
|
||||
|
||||
export function uart_get_default_num() {
|
||||
const cmd = {
|
||||
cmd: WtUartCmd.GET_DEFAULT_NUM,
|
||||
module: WtModuleID.UART,
|
||||
}
|
||||
sendJsonMsg(cmd);
|
||||
}
|
|
@ -31,9 +31,11 @@ export interface ServerMsg {
|
|||
}
|
||||
|
||||
export enum WtModuleID {
|
||||
SYSTEM = 0,
|
||||
WIFI = 1,
|
||||
DATA_FLOW = 2,
|
||||
UART = 4,
|
||||
OTA = 5,
|
||||
}
|
||||
|
||||
export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="M480-360 280-560h400L480-360Z"/></svg>
|
After (image error) Size: 127 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="m280-400 200-200 200 200H280Z"/></svg>
|
After (image error) Size: 127 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"/></svg>
|
After (image error) Size: 411 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>
|
After (image error) Size: 292 B |
|
@ -1,4 +1,17 @@
|
|||
export const toServer = new BroadcastChannel("toServer");
|
||||
export const toClient = new BroadcastChannel("toClient");
|
||||
export const toWebsocketCtrl = new BroadcastChannel("toWebsocketCtrl");
|
||||
export const toClientCtrl = new BroadcastChannel("toClientCtrl");
|
||||
// Define a fallback mock class only if BroadcastChannel is undefined
|
||||
const BC: typeof BroadcastChannel = typeof BroadcastChannel !== 'undefined'
|
||||
? BroadcastChannel
|
||||
: class {
|
||||
constructor(name: string) {
|
||||
// no-op
|
||||
}
|
||||
postMessage(_: any) {}
|
||||
close() {}
|
||||
addEventListener(_: string, __: any) {}
|
||||
removeEventListener(_: string, __: any) {}
|
||||
} as unknown as typeof BroadcastChannel;
|
||||
|
||||
export const toServer = new BC("toServer");
|
||||
export const toClient = new BC("toClient");
|
||||
export const toWebsocketCtrl = new BC("toWebsocketCtrl");
|
||||
export const toClientCtrl = new BC("toClientCtrl");
|
|
@ -1,3 +1,19 @@
|
|||
export function isDevMode() {
|
||||
return import.meta.env.VITE_APP_MODE === 'dev';
|
||||
}
|
||||
}
|
||||
|
||||
export function isOTAEnabled() {
|
||||
return import.meta.env.VITE_ENABLE_OTA === 'true' || false;
|
||||
}
|
||||
|
||||
export function isTrialMode() {
|
||||
return import.meta.env.VITE_TRIAL_MODE === "true" || false;
|
||||
}
|
||||
|
||||
export function getTrialDate() {
|
||||
return import.meta.env.VITE_TRIAL_DATE || "1970-01-01";
|
||||
}
|
||||
|
||||
export function getTrialMsg() {
|
||||
return import.meta.env.VITE_TRIAL_MSG || "感谢您试用允斯开放固件,若您喜欢,欢迎关注我的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,
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import {registerModule} from "@/router/msgRouter";
|
||||
import {type ApiJsonMsg, type ControlMsg, ControlMsgType, WtModuleID} from "@/api";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
import {useDataFlowStore} from "@/stores/useDataFlowStore";
|
||||
import {type IInstanceList, WtDataFlowCmd} from "@/api/apiDataFlow";
|
||||
|
||||
|
||||
export function useDataFlowModule() {
|
||||
const dfStore = useDataFlowStore()
|
||||
|
||||
function onClientCtrl(msg: ControlMsg) {
|
||||
if (msg.type !== ControlMsgType.WS_EVENT) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function onClientMsg(msg: ApiJsonMsg) {
|
||||
switch (msg.cmd as WtDataFlowCmd) {
|
||||
case WtDataFlowCmd.GET_INS_LIST: {
|
||||
const insList = msg as IInstanceList;
|
||||
dfStore.instanceList = insList.instances;
|
||||
break;
|
||||
}
|
||||
case WtDataFlowCmd.GET_ATTACH_LIST: {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (isDevMode()) {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
registerModule(WtModuleID.DATA_FLOW, {
|
||||
ctrlCallback: onClientCtrl,
|
||||
serverJsonMsgCallback: onClientMsg,
|
||||
serverBinMsgCallback: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import {useSystemStore} from "@/stores/useSystemStore";
|
||||
import {registerModule} from "@/router/msgRouter";
|
||||
import {type ApiJsonMsg, ControlEvent, type ControlMsg, ControlMsgType, WtModuleID} from "@/api";
|
||||
import {type ISysFmInfo, type ISysInfo, wt_sys_get_fm_info, wt_sys_get_sys_info, WtSytemCmd} from "@/api/apiSystem";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
|
||||
|
||||
export function useSystemModule() {
|
||||
const sysStore = useSystemStore()
|
||||
|
||||
function onClientCtrl(msg: ControlMsg) {
|
||||
if (msg.type !== ControlMsgType.WS_EVENT) {
|
||||
return
|
||||
}
|
||||
|
||||
if (msg.data === ControlEvent.CONNECTED) {
|
||||
wt_sys_get_fm_info();
|
||||
wt_sys_get_sys_info();
|
||||
sysStore.rebootInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onClientMsg(msg: ApiJsonMsg) {
|
||||
switch (msg.cmd as WtSytemCmd) {
|
||||
case WtSytemCmd.WT_SYS_REBOOT:
|
||||
sysStore.rebootInProgress = true;
|
||||
break;
|
||||
case WtSytemCmd.WT_SYS_GET_FM_INFO: {
|
||||
const fm_info = msg as ISysFmInfo;
|
||||
sysStore.curFmInfo.date = fm_info.upd_date;
|
||||
sysStore.curFmInfo.ver = fm_info.fm_ver;
|
||||
break;
|
||||
}
|
||||
case WtSytemCmd.WT_SYS_GET_SYS_INFO: {
|
||||
const sysInfo: ISysInfo = msg as ISysInfo & ApiJsonMsg;
|
||||
Object.assign(sysStore.sysInfo, sysInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isDevMode()) {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
registerModule(WtModuleID.SYSTEM, {
|
||||
ctrlCallback: onClientCtrl,
|
||||
serverJsonMsgCallback: onClientMsg,
|
||||
serverBinMsgCallback: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import {registerModule} from "@/router/msgRouter";
|
||||
import {type ApiJsonMsg, ControlEvent, type ControlMsg, ControlMsgType, WtModuleID} from "@/api";
|
||||
import {useUpdateStore} from "@/stores/useUpdateStore";
|
||||
import {
|
||||
type IOTAFmInfo,
|
||||
type IOTAProgress,
|
||||
WtOTACmd,
|
||||
WtOTAProgressStatus,
|
||||
wt_ota_get_progress,
|
||||
wt_ota_get_update_info,
|
||||
} from "@/api/apiOTA";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
import {useSystemStore} from "@/stores/useSystemStore";
|
||||
|
||||
export function useUpdateModule() {
|
||||
const updateStore = useUpdateStore()
|
||||
const sysStore = useSystemStore()
|
||||
|
||||
function onClientCtrl(msg: ControlMsg) {
|
||||
if (msg.type !== ControlMsgType.WS_EVENT) {
|
||||
return
|
||||
}
|
||||
|
||||
if (msg.data === ControlEvent.CONNECTED) {
|
||||
wt_ota_get_update_info();
|
||||
wt_ota_get_progress();
|
||||
}
|
||||
}
|
||||
|
||||
function onClientMsg(msg: ApiJsonMsg) {
|
||||
switch (msg.cmd as WtOTACmd) {
|
||||
case WtOTACmd.WT_OTA_GET_UPDATE_INFO: {
|
||||
const info = msg as IOTAFmInfo;
|
||||
Object.assign(updateStore.newFmInfo, info);
|
||||
if (updateStore.newFmInfo.fm_ver !== sysStore.curFmInfo.ver && updateStore.newFmInfo.fm_ver[0] !== '-'
|
||||
&& (updateStore.updateStatus === 'IDLE' || updateStore.updateStatus === 'FAILED')) {
|
||||
updateStore.canUpdate = true;
|
||||
} else {
|
||||
updateStore.canUpdate = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WtOTACmd.WT_OTA_DO_UPDATE:
|
||||
break;
|
||||
case WtOTACmd.WT_OTA_GET_PROGRESS: {
|
||||
const progress = msg as IOTAProgress;
|
||||
updateStore.updateStatus = progress.status;
|
||||
if (progress.total_size !== 0) {
|
||||
updateStore.updateProgress = (progress.progress / progress.total_size) * 100;
|
||||
} else {
|
||||
updateStore.updateProgress = 0;
|
||||
}
|
||||
if (progress.status === WtOTAProgressStatus.IDLE) {
|
||||
if (updateStore.newFmInfo.fm_ver !== sysStore.curFmInfo.ver && updateStore.newFmInfo.fm_ver[0] !== '-') {
|
||||
updateStore.canUpdate = true;
|
||||
} else {
|
||||
updateStore.canUpdate = false;
|
||||
}
|
||||
updateStore.clearProgressInterval();
|
||||
updateStore.progressBarStatus = '';
|
||||
} else if (progress.status === WtOTAProgressStatus.FAILED) {
|
||||
if (updateStore.newFmInfo.fm_ver !== sysStore.curFmInfo.ver && updateStore.newFmInfo.fm_ver[0] !== '-') {
|
||||
updateStore.canUpdate = true;
|
||||
} else {
|
||||
updateStore.canUpdate = false;
|
||||
}
|
||||
updateStore.clearProgressInterval();
|
||||
updateStore.progressBarStatus = 'exception';
|
||||
} else if (progress.status === WtOTAProgressStatus.IN_PROGRESS) {
|
||||
updateStore.setProgressInterval();
|
||||
updateStore.progressBarStatus = '';
|
||||
updateStore.canUpdate = false;
|
||||
} else if (progress.status === WtOTAProgressStatus.OK) {
|
||||
updateStore.clearProgressInterval();
|
||||
updateStore.canUpdate = false;
|
||||
updateStore.progressBarStatus = 'success';
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isDevMode()) {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
registerModule(WtModuleID.OTA, {
|
||||
ctrlCallback: onClientCtrl,
|
||||
serverJsonMsgCallback: onClientMsg,
|
||||
serverBinMsgCallback: () => {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ import MyWorker from '@/composables/websocket/ws.sharedworker?sharedworker'
|
|||
import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper";
|
||||
import {toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef";
|
||||
import type {ControlMsg, ServerMsg} from "@/api";
|
||||
import {ControlEvent, ControlMsgType} from "@/api";
|
||||
import {ControlMsgType} from "@/api";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
|
||||
export interface IWebsocketService {
|
||||
|
@ -14,12 +14,13 @@ export interface IWebsocketService {
|
|||
deinit(): void;
|
||||
|
||||
send(msg: ServerMsg): void;
|
||||
getSocketStatus(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Websocket that run in a shared worker, shared across tabs
|
||||
*/
|
||||
class WebsocketShared implements IWebsocketService{
|
||||
class WebsocketShared implements IWebsocketService {
|
||||
private static instance: IWebsocketService;
|
||||
|
||||
private worker: SharedWorker;
|
||||
|
@ -82,6 +83,10 @@ class WebsocketShared implements IWebsocketService{
|
|||
|
||||
this.ctrlCallback(ev.data);
|
||||
}
|
||||
|
||||
getSocketStatus() {
|
||||
this.worker.port.postMessage({type: ControlMsgType.WS_GET_STATE} as ControlMsg)
|
||||
}
|
||||
}
|
||||
|
||||
class WebsocketClassic implements IWebsocketService{
|
||||
|
@ -115,10 +120,14 @@ class WebsocketClassic implements IWebsocketService{
|
|||
send(msg: ServerMsg): void {
|
||||
this.socket.send(msg);
|
||||
}
|
||||
|
||||
getSocketStatus(): void {
|
||||
this.socket.getSocketStatus();
|
||||
}
|
||||
}
|
||||
|
||||
export function getWebsocketService(): IWebsocketService {
|
||||
if (typeof SharedWorker !== 'undefined') {
|
||||
if (typeof SharedWorker !== 'undefined' && typeof localStorage !== 'undefined') {
|
||||
return WebsocketShared.getInstance();
|
||||
} else {
|
||||
return WebsocketClassic.getInstance();
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api";
|
||||
import type {ControlMsg, ServerMsg} from "@/api";
|
||||
import {ControlEvent, ControlMsgType} from "@/api";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
|
||||
|
@ -9,6 +8,8 @@ interface IWebsocket {
|
|||
close(): void;
|
||||
|
||||
send(msg: ServerMsg): void;
|
||||
|
||||
getSocketStatus(): void;
|
||||
}
|
||||
|
||||
class WebsocketDummy implements IWebsocket {
|
||||
|
@ -20,6 +21,9 @@ class WebsocketDummy implements IWebsocket {
|
|||
send(msg: ServerMsg) {
|
||||
|
||||
}
|
||||
|
||||
getSocketStatus(): void {
|
||||
}
|
||||
}
|
||||
|
||||
class OneTimeWebsocket implements IWebsocket {
|
||||
|
@ -61,6 +65,8 @@ class OneTimeWebsocket implements IWebsocket {
|
|||
console.log("No heart beat, break connection");
|
||||
this.close();
|
||||
this.clear();
|
||||
// } else if (this.socket.readyState === this.socket.CONNECTING) {
|
||||
// this.close();
|
||||
}
|
||||
if (isDevMode()) {
|
||||
console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState);
|
||||
|
@ -159,6 +165,26 @@ class OneTimeWebsocket implements IWebsocket {
|
|||
this.ctrlCallback(msg);
|
||||
this.closeCallback();
|
||||
}
|
||||
|
||||
getSocketStatus() {
|
||||
let type: ControlEvent;
|
||||
switch (this.socket.readyState) {
|
||||
case WebSocket.CONNECTING:
|
||||
type = ControlEvent.CONNECTING;
|
||||
break;
|
||||
case WebSocket.OPEN:
|
||||
type = ControlEvent.CONNECTED;
|
||||
break;
|
||||
default:
|
||||
type = ControlEvent.DISCONNECTED;
|
||||
break;
|
||||
}
|
||||
const msg: ControlMsg = {
|
||||
type: ControlMsgType.WS_EVENT,
|
||||
data: type,
|
||||
};
|
||||
this.ctrlCallback(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class WebsocketWrapper {
|
||||
|
@ -219,4 +245,8 @@ export class WebsocketWrapper {
|
|||
send(msg: ServerMsg) {
|
||||
this.socket.send(msg)
|
||||
}
|
||||
|
||||
getSocketStatus() {
|
||||
this.socket.getSocketStatus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import type {ControlMsg, ServerMsg} from "@/api";
|
||||
|
||||
declare const self: SharedWorkerGlobalScope;
|
||||
|
||||
import {ControlEvent, ControlMsgType} from "@/api";
|
||||
import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper";
|
||||
import {toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef";
|
||||
import {ControlEvent, ControlMsgType} from "@/api";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
|
||||
declare const self: SharedWorkerGlobalScope;
|
||||
|
||||
const websocket = new WebsocketWrapper();
|
||||
let host = "";
|
||||
|
||||
|
@ -30,6 +29,8 @@ self.onconnect = function(event) {
|
|||
host = e.data.data;
|
||||
websocket.init(host, msgBroadcast, ctrlBroadcast);
|
||||
}
|
||||
} else if (e.data.type === ControlMsgType.WS_GET_STATE) {
|
||||
websocket.getSocketStatus();
|
||||
}
|
||||
};
|
||||
const msg: ControlMsg = {
|
||||
|
|
47
src/i18n.ts
47
src/i18n.ts
|
@ -1,19 +1,52 @@
|
|||
import { createI18n } from 'vue-i18n';
|
||||
import {createI18n} from 'vue-i18n';
|
||||
import zh from '@/locales/zh'
|
||||
import en from '@/locales/en'
|
||||
import fr from '@/locales/fr'
|
||||
|
||||
// const locale = localStorage.getItem('lang') || 'zh';
|
||||
export const locale = 'zh';
|
||||
const userLanguage = navigator.language || 'en';
|
||||
|
||||
// Get the language code (e.g., 'en' from 'en-US')
|
||||
export const locale = userLanguage.split('-')[0];
|
||||
const messages = {
|
||||
zh,
|
||||
en,
|
||||
fr,
|
||||
} as const;
|
||||
|
||||
type Locale = keyof typeof messages;
|
||||
|
||||
export const availableLanguages = Object.keys(messages);
|
||||
|
||||
// export const locale = 'zh';
|
||||
console.log(userLanguage, locale, availableLanguages)
|
||||
|
||||
const i18n = createI18n({
|
||||
globalInjection: true,
|
||||
legacy: false,
|
||||
locale: locale,
|
||||
fallbackLocale: 'zh',
|
||||
messages: {
|
||||
zh,
|
||||
// en,
|
||||
}
|
||||
messages: messages
|
||||
});
|
||||
|
||||
export function getFlagFromLang(lang: string) {
|
||||
if (lang === 'zh') {
|
||||
return '🇨🇳';
|
||||
} else if (lang === 'en') {
|
||||
return '🇺🇸';
|
||||
} else if (lang === 'fr') {
|
||||
return '🇫🇷';
|
||||
}
|
||||
return '🏳️';
|
||||
}
|
||||
|
||||
export function setLang(lang: string): void {
|
||||
if (availableLanguages.includes(lang)) {
|
||||
i18n.global.locale.value = lang as Locale;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLang() {
|
||||
return i18n.global.locale;
|
||||
}
|
||||
|
||||
export default i18n;
|
||||
|
|
|
@ -1,3 +1,204 @@
|
|||
export default {
|
||||
disconnected: "disconnected"
|
||||
emoji: {
|
||||
flag: "🇺🇸",
|
||||
},
|
||||
disconnected: "Disconnected",
|
||||
connected: "Connected",
|
||||
connecting: "Connecting",
|
||||
use: "use",
|
||||
author: "author",
|
||||
studioYunSi: "Yunsi Studio",
|
||||
authorEmail: "Author email",
|
||||
TencentQQGroup: "QQ Group",
|
||||
Discord: "Discord",
|
||||
BiliBili: "BiliBili",
|
||||
|
||||
suggestion: "suggestion",
|
||||
feature: "feature",
|
||||
version: "Version",
|
||||
releaseTime: "Release Time",
|
||||
credit: "Credit",
|
||||
aboutWebHost: "About the Web Host Application",
|
||||
aboutDebugger: "About the Debugger",
|
||||
officialWebsite: "Official Website",
|
||||
email: "Email",
|
||||
note: "Note",
|
||||
welcomeMessage: "Welcome to reach out anytime",
|
||||
serialNumber: "Serial Number",
|
||||
|
||||
ws: {
|
||||
disconnected: "Disconnected",
|
||||
connected: "Connected",
|
||||
connecting: "Connecting",
|
||||
},
|
||||
|
||||
page: {
|
||||
home: "Home",
|
||||
wifi: "Wi-Fi",
|
||||
about: "About",
|
||||
uart: "Uart",
|
||||
feedback: "Feedback",
|
||||
close: "Close",
|
||||
update: "Update",
|
||||
fullscreen: "Fullscreen",
|
||||
windowed: "Windowed"
|
||||
},
|
||||
|
||||
uart: {
|
||||
port: "Port",
|
||||
startCommunication: "Start Communication",
|
||||
stopCommunication: "Stop Communication",
|
||||
commonlyUsed: "Common",
|
||||
baudrate: "Baud Rate",
|
||||
customBaud: "Custom Baud",
|
||||
use: "Use",
|
||||
actual: "Actual",
|
||||
dataBits: "Data Bits",
|
||||
stopBits: "Stop Bits",
|
||||
parity: "Parity",
|
||||
parityNone: "None",
|
||||
parityOdd: "Odd",
|
||||
parityEven: "Even",
|
||||
flowControl: "Flow Control",
|
||||
send: "Send",
|
||||
clear: "Clear",
|
||||
clearTooltip: "Only clears the display area, can be restored with refresh.",
|
||||
updateTooltip: "Sync with cache + filter",
|
||||
autoUpdateTooltip: "Only stop refreshing the display area; the background continues to receive data.",
|
||||
receive: "Receive",
|
||||
|
||||
displayOptions: "Display Options",
|
||||
display: "Display",
|
||||
show: "Show",
|
||||
text: "Text",
|
||||
timestamp: "Timestamp",
|
||||
enable: "Enable",
|
||||
lineWrap: "Line Wrap",
|
||||
highlight: "Highlight",
|
||||
|
||||
frameBreakStrategy: "Frame Break Strategy",
|
||||
priority: "Priority",
|
||||
rule: "Rule",
|
||||
ruleTips:
|
||||
"<p>Timeout=-1: Disable timeout frame break</p>" +
|
||||
"<p>Timeout=0: Immediate break, any received data is considered complete</p>" +
|
||||
"<p>Match after break: Typical \\n scenario</p>" +
|
||||
"<p>Match before break: For scenarios with special frame headers</p>" +
|
||||
"<p>Fixed byte frame break: Useful for large data transfer, e.g., break frame every 1024 bytes for easy data viewing</p>",
|
||||
value: "Value",
|
||||
timeout: "Timeout",
|
||||
match: "Match",
|
||||
byte: "Byte",
|
||||
begin: "b",
|
||||
end: "b",
|
||||
|
||||
other: "Other",
|
||||
decodeAnsiEscapeCodes: "Decode ANSI Escape Codes",
|
||||
ansiTooltips:
|
||||
"<p>ANSI escape codes have many uses for terminals and text, such as changing text colors, among other effects.</p>\n" +
|
||||
"<p>\n Learn more ->\n <a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/ANSI_escape_code\">" +
|
||||
"https://en.wikipedia.org/wiki/ANSI_escape_code\n </a></p>",
|
||||
filter: "Filter",
|
||||
textAndEscape: "Text with \\n\\x support",
|
||||
autoUpdateNewData: "Auto-refresh new data",
|
||||
updateFrequency: "Data Display Update Interval (ms)",
|
||||
updateFrequencyTooltip: "Increasing the interval can reduce CPU usage.",
|
||||
|
||||
addHeader: "Add Header",
|
||||
addFooter: "Add Footer",
|
||||
|
||||
passthrough: "Passthrough",
|
||||
proxy: "Proxy",
|
||||
serverPort: "Server Port",
|
||||
connectedClient: "Connected Client",
|
||||
refresh: "Refresh",
|
||||
interface: "Interface",
|
||||
noClientConnected: "No Client Connected",
|
||||
|
||||
import: "Import",
|
||||
export: "Export",
|
||||
reset: "Reset",
|
||||
resetTooltip: "Takes effect after refreshing the page.",
|
||||
saveToLocal: "Save to Local",
|
||||
saveToLocalTooltip: "If multiple pages exist, they will overwrite each other.",
|
||||
add: "Add",
|
||||
edit: "Edit",
|
||||
drag: "Drag",
|
||||
ipChangeAlert: "Changing the IP address will cause the configuration to be lost.",
|
||||
|
||||
layout: "Layout",
|
||||
landscape: "Landscape",
|
||||
portrait: "Portrait",
|
||||
responsive: "Responsive",
|
||||
configPannel: "Config",
|
||||
displayPannel: "Display",
|
||||
macroPannel: "Quick Send",
|
||||
autoScrollToBottom: "Auto Scroll",
|
||||
clearScreen: "Clear",
|
||||
autoUpdate: "Auto Update",
|
||||
tempDisplayTooltip: "Data that does not meet the frame-break rules (e.g., not timed out) is temporarily displayed in real-time in this area. If it exceeds 8192 bytes, it will automatically break frames.",
|
||||
loopSend: "Loop Send",
|
||||
loopSendTooltip: "The actual frequency is affected by the interface refresh rate. For more accuracy, you can try turning off 'auto-refresh'.",
|
||||
sendFormat: "Send Format",
|
||||
cachedFrame: "Cached",
|
||||
format: "Format",
|
||||
},
|
||||
|
||||
wifi: {
|
||||
settings: "Settings",
|
||||
setFailed: "Settings failed to set",
|
||||
setSuccess: "Settings saved",
|
||||
connection: "Connection",
|
||||
scanning: "Scanning",
|
||||
scan: "Scan",
|
||||
scanDone: "Scan done",
|
||||
warnWifiName: "Enter Wi-Fi Name",
|
||||
password: "Password",
|
||||
connectInfoHTML: "Changing Wi-Fi will disconnect this interface from the passthrough device if not connected through its hotspot.",
|
||||
connect: "Connect",
|
||||
mode: "Mode",
|
||||
save: "Save",
|
||||
station: "Station",
|
||||
intelligent: "Smart",
|
||||
APOnly: "Hotspot Only",
|
||||
disconnected: "Disconnected",
|
||||
modeTipsHtml: "<p>\n" +
|
||||
"<el-textsize=\"small\">Smart Mode:</el-text>\n" +
|
||||
"After connecting to Wi-Fi, the hotspot will turn off automatically after 30 seconds if no device is connected. It will turn on after 5 seconds if disconnected from AP.\n" +
|
||||
"</p>\n" +
|
||||
"<p>\n" +
|
||||
"<el-textsize=\"small\">Coexistence Mode:</el-text>\n" +
|
||||
"Convenient but impacts stability and increases power consumption.\n" +
|
||||
"</p>\n" +
|
||||
"<p>\n" +
|
||||
"<el-textsize=\"small\">Hotspot-Only Mode Drawback:</el-text>\n" +
|
||||
"No network connection.\n" +
|
||||
"</p>",
|
||||
enabled: "Enabled",
|
||||
disabled: "Disabled",
|
||||
|
||||
stationInfo: "Terminal (STA)",
|
||||
hotspotInfo: "Hotspot (AP)",
|
||||
signalStrength: "Signal Strength",
|
||||
gateway: "Gateway",
|
||||
netmask: "Netmask",
|
||||
primaryDNS: "Primary DNS",
|
||||
backupDNS: "Backup DNS",
|
||||
IPmode: "IP Allocation Mode",
|
||||
DNSmode: "DNS Mode",
|
||||
internalAddress: "Internal Address",
|
||||
|
||||
autoIP: "Automatic (DHCP)",
|
||||
staticIP: "Static IP",
|
||||
autoDNS: "Automatic (Use Gateway)",
|
||||
staticDNS: "Static DNS",
|
||||
APauto_STA: "Smart Hotspot + Persistent Terminal (AP+STA)",
|
||||
APonly: "Hotspot Only (AP)",
|
||||
AP_STA: "Persistent Hotspot + Persistent Terminal (AP+STA)",
|
||||
|
||||
connectionSuccess: "Connection Successful",
|
||||
enterAPName: "Entre the AP name",
|
||||
debuggerNotConnected: "Debugger not connected",
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,204 @@
|
|||
export default {
|
||||
emoji: {
|
||||
flag: "🇫🇷",
|
||||
},
|
||||
disconnected: "Déconnecté",
|
||||
connected: "Connecté",
|
||||
connecting: "Connexion..",
|
||||
use: "utiliser",
|
||||
author: "Auteur",
|
||||
studioYunSi: "Studio Yunsi",
|
||||
authorEmail: "Email de l'auteur",
|
||||
TencentQQGroup: "Groupe QQ",
|
||||
Discord: "Discord",
|
||||
BiliBili: "BiliBili",
|
||||
|
||||
suggestion: "suggestion",
|
||||
feature: "fonctionnalité",
|
||||
version: "Version",
|
||||
releaseTime: "Date de Publication",
|
||||
credit: "Remerciements",
|
||||
aboutWebHost: "À propos de l'Hôte Web",
|
||||
aboutDebugger: "À propos du Débogueur",
|
||||
officialWebsite: "Site Officiel",
|
||||
email: "E-mail",
|
||||
note: "Remarque",
|
||||
welcomeMessage: "N'hésitez pas à venir nous solliciter.",
|
||||
serialNumber: "Numéro de série",
|
||||
|
||||
ws: {
|
||||
disconnected: "Déconnecté",
|
||||
connected: "Connecté",
|
||||
connecting: "Connexion..",
|
||||
},
|
||||
|
||||
page: {
|
||||
home: "Accueil",
|
||||
wifi: "Wi-Fi",
|
||||
about: "À propos",
|
||||
uart: "Uart",
|
||||
feedback: "Feedback",
|
||||
close: "Fermer",
|
||||
update: "Mise à jour",
|
||||
fullscreen: "Plein écran",
|
||||
windowed: "Fenêtré",
|
||||
},
|
||||
|
||||
uart: {
|
||||
port: "Port",
|
||||
startCommunication: "Démarrer la communication",
|
||||
stopCommunication: "Arrêter la communication",
|
||||
commonlyUsed: "Fréquemment utilisé",
|
||||
baudrate: "Taux de Baud",
|
||||
customBaud: "Baud",
|
||||
use: "Utiliser",
|
||||
actual: "Actuel",
|
||||
dataBits: "Bits de Données",
|
||||
stopBits: "Bits d'Arrêt",
|
||||
parity: "Parité",
|
||||
parityNone: "Aucune",
|
||||
parityOdd: "Impair(Odd)",
|
||||
parityEven: "Pair(Even)",
|
||||
flowControl: "Contrôle de Flux",
|
||||
send: "Envoyer",
|
||||
clear: "Effacer",
|
||||
clearTooltip: "Ne supprime que la zone d'affichage, peut être restaurée en actualisant.",
|
||||
updateTooltip: "Synchroniser avec le cache + filtrer",
|
||||
autoUpdateTooltip: "Arrête uniquement le rafraîchissement de la zone d'affichage ; l'arrière-plan continue de recevoir des données.",
|
||||
receive: "Recevoir",
|
||||
|
||||
displayOptions: "Options d'Affichage",
|
||||
display: "Affichage",
|
||||
show: "Afficher",
|
||||
text: "Texte",
|
||||
timestamp: "Horodatage",
|
||||
enable: "Activer",
|
||||
lineWrap: "Retour à la Ligne",
|
||||
highlight: "Surligner",
|
||||
|
||||
frameBreakStrategy: "Stratégie de Coupure de Trame",
|
||||
priority: "Priorité",
|
||||
rule: "Règle",
|
||||
ruleTips:
|
||||
"<p>Délai d'expiration=-1 : Désactiver la coupure de trame par délai d'expiration</p>" +
|
||||
"<p>Délai d'expiration=0 : Coupure immédiate, toutes données reçues sont considérées complètes</p>" +
|
||||
"<p>Match après coupure : Scénario typique \\n</p>" +
|
||||
"<p>Match avant coupure : Pour des scénarios avec en-têtes de trame spécifiques</p>" +
|
||||
"<p>Coupure de trame par octets fixes : Utile pour le transfert de grandes quantités de données, par exemple, couper la trame tous les 1024 octets pour faciliter la visualisation des données</p>",
|
||||
value: "Valeur",
|
||||
timeout: "Timeout",
|
||||
match: "Match",
|
||||
byte: "Byte",
|
||||
begin: "b",
|
||||
end: "b",
|
||||
|
||||
other: "Autres",
|
||||
decodeAnsiEscapeCodes: "Décode Échappement ANSI",
|
||||
ansiTooltips:
|
||||
"<p>Les codes d'échappement ANSI ont de nombreuses utilisations pour les terminaux et le texte, comme changer les couleurs du texte, entre autres effets.</p>" +
|
||||
"<p>\n En savoir plus ->\n " +
|
||||
"<a target=\"_blank\" href=\"https://en.wikipedia.org/wiki/ANSI_escape_code\">\n" +
|
||||
"https://en.wikipedia.org/wiki/ANSI_escape_code\n </a>\n</p>",
|
||||
filter: "Filtrer",
|
||||
textAndEscape: "Texte;supporte\\n\\x",
|
||||
autoUpdateNewData: "Auto-update nouvelles données",
|
||||
updateFrequency: "Délais rafraîchissement des Données (ms)",
|
||||
updateFrequencyTooltip: "Augmenter l'intervalle peut réduire l'utilisation des ressources CPU.",
|
||||
|
||||
addHeader: "Ajouter un En-tête",
|
||||
addFooter: "Ajouter un Pied de page",
|
||||
|
||||
passthrough: "Transmission",
|
||||
proxy: "Proxy",
|
||||
serverPort: "Port Serveur",
|
||||
connectedClient: "Client Connecté",
|
||||
refresh: "Rafraîchir",
|
||||
interface: "Interface",
|
||||
noClientConnected: "Aucun Client Connecté",
|
||||
|
||||
import: "Importer",
|
||||
export: "Exporter",
|
||||
reset: "Réinitialiser",
|
||||
resetTooltip: "Prend effet après le rafraîchissement de la page.",
|
||||
saveToLocal: "Enregistrer Localement",
|
||||
saveToLocalTooltip: "S'il existe plusieurs pages, elles se chevaucheront mutuellement.",
|
||||
add: "Ajouter",
|
||||
edit: "Éditer",
|
||||
drag: "Glisser",
|
||||
ipChangeAlert: "Le changement d'adresse IP entraînera la perte de la configuration.",
|
||||
|
||||
layout: "Disposition",
|
||||
landscape: "Paysage",
|
||||
portrait: "Portrait",
|
||||
responsive: "Résponsive",
|
||||
configPannel: "Configuration",
|
||||
displayPannel: "Données",
|
||||
macroPannel: "Envoie Rapide",
|
||||
autoScrollToBottom: "Auto Scroll",
|
||||
clearScreen: "Effacer",
|
||||
autoUpdate: "Auto Update",
|
||||
tempDisplayTooltip: "es données qui ne respectent pas les règles de rupture de trame (par exemple : non expirées) s'affichent temporairement en temps réel dans cette zone. Au-delà de 8192 octets, une rupture de trame est automatique.",
|
||||
loopSend: "Envoi en Boucle",
|
||||
loopSendTooltip: "La fréquence réelle est influencée par le taux de rafraîchissement de l'interface. Pour plus de précision, vous pouvez essayer de désactiver 'l'actualisation automatique'.",
|
||||
sendFormat: "Format d'Envoi",
|
||||
cachedFrame: "Cache",
|
||||
format: "Format",
|
||||
},
|
||||
|
||||
wifi: {
|
||||
settings: "Paramètres",
|
||||
setFailed: "Echec d'enregistrement de paramètres",
|
||||
setSuccess: "Paramètres enregistrés",
|
||||
connection: "Connexion",
|
||||
scanning: "Recherche en cours",
|
||||
scan: "Rechercher",
|
||||
scanDone: "Fin recherche de Wi-Fi",
|
||||
warnWifiName: "Entrez le nom du Wi-Fi",
|
||||
password: "Mot de passe",
|
||||
connectInfoHTML: "Changer de Wi-Fi déconnectera cette interface du dispositif de transmission s'il ne passe pas par le point d'accès.",
|
||||
connect: "Connecter",
|
||||
mode: "Mode",
|
||||
save: "Enregistrer",
|
||||
station: "Station",
|
||||
intelligent: "Intelligent",
|
||||
APOnly: "Point d'accès uniquement",
|
||||
disconnected: "Déconnecté",
|
||||
modeTipsHtml: "<p>\n" +
|
||||
"<el-textsize=\"small\">Mode intelligent :</el-text>\n" +
|
||||
"Après la connexion au Wi-Fi, le point d'accès s'éteindra automatiquement après 30 secondes si aucun appareil n'est connecté. Il s'allumera après 5 secondes si la connexion AP est perdue.\n" +
|
||||
"</p>\n" +
|
||||
"<p>\n" +
|
||||
"<el-textsize=\"small\">Mode coexistence :</el-text>\n" +
|
||||
"Pratique mais réduit la stabilité et augmente la consommation d'énergie.\n" +
|
||||
"</p>\n" +
|
||||
"<p>\n" +
|
||||
"<el-textsize=\"small\">Inconvénient du mode point d'accès seul :</el-text>\n" +
|
||||
"Pas de connexion réseau.\n" +
|
||||
"</p>",
|
||||
enabled: "Activé",
|
||||
disabled: "Désactivé",
|
||||
|
||||
stationInfo: "Terminal(STA)",
|
||||
hotspotInfo: "Point d'Accès(AP)",
|
||||
signalStrength: "Puissance du Signal",
|
||||
gateway: "Passerelle",
|
||||
netmask: "Masque de Sous-réseau",
|
||||
primaryDNS: "DNS Primaire",
|
||||
backupDNS: "DNS Secondaire",
|
||||
IPmode: "Mode d'Attribution IP",
|
||||
DNSmode: "Mode DNS",
|
||||
internalAddress: "Adresse Interne",
|
||||
|
||||
autoIP: "Automatique (DHCP)",
|
||||
staticIP: "IP Statique",
|
||||
autoDNS: "Automatique (gateway)",
|
||||
staticDNS: "DNS Statique",
|
||||
APauto_STA: "Point d'Accès Intelligent + Terminal Permanent (AP+STA)",
|
||||
APonly: "Point d'Accès Seul (AP)",
|
||||
AP_STA: "Point d'Accès Permanent + Terminal Permanent (AP+STA)",
|
||||
|
||||
connectionSuccess: "Connexion Réussie",
|
||||
enterAPName: "Entrez le nom du AP",
|
||||
debuggerNotConnected: "Debugger non connecté",
|
||||
}
|
||||
};
|
|
@ -7,8 +7,8 @@ type NestedKeyOf<ObjectType extends object> = {
|
|||
: `${Key}`
|
||||
}[keyof ObjectType & (string | number)];
|
||||
|
||||
type TranslationKeys = NestedKeyOf<typeof zh>;
|
||||
export type TranslationKeys = NestedKeyOf<typeof zh>;
|
||||
|
||||
export function translate<K extends TranslationKeys>(key: K | string): string {
|
||||
return i18n.global.t(key.toLowerCase());
|
||||
export function translate(key: TranslationKeys | string): string {
|
||||
return i18n.global.t(key);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,30 @@
|
|||
export default {
|
||||
emoji: {
|
||||
flag: "🇨🇳",
|
||||
},
|
||||
disconnected: "未连接",
|
||||
connected: "已连接",
|
||||
connecting: "连接中",
|
||||
use: "使用",
|
||||
author: "作者",
|
||||
studioYunSi: "允斯工作室",
|
||||
authorEmail: "作者邮箱",
|
||||
TencentQQGroup: "QQ群",
|
||||
Discord: "Discord",
|
||||
BiliBili: "哔哩哔哩",
|
||||
|
||||
suggestion: "建议",
|
||||
feature: "需求",
|
||||
version: "版本",
|
||||
releaseTime: "发布时间",
|
||||
credit: "鸣谢",
|
||||
aboutWebHost: "关于网页版上位机",
|
||||
aboutDebugger: "关于调试器",
|
||||
officialWebsite: "官网",
|
||||
email: "邮箱",
|
||||
note: "备注",
|
||||
welcomeMessage: "欢迎来打扰啊~",
|
||||
serialNumber: "序列号",
|
||||
|
||||
ws: {
|
||||
disconnected: "未连接",
|
||||
|
@ -13,8 +36,172 @@ export default {
|
|||
home: "主页",
|
||||
wifi: "Wi-Fi",
|
||||
about: "关于",
|
||||
uart: "UART透传",
|
||||
uart: "UART",
|
||||
feedback: "反馈",
|
||||
close: "关闭",
|
||||
update: "更新",
|
||||
fullscreen: "全屏",
|
||||
windowed: "窗口",
|
||||
},
|
||||
|
||||
uart: {
|
||||
port: "接口",
|
||||
startCommunication: "开始数据收发",
|
||||
stopCommunication: "停止数据收发",
|
||||
commonlyUsed: "常用",
|
||||
baudrate: "波特率",
|
||||
customBaud: "自定义波特率",
|
||||
use: "使用",
|
||||
actual: "实际",
|
||||
dataBits: "数据位",
|
||||
stopBits: "停止位",
|
||||
parity: "校验位",
|
||||
parityNone: "无(None)",
|
||||
parityOdd: "奇(Odd)",
|
||||
parityEven: "偶(Even)",
|
||||
flowControl: "流控制",
|
||||
send: "发送",
|
||||
clear: "清空",
|
||||
clearTooltip: "仅清除显示区域,可用刷新恢复",
|
||||
updateTooltip: "与缓存同步+过滤",
|
||||
autoUpdateTooltip: "仅停止刷新显示区,后台继续接收数据",
|
||||
receive: "接收",
|
||||
|
||||
displayOptions: "显示选项",
|
||||
display: "显示框",
|
||||
show: "显示",
|
||||
text: "文本",
|
||||
timestamp: "时间戳",
|
||||
enable: "启用",
|
||||
lineWrap: "换行",
|
||||
highlight: "高亮",
|
||||
|
||||
frameBreakStrategy: "断帧策略",
|
||||
priority: "优先级",
|
||||
rule: "规则",
|
||||
ruleTips:
|
||||
"<p>超时=-1: 禁用超时断帧</p>" +
|
||||
"<p>超时=0: 当机立断,收到任何数据都视为完整数据</p>" +
|
||||
"<p>匹配断后:典型\\n的场景</p>" +
|
||||
"<p>匹配断前:用于有特殊帧头的场景</p>" +
|
||||
"<p>固定字节断帧:传输大量数据,比如可以每隔1024字节断帧,方便查看数据</p>",
|
||||
value: "值",
|
||||
timeout: "超时",
|
||||
match: "匹配",
|
||||
byte: "字节",
|
||||
begin: "断",
|
||||
end: "断",
|
||||
|
||||
other: "其他",
|
||||
decodeAnsiEscapeCodes: "解码ANSI转义码",
|
||||
ansiTooltips:
|
||||
"<p>ANSI转义码对终端和文本有很多作用,比如改变文本颜色等。</p>\n" +
|
||||
"<p>\n" +
|
||||
" 简单了解->\n" +
|
||||
" <a target=\"_blank\" href=\"https://yunsi.studio/wireless-debugger/docs/uart-webhost/ansi-escape-code\">\n" +
|
||||
" https://yunsi.studio/wireless-debugger/docs/uart-webhost/ansi-escape-code\n" +
|
||||
" </a>\n" +
|
||||
"</p>",
|
||||
filter: "过滤",
|
||||
textAndEscape: "文本,支持\\n\\x",
|
||||
autoUpdateNewData: "新数据自动刷新",
|
||||
updateFrequency: "数据显示刷新间隔(ms)",
|
||||
updateFrequencyTooltip: "提高间隔可减少CPU资源的使用",
|
||||
|
||||
addHeader: "增加帧头",
|
||||
addFooter: "增加帧尾",
|
||||
|
||||
passthrough: "透传",
|
||||
proxy: "透传",
|
||||
serverPort: "服务器端口",
|
||||
connectedClient: "已连接的客户端",
|
||||
refresh: "刷新",
|
||||
interface: "接口",
|
||||
noClientConnected: "无客户端连接",
|
||||
|
||||
import: "导入",
|
||||
export: "导出",
|
||||
reset: "重置",
|
||||
resetTooltip: "刷新页面后生效",
|
||||
saveToLocal: "保存到本地",
|
||||
saveToLocalTooltip: "若存在多个页面,会相互覆盖",
|
||||
add: "添加",
|
||||
edit: "编辑",
|
||||
drag: "拖拽",
|
||||
ipChangeAlert: "IP地址改变会导致配置丢失",
|
||||
|
||||
layout: "布局",
|
||||
landscape: "横/行",
|
||||
portrait: "竖/列",
|
||||
responsive: "自适应",
|
||||
configPannel: "设置窗",
|
||||
displayPannel: "数据窗",
|
||||
macroPannel: "快捷窗",
|
||||
autoScrollToBottom: "自动滚动到底部",
|
||||
clearScreen: "清屏",
|
||||
autoUpdate: "自动刷新",
|
||||
tempDisplayTooltip: "未满足断帧规则的数据(如:未超时),暂时实时显示在此区域。超过8192字节,自动断帧;",
|
||||
loopSend: "循环发送",
|
||||
loopSendTooltip: "实际频率受界面刷新率影响,如需要更精确,可以尝试关闭‘自动刷新’",
|
||||
sendFormat: "发送格式",
|
||||
cachedFrame: "缓存帧数",
|
||||
format: "格式化",
|
||||
},
|
||||
|
||||
wifi: {
|
||||
settings: "配置",
|
||||
setFailed: "设置失败",
|
||||
setSuccess: "配置成功",
|
||||
connection: "连接",
|
||||
scanning: "扫描中",
|
||||
scan: "扫描",
|
||||
scanDone: "扫描成功",
|
||||
warnWifiName: "请输入WIFI名",
|
||||
password: "密码",
|
||||
connectInfoHTML: "如果不是通过透传器的热点连接,更换Wi-Fi将导致此界面与透传器断开连接。",
|
||||
connect: "连接",
|
||||
mode: "模式",
|
||||
save: "保存",
|
||||
station: "终端",
|
||||
intelligent: "智能",
|
||||
APOnly: "仅开启热点",
|
||||
disconnected: "未连接",
|
||||
modeTipsHtml: "<p>\n" +
|
||||
"<el-textsize=\"small\">智能模式:</el-text>\n" +
|
||||
"成功连接至Wi-Fi后,如果此设备的热点未被其他设备连接,将在30秒后自动关闭热点;如果此设备与AP断开连接,将在5秒后自动开启热点\n" +
|
||||
"</p>\n" +
|
||||
"<p>\n" +
|
||||
"<el-textsize=\"small\">热点+终端共存模式:</el-text>\n" +
|
||||
"方便使用,但是影响稳定性,增加功耗\n" +
|
||||
"</p>\n" +
|
||||
"<p>\n" +
|
||||
"<el-textsize=\"small\">单热点模式缺点:</el-text>\n" +
|
||||
"无网络\n" +
|
||||
"</p>",
|
||||
enabled: "已开启",
|
||||
disabled: "未开启",
|
||||
|
||||
stationInfo: "终端(STA)",
|
||||
hotspotInfo: "自发热点(AP)",
|
||||
signalStrength: "信号强度",
|
||||
gateway: "网关",
|
||||
netmask: "掩码",
|
||||
primaryDNS: "首选DNS",
|
||||
backupDNS: "备用DNS",
|
||||
IPmode: "IP分配模式",
|
||||
DNSmode: "DNS模式",
|
||||
internalAddress: "内网地址",
|
||||
|
||||
autoIP: "自动 (DHCP)",
|
||||
staticIP: "静态IP",
|
||||
autoDNS: "自动 (使用网关)",
|
||||
staticDNS: "静态DNS",
|
||||
APauto_STA: "智能热点+常开终端 (AP+STA)",
|
||||
APonly: "仅开启热点 (AP)",
|
||||
AP_STA: "常开热点+常开终端 (AP+STA)",
|
||||
|
||||
connectionSuccess: "连接成功",
|
||||
enterAPName: "请输入AP名",
|
||||
debuggerNotConnected: "调试器未连接",
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ import '@/assets/page.css'
|
|||
import '@/assets/navigation.css'
|
||||
import 'element-plus/dist/index.css';
|
||||
|
||||
import 'vuetify/styles'
|
||||
import { createVuetify } from 'vuetify'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
@ -17,5 +19,6 @@ const app = createApp(App)
|
|||
app.use(createPinia())
|
||||
app.use(i18n);
|
||||
app.use(router)
|
||||
app.use(createVuetify())
|
||||
|
||||
app.mount('#app')
|
||||
|
|
|
@ -1,12 +1,46 @@
|
|||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import Home from '@/views/Home.vue'
|
||||
import {createRouter, createWebHistory, type RouteLocationNormalizedLoaded} from 'vue-router'
|
||||
import Wifi from '@/views/Wifi.vue'
|
||||
import Feedback from '@/views/Feedback.vue'
|
||||
import About from '@/views/About.vue'
|
||||
import Uart from '@/views/Uart.vue'
|
||||
import Page404 from '@/views/404.vue'
|
||||
import Update from '@/views/Update.vue'
|
||||
import {translate} from "@/locales";
|
||||
import {isOTAEnabled} from "@/composables/buildMode";
|
||||
import {reactive, watch} from "vue";
|
||||
import {getLang} from "@/i18n";
|
||||
|
||||
const languageState = reactive({
|
||||
currentLanguage: getLang(), // Get the current language from your i18n setup
|
||||
});
|
||||
|
||||
interface AppRouteMeta {
|
||||
title?: string;
|
||||
titleKey?: string;
|
||||
}
|
||||
|
||||
const updateMetaTitles = () => {
|
||||
router.getRoutes().forEach(route => {
|
||||
const meta = route.meta as AppRouteMeta;
|
||||
if (meta.titleKey) {
|
||||
meta.title = translate(meta.titleKey);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function updateDocumentTitle(route: RouteLocationNormalizedLoaded) {
|
||||
const meta = route.meta as AppRouteMeta;
|
||||
document.title = typeof route.meta.title === 'string'
|
||||
? `${translate(meta.titleKey || "")} | ${translate('studioYunSi')}`
|
||||
: '允斯调试器';
|
||||
}
|
||||
|
||||
// Watch for language changes to update the titles dynamically
|
||||
watch(() => languageState.currentLanguage, () => {
|
||||
// Recompute all route meta titles
|
||||
updateMetaTitles();
|
||||
updateDocumentTitle(router.currentRoute.value);
|
||||
}, {deep: true});
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
@ -14,41 +48,49 @@ const router = createRouter({
|
|||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
meta: {title: translate("page.home")},
|
||||
// component: Wifi
|
||||
redirect: () => '/wifi',
|
||||
meta: { titleKey: 'page.home' },
|
||||
redirect: () => '/uart',
|
||||
}, {
|
||||
path: '/home:ext(.*)',
|
||||
meta: {title: translate("page.home")},
|
||||
meta: { titleKey: 'page.home' },
|
||||
redirect: () => '/',
|
||||
}, {
|
||||
path: '/wifi:ext(.*)',
|
||||
meta: {title: translate('page.wifi')},
|
||||
meta: { titleKey: 'page.wifi' },
|
||||
component: Wifi,
|
||||
}, {
|
||||
path: '/about:ext(.*)',
|
||||
meta: {title: translate('page.about')},
|
||||
meta: { titleKey: 'page.about' },
|
||||
component: About,
|
||||
}, {
|
||||
path: '/uart:ext(.*)',
|
||||
meta: {title: translate('page.uart')},
|
||||
meta: { titleKey: 'page.uart' },
|
||||
component: Uart,
|
||||
}, {
|
||||
path: '/feedback:ext(.*)',
|
||||
meta: {title: translate('page.feedback')},
|
||||
meta: { titleKey: 'page.feedback' },
|
||||
name: 'feedback',
|
||||
component: Feedback,
|
||||
}, {
|
||||
path: '/:catchAll(.*)', // This will match all paths that aren't matched by above routes
|
||||
}, {
|
||||
path: '/update:ext(.*)',
|
||||
meta: { titleKey: 'page.update' },
|
||||
name: 'update',
|
||||
component: isOTAEnabled() ? Update : Page404,
|
||||
}, {
|
||||
path: '/:catchAll(.*)', // Catch-all route for 404
|
||||
name: 'NotFound',
|
||||
component: Page404,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
// Update document title dynamically
|
||||
router.beforeEach((to, from, next) => {
|
||||
document.title = typeof to.meta.title === 'string' ? to.meta.title + " | 允斯工作室" : '允斯调试器';
|
||||
updateDocumentTitle(to);
|
||||
next();
|
||||
});
|
||||
|
||||
export default router
|
||||
// Initialize titles on load
|
||||
updateMetaTitles();
|
||||
|
||||
export default router;
|
|
@ -12,6 +12,9 @@ const moduleMap = new Map<number, IModuleCallback>();
|
|||
|
||||
export function registerModule(moduleId: number, moduleCallback: IModuleCallback): boolean {
|
||||
if (moduleMap.has(moduleId)) {
|
||||
if (isDevMode()) {
|
||||
console.log("module ", moduleId, "already registered");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
import {defineStore} from "pinia";
|
||||
import {type Ref, ref} from "vue";
|
||||
import type {InstanceInfo} from "@/api/apiDataFlow";
|
||||
|
||||
export const useDataFlowStore = defineStore('data_flow', () => {
|
||||
const instanceList: Ref<InstanceInfo[]> = ref([]);
|
||||
|
||||
return {
|
||||
instanceList,
|
||||
}
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
import {defineStore} from "pinia";
|
||||
import {ref} from "vue";
|
||||
|
||||
export const useSystemStore = defineStore('system', () => {
|
||||
|
||||
const curFmInfo = ref({
|
||||
ver: "-",
|
||||
date: "-",
|
||||
});
|
||||
|
||||
const hwInfo = ref({
|
||||
ver: "-",
|
||||
date: "-",
|
||||
})
|
||||
|
||||
const sys_info = ref({
|
||||
sn: "-",
|
||||
});
|
||||
|
||||
const rebootInProgress = ref(false);
|
||||
|
||||
return {
|
||||
curFmInfo,
|
||||
hwInfo,
|
||||
sysInfo: sys_info,
|
||||
rebootInProgress,
|
||||
}
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useUartStore = defineStore('uart', () => {
|
||||
const uartNum = ref(1);
|
||||
|
||||
return { uartNum }
|
||||
})
|
|
@ -0,0 +1,45 @@
|
|||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import {wt_ota_get_progress} from "@/api/apiOTA";
|
||||
|
||||
export const useUpdateStore = defineStore('update', () => {
|
||||
const canUpdate = ref(false);
|
||||
const updateProgress = ref(0);
|
||||
const updateStatus = ref('');
|
||||
|
||||
const progressBarStatus = ref('');
|
||||
|
||||
let progressIntervalID = -1;
|
||||
|
||||
const newFmInfo = ref({
|
||||
fm_size: 0,
|
||||
fm_ver: "-",
|
||||
upd_date: "-",
|
||||
upd_note: "-",
|
||||
})
|
||||
|
||||
function setProgressInterval() {
|
||||
if (progressIntervalID < 0) {
|
||||
progressIntervalID = setInterval(() => {
|
||||
wt_ota_get_progress();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function clearProgressInterval() {
|
||||
if (progressIntervalID >= 0) {
|
||||
clearInterval(progressIntervalID);
|
||||
progressIntervalID = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
canUpdate,
|
||||
updateProgress,
|
||||
updateStatus,
|
||||
progressBarStatus,
|
||||
newFmInfo,
|
||||
setProgressInterval,
|
||||
clearProgressInterval,
|
||||
}
|
||||
})
|
|
@ -1,22 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import {useSystemStore} from "@/stores/useSystemStore";
|
||||
import {translate} from "@/locales";
|
||||
|
||||
const version = import.meta.env.VITE_APP_GIT_TAG || "v0.0.0";
|
||||
const compileTime = import.meta.env.VITE_APP_LAST_COMMIT || "1970-00-00";
|
||||
const sysStore = useSystemStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-layout">
|
||||
<el-divider></el-divider>
|
||||
<el-divider>关于</el-divider>
|
||||
<el-divider>{{ translate('page.about') }}</el-divider>
|
||||
<el-divider></el-divider>
|
||||
<el-collapse>
|
||||
<el-collapse-item title="关于网页版上位机">
|
||||
<el-collapse-item :title="translate('aboutWebHost')">
|
||||
<el-descriptions border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions-item label="版本">{{ version }}</el-descriptions-item>
|
||||
<el-descriptions-item label="发布时间">{{ compileTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="许可证">MIT</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('version')">{{ version }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('releaseTime')">{{ compileTime }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions title="鸣谢" border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions :title="translate('credit')" border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions-item label="vuejs"><a target="_blank" href="https://github.com/vuejs/vue/blob/main/LICENSE">MIT</a>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="typescript"><a
|
||||
|
@ -40,13 +43,15 @@ const compileTime = import.meta.env.VITE_APP_LAST_COMMIT || "1970-00-00";
|
|||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="关于下位机">
|
||||
<el-collapse-item :title="translate('aboutDebugger')">
|
||||
<el-descriptions border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions-item label="官网"><a target="_blank" href="https://yunsi.studio/wireless-proxy">允斯工作室</a></el-descriptions-item>
|
||||
<el-descriptions-item label="版本">-</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('officialWebsite')"><a target="_blank" href="https://yunsi.studio/wireless-debugger">https://yunsi.studio/wireless-debugger</a></el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('version')">{{ sysStore.curFmInfo.ver }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('releaseTime')">{{ sysStore.curFmInfo.date }}</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('serialNumber')">{{ sysStore.sysInfo.sn }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions title="鸣谢" border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions :title="translate('credit')" border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions-item label="windowsair"><a target="_blank" href="https://github.com/windowsair/wireless-esp8266-dap">wireless-esp8266-dap</a>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
@ -54,22 +59,22 @@ const compileTime = import.meta.env.VITE_APP_LAST_COMMIT || "1970-00-00";
|
|||
</el-collapse>
|
||||
|
||||
|
||||
<el-descriptions title="作者:空空(kerms)" border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions-item label="官网"><a target="_blank" href="https://yunsi.studio/">允斯工作室(https://yunsi.studio/)</a></el-descriptions-item>
|
||||
<el-descriptions :title="translate('author') + ' :空空(kerms)'" border :column="1" class="mt-5 description-style">
|
||||
<el-descriptions-item :label="translate('officialWebsite')"><a target="_blank" href="https://yunsi.studio/">https://yunsi.studio/</a></el-descriptions-item>
|
||||
<el-descriptions-item label="github"><a target="_blank" href="https://github.com/kerms">https://github.com/kerms</a>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">kerms@niazo.org</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('email')">kerms@niazo.org</el-descriptions-item>
|
||||
<el-descriptions-item label="BiliBili"><a target="_blank" href="https://space.bilibili.com/3461571571353885">3461571571353885</a>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="QQ群">642246000</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">欢迎大家来打扰啊~</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('TencentQQGroup')">642246000</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('note')">{{ translate('welcomeMessage') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="postcss">
|
||||
.description-style :deep(.el-descriptions__label) {
|
||||
@apply w-32
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
<div class="text-layout">
|
||||
<el-divider></el-divider>
|
||||
<el-divider>反馈</el-divider>
|
||||
<el-divider>{{ translate('page.feedback') }}</el-divider>
|
||||
<el-divider></el-divider>
|
||||
|
||||
<el-descriptions title="反馈/建议/需要新功能" border :column="1">
|
||||
<el-descriptions-item label="QQ群">642246000</el-descriptions-item>
|
||||
<el-descriptions-item label="作者邮箱">kerms@niazo.org</el-descriptions-item>
|
||||
<el-descriptions :title="translate('page.feedback') + '/' + translate('suggestion') + '/' + translate('feature')" border :column="1">
|
||||
<el-descriptions-item :label="translate('TencentQQGroup')">642246000</el-descriptions-item>
|
||||
<el-descriptions-item :label="translate('authorEmail')">kerms@niazo.org</el-descriptions-item>
|
||||
<!-- TODO: add discord + BiliBili / instagram ? -->
|
||||
</el-descriptions>
|
||||
|
||||
</div>
|
||||
|
@ -16,5 +17,5 @@
|
|||
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {translate} from "@/locales";
|
||||
</script>
|
||||
|
|
|
@ -1,9 +1,483 @@
|
|||
<template>
|
||||
<div class="button-m-0 messages-container flex flex-grow overflow-hidden" :class="{'flex-col': store.winLayoutMode ==='col'}">
|
||||
<div v-show="store.winLeft.show" ref="win1Ref" class="bg-gray-50 flex-shrink-0 overflow-auto"
|
||||
:class="{
|
||||
'max-w-60': store.winLayoutMode==='row', 'xl:max-w-80': store.winLayoutMode==='row',
|
||||
'min-w-60': store.winLayoutMode==='row', 'xl:min-w-80': store.winLayoutMode==='row'
|
||||
}"
|
||||
>
|
||||
<text-data-config></text-data-config>
|
||||
</div>
|
||||
|
||||
<div v-show="store.winLeft.show && (winDataView.show || store.winRight.show)" ref="firstWinResizeRef"></div>
|
||||
|
||||
<div v-show="winDataView.show" class="flex flex-col flex-grow overflow-hidden p-2">
|
||||
<textDataViewer></textDataViewer>
|
||||
</div>
|
||||
|
||||
<div v-show="winDataView.show && store.winRight.show" ref="thirdWinResizeRef"></div>
|
||||
|
||||
<div v-show="store.winRight.show" ref="win2Ref" :class="{
|
||||
'max-w-80': store.winLayoutMode==='row', 'xl:max-w-96': store.winLayoutMode==='row',
|
||||
'min-w-80': store.winLayoutMode==='row', 'xl:min-w-96': store.winLayoutMode==='row'
|
||||
}"
|
||||
class="bg-gray-50 flex flex-col flex-shrink-0 min-h-32 overflow-auto p-2">
|
||||
<TextDataMacro @winSizeRefresh="handleWinSizeRefresh"></TextDataMacro>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<teleport to="#page-spec-slot">
|
||||
<div>
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
:hide-after="0"
|
||||
transition="none"
|
||||
>
|
||||
<div class="button-m-0 flex flex-col space-y-2">
|
||||
<div class="custom-style flex justify-center">
|
||||
<el-segmented v-model="store.winLayoutMode" :options="layoutOptions" size="small"/>
|
||||
</div>
|
||||
<el-checkbox v-model="store.winAutoLayout" border size="small"
|
||||
:disabled="store.winLayoutMode==='col'">
|
||||
{{ $t('uart.responsive') }}
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="store.winLeft.show" border size="small" :disabled="store.winAutoLayout">
|
||||
{{ $t("uart.configPannel") }}
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="winDataView.show" border size="small" :disabled="store.winAutoLayout">
|
||||
{{ $t('uart.displayPannel') }}
|
||||
</el-checkbox>
|
||||
<el-checkbox v-model="store.winRight.show" border size="small" :disabled="store.winAutoLayout">
|
||||
{{ $t('uart.macroPannel') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
|
||||
<template #reference>
|
||||
<el-button class="min-h-full" type="primary" :size="layoutConf.isMedium ? 'small' : 'default'">
|
||||
{{ $t('uart.layout') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="mx-1"></div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, onUnmounted, reactive, type Ref, ref, type UnwrapRef, watch} from "vue";
|
||||
import {breakpointsTailwind, useBreakpoints} from '@vueuse/core'
|
||||
import {useDataViewerStore} from '@/stores/dataViewerStore';
|
||||
import * as api from '@/api';
|
||||
import {ControlEvent} from '@/api';
|
||||
import {
|
||||
type IUartMsgBaud,
|
||||
type IUartMsgConfig,
|
||||
type IUartMsgNum,
|
||||
uart_get_baud,
|
||||
uart_get_config,
|
||||
uart_get_default_num,
|
||||
WtUartCmd
|
||||
} from '@/api/apiUart';
|
||||
|
||||
/* TODO: use https://antoniandre.github.io/splitpanes/ */
|
||||
|
||||
|
||||
import {type ApiBinaryMsg} from '@/api/binDataDef';
|
||||
import * as df from '@/api/apiDataFlow';
|
||||
import textDataViewer from "@/views/text-data-viewer/textDataViewer.vue";
|
||||
import textDataConfig from "@/views/text-data-viewer/textDataConfig.vue"
|
||||
import {registerModule} from "@/router/msgRouter";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
import {useWsStore} from "@/stores/websocket";
|
||||
import {useUartStore} from "@/stores/useUartStore";
|
||||
import TextDataMacro from "@/views/text-data-viewer/textDataMacro.vue";
|
||||
import {translate} from "@/locales";
|
||||
|
||||
const store = useDataViewerStore()
|
||||
const wsStore = useWsStore()
|
||||
const uartStore = useUartStore()
|
||||
|
||||
const firstWinResizeRef = ref(document.body);
|
||||
const thirdWinResizeRef = ref(document.body);
|
||||
const win1Ref = ref(document.body);
|
||||
const win2Ref = ref(document.body);
|
||||
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||
|
||||
const layoutConf = reactive({
|
||||
isSmall: breakpoints.smaller("sm"),
|
||||
isMedium: breakpoints.smaller("lg"),
|
||||
});
|
||||
|
||||
const layoutOptions = computed(() => [{
|
||||
label: translate("uart.landscape"),
|
||||
value: 'row'
|
||||
}, {
|
||||
label: translate("uart.portrait"),
|
||||
value: 'col'
|
||||
}]);
|
||||
|
||||
interface WinProperty {
|
||||
show: boolean;
|
||||
width: string;
|
||||
height: string;
|
||||
borderSize: number;
|
||||
}
|
||||
|
||||
|
||||
const winDataView = reactive({
|
||||
show: true,
|
||||
})
|
||||
|
||||
const ctx = reactive({
|
||||
curResizeTarget: "none",
|
||||
curHeightOffset: 0,
|
||||
});
|
||||
|
||||
function updateCursor(i: HTMLElement) {
|
||||
if (store.winLayoutMode === 'row') {
|
||||
i.style.cursor = "col-resize";
|
||||
} else {
|
||||
i.style.cursor = "row-resize";
|
||||
}
|
||||
}
|
||||
|
||||
function updateWin(r: Ref<HTMLElement>, p: UnwrapRef<WinProperty>) {
|
||||
if (store.winLayoutMode === 'row') {
|
||||
r.value.style.minHeight = "";
|
||||
r.value.style.maxHeight = ""
|
||||
if (winDataView.show) {
|
||||
r.value.style.minWidth = p.width;
|
||||
r.value.style.maxWidth = p.width;
|
||||
}
|
||||
} else {
|
||||
r.value.style.minWidth = ""
|
||||
r.value.style.maxWidth = ""
|
||||
if (winDataView.show) {
|
||||
r.value.style.minHeight = p.height;
|
||||
r.value.style.maxHeight = p.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateCursors() {
|
||||
updateCursor(firstWinResizeRef.value);
|
||||
updateCursor(thirdWinResizeRef.value);
|
||||
}
|
||||
|
||||
function updateResizer() {
|
||||
updateCursors();
|
||||
updateWin(win1Ref, store.winLeft);
|
||||
updateWin(win2Ref, store.winRight);
|
||||
}
|
||||
|
||||
function mouseResize(e: MouseEvent) {
|
||||
const curTarget = e.target as HTMLElement
|
||||
|
||||
if (store.winLayoutMode === 'row') {
|
||||
let f = e.clientX;
|
||||
if (ctx.curResizeTarget === "first") {
|
||||
win1Ref.value.style.minWidth = f + "px";
|
||||
win1Ref.value.style.maxWidth = f + "px";
|
||||
} else {
|
||||
if (isDevMode()) {
|
||||
console.log("Row clientX", e.clientX, "clientY", e.clientY,
|
||||
"layerX", e.layerX, "layerY", e.layerY, "offsetX", e.offsetX, "offsetY", e.offsetY,
|
||||
"pageX", e.pageX, "pageY", e.pageY, win2Ref.value.clientHeight);
|
||||
}
|
||||
win2Ref.value.style.minWidth = document.body.scrollWidth - f - store.winRight.borderSize + "px";
|
||||
win2Ref.value.style.maxWidth = document.body.scrollWidth - f - store.winRight.borderSize + "px";
|
||||
}
|
||||
} else {
|
||||
/* col mode */
|
||||
let f = e.clientY;
|
||||
if (ctx.curResizeTarget === "first") {
|
||||
win1Ref.value.style.minHeight = f - ctx.curHeightOffset + "px";
|
||||
win1Ref.value.style.maxHeight = f - ctx.curHeightOffset + "px";
|
||||
} else {
|
||||
if (isDevMode()) {
|
||||
console.log("Col clientX", e.clientX, "clientY", e.clientY,
|
||||
"layerX", e.layerX, "layerY", e.layerY, "offsetX", e.offsetX, "offsetY", e.offsetY,
|
||||
"pageX", e.pageX, "pageY", e.pageY, curTarget.offsetWidth, ctx.curHeightOffset);
|
||||
}
|
||||
win2Ref.value.style.minHeight = ctx.curHeightOffset - f + "px";
|
||||
win2Ref.value.style.maxHeight = ctx.curHeightOffset - f + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function touchResize(e: TouchEvent) {
|
||||
let t = e.touches[0];
|
||||
let f: number;
|
||||
|
||||
if (store.winLayoutMode === 'row') {
|
||||
f = t.clientX;
|
||||
if (ctx.curResizeTarget === "first") {
|
||||
win1Ref.value.style.minWidth = f + "px";
|
||||
win1Ref.value.style.maxWidth = f + "px";
|
||||
} else {
|
||||
win2Ref.value.style.minWidth = document.body.scrollWidth - f - store.winRight.borderSize + "px";
|
||||
win2Ref.value.style.maxWidth = document.body.scrollWidth - f - store.winRight.borderSize + "px";
|
||||
}
|
||||
} else {
|
||||
/* column layout mode */
|
||||
f = t.clientY;
|
||||
if (ctx.curResizeTarget === "first") {
|
||||
/* setting window */
|
||||
win1Ref.value.style.minHeight = f - ctx.curHeightOffset + "px";
|
||||
win1Ref.value.style.maxHeight = f - ctx.curHeightOffset + "px";
|
||||
} else {
|
||||
/* quick access window */
|
||||
win2Ref.value.style.minHeight = document.body.scrollHeight - f - store.winRight.borderSize + "px";
|
||||
win2Ref.value.style.maxHeight = document.body.scrollHeight - f - store.winRight.borderSize + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startResize(event: Event) {
|
||||
// Normalize touch and mouse events
|
||||
if (event.type.includes('touch')) {
|
||||
ctx.curHeightOffset = (event as TouchEvent).touches[0].clientY;
|
||||
} else {
|
||||
ctx.curHeightOffset = (event as MouseEvent).clientY;
|
||||
}
|
||||
|
||||
const divRef = event.target;
|
||||
|
||||
if (divRef === firstWinResizeRef.value) {
|
||||
ctx.curResizeTarget = "first";
|
||||
ctx.curHeightOffset -= win1Ref.value.clientHeight;
|
||||
// ctx.curOffset = win1Ref.value.clientHeight;
|
||||
} else if (divRef === thirdWinResizeRef.value) {
|
||||
ctx.curResizeTarget = "third";
|
||||
ctx.curHeightOffset += win2Ref.value.clientHeight;
|
||||
}
|
||||
|
||||
win1Ref.value.style.transition = 'initial';
|
||||
win2Ref.value.style.transition = 'initial';
|
||||
document.addEventListener("mousemove", mouseResize, false);
|
||||
document.addEventListener("touchmove", touchResize, false);
|
||||
store.winAutoLayout = false;
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
if (win1Ref.value) {
|
||||
win1Ref.value.style.transition = '';
|
||||
if (store.winLayoutMode === "row") {
|
||||
store.winLeft.width = win1Ref.value.style.minWidth;
|
||||
} else {
|
||||
store.winLeft.height = win1Ref.value.style.minHeight;
|
||||
}
|
||||
}
|
||||
if (win2Ref.value) {
|
||||
win2Ref.value.style.transition = '';
|
||||
if (store.winLayoutMode === "row") {
|
||||
store.winRight.width = win2Ref.value.style.minWidth;
|
||||
} else {
|
||||
store.winRight.height = win2Ref.value.style.minHeight;
|
||||
}
|
||||
}
|
||||
document.body.style.cursor = '';
|
||||
document.removeEventListener("mousemove", mouseResize, false);
|
||||
document.removeEventListener("touchmove", touchResize, false);
|
||||
}
|
||||
|
||||
watch(() => store.winLayoutMode, (value) => {
|
||||
updateResizer();
|
||||
if (value === "col") {
|
||||
store.winAutoLayout = false;
|
||||
}
|
||||
});
|
||||
|
||||
watch([
|
||||
() => layoutConf.isSmall,
|
||||
() => store.winAutoLayout
|
||||
], (value) => {
|
||||
if (store.winAutoLayout) {
|
||||
store.winRight.show = !value[0];
|
||||
win1Ref.value.style.minWidth = "";
|
||||
win1Ref.value.style.maxWidth = "";
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
watch([
|
||||
() => layoutConf.isMedium,
|
||||
() => store.winAutoLayout
|
||||
], (value) => {
|
||||
if (store.winAutoLayout) {
|
||||
store.winLeft.show = !value[0];
|
||||
win1Ref.value.style.minWidth = "";
|
||||
win1Ref.value.style.maxWidth = "";
|
||||
win2Ref.value.style.minWidth = "";
|
||||
win2Ref.value.style.maxWidth = "";
|
||||
winDataView.show = true;
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
});
|
||||
|
||||
watch(() => winDataView.show, value => {
|
||||
if (!value) {
|
||||
win1Ref.value.style.minWidth = "";
|
||||
win1Ref.value.style.maxWidth = "";
|
||||
win1Ref.value.style.maxHeight = "";
|
||||
win1Ref.value.style.maxHeight = "";
|
||||
|
||||
win2Ref.value.style.minWidth = "";
|
||||
win2Ref.value.style.maxWidth = "";
|
||||
win2Ref.value.style.maxHeight = "";
|
||||
win2Ref.value.style.maxHeight = "";
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => store.winRight.show, value => {
|
||||
if (!value && !winDataView.show) {
|
||||
win1Ref.value.style.maxHeight = "";
|
||||
win1Ref.value.style.maxHeight = "";
|
||||
win1Ref.value.style.maxWidth = "";
|
||||
win1Ref.value.style.maxWidth = "";
|
||||
}
|
||||
});
|
||||
|
||||
const onUartJsonMsg = (msg: api.ApiJsonMsg) => {
|
||||
switch (msg.cmd as WtUartCmd) {
|
||||
case WtUartCmd.GET_BAUD:
|
||||
case WtUartCmd.SET_BAUD:{
|
||||
const uartMsg = msg as IUartMsgBaud;
|
||||
if (uartMsg.baud) {
|
||||
store.setUartBaud(uartMsg.baud)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WtUartCmd.GET_CONFIG:
|
||||
case WtUartCmd.SET_CONFIG:{
|
||||
const uartMsg = msg as IUartMsgConfig;
|
||||
store.uartConfig.data_bits = uartMsg.data_bits;
|
||||
store.uartConfig.stop_bits = uartMsg.stop_bits;
|
||||
store.uartConfig.parity = uartMsg.parity;
|
||||
break;
|
||||
}
|
||||
case WtUartCmd.GET_DEFAULT_NUM:
|
||||
uartStore.uartNum = (msg as IUartMsgNum).num;
|
||||
uart_get_baud(uartStore.uartNum);
|
||||
uart_get_config(uartStore.uartNum);
|
||||
break;
|
||||
default:
|
||||
if (isDevMode()) {
|
||||
console.log("uart not treated", msg);
|
||||
}
|
||||
break
|
||||
}
|
||||
};
|
||||
|
||||
const onUartBinaryMsg = (msg: ApiBinaryMsg) => {
|
||||
if (isDevMode()) {
|
||||
console.log("uart", msg);
|
||||
}
|
||||
|
||||
store.addSegment(new Uint8Array(msg.payload), true);
|
||||
};
|
||||
|
||||
const onClientCtrl = (msg: api.ControlMsg) => {
|
||||
if (msg.type !== api.ControlMsgType.WS_EVENT) {
|
||||
return
|
||||
}
|
||||
|
||||
if (msg.data === ControlEvent.DISCONNECTED) {
|
||||
store.acceptIncomingData = false;
|
||||
} else if (msg.data === ControlEvent.CONNECTED) {
|
||||
updateUartData();
|
||||
store.acceptIncomingData = true;
|
||||
}
|
||||
};
|
||||
|
||||
function updateUartData() {
|
||||
/* TODO: hard code for the moment, 0 is UART instance id (can be changed in the future) */
|
||||
uart_get_default_num();
|
||||
df.wt_data_flow_attach_cur_to_sender(0);
|
||||
}
|
||||
|
||||
function handleWinSizeRefresh() {
|
||||
if (!store.winAutoLayout) {
|
||||
if (win1Ref.value) {
|
||||
if (store.winLayoutMode === "row") {
|
||||
win1Ref.value.style.minWidth = store.winLeft.width;
|
||||
} else {
|
||||
win1Ref.value.style.minHeight = store.winLeft.height;
|
||||
win1Ref.value.style.maxHeight = store.winRight.height;
|
||||
}
|
||||
}
|
||||
if (win2Ref.value) {
|
||||
if (store.winLayoutMode === "row") {
|
||||
win2Ref.value.style.minWidth = store.winRight.width;
|
||||
} else {
|
||||
win2Ref.value.style.minHeight = store.winRight.height;
|
||||
win2Ref.value.style.maxHeight = store.winRight.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
registerModule(api.WtModuleID.UART, {
|
||||
ctrlCallback: onClientCtrl,
|
||||
serverJsonMsgCallback: onUartJsonMsg,
|
||||
serverBinMsgCallback: onUartBinaryMsg,
|
||||
});
|
||||
|
||||
firstWinResizeRef.value.style.borderWidth = store.winLeft.borderSize + "px";
|
||||
thirdWinResizeRef.value.style.borderWidth = store.winRight.borderSize + "px";
|
||||
updateCursors()
|
||||
|
||||
if (firstWinResizeRef.value) {
|
||||
firstWinResizeRef.value.addEventListener("mousedown", startResize, false);
|
||||
firstWinResizeRef.value.addEventListener("touchstart", startResize, false);
|
||||
}
|
||||
if (thirdWinResizeRef.value) {
|
||||
thirdWinResizeRef.value.addEventListener("mousedown", startResize, false);
|
||||
thirdWinResizeRef.value.addEventListener("touchstart", startResize, false);
|
||||
}
|
||||
|
||||
document.addEventListener("mouseup", stopResize, false);
|
||||
document.addEventListener("touchend", stopResize, false);
|
||||
updateUartData();
|
||||
store.acceptIncomingData = wsStore.state === ControlEvent.CONNECTED;
|
||||
handleWinSizeRefresh()
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (firstWinResizeRef.value) {
|
||||
firstWinResizeRef.value.removeEventListener("mousedown", startResize, false);
|
||||
firstWinResizeRef.value.removeEventListener("touchstart", startResize, false);
|
||||
}
|
||||
|
||||
if (thirdWinResizeRef.value) {
|
||||
thirdWinResizeRef.value.removeEventListener("mousedown", startResize, false);
|
||||
thirdWinResizeRef.value.removeEventListener("touchstart", startResize, false);
|
||||
}
|
||||
|
||||
document.removeEventListener("mouseup", stopResize, false);
|
||||
document.removeEventListener("touchend", stopResize, false);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="text-layout">
|
||||
<h2 class="page-title opacity-10">尽请期待</h2>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.button-m-0 :deep(.el-button + .el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.custom-style .el-segmented {
|
||||
--el-segmented-item-selected-color: var(--el-text-color-primary);
|
||||
--el-segmented-item-selected-bg-color: var(--el-color-primary);
|
||||
--el-border-radius-base: 16px;
|
||||
}
|
||||
|
||||
.button-m-0 :deep(.el-checkbox) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div class="text-layout description-style">
|
||||
<h1 class="page-title">
|
||||
固件更新
|
||||
</h1>
|
||||
<p class="text-center">(需联网)</p>
|
||||
<el-divider></el-divider>
|
||||
|
||||
<el-descriptions title="当前版本" border :column="1">
|
||||
<el-descriptions-item label="硬件版本">{{ sysStore.hwInfo.ver }}</el-descriptions-item>
|
||||
<el-descriptions-item label="固件版本">{{ sysStore.curFmInfo.ver }}</el-descriptions-item>
|
||||
<el-descriptions-item label="固件日期">{{ sysStore.curFmInfo.date }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<el-descriptions title="最新版本" border :column="1">
|
||||
<template #extra>
|
||||
<div class="flex">
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>
|
||||
<p>2秒延迟后重启</p>
|
||||
</template>
|
||||
<el-button @click="doReboot" type="warning" :disabled="sysStore.rebootInProgress">
|
||||
重启{{ sysStore.rebootInProgress ? '中' : ''}}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-button @click="doUpdate" type="primary" :disabled="!updateStore.canUpdate">
|
||||
更新
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<el-descriptions-item label="固件版本">{{ updateStore.newFmInfo.fm_ver }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新日期">{{ updateStore.newFmInfo.upd_date }}</el-descriptions-item>
|
||||
<el-descriptions-item label="固件大小">{{ updateStore.newFmInfo.fm_size }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新进度">
|
||||
<el-alert v-if="updateStore.updateStatus === 'OK'" title="更新已完成,重启后,刷新网页生效" type="success" show-icon :closable="false" />
|
||||
<el-progress v-else :percentage="updateStore.updateProgress" :format="format" :status="updateStore.progressBarStatus"/>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新内容">
|
||||
<pre>{{ updateStore.newFmInfo.upd_note }}</pre>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider @click="showHidden = !showHidden">底部</el-divider>
|
||||
<div v-if="showHidden">
|
||||
<p>直链更新(仅用于测试,请勿使用)</p>
|
||||
<el-input placeholder="https://..." v-model="directLinkUpdate"></el-input>
|
||||
<el-button type="primary" @click="doDirectLinkUpdate">更新</el-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {onMounted, onUnmounted, ref} from "vue";
|
||||
import {
|
||||
wt_ota_do_update, wt_ota_do_url_update,
|
||||
wt_ota_get_progress, wt_ota_get_update_info,
|
||||
} from "@/api/apiOTA";
|
||||
import {useSystemStore} from "@/stores/useSystemStore";
|
||||
import {wt_sys_reboot} from "@/api/apiSystem";
|
||||
import {useUpdateStore} from "@/stores/useUpdateStore";
|
||||
|
||||
const sysStore = useSystemStore();
|
||||
const updateStore = useUpdateStore();
|
||||
const showHidden = ref(false)
|
||||
|
||||
const directLinkUpdate = ref("");
|
||||
|
||||
const format = (percentage: number) => (percentage.toFixed(2) + '%')
|
||||
|
||||
function doUpdate() {
|
||||
wt_ota_do_update();
|
||||
updateStore.setProgressInterval();
|
||||
}
|
||||
|
||||
function doReboot() {
|
||||
wt_sys_reboot();
|
||||
}
|
||||
|
||||
function doDirectLinkUpdate() {
|
||||
if (directLinkUpdate.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
updateStore.setProgressInterval();
|
||||
wt_ota_do_url_update(directLinkUpdate.value);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
wt_ota_get_update_info();
|
||||
wt_ota_get_progress();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
updateStore.clearProgressInterval();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.description-style :deep(.el-descriptions__label) {
|
||||
@apply w-32
|
||||
}
|
||||
</style>
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<div class="text-layout">
|
||||
<h1 class="page-title">
|
||||
Wi-Fi 配置
|
||||
Wi-Fi {{ translate('wifi.settings') }}
|
||||
</h1>
|
||||
<el-divider></el-divider>
|
||||
|
||||
<h2 class="mb-4 text-xl font-bold tracking-tight md:text-2xl lg:text-3xl">连接Wi-Fi</h2>
|
||||
<h2 class="mb-4 text-xl font-bold tracking-tight md:text-2xl lg:text-3xl">{{ translate('wifi.connection') }} Wi-Fi</h2>
|
||||
<el-form label-width="auto" ref="formRef" :model="ssidValidateForm" class="m-auto">
|
||||
<el-form-item
|
||||
label="Wi-Fi名"
|
||||
label="Wi-Fi"
|
||||
prop="wifiSsid"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入WIFI名'},
|
||||
{ required: true, message: translate('wifi.warnWifiName')},
|
||||
]"
|
||||
>
|
||||
<div class="flex w-full">
|
||||
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-form-item :label="translate('wifi.password')">
|
||||
<el-input
|
||||
v-model="ssidValidateForm.password"
|
||||
show-password
|
||||
|
@ -46,33 +46,21 @@
|
|||
</el-form-item>
|
||||
<div class="mb-2">
|
||||
<el-alert type="info" show-icon>
|
||||
如果不是通过透传器的热点连接,更换Wi-Fi将导致此界面与透传器断开连接。
|
||||
{{ translate("wifi.connectInfoHTML")}}
|
||||
</el-alert>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<el-button @click="onConnectClick" type="primary">连接</el-button>
|
||||
<el-button @click="onConnectClick" type="primary">{{ translate('wifi.connect') }}</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-divider></el-divider>
|
||||
<div class="flex items-center">
|
||||
<h5 class="text-md font-bold text-gray-800 w-32">Wi-Fi模式</h5>
|
||||
<h5 class="text-md font-bold text-gray-800 w-32">Wi-Fi {{ translate('wifi.mode') }}</h5>
|
||||
<div class="flex shrink-0">
|
||||
<el-tooltip effect="light">
|
||||
<template #content>
|
||||
<p>热点+终端模式并存会影响稳定性。且保持热点开启会增加功耗。</p>
|
||||
<p>
|
||||
<el-text size="small">智能模式:</el-text>
|
||||
成功连接Wi-Fi,30秒后自动关闭热点;断开连接,5秒后自动打开热点
|
||||
</p>
|
||||
<p>
|
||||
<el-text size="small">热点+终端共存模式:</el-text>
|
||||
方便使用,但是影响稳定性
|
||||
</p>
|
||||
<p>
|
||||
<el-text size="small">单热点模式缺点:</el-text>
|
||||
无网络
|
||||
</p>
|
||||
<div v-html="translate('wifi.modeTipsHtml')"></div>
|
||||
</template>
|
||||
<InlineSvg name="help" class="w-3.5 h-3.5 text-gray-500 cursor-help"></InlineSvg>
|
||||
</el-tooltip>
|
||||
|
@ -85,38 +73,41 @@
|
|||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="wifiChangeMode" :loading="wifiMode_loading">保存</el-button>
|
||||
<el-button type="primary" @click="wifiChangeMode" :loading="wifiMode_loading">{{ translate('wifi.save') }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
|
||||
<el-descriptions
|
||||
title="Wi-Fi终端(STA)信息"
|
||||
:column="1"
|
||||
border
|
||||
class="description-style"
|
||||
>
|
||||
<template #title>
|
||||
Wi-Fi {{ translate('wifi.stationInfo') }}
|
||||
<el-tag v-if="!isConnected" type="danger">{{ translate('wifi.disconnected') }}</el-tag>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-switch v-model="wifiSta_On" :disabled="wsStore.state != ControlEvent.CONNECTED || !wifiAp_On"
|
||||
active-text="已开启" inactive-text="未开启" :loading="wifiMode_loading"
|
||||
<el-switch v-model="wifiSta_On" :disabled="!isConnected || !wifiAp_On"
|
||||
:active-text="translate('wifi.enabled')" :inactive-text="translate('wifi.disabled')" :loading="wifiMode_loading"
|
||||
:before-change="()=>beforeWifiModeChange('STA')"
|
||||
/>
|
||||
</template>
|
||||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
信号强度
|
||||
{{ translate('wifi.signalStrength') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<p>{{ wifiStaApInfo.rssi }}</p>
|
||||
<p> {{ wifi_rssi_to_percent(wifiStaApInfo.rssi) }} % ({{ wifiStaApInfo.rssi }} dBm)</p>
|
||||
</template>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
Wi-Fi名(SSID)
|
||||
Wi-Fi(SSID)
|
||||
</div>
|
||||
</template>
|
||||
<p>{{ wifiStaApInfo.ssid }}</p>
|
||||
|
@ -139,14 +130,14 @@
|
|||
</el-descriptions-item>
|
||||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>IP(内网地址)</div>
|
||||
<div>IP({{ translate('wifi.internalAddress') }})</div>
|
||||
</template>
|
||||
<p>{{ wifiStaApInfo.ip }}</p>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
网关
|
||||
{{ translate('wifi.gateway') }}
|
||||
</div>
|
||||
</template>
|
||||
<p>{{ wifiStaApInfo.gateway }}</p>
|
||||
|
@ -154,7 +145,7 @@
|
|||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
掩码
|
||||
{{ translate('wifi.netmask') }}
|
||||
</div>
|
||||
</template>
|
||||
<p>{{ wifiStaApInfo.netmask }}</p>
|
||||
|
@ -162,7 +153,7 @@
|
|||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
首选DNS
|
||||
{{ translate('wifi.primaryDNS') }}
|
||||
</div>
|
||||
</template>
|
||||
<p>{{ wifiStaApInfo.dns_main }}</p>
|
||||
|
@ -170,7 +161,7 @@
|
|||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
备用DNS
|
||||
{{ translate('wifi.backupDNS') }}
|
||||
</div>
|
||||
</template>
|
||||
<p>{{ wifiStaApInfo.dns_backup }}</p>
|
||||
|
@ -179,10 +170,10 @@
|
|||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
IP分配模式
|
||||
{{ translate('wifi.IPmode') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-select v-model="wifiStaticInfo.static_ip_en" :disabled="wsStore.state != ControlEvent.CONNECTED">
|
||||
<el-select v-model="wifiStaticInfo.static_ip_en" :disabled="!isConnected">
|
||||
<el-option
|
||||
v-for="item in staIPModeOptions"
|
||||
:key="item.key"
|
||||
|
@ -193,14 +184,14 @@
|
|||
</el-descriptions-item>
|
||||
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_ip_en">
|
||||
<template #label>
|
||||
<div>IP(内网地址)</div>
|
||||
<div>IP({{ translate('wifi.internalAddress') }})</div>
|
||||
</template>
|
||||
<el-input v-model="wifiStaticInfo.ip"></el-input>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_ip_en">
|
||||
<template #label>
|
||||
<div>
|
||||
网关
|
||||
{{ translate('wifi.gateway') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="wifiStaticInfo.gateway"></el-input>
|
||||
|
@ -208,7 +199,7 @@
|
|||
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_ip_en">
|
||||
<template #label>
|
||||
<div>
|
||||
掩码
|
||||
{{ translate('wifi.netmask') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="wifiStaticInfo.netmask"></el-input>
|
||||
|
@ -217,10 +208,10 @@
|
|||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
DNS模式
|
||||
{{ translate('wifi.DNSmode') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-select v-model="wifiStaticInfo.static_dns_en" :disabled="wsStore.state != ControlEvent.CONNECTED">
|
||||
<el-select v-model="wifiStaticInfo.static_dns_en" :disabled="!isConnected">
|
||||
<el-option
|
||||
v-for="item in staDNSModeOptions"
|
||||
:key="item.key"
|
||||
|
@ -232,7 +223,7 @@
|
|||
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_dns_en">
|
||||
<template #label>
|
||||
<div>
|
||||
首选DNS
|
||||
{{ translate('wifi.primaryDNS') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="wifiStaticInfo.dns_main"></el-input>
|
||||
|
@ -240,34 +231,37 @@
|
|||
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_dns_en">
|
||||
<template #label>
|
||||
<div>
|
||||
备用DNS
|
||||
{{ translate('wifi.backupDNS') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="wifiStaticInfo.dns_backup"></el-input>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="flex justify-center mt-4">
|
||||
<el-button type="primary" :loading="wifiMode_loading" @click="wifiStaSetStaticInfo">保存</el-button>
|
||||
<el-button type="primary" :loading="wifiMode_loading" @click="wifiStaSetStaticInfo">{{ translate('wifi.save') }}</el-button>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<el-descriptions
|
||||
title="Wi-Fi自发热点(AP)信息"
|
||||
:column="1"
|
||||
border
|
||||
class="description-style"
|
||||
>
|
||||
<template #title>
|
||||
Wi-Fi {{ translate('wifi.hotspotInfo') }}
|
||||
<el-tag v-if="!isConnected" type="danger">{{ translate('wifi.disconnected') }}</el-tag>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-switch v-model="wifiAp_On" :disabled="wsStore.state != ControlEvent.CONNECTED || !wifiSta_On"
|
||||
:loading="wifiMode_loading" active-text="已开启" inactive-text="未开启"
|
||||
<el-switch v-model="wifiAp_On" :disabled="!isConnected || !wifiSta_On"
|
||||
:loading="wifiMode_loading" :active-text="translate('wifi.enabled')" :inactive-text="translate('wifi.disabled')"
|
||||
:before-change="()=>beforeWifiModeChange('AP')"
|
||||
/>
|
||||
</template>
|
||||
<el-descriptions-item span="6">
|
||||
<template #label>
|
||||
<div>
|
||||
Wi-Fi名(SSID)
|
||||
Wi-Fi(SSID)
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex">
|
||||
|
@ -277,7 +271,7 @@
|
|||
<el-descriptions-item span="6">
|
||||
<template #label>
|
||||
<div>
|
||||
密码
|
||||
{{ translate('wifi.password') }}
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="wifiApInfo.password"></el-input>
|
||||
|
@ -303,7 +297,7 @@
|
|||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
网关
|
||||
{{ translate('wifi.gateway') }}
|
||||
</div>
|
||||
</template>
|
||||
{{ wifiApInfo.gateway }}
|
||||
|
@ -312,14 +306,14 @@
|
|||
<el-descriptions-item span="4">
|
||||
<template #label>
|
||||
<div>
|
||||
掩码
|
||||
{{ translate('wifi.netmask') }}
|
||||
</div>
|
||||
</template>
|
||||
{{ wifiApInfo.netmask }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="flex justify-center mt-4">
|
||||
<el-button type="primary" :loading="wifiMode_loading" @click="wifiApChangeCredential">保存</el-button>
|
||||
<el-button type="primary" :loading="wifiMode_loading" @click="wifiApChangeCredential">{{ translate('wifi.save') }}</el-button>
|
||||
</div>
|
||||
<el-divider></el-divider>
|
||||
</div>
|
||||
|
@ -327,7 +321,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, onUnmounted, reactive, ref} from "vue";
|
||||
import {computed, type ComputedRef, onMounted, onUnmounted, reactive, ref} from "vue";
|
||||
import {
|
||||
type IWifiMode,
|
||||
wifi_ap_get_info,
|
||||
|
@ -355,9 +349,10 @@ import {registerModule, unregisterModule} from "@/router/msgRouter";
|
|||
import {useWsStore} from "@/stores/websocket";
|
||||
import {globalNotify, globalNotifyRightSide} from "@/composables/notification";
|
||||
import {isDevMode} from "@/composables/buildMode";
|
||||
import {translate} from "@/locales";
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
let wifiListPlaceholder = ref("我的WIFI")
|
||||
let wifiListPlaceholder = ref("MY-WIFI")
|
||||
let ssidValidateForm = reactive({
|
||||
wifiSsid: "",
|
||||
password: "",
|
||||
|
@ -370,58 +365,60 @@ let wifiAp_On = ref(false);
|
|||
|
||||
let wifiMode = ref(-1);
|
||||
|
||||
let wifiModeOptions = [
|
||||
let wifiModeOptions = computed( () => [
|
||||
{
|
||||
label: "智能热点+常开终端 (AP+STA)",
|
||||
label: translate('wifi.APauto_STA'),
|
||||
key: WifiMode.WIFI_AP_AUTO_STA_ON,
|
||||
}, {
|
||||
label: "仅开启热点 (AP)",
|
||||
label: translate('wifi.APonly'),
|
||||
key: WifiMode.WIFI_AP_ON_STA_OFF,
|
||||
}, {
|
||||
label: "[不推荐] 常开热点+常开终端 (AP+STA)",
|
||||
label: translate('wifi.AP_STA'),
|
||||
key: WifiMode.WIFI_AP_STA_ON,
|
||||
}, /* {
|
||||
}, /*
|
||||
{
|
||||
value: "仅开启终端(STA)",
|
||||
key: 2,
|
||||
},*/
|
||||
|
||||
]
|
||||
])
|
||||
|
||||
let wsStore = useWsStore();
|
||||
|
||||
const defWifiInfo: WifiInfo = {
|
||||
cmd: 1,
|
||||
module: 1,
|
||||
gateway: "未连接",
|
||||
ip: "未连接",
|
||||
mac: "未连接",
|
||||
dns_main: "未连接",
|
||||
dns_backup: "未连接",
|
||||
gateway: "-",
|
||||
ip: "-",
|
||||
mac: "-",
|
||||
dns_main: "-",
|
||||
dns_backup: "-",
|
||||
rssi: 0,
|
||||
netmask: "未连接",
|
||||
ssid: "未连接",
|
||||
netmask: "-",
|
||||
ssid: "-",
|
||||
password: "",
|
||||
}
|
||||
|
||||
const staIPModeOptions = [
|
||||
{
|
||||
label: "自动 (DHCP)",
|
||||
label: translate('wifi.autoIP'),
|
||||
key: 0,
|
||||
}, {
|
||||
label: "静态IP",
|
||||
label: translate('wifi.staticIP'),
|
||||
key: 1,
|
||||
},
|
||||
]
|
||||
|
||||
const staDNSModeOptions = [
|
||||
{
|
||||
label: "自动 (使用网关)",
|
||||
label: translate('wifi.autoDNS'),
|
||||
key: 0,
|
||||
}, {
|
||||
label: "静态DNS",
|
||||
label: translate('wifi.staticDNS'),
|
||||
key: 1,
|
||||
},
|
||||
]
|
||||
|
||||
const isConnected = computed(() => wsStore.state === ControlEvent.CONNECTED)
|
||||
let wifiStaApInfo = reactive<WifiInfo>({...defWifiInfo});
|
||||
let wifiApInfo = reactive<WifiInfo>({...defWifiInfo});
|
||||
let wifiStaticInfo = reactive<IWifiStaStaticInfo>({
|
||||
|
@ -439,7 +436,7 @@ let scan_cb: any;
|
|||
let connectBtnClicked = 0;
|
||||
let options: Array<WifiScanInfo> = [];
|
||||
const scanText = computed(() => {
|
||||
return scanning.value ? "扫描中" : "扫描";
|
||||
return scanning.value ? translate("wifi.scanning") : translate("wifi.scan");
|
||||
});
|
||||
|
||||
const querySearch = (queryString: string, cb: any) => {
|
||||
|
@ -463,7 +460,7 @@ const onClientMsg = (msg: ApiJsonMsg) => {
|
|||
}
|
||||
if (connectBtnClicked) {
|
||||
connectBtnClicked = 0;
|
||||
globalNotifyRightSide(wifiStaApInfo.ssid + " 连接成功", "success");
|
||||
globalNotifyRightSide(wifiStaApInfo.ssid + " " + translate('wifi.connectionSuccess'), "success");
|
||||
wifi_sta_get_static_info();
|
||||
}
|
||||
break;
|
||||
|
@ -487,7 +484,7 @@ const onClientMsg = (msg: ApiJsonMsg) => {
|
|||
scan_cb(options);
|
||||
scan_cb = null;
|
||||
}
|
||||
globalNotifyRightSide("扫描完成", "success");
|
||||
globalNotifyRightSide(translate('wifi.scanDone'), "success");
|
||||
break;
|
||||
}
|
||||
case WifiCmd.WIFI_API_JSON_DISCONNECT:
|
||||
|
@ -504,7 +501,7 @@ const onClientMsg = (msg: ApiJsonMsg) => {
|
|||
const modeInfo = msg as IWifiMode;
|
||||
wifiMode_loading.value = false;
|
||||
if (modeInfo.err !== undefined) {
|
||||
globalNotifyRightSide("设置失败", "error");
|
||||
globalNotifyRightSide(translate('wifi.setFailed'), "error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -532,7 +529,7 @@ const onClientMsg = (msg: ApiJsonMsg) => {
|
|||
if (wifiCred.err !== undefined) {
|
||||
globalNotifyRightSide(wifiCred.err, "error");
|
||||
} else {
|
||||
globalNotifyRightSide("已保存配置", "success");
|
||||
globalNotifyRightSide(translate('wifi.setSuccess'), "success");
|
||||
}
|
||||
wifiMode_loading.value = false;
|
||||
|
||||
|
@ -540,7 +537,6 @@ const onClientMsg = (msg: ApiJsonMsg) => {
|
|||
}
|
||||
case WifiCmd.WIFI_API_JSON_STA_GET_STATIC_INFO: {
|
||||
const staticInfo = msg as IWifiStaStaticInfo & ApiJsonMsg;
|
||||
console.log("@@@", staticInfo);
|
||||
Object.assign(wifiStaticInfo, staticInfo);
|
||||
break;
|
||||
}
|
||||
|
@ -574,8 +570,8 @@ const onClientCtrl = (msg: ControlMsg) => {
|
|||
};
|
||||
|
||||
function onScanClick() {
|
||||
if (wsStore.state !== ControlEvent.CONNECTED) {
|
||||
globalNotify("调试器未连接", 'error');
|
||||
if (!isConnected.value) {
|
||||
globalNotify(translate('wifi.debuggerNotConnected'), 'error');
|
||||
return;
|
||||
}
|
||||
scanning.value = true;
|
||||
|
@ -583,8 +579,8 @@ function onScanClick() {
|
|||
}
|
||||
|
||||
function onConnectClick() {
|
||||
if (wsStore.state !== ControlEvent.CONNECTED) {
|
||||
globalNotify("调试器未连接", 'error');
|
||||
if (!isConnected.value) {
|
||||
globalNotify(translate('wifi.debuggerNotConnected'), 'error');
|
||||
return;
|
||||
}
|
||||
if (ssidValidateForm.wifiSsid !== "") {
|
||||
|
@ -610,9 +606,20 @@ function wifiChangeMode() {
|
|||
wifi_set_mode(wifiMode.value);
|
||||
}
|
||||
|
||||
function wifi_rssi_to_percent(rssi: number)
|
||||
{
|
||||
if (rssi <= -100) {
|
||||
return 0;
|
||||
} else if (rssi >= -50) {
|
||||
return 100;
|
||||
} else {
|
||||
return 2 * (rssi + 100);
|
||||
}
|
||||
}
|
||||
|
||||
function wifiApChangeCredential() {
|
||||
if (wifiApInfo.ssid === "") {
|
||||
globalNotifyRightSide("请输入AP名称", "error");
|
||||
globalNotifyRightSide(translate('wifi.enterAPName'), "error");
|
||||
return;
|
||||
}
|
||||
wifiMode_loading.value = true;
|
||||
|
@ -644,7 +651,7 @@ onUnmounted(() => {
|
|||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="postcss">
|
||||
.description-style :deep(.el-descriptions__label) {
|
||||
@apply w-32
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<template>
|
||||
<nav class="relative px-2 py-0.5 sm:py-1 flex justify-between items-center border-b h-full">
|
||||
<div class="flex">
|
||||
<button @click.prevent="sideMenuOpen=true" class="flex items-center hover:text-blue-600 pl-1 mx-4">
|
||||
<button @click.prevent="sideMenuOpen=true" class="flex items-center hover:text-blue-600 pl-1 mx-2 sm:mx-4">
|
||||
<svg class="block h-3 lg:h-4 lg:w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>导航侧栏</title>
|
||||
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
|
||||
</svg>
|
||||
<el-badge v-if="updateStore.canUpdate" is-dot></el-badge>
|
||||
</button>
|
||||
|
||||
<router-link to="/" class="text-3xl px-4 font-bold leading-none hidden items-center sm:flex" title="走,去码头整点薯条">
|
||||
|
@ -19,8 +20,8 @@
|
|||
<!-- <router-link to="/" class="flex items-center text-sm text-blue-600 font-bold">主页</router-link>-->
|
||||
<!-- <a class="flex items-center text-sm text-blue-600 font-bold" href="/">主页6</a>-->
|
||||
|
||||
<div class="flex pt-0.5 sm:pt-1 ml-4 text-sm items-center sm:hidden">
|
||||
<router-link :to="route.fullPath">{{ route.meta.title }}</router-link>
|
||||
<div class="flex pt-0.5 sm:pt-1 ml-4 text-xs items-center sm:hidden">
|
||||
<router-link :to="route.fullPath">{{ $route.meta.title }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -35,6 +36,20 @@
|
|||
<!-- <a class="md:ml-auto md:mr-3"></a>-->
|
||||
<div class="flex h-full">
|
||||
<div id="page-spec-slot" class="content-center h-full flex flex-row"></div>
|
||||
<div class="mr-2">
|
||||
<el-select v-model="language" class="min-w-20 h-full" @change="handleLanguageChange">
|
||||
<el-option value="en">🇺🇸 English</el-option>
|
||||
<el-option value="zh">🇨🇳 简体中文</el-option>
|
||||
<el-option value="fr">🇫🇷 Français</el-option>
|
||||
<template #label>
|
||||
<div class="flex">
|
||||
<InlineSvg name="translate" class="w-4 mr-1"></InlineSvg>
|
||||
{{ languageFlag }}
|
||||
</div>
|
||||
</template>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="lg:hidden">
|
||||
<el-button :type="wsColor" size="small" class="transition duration-1000 min-h-full">
|
||||
<InlineSvg v-show="wsColor!=='success'" name="link-off" class="mr-2" width="20"></InlineSvg>
|
||||
|
@ -67,8 +82,11 @@
|
|||
|
||||
<div class="flex flex-col justify-between m-4 mt-0">
|
||||
<ul>
|
||||
<li v-for="(item, index) in menuItems" class="mb-1" :key="index">
|
||||
<router-link @click="sideMenuOpen=false" :title="item.name" :to="item.href" :class="[sideMenuItemClass, item?.class]">{{ item.name }}</router-link>
|
||||
<li v-for="(item, index) in sideBarItems" class="mb-1" :key="index">
|
||||
<router-link @click="sideMenuOpen=false" :title="item.name" :to="item.href" :class="[sideMenuItemClass, item?.class]">
|
||||
{{ item.name }}
|
||||
<el-badge v-if="item?.badge?.value" is-dot></el-badge>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -77,9 +95,9 @@
|
|||
<div>
|
||||
<el-button @click="toggle">
|
||||
<InlineSvg v-if="!isFullscreen" name="open-in-full" width="16px" fill="#000000"></InlineSvg>
|
||||
<p v-if="!isFullscreen">全屏</p>
|
||||
<p v-if="!isFullscreen">{{ translate('page.fullscreen') }}</p>
|
||||
<InlineSvg v-if="isFullscreen" name="close-fullscreen" width="16px" fill="#000000"></InlineSvg>
|
||||
<p v-if="isFullscreen">缩小</p>
|
||||
<p v-if="isFullscreen">{{ translate('page.windowed') }}</p>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -105,18 +123,31 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import InlineSvg from "@/components/InlineSvg.vue";
|
||||
import {computed, ref} from "vue";
|
||||
import {computed, type ComputedRef, type Ref, ref} from "vue";
|
||||
import {useWsStore} from "@/stores/websocket";
|
||||
import {translate} from "@/locales";
|
||||
import {ControlEvent} from "@/api";
|
||||
import {useRoute} from "vue-router";
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import {useUpdateStore} from "@/stores/useUpdateStore";
|
||||
import {isOTAEnabled} from "@/composables/buildMode";
|
||||
import {getFlagFromLang, locale, setLang} from "@/i18n"
|
||||
|
||||
const wsStore = useWsStore();
|
||||
const updateStore = useUpdateStore();
|
||||
const {isFullscreen, toggle} = useFullscreen();
|
||||
const route = useRoute();
|
||||
const language = ref(locale);
|
||||
|
||||
const sideMenuItemClass = "block p-4 text-sm font-semibold hover:bg-blue-50 hover:text-blue-600 rounded"
|
||||
const languageFlag = computed(() => {
|
||||
return getFlagFromLang(language.value);
|
||||
});
|
||||
|
||||
function handleLanguageChange(lang: string) {
|
||||
setLang(lang);
|
||||
}
|
||||
|
||||
const sideMenuItemClass = "block p-4 text-sm font-semibold hover:bg-blue-50 hover:text-blue-600 rounded flex"
|
||||
const sideMenuOpen = ref(false);
|
||||
const stateMenuOpen = ref(false)
|
||||
|
||||
|
@ -137,30 +168,56 @@ const wsColor = computed(() => {
|
|||
});
|
||||
|
||||
const wsState = computed(() => {
|
||||
return translate(wsStore.state);
|
||||
return translate(wsStore.state.toLocaleLowerCase());
|
||||
});
|
||||
|
||||
type Item = {
|
||||
name: string;
|
||||
href: string;
|
||||
class?: string;
|
||||
badge?: Ref<boolean>;
|
||||
};
|
||||
|
||||
const menuItems: Item[] = ([
|
||||
/* {
|
||||
name: translate("page.home"),
|
||||
href: "/",
|
||||
}, */{
|
||||
const menuItems: ComputedRef<Item[]> = computed(() => ([
|
||||
{
|
||||
name: translate("page.uart"),
|
||||
href: "/uart",
|
||||
}, {
|
||||
name: translate("page.wifi"),
|
||||
href: "/wifi",
|
||||
}, {
|
||||
name: translate("page.about"),
|
||||
href: "/about",
|
||||
}, {
|
||||
name: translate("page.feedback"),
|
||||
href: "/feedback",
|
||||
},
|
||||
]);
|
||||
]));
|
||||
|
||||
const sideBarItems: ComputedRef<Item[]> = computed(() => {
|
||||
const items: Item[] = [
|
||||
{
|
||||
name: translate("page.uart"),
|
||||
href: "/uart",
|
||||
}, {
|
||||
name: translate("page.wifi"),
|
||||
href: "/wifi",
|
||||
}, {
|
||||
name: translate("page.about"),
|
||||
href: "/about",
|
||||
}, {
|
||||
name: translate("page.feedback"),
|
||||
href: "/feedback",
|
||||
},
|
||||
];
|
||||
if (isOTAEnabled()) {
|
||||
items.push({
|
||||
name: translate("page.update"),
|
||||
href: "/update",
|
||||
badge: computed(() => updateStore.canUpdate),
|
||||
})
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -188,5 +245,9 @@ const menuItems: Item[] = ([
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.el-select :deep(.el-select__wrapper) {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -0,0 +1,457 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-tabs v-model="store.configPanelTab" class="mx-2 custom-tabs fit">
|
||||
<el-tab-pane name="first" class="min-h-80">
|
||||
<template #label>{{ $t("uart.port") }}</template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<el-form :size="store.winLeft.show ? '' : 'small'" label-position="left" label-width="auto">
|
||||
<el-form-item
|
||||
class="mb-2"
|
||||
>
|
||||
<template #label>{{ $t("uart.baudrate") }}</template>
|
||||
<div class="flex w-full">
|
||||
<el-select v-model="store.uartBaud" :teleported="false" @change="onUartBaudChange">
|
||||
<template #header>
|
||||
<div class="overflow-auto max-h-40">
|
||||
<div class="flex gap-0">
|
||||
<el-input-number
|
||||
v-model="uartCustomBaud"
|
||||
:placeholder="translate('uart.customBaud')"
|
||||
size="small"
|
||||
:controls="false"
|
||||
:min="110"
|
||||
class="flex-grow"
|
||||
></el-input-number>
|
||||
<el-button size="small" @click="onUseCustomUartBaud">{{ $t('uart.use') }}</el-button>
|
||||
<!-- <el-button size="small" @click="onConfirm" class="ml-0">增加</el-button>-->
|
||||
</div>
|
||||
|
||||
<el-option-group :label="translate('uart.commonlyUsed')">
|
||||
<el-option
|
||||
v-for="item in store.predefinedUartBaudFrequent"
|
||||
:key="item.baud"
|
||||
:value="item.baud"
|
||||
class="border-b list-none"
|
||||
/>
|
||||
</el-option-group>
|
||||
|
||||
<el-option-group :label="translate('uart.other')">
|
||||
<el-option
|
||||
v-for="item in store.uartBaudList"
|
||||
:key="item.baud"
|
||||
:value="item.baud"
|
||||
class="border-b list-none"
|
||||
/>
|
||||
</el-option-group>
|
||||
</div>
|
||||
</template>
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<p class="text-xs">{{ $t('uart.actual') }} {{ $t('uart.baudrate') }}:{{ store.uartBaudReal }}</p>
|
||||
|
||||
<el-form-item :label="translate('uart.dataBits')" class="mb-2">
|
||||
<el-select v-model="store.uartConfig.data_bits" :teleported="false"
|
||||
placeholder="Select" @change="onUartConfigChange">
|
||||
<el-option
|
||||
v-for="item in uartDataBitsOptions"
|
||||
:key="item.key"
|
||||
:value="item.key"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="translate('uart.parity')" class="mb-2">
|
||||
<el-select v-model="store.uartConfig.parity" :teleported="false"
|
||||
placeholder="Select" @change="onUartConfigChange">
|
||||
<el-option
|
||||
v-for="item in uartParityOptions"
|
||||
:key="item.key"
|
||||
:value="item.key"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="translate('uart.stopBits')">
|
||||
<el-select v-model="store.uartConfig.stop_bits" :teleported="false"
|
||||
placeholder="Select" @change="onUartConfigChange">
|
||||
<el-option
|
||||
v-for="item in uartStopBitsOptions"
|
||||
:key="item.key"
|
||||
:value="item.key"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<el-button :type="store.acceptIncomingData ? 'danger': 'success'"
|
||||
:disabled="wsStore.state !== ControlEvent.CONNECTED"
|
||||
@click="store.acceptIncomingData = !store.acceptIncomingData"
|
||||
>
|
||||
{{ store.acceptIncomingData ? $t("uart.stopCommunication") : $t("uart.startCommunication") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
|
||||
</el-tab-pane>
|
||||
<!-- ///////////////////////////////////////////////////////////////// -->
|
||||
<el-tab-pane name="second">
|
||||
<template #label>{{ $t("uart.displayPannel") }}</template>
|
||||
<div class="flex flex-col">
|
||||
<el-collapse v-model="collapseActiveName">
|
||||
<el-collapse-item name="1">
|
||||
<template #title>
|
||||
{{ $t('uart.displayOptions') }}
|
||||
</template>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col">
|
||||
<el-checkbox border v-model="store.showText" :label="translate('uart.text')"/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<el-checkbox border v-model="store.showHex" label="HEX"/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<el-checkbox border v-model="store.showHexdump" label="HEXDUMP"/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<el-checkbox border v-model="store.showTimestamp" :label="translate('uart.timestamp')"/>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<el-checkbox border v-model="store.enableLineWrap" :label="translate('uart.lineWrap')"/>
|
||||
</div>
|
||||
|
||||
<el-tag type="success">
|
||||
<el-text type="success">RX HEXDUMP {{ $t("uart.highlight") }}</el-text>
|
||||
<el-color-picker v-model="store.RxHexdumpColor" show-alpha :predefine="store.predefineColors"
|
||||
size="small"/>
|
||||
</el-tag>
|
||||
|
||||
<el-tag type="primary">
|
||||
<el-text type="primary">TX HEXDUMP {{ $t("uart.highlight") }}</el-text>
|
||||
<el-color-picker v-model="store.TxHexdumpColor" show-alpha :predefine="store.predefineColors"
|
||||
size="small"/>
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="2" :title="translate('uart.frameBreakStrategy')">
|
||||
<VueDraggable v-model="store.frameBreakRules" target="tbody" handle=".sort-target"
|
||||
:animation="150"
|
||||
:on-move="checkMove">
|
||||
<table class="w-full bg-white">
|
||||
<thead>
|
||||
<tr class="text-sm h-7">
|
||||
<th>{{ $t('uart.priority') }}</th>
|
||||
<th>
|
||||
<div class="flex justify-center">
|
||||
{{ translate('uart.rule' as TranslationKeys) }}
|
||||
<el-tooltip placement="top" effect="light">
|
||||
<template #content>
|
||||
<div v-html="translate('uart.ruleTips')"></div>
|
||||
</template>
|
||||
<InlineSvg name="help" class="w-4 text-gray-500 cursor-help"></InlineSvg>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</th>
|
||||
<th>{{ translate('uart.value' as TranslationKeys) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-xs text-center">
|
||||
<tr v-for="(item, index) in store.frameBreakRules" :key="index">
|
||||
<td :class="item.draggable ? 'sort-target' : ''">
|
||||
{{ item.draggable ? index : 'NaN' }}
|
||||
</td>
|
||||
<td :class="item.draggable ? 'sort-target' : ''">
|
||||
{{ translate("uart." + item.name) }}
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="item.type === 'number'">
|
||||
<el-input-number v-if="item.name === 'timeout'" v-model="store.frameBreakDelay" :min="item.min || 0" size="small" style="width: 100px"/>
|
||||
<el-input-number v-else v-model="store.frameBreakSize" :min="item.min || 0" size="small" style="width: 100px"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-input class="break-input" v-model="store.frameBreakSequence" :placeholder="translate('uart.textAndEscape')" 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'">
|
||||
{{ translate("uart.begin") }}
|
||||
</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'">
|
||||
{{ translate("uart.end") }}
|
||||
</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</VueDraggable>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item name="3" :title="translate('uart.other')">
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-2">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
placement="right-start"
|
||||
>
|
||||
<template #content>
|
||||
<div v-html="translate('uart.ansiTooltips')"></div>
|
||||
</template>
|
||||
<el-checkbox border v-model="store.enableAnsiDecode">{{ translate('uart.decodeAnsiEscapeCodes') }}</el-checkbox>
|
||||
</el-tooltip>
|
||||
<el-input v-model="store.filterValue" :placeholder="translate('uart.textAndEscape')" clearable>
|
||||
<template #prepend>
|
||||
{{ translate("uart.filter") }}
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<div class="border rounded flex flex-col">
|
||||
|
||||
<el-checkbox border v-model="store.dataFilterAutoUpdate">{{ translate('uart.autoUpdateNewData') }}</el-checkbox>
|
||||
|
||||
<el-tooltip :content="translate('uart.updateFrequencyTooltip')" placement="right" effect="light"
|
||||
:show-after="500">
|
||||
<div class="flex gap-4 p-2">
|
||||
<el-text>{{ translate('uart.updateFrequency') }}</el-text>
|
||||
<el-input-number
|
||||
:step="10"
|
||||
:min="10"
|
||||
size="small"
|
||||
v-model="store.batchUpdateTime"
|
||||
>
|
||||
</el-input-number>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<!-- <div class="flex flex-col">-->
|
||||
<!-- <el-text type="success">断帧设置</el-text>-->
|
||||
<!-- <el-input v-model="store.frameBreakSequence" class="max-w-52">-->
|
||||
<!-- <template #prepend>-->
|
||||
<!-- 文本匹配断帧-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-input>-->
|
||||
<!-- <el-input v-model="store.frameBreakDelay" type="number" class="max-w-52">-->
|
||||
<!-- <template #prepend>-->
|
||||
<!-- 超时断帧-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-input>-->
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
<!-- <div class="flex flex-col flex-wrap">-->
|
||||
<!-- <el-button size="small">滚动到底</el-button>-->
|
||||
|
||||
|
||||
<!-- <div>显示-->
|
||||
<!-- <el-checkbox size="small" border>数据差异高亮</el-checkbox>-->
|
||||
<!-- <el-checkbox size="small" border>TX高亮</el-checkbox>-->
|
||||
<!-- <el-checkbox size="small" border>显示RX</el-checkbox>-->
|
||||
<!-- <el-checkbox size="small" border>显示TX</el-checkbox>-->
|
||||
<!-- <el-checkbox size="small" border>RX右对齐</el-checkbox>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <!– <div>专有协议–>-->
|
||||
<!-- <!– <el-button size="small">输入格式</el-button>–>-->
|
||||
<!-- <!– <el-button size="small">输出格式</el-button>–>-->
|
||||
<!-- <!– </div>–>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ///////////////////////////////////////////////////////////// -->
|
||||
<el-tab-pane :label="translate('uart.send')" name="third">
|
||||
<template #label>{{ $t("uart.send") }}</template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<el-input v-model="store.textPrefixValue" :placeholder="translate('uart.textAndEscape')" clearable>
|
||||
<template #prepend>
|
||||
{{ translate('uart.addHeader') }}►
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="store.textSuffixValue" :placeholder="translate('uart.textAndEscape')" clearable>
|
||||
<template #append>
|
||||
◄{{ translate('uart.addFooter') }}
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
|
||||
<el-tab-pane :label="translate('uart.proxy')" name="fourth" class="min-h-80">
|
||||
<template #label>{{ $t("uart.passthrough") }}</template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="border rounded bg-white p-2">
|
||||
<span class="border-r px-2">TCP {{ translate('uart.serverPort') }}</span>
|
||||
<span class="px-2 cursor-not-allowed">1346</span>
|
||||
</div>
|
||||
<div>
|
||||
<p><el-button @click="refreshTCPClientList" size="small" type="primary" :plain="true">{{ translate('uart.refresh') }}</el-button> {{ translate('uart.connectedClient') }}</p>
|
||||
|
||||
<el-table :data="dfStore.instanceList.filter((item) => (item.port_info as ISocketInfo).local_port === 1346)" :empty-text="translate('uart.noClientConnected')">
|
||||
<el-table-column label="IP" prop="port_info.foreign_ip" />
|
||||
<el-table-column :label="translate('uart.port')" prop="port_info.foreign_port"/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {VueDraggable} from 'vue-draggable-plus'
|
||||
import {computed, 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";
|
||||
import {useDataFlowStore} from "@/stores/useDataFlowStore";
|
||||
import {wt_data_flow_get_instance_list, type ISocketInfo} from "@/api/apiDataFlow";
|
||||
import {uart_set_baud, uart_set_config} from "@/api/apiUart";
|
||||
import {useUartStore} from "@/stores/useUartStore";
|
||||
import {translate, type TranslationKeys} from "@/locales";
|
||||
|
||||
const store = useDataViewerStore()
|
||||
const uartStore = useUartStore()
|
||||
const wsStore = useWsStore()
|
||||
const dfStore = useDataFlowStore()
|
||||
|
||||
const collapseActiveName = ref(["1", "2", "3"])
|
||||
|
||||
const uartCustomBaud = ref(1500000)
|
||||
|
||||
const uartDataBitsOptions = [
|
||||
{
|
||||
key: 5,
|
||||
label: "5 bits",
|
||||
}, {
|
||||
key: 6,
|
||||
label: "6 bits",
|
||||
}, {
|
||||
key: 7,
|
||||
label: "7 bits",
|
||||
}, {
|
||||
key: 8,
|
||||
label: "8 bits",
|
||||
}
|
||||
]
|
||||
|
||||
const uartParityOptions = computed(() => [
|
||||
{
|
||||
key: 0,
|
||||
label: translate("uart.parityNone"),
|
||||
}, {
|
||||
key: 1,
|
||||
label: translate("uart.parityOdd"),
|
||||
}, {
|
||||
key: 2,
|
||||
label: translate("uart.parityEven"),
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
const uartStopBitsOptions = [
|
||||
{
|
||||
key: 1,
|
||||
label: "1",
|
||||
}, {
|
||||
key: 15,
|
||||
label: "1.5",
|
||||
}, {
|
||||
key: 2,
|
||||
label: "2",
|
||||
}
|
||||
]
|
||||
|
||||
const onUseCustomUartBaud = () => {
|
||||
if (uartCustomBaud.value) {
|
||||
store.uartBaud = uartCustomBaud.value;
|
||||
onUartBaudChange();
|
||||
} else {
|
||||
globalNotify("波特率格式错误", "warning")
|
||||
}
|
||||
}
|
||||
|
||||
function onUartBaudChange() {
|
||||
uart_set_baud(store.uartBaud, uartStore.uartNum);
|
||||
}
|
||||
|
||||
function onUartConfigChange() {
|
||||
uart_set_config(store.uartConfig, uartStore.uartNum);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function refreshTCPClientList() {
|
||||
dfStore.instanceList = [];
|
||||
wt_data_flow_get_instance_list();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-tabs {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.custom-tabs :deep(.el-tabs__item.is-top) {
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.custom-tabs :deep(.el-tabs__nav.is-top) {
|
||||
@apply w-full flex justify-around
|
||||
}
|
||||
|
||||
.custom-tabs :deep(.el-collapse-item__wrap) {
|
||||
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>
|
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<div class="flex items-center mb-2 flex-wrap gap-2">
|
||||
<el-button type="primary" @click="importSettings">{{ translate('uart.import') }}</el-button>
|
||||
<el-button type="warning" @click="exportSettings">{{ translate('uart.export') }}</el-button>
|
||||
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="top"
|
||||
:show-after="500"
|
||||
>
|
||||
<template #content>
|
||||
<p>{{ translate('uart.resetTooltip') }}</p>
|
||||
</template>
|
||||
<el-button type="info" @click="resetSettings">{{ translate('uart.reset') }}</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
placement="top"
|
||||
:show-after="500"
|
||||
>
|
||||
<template #content>
|
||||
<p>{{ translate('uart.saveToLocalTooltip') }}</p>
|
||||
</template>
|
||||
<el-checkbox border v-model="store.autoSaveSettings">{{ translate('uart.saveToLocal') }}</el-checkbox>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-2 flex-wrap gap-2">
|
||||
<el-button type="primary" @click="() => {
|
||||
store.macroData.push({
|
||||
value: '',
|
||||
label: translate('uart.send'),
|
||||
id: store.macroId,
|
||||
})
|
||||
store.macroId++;
|
||||
}">{{ translate('uart.add') }}
|
||||
</el-button>
|
||||
<el-checkbox v-model="editMode" border>{{ translate('uart.edit') }}</el-checkbox>
|
||||
<el-checkbox v-model="draggableEnabled" border>{{ translate('uart.drag') }}</el-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<el-alert v-if="store.ipChangeAlert" @close="store.ipChangeAlert=false">{{ translate('uart.ipChangeAlert') }}</el-alert>
|
||||
</div>
|
||||
|
||||
<VueDraggable v-model="store.macroData" handle=".sort-target"
|
||||
:animation="150" class="break-input">
|
||||
<div v-for="(item, index) in store.macroData" :key="item.id" class="w-full text-xs flex items-center py-0.5"
|
||||
:class="editMode ? 'macroButtons' : ''">
|
||||
<el-tag size="large" type="success" v-if="draggableEnabled" class="sort-target mr-1">
|
||||
=
|
||||
</el-tag>
|
||||
<el-input v-model="item.value" class="font-mono">
|
||||
<template #append>
|
||||
<el-input v-if="editMode" v-model="item.label"></el-input>
|
||||
<el-button v-else @click="onSendClick(item.value)" type="primary">{{ item.label }}</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-link :underline="false" @click="store.macroData.splice(index, 1);">
|
||||
<el-tag size="large" type="danger" v-if="editMode" class="ml-1">
|
||||
x
|
||||
</el-tag>
|
||||
</el-link>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {VueDraggable} from "vue-draggable-plus";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {globalNotify, globalNotifyRightSide} from "@/composables/notification";
|
||||
import {useDataViewerStore} from "@/stores/dataViewerStore";
|
||||
import {translate} from "@/locales";
|
||||
|
||||
const editMode = ref(false);
|
||||
const draggableEnabled = ref(true);
|
||||
const store = useDataViewerStore();
|
||||
const emit = defineEmits(['winSizeRefresh'])
|
||||
|
||||
function onSendClick(val: string) {
|
||||
if (!val && !store.hasAddedText) {
|
||||
globalNotify("无帧头帧尾、发送框无数据发送")
|
||||
return;
|
||||
}
|
||||
|
||||
if (store.acceptIncomingData) {
|
||||
store.addString(val, false, true);
|
||||
} else {
|
||||
store.addString(val, false, true, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function importSettings() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'application/json';
|
||||
|
||||
input.onchange = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (!target.files) return;
|
||||
const file = target.files[0];
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: ProgressEvent<FileReader>) => {
|
||||
const text = e.target?.result;
|
||||
if (typeof text !== 'string') return;
|
||||
|
||||
try {
|
||||
store.loadSettings(text);
|
||||
emit('winSizeRefresh', '');
|
||||
} catch (error) {
|
||||
globalNotifyRightSide('导入失败', "error");
|
||||
console.log("error", error);
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
input.click();
|
||||
}
|
||||
|
||||
function exportSettings() {
|
||||
let obj = {
|
||||
version: "v0.1.0",
|
||||
|
||||
/* Macro Window */
|
||||
...store.settings
|
||||
};
|
||||
|
||||
const dataStr = JSON.stringify(obj, null, 2);
|
||||
const blob = new Blob([dataStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = "settingsBackup.json";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
localStorage.clear();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
store.loadSettings();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.sortable-chosen {
|
||||
background-color: var(--el-color-primary-light-7);
|
||||
}
|
||||
|
||||
.sort-target {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.macroButtons :deep(.el-input-group__append) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.break-input :deep(.el-input-group__append) {
|
||||
background-color: unset;
|
||||
border-color: unset;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
.break-input :deep(.el-input-group__append button.el-button) {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border-color: var(--el-border-color);
|
||||
color: unset;
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
.break-input :deep(.el-input-group__append button.el-button):hover {
|
||||
background-color: var(--el-color-primary-light-7);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,440 @@
|
|||
<template>
|
||||
<div class="flex min-h-7 overflow-auto">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
:hide-after="0"
|
||||
transition="none"
|
||||
width="300"
|
||||
>
|
||||
<div v-if="!store.winLeft.show" class="h-[40vh] overflow-auto">
|
||||
<text-data-config></text-data-config>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-link v-show="!store.winLeft.show" type="primary">
|
||||
<InlineSvg name="arrow_drop_down" class="h-6 mb-1 px-2"></InlineSvg>
|
||||
</el-link>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
<div class="flex">
|
||||
<el-checkbox size="small" v-model="store.forceToBottom" :label="translate('uart.autoScrollToBottom')" border/>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<template #content>
|
||||
<p>{{ translate('uart.clearTooltip') }}</p>
|
||||
</template>
|
||||
<el-button size="small" @click="store.clearFilteredBuff">
|
||||
<InlineSvg class="h-5" name="trash"></InlineSvg>
|
||||
{{ $t('uart.clearScreen') }} ⇩
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<template #content>
|
||||
<p>{{ translate('uart.clearTooltip') }}</p>
|
||||
</template>
|
||||
<el-button size="small" @click="store.refreshFilteredBuff">
|
||||
{{ $t('page.update') }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
placement="top"
|
||||
>
|
||||
<template #content>
|
||||
<p>{{ translate('uart.autoUpdateTooltip') }}</p>
|
||||
</template>
|
||||
<el-checkbox size="small" border v-model="store.dataFilterAutoUpdate">
|
||||
{{ $t('uart.autoUpdate') }}
|
||||
</el-checkbox>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-grow overflow-hidden border-2 rounded scroll-m-2">
|
||||
<v-virtual-scroll
|
||||
v-if="store.showVirtualScroll"
|
||||
:items="store.dataFiltered"
|
||||
id="myScrollerID"
|
||||
ref="vuetifyVirtualScrollRef"
|
||||
class="font-mono break-all text-sm"
|
||||
:class="[store.enableLineWrap ? 'break-all' : 'text-nowrap']"
|
||||
>
|
||||
<template v-slot:default="{ item, }">
|
||||
<div class="">
|
||||
<div class="flex" :class="[store.enableLineWrap ? 'whitespace-pre-wrap' : 'whitespace-pre']">
|
||||
<p class="text-nowrap text-sm text-lime-500" v-if="item.isRX" type="success" v-show="store.showTimestamp">
|
||||
<span>{{ item.time }}</span>◄-RX|</p>
|
||||
<p class="text-nowrap text-sm text-sky-500" v-else-if="item.type === 0" type="primary" v-show="store.showTimestamp">
|
||||
<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>NS-►|</p>
|
||||
|
||||
<p v-show="store.showText"
|
||||
v-html="item.str"></p>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<p v-show="store.showHex" class="">{{ item.hex }}</p>
|
||||
</div>
|
||||
<div class="flex whitespace-pre">
|
||||
<p v-show="store.showHexdump"
|
||||
class="text-nowrap"
|
||||
:style="{ 'background-color': item.isRX ? store.RxHexdumpColor : store.TxHexdumpColor }"
|
||||
v-html="item.hexdump"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
<v-virtual-scroll
|
||||
v-else
|
||||
:items="store.dataFiltered"
|
||||
id="myScrollerID"
|
||||
ref="vuetifyVirtualScrollRef2"
|
||||
class="font-mono break-all text-sm"
|
||||
:class="[store.enableLineWrap ? 'break-all' : 'text-nowrap']"
|
||||
>
|
||||
<template v-slot:default="{ item, }">
|
||||
<div>
|
||||
<div class="flex" :class="[store.enableLineWrap ? 'whitespace-pre-wrap' : 'whitespace-pre']">
|
||||
<p class="text-nowrap text-sm text-lime-500" v-if="item.isRX" type="success" v-show="store.showTimestamp">
|
||||
<span>{{ item.time }}</span>◄-RX|</p>
|
||||
<p class="text-nowrap text-sm text-sky-500" v-else-if="item.type === 0" type="primary" v-show="store.showTimestamp">
|
||||
<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>NS-►|</p>
|
||||
<p v-show="store.showText"
|
||||
v-html="item.str"></p>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<p v-show="store.showHex" class="">{{ item.hex }}</p>
|
||||
</div>
|
||||
<div class="flex whitespace-pre">
|
||||
<p v-show="store.showHexdump"
|
||||
class="text-nowrap"
|
||||
:style="{ 'background-color': item.isRX ? store.RxHexdumpColor : store.TxHexdumpColor }"
|
||||
v-html="item.hexdump"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-virtual-scroll>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 flex h-8 mt-0.5 text-xs">
|
||||
<div class="flex shrink-0">
|
||||
<el-tooltip :content="translate('uart.tempDisplayTooltip')" effect="light">
|
||||
<InlineSvg name="help" class="w-3.5 h-3.5 text-gray-500 cursor-help"></InlineSvg>
|
||||
</el-tooltip>
|
||||
<p>►</p>
|
||||
</div>
|
||||
<div ref="RxHexDumpRef" class="p-0.5 border-2 rounded w-full overflow-y-scroll 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-auto">
|
||||
<div class="flex gap-2">
|
||||
<el-link @click="clearSendInput">
|
||||
<el-tag class="font-mono" size="small">
|
||||
<div class="flex ">
|
||||
<InlineSvg class="h-5" name="trash"></InlineSvg>
|
||||
<span class="content-end text-xs">⇩</span>
|
||||
</div>
|
||||
</el-tag>
|
||||
</el-link>
|
||||
|
||||
<el-tooltip :content="translate('uart.loopSendTooltip')" placement="right" effect="light" :show-after="1000">
|
||||
<div class="flex align-center">
|
||||
<el-checkbox v-model="store.enableLoopSend" class="font-mono font-bold max-h-5" size="small" border>
|
||||
{{ translate('uart.loopSend') }}(ms)
|
||||
</el-checkbox>
|
||||
<el-input-number
|
||||
v-model="store.loopSendFreq"
|
||||
class="h-5"
|
||||
size="small"
|
||||
:step="10"
|
||||
:min="1"
|
||||
>
|
||||
</el-input-number>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
|
||||
<el-link @click="store.isSendTextFormat = !store.isSendTextFormat">
|
||||
<el-tag class="font-mono font-bold" size="small">{{ translate('uart.sendFormat') }}:{{ store.isSendTextFormat ? translate("uart.text") : "HEX" }}</el-tag>
|
||||
</el-link>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<el-link @click="store.clearTxCounter()">
|
||||
<el-tag class="font-mono font-bold" size="small">
|
||||
{{ `TX(B):${store.TxByteCount}/ ${store.TxTotalByteCount}` }}
|
||||
</el-tag>
|
||||
</el-link>
|
||||
<el-link type="success" @click="store.clearRxCounter()">
|
||||
<el-tag class="font-mono font-bold" size="small" type="success">
|
||||
{{ `RX(B):${store.RxByteCount}/ ${store.RxTotalByteCount}` }}
|
||||
</el-tag>
|
||||
</el-link>
|
||||
<div class="flex align-center">
|
||||
<el-tag class="font-mono font-bold" size="small" type="info">
|
||||
<el-link class="flex" @click="store.clearDataBuff" type="warning">
|
||||
<InlineSvg class="h-5" name="trash"></InlineSvg>
|
||||
</el-link>
|
||||
<span class="align-text-bottom">{{ translate('uart.cachedFrame') }}: {{ store.dataBufLength }}/30000</span>
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row font-mono">
|
||||
<el-input type="textarea" :autosize="{ minRows: 1, maxRows: 6}" v-model="store.uartInputTextBox" clearable
|
||||
:placeholder="store.isSendTextFormat ?
|
||||
translate('uart.textAndEscape') :
|
||||
'HEX'"
|
||||
@keydown="handleTextboxKeydown"
|
||||
></el-input>
|
||||
<el-tooltip content="Ctrl+Enter" placement="top" :auto-close="500">
|
||||
<el-button type="primary"
|
||||
@click="onSendClick">
|
||||
{{ (store.isSendTextFormat || store.isHexStringValid) ? translate("uart.send") : translate("格式化") }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {nextTick, onMounted, onUnmounted, ref, watch} from "vue";
|
||||
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";
|
||||
import {translate} from "@/locales";
|
||||
|
||||
const count = ref(0);
|
||||
const vuetifyVirtualScrollBarRef = ref(document.body);
|
||||
const vuetifyVirtualScrollContainerRef = ref(document.body);
|
||||
|
||||
const store = useDataViewerStore();
|
||||
|
||||
const RxHexDumpRef = ref(document.body);
|
||||
|
||||
let lastScrollHeight = 0;
|
||||
|
||||
const mutationObserver = new MutationObserver(() => {
|
||||
if (store.forceToBottom) {
|
||||
lastScrollHeight = vuetifyVirtualScrollBarRef.value.scrollTop;
|
||||
scrollToBottom();
|
||||
}
|
||||
});
|
||||
|
||||
function attachScroll() {
|
||||
const parent = document.getElementById('myScrollerID') || document.body;
|
||||
|
||||
// used to scroll to bottom
|
||||
vuetifyVirtualScrollBarRef.value = parent || document.body;
|
||||
|
||||
// used to monitor height changes, so that one new Items are rendered, the height change -> scroll to bottom
|
||||
vuetifyVirtualScrollContainerRef.value = parent.querySelector('.v-virtual-scroll__container') || document.body;
|
||||
|
||||
vuetifyVirtualScrollBarRef.value.onscroll = handleScroll;
|
||||
|
||||
if (vuetifyVirtualScrollContainerRef.value) {
|
||||
const config = {childList: true, subtree: true, attributes: true};
|
||||
mutationObserver.observe(vuetifyVirtualScrollBarRef.value, config)
|
||||
}
|
||||
|
||||
if (store.forceToBottom) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
attachScroll();
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
mutationObserver.disconnect();
|
||||
if (!store.isHexStringValid) {
|
||||
store.uartInputTextBox = formatHexInput(store.uartInputTextBox);
|
||||
}
|
||||
});
|
||||
|
||||
debouncedWatch(() => store.showVirtualScroll, () => {
|
||||
lastScrollHeight = 0;
|
||||
mutationObserver.disconnect();
|
||||
attachScroll();
|
||||
}, {debounce: 80});
|
||||
|
||||
|
||||
function addItem(nr: number) {
|
||||
let rawText = "";
|
||||
|
||||
let maxcount = count.value + nr;
|
||||
|
||||
for (; count.value < maxcount; count.value++) {
|
||||
let text = "";
|
||||
if ((count.value & 3) === 3) {
|
||||
text = `<p class="border-4">${count.value}inputasdf<br/>${count.value}asdf <br/>${count.value}asdfasdf <br/>${count.value}asdf </p>`;
|
||||
text += text;
|
||||
|
||||
} else if ((count.value & 2) === 2) {
|
||||
text = `<p class="border-4">${count.value}inputas df2<br/>${count.value} asdf asd <br/>${count.value}fasdf asdf </p>`;
|
||||
text += text;
|
||||
} else if ((count.value & 1) === 1) {
|
||||
text = `<p class="border-4">${count.value}inputas df2asdf asd <br/>${count.value}fasdf asdf ${count.value} </p>`;
|
||||
text += text;
|
||||
} else {
|
||||
text = `<p class="border-4">${count.value}inputasa<br/>jdhfklasjdhfklasdhflasidfhilasdfhlasdiufhlasdkfhuasnlfcyerhfcibnkuaweghnfctiklaweuyrchnlaweirtucgnawertkcgyawertcnawelcrvnawgervcawencrgf${count.value} </p>`;
|
||||
}
|
||||
|
||||
rawText = count.value + "<p class=\"border-4\"> 666666666b\n6666 666\x1b[33m6666666666666666666666666</p>b\n"
|
||||
const encoder = new TextEncoder();
|
||||
const arr = encoder.encode(rawText);
|
||||
store.addItem(arr, false, false, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
nextTick(() => {
|
||||
const scrollerElement = vuetifyVirtualScrollBarRef.value; // Adjust according to your setup
|
||||
scrollerElement.scrollTop = scrollerElement.scrollHeight;
|
||||
});
|
||||
}
|
||||
|
||||
function formatHexInput(input: string) {
|
||||
// Split the input string on spaces to process each segment separately
|
||||
let str;
|
||||
|
||||
// Remove any "0x" prefix and handle uppercase conversion
|
||||
str = input.replace(/^0x/i, ' ').toUpperCase();
|
||||
|
||||
// Remove any non-hexadecimal characters
|
||||
str = str.replace(/[^0-9A-F]/gi, ' ');
|
||||
|
||||
let segments = str.split(/\s+/);
|
||||
let output: string[] = [];
|
||||
|
||||
segments.forEach(segment => {
|
||||
// Check if segment length is odd and needs padding
|
||||
if (segment.length % 2 !== 0) {
|
||||
segment = '0' + segment; // Prepend '0' to make the length even
|
||||
}
|
||||
|
||||
// Split segment into array of two-character chunks
|
||||
let chunked = [];
|
||||
for (let i = 0; i < segment.length; i += 2) {
|
||||
chunked.push(segment.substring(i, i + 2));
|
||||
}
|
||||
|
||||
// Concatenate chunked segments and add to output
|
||||
output.push(chunked.join(' '));
|
||||
});
|
||||
|
||||
// Join all processed segments with a space and return
|
||||
return output.join(' ');
|
||||
}
|
||||
|
||||
function checkHexTextValid() {
|
||||
store.isHexStringValid = store.uartInputTextBox.toUpperCase() === formatHexInput(store.uartInputTextBox);
|
||||
if (!store.isHexStringValid) {
|
||||
store.enableLoopSend = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => store.isSendTextFormat, (value) => {
|
||||
if (!value) {
|
||||
checkHexTextValid()
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => store.uartInputTextBox, () => {
|
||||
if (!store.isSendTextFormat) {
|
||||
checkHexTextValid()
|
||||
}
|
||||
})
|
||||
|
||||
/* patch scroll container does not update clear filter */
|
||||
watch(() => store.filterChanged, (value) => {
|
||||
if (value && store.forceToBottom) {
|
||||
scrollToBottom();
|
||||
}
|
||||
store.filterChanged = false;
|
||||
})
|
||||
|
||||
watch(() => store.RxRemainHexdump, value => {
|
||||
if (value) {
|
||||
RxHexDumpRef.value.scrollTop = RxHexDumpRef.value.scrollHeight;
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => store.showVirtualScroll, () => {
|
||||
if (store.forceToBottom) {
|
||||
scrollToBottom();
|
||||
}
|
||||
});
|
||||
|
||||
const handleScroll = () => {
|
||||
if (store.forceToBottom) {
|
||||
if (vuetifyVirtualScrollBarRef.value.scrollTop - lastScrollHeight < 0) {
|
||||
store.forceToBottom = false;
|
||||
} else {
|
||||
scrollToBottom();
|
||||
}
|
||||
} else if ((vuetifyVirtualScrollBarRef.value.scrollHeight -
|
||||
vuetifyVirtualScrollBarRef.value.scrollTop) <= vuetifyVirtualScrollBarRef.value.clientHeight) {
|
||||
store.forceToBottom = true;
|
||||
}
|
||||
lastScrollHeight = vuetifyVirtualScrollBarRef.value.scrollTop;
|
||||
};
|
||||
|
||||
watch(() => store.forceToBottom, value => {
|
||||
if (value) {
|
||||
setTimeout(scrollToBottom, 0);
|
||||
}
|
||||
});
|
||||
|
||||
function clearSendInput() {
|
||||
store.uartInputTextBox = ""
|
||||
}
|
||||
|
||||
function handleTextboxKeydown(ev: KeyboardEvent) {
|
||||
if (ev.ctrlKey && ev.key === 'Enter') {
|
||||
onSendClick();
|
||||
}
|
||||
}
|
||||
|
||||
function onSendClick() {
|
||||
if (!store.uartInputTextBox && !store.hasAddedText) {
|
||||
globalNotify("无帧头帧尾、发送框无数据发送")
|
||||
return;
|
||||
}
|
||||
|
||||
if (store.acceptIncomingData) {
|
||||
if (store.isSendTextFormat) {
|
||||
store.addString(store.uartInputTextBox, false, true);
|
||||
} else if (!store.isHexStringValid) {
|
||||
store.uartInputTextBox = formatHexInput(store.uartInputTextBox);
|
||||
} else {
|
||||
store.addHexString(store.uartInputTextBox, false, true);
|
||||
}
|
||||
} else {
|
||||
if (store.isSendTextFormat) {
|
||||
store.addString(store.uartInputTextBox, false, true, 1);
|
||||
} else if (!store.isHexStringValid) {
|
||||
store.uartInputTextBox = formatHexInput(store.uartInputTextBox);
|
||||
} else {
|
||||
store.addHexString(store.uartInputTextBox, false, true, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -7,7 +7,7 @@ import vue from '@vitejs/plugin-vue'
|
|||
import svgLoader from "vite-svg-loader";
|
||||
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
|
||||
import { viteSingleFile } from 'vite-plugin-singlefile'
|
||||
|
||||
import vuetify from 'vite-plugin-vuetify'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default ({mode}: ConfigEnv) => {
|
||||
|
@ -25,6 +25,7 @@ export default ({mode}: ConfigEnv) => {
|
|||
svgLoader(),
|
||||
cssInjectedByJsPlugin(),
|
||||
viteSingleFile(),
|
||||
vuetify(),
|
||||
],
|
||||
define: {},
|
||||
resolve: {
|
||||
|
|
Loading…
Reference in New Issue