Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

28 changed files with 295 additions and 6320 deletions

1
.gitattributes vendored
View File

@ -1 +0,0 @@
*.sh eol=lf

2
.gitignore vendored
View File

@ -28,9 +28,9 @@ coverage
*.sw? *.sw?
*.tsbuildinfo *.tsbuildinfo
package-lock.json
components.d.ts components.d.ts
auto-imports.d.ts auto-imports.d.ts
# Personal # Personal
**/_priv_* **/_priv_*
Makefile

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2024 kerms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,18 +1 @@
# 允斯无线透传器的内嵌网页版上位机 # 允斯调试器的内嵌网页版上位机
此项目使用`NPM`包管理, 需要先安装`node`工具。
# 环境准备步骤
##### 本地测试:
1. `npm install`
2. `npm run dev`,或用 `npm run devh` 则可以用其他设备访问,如手机调试移动界面。
3. 根据显示的地址,使用浏览器打开,默认地址为`localhost:5173` 或者其他设备访问`192.168.X.X:5173`
##### 发布至esp32
1. `npm install`
2. `npm run build` -> 会在`dist/`生成`index.html`和`ws.sharedworker.js`
3. 在`dist/`里执行`gzip *` -> -> 会在`dist/`生成`index.html.gz`和`ws.sharedworker.js.gz`
4. 至此可以使用这两个文件覆盖ESP32目录中的`project_components/html`里相对应的文件了。

2
env.d.ts vendored
View File

@ -1 +1,3 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare const __APP_VERSION__: string; // defined in vite.config.ts, imported from package.json.version
declare const __BUILD_TIME__: string;

5365
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,28 +4,22 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": ". ./set_env.sh && vite", "dev": "vite",
"devh": ". ./set_env.sh && vite --host",
"build": "run-p type-check \"build-only {@}\" --", "build": "run-p type-check \"build-only {@}\" --",
"preview": ". ./set_env.sh && vite preview", "preview": "vite preview",
"previewh": ". ./set_env.sh && vite preview --host", "build-only": "vite build",
"build-only": ". ./set_env.sh && vite build", "build:dev": "vite build --mode development",
"build:dev": ". ./set_env.sh && vite build --mode development",
"type-check": "vue-tsc --build --force", "type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^10.9.0", "element-plus": "^2.6.1",
"ansi_up": "^6.0.2",
"element-plus": "^2.7.3",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-draggable-plus": "^0.4.1",
"vue-i18n": "^9.10.2", "vue-i18n": "^9.10.2",
"vue-router": "^4.3.0", "vue-router": "^4.3.0"
"vuetify": "^3.6.5"
}, },
"devDependencies": { "devDependencies": {
"@rushstack/eslint-patch": "^1.3.3", "@rushstack/eslint-patch": "^1.3.3",
@ -46,11 +40,10 @@
"typescript": "~5.4.0", "typescript": "~5.4.0",
"unplugin-auto-import": "^0.17.5", "unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"vite": "^5.3.3", "vite": "^5.1.6",
"vite-plugin-css-injected-by-js": "^3.5.0", "vite-plugin-css-injected-by-js": "^3.5.0",
"vite-plugin-html": "^3.2.2", "vite-plugin-html": "^3.2.2",
"vite-plugin-singlefile": "^2.0.2", "vite-plugin-singlefile": "^2.0.1",
"vite-plugin-vuetify": "^2.0.3",
"vite-svg-loader": "^5.1.0", "vite-svg-loader": "^5.1.0",
"vue-tsc": "^2.0.6" "vue-tsc": "^2.0.6"
} }

View File

@ -1,7 +0,0 @@
#!/usr/bin/env bash
VITE_APP_GIT_TAG=$(git describe --tags | cut -d'-' -f1,2)
VITE_APP_LAST_COMMIT=$(git log -1 --format=%cd)
export VITE_APP_GIT_TAG
export VITE_APP_LAST_COMMIT

View File

@ -38,9 +38,10 @@ let websocketService: IWebsocketService;
onMounted(() => { onMounted(() => {
logHelloMessage(); logHelloMessage();
let host = ""; let host = "";
if (isDevMode()) { if (isDevMode()) {
host = import.meta.env.VITE_DEVICE_HOST_NAME || "dap.local"; host = "192.168.43.61";
} else { } else {
host = window.location.host host = window.location.host
} }
@ -55,10 +56,8 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<div class="flex flex-col h-screen">
<header> <header>
<nav-bar/> <nav-bar/>
</header> </header>
<RouterView/> <RouterView/>
</div>
</template> </template>

View File

@ -1,75 +1,47 @@
import {type ApiJsonMsg, sendJsonMsg, WtModuleID} from '@/api' import {type ApiJsonMsg, sendJsonMsg} from '@/api'
export const WifiModuleID = 1;
export enum WifiCmd { export enum WifiCmd {
UNKNOWN = 0, UNKNOWN = 0,
WIFI_API_JSON_STA_GET_AP_INFO = 1, WIFI_API_JSON_STA_GET_AP_INFO,
WIFI_API_JSON_CONNECT = 2, WIFI_API_JSON_CONNECT,
WIFI_API_JSON_GET_SCAN = 3, WIFI_API_JSON_GET_SCAN,
WIFI_API_JSON_DISCONNECT = 4, WIFI_API_JSON_DISCONNECT,
WIFI_API_JSON_AP_GET_INFO = 5, WIFI_API_JSON_AP_GET_INFO,
WIFI_API_JSON_GET_MODE = 6,
WIFI_API_JSON_SET_MODE = 7,
WIFI_API_JSON_AP_SET_CRED = 8,
WIFI_API_JSON_STA_GET_STATIC_INFO = 9,
WIFI_API_JSON_STA_SET_STATIC_CONF = 10,
} }
export enum WifiMode { interface WifiMsgOut extends ApiJsonMsg {
/* permanent */
WIFI_AP_AUTO_STA_ON = 0,
WIFI_AP_STA_OFF = 4, /* 100 */
WIFI_AP_OFF_STA_ON = 5, /* 101 */
WIFI_AP_ON_STA_OFF = 6, /* 110 */
WIFI_AP_STA_ON = 7, /* 111 */
/* temporary */
WIFI_AP_STOP = 8,
WIFI_AP_START = 9,
WIFI_STA_STOP = 10,
WIFI_STA_START = 11,
}
export enum WifiStatus {
WIFI_MODE_NULL = 0, /**< null mode */
WIFI_MODE_STA, /**< WiFi station mode */
WIFI_MODE_AP, /**< WiFi soft-AP mode */
WIFI_MODE_APSTA, /**< WiFi station + soft-AP mode */
}
export interface WiFiCredential extends ApiJsonMsg {
ssid?: string; ssid?: string;
password?: string; password?: string;
err?: string;
} }
export function wifi_get_scan_list() { export function wifi_get_scan_list() {
const msg : WiFiCredential = { const msg : WifiMsgOut = {
module: WtModuleID.WIFI, module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_GET_SCAN, cmd: WifiCmd.WIFI_API_JSON_GET_SCAN,
} }
sendJsonMsg(msg); sendJsonMsg(msg);
} }
export function wifi_sta_get_ap_info() { export function wifi_sta_get_ap_info() {
const msg : WiFiCredential = { const msg : WifiMsgOut = {
module: WtModuleID.WIFI, module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO, cmd: WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO,
} }
sendJsonMsg(msg); sendJsonMsg(msg);
} }
export function wifi_ap_get_info() { export function wifi_ap_get_info() {
const msg : WiFiCredential = { const msg : WifiMsgOut = {
module: WtModuleID.WIFI, module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_AP_GET_INFO, cmd: WifiCmd.WIFI_API_JSON_AP_GET_INFO,
} }
sendJsonMsg(msg); sendJsonMsg(msg);
} }
export function wifi_connect_to(ssid: string, password: string) { export function wifi_connect_to(ssid: string, password: string) {
const msg: WiFiCredential = { const msg: WifiMsgOut = {
module: WtModuleID.WIFI, module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_CONNECT, cmd: WifiCmd.WIFI_API_JSON_CONNECT,
ssid: ssid, ssid: ssid,
password: password, password: password,
@ -78,19 +50,6 @@ export function wifi_connect_to(ssid: string, password: string) {
} }
export interface WifiInfo extends ApiJsonMsg { export interface WifiInfo extends ApiJsonMsg {
rssi: number;
ssid: string;
password: string;
gateway: string;
ip: string;
mac: string;
netmask: string;
dns_main: string;
dns_backup: string;
wifiLogo?: string;
}
export interface WifiScanInfo extends ApiJsonMsg {
rssi: number; rssi: number;
ssid: string; ssid: string;
gateway: string; gateway: string;
@ -100,87 +59,6 @@ export interface WifiScanInfo extends ApiJsonMsg {
wifiLogo?: string; wifiLogo?: string;
} }
export interface WifiList extends ApiJsonMsg { export interface WifiList {
scan_list: Array<WifiScanInfo>; scan_list: Array<WifiInfo>;
} }
export interface IWifiMode extends ApiJsonMsg {
mode?: WifiMode;
status?: WifiStatus;
ap_on_delay?: number;
ap_off_delay?: number;
err?: string;
}
export function wifi_set_mode(req_mode: WifiMode, ap_on_delay = -1, ap_off_delay = -1) {
let msg: IWifiMode;
if (req_mode === WifiMode.WIFI_AP_AUTO_STA_ON && ap_on_delay !== -1 && ap_off_delay !== -1) {
msg = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_SET_MODE,
mode: req_mode,
ap_on_delay: ap_on_delay,
ap_off_delay: ap_off_delay,
};
} else {
msg = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_SET_MODE,
mode: req_mode,
};
}
sendJsonMsg(msg);
}
export function wifi_get_mode() {
const msg: IWifiMode = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_GET_MODE,
};
sendJsonMsg(msg);
}
export function wifi_ap_set_credential(ssid: string, password: string) {
const msg : WiFiCredential = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_AP_SET_CRED,
ssid: ssid,
password: password,
}
sendJsonMsg(msg);
}
export interface IWifiStaStaticInfo {
static_ip_en: number;
static_dns_en: number;
ip: string;
gateway: string;
netmask: string;
dns_main: string;
dns_backup: string;
}
export function wifi_sta_get_static_info() {
const msg: ApiJsonMsg = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_STA_GET_STATIC_INFO,
}
sendJsonMsg(msg);
}
export function wifi_sta_set_static_conf(static_info: IWifiStaStaticInfo) {
const msg: IWifiStaStaticInfo & ApiJsonMsg = {
module: WtModuleID.WIFI,
cmd: WifiCmd.WIFI_API_JSON_STA_SET_STATIC_CONF,
static_dns_en: static_info.static_dns_en,
static_ip_en: static_info.static_ip_en,
ip: static_info.ip,
gateway: static_info.gateway,
netmask: static_info.netmask,
dns_main: static_info.dns_main,
dns_backup: static_info.dns_backup,
}
sendJsonMsg(msg);
}

View File

@ -1,52 +0,0 @@
import type {WtModuleID} from "@/api/index";
export enum WtDataType {
RESERVED = 0x00,
/* primitive type */
EVENT = 0x02,
ROUTE_HDR = 0x03,
RAW_BROADCAST = 0x04,
/* broadcast data */
CMD_BROADCAST = 0x11,
/* targeted data */
RAW = 0x20,
CMD = 0x21,
RESPONSE = 0x22,
/* standard protocols */
PROTOBUF = 0x40,
JSON = 0x41,
MQTT = 0x42,
}
export interface ApiBinaryMsg {
data_type: WtDataType,
module: WtModuleID,
sub_mod: number,
payload: Uint8Array;
}
export function decodeHeader(arrayBuffer: ArrayBuffer) : ApiBinaryMsg {
// Create a DataView to access the data in the ArrayBuffer
const dataView = new DataView(arrayBuffer);
// Extract the data_type from the first byte
const data_type = dataView.getUint8(0) as WtDataType;
// Extract the module_id and sub_id from the next bytes
const module = dataView.getUint8(1);
const sub_mod = dataView.getUint8(2);
const payload = new Uint8Array(arrayBuffer.slice(4));
// Constructing the header object
return {
data_type,
module,
sub_mod,
payload,
};
}

View File

@ -1,5 +1,4 @@
import {getWebsocketService} from "@/composables/websocket/websocketService"; import {getWebsocketService} from "@/composables/websocket/websocketService";
import type {ApiBinaryMsg} from "@/api/binDataDef";
export interface ApiJsonMsg { export interface ApiJsonMsg {
module: number; module: number;
@ -27,34 +26,18 @@ export interface ControlMsg {
export interface ServerMsg { export interface ServerMsg {
type: "json" | "binary" type: "json" | "binary"
data: string | ArrayBuffer; data: ApiJsonMsg | object;
}
export enum WtModuleID {
WIFI = 1,
DATA_FLOW = 2,
UART = 4,
} }
export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) { export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) {
const msg: ServerMsg = { const msg: ServerMsg = {
type: "json", type: "json",
data: JSON.stringify(apiJsonMsg), data: apiJsonMsg,
}; };
getWebsocketService().send(msg); getWebsocketService().send(msg);
// toServer.postMessage(msg);
} }
export function sendBinMsg(binMsg: ApiBinaryMsg) { export function sendBinMsg(msg: ApiJsonMsg) {
const buffer = new Uint8Array(4 + binMsg.payload.length); // toServer.postMessage(JSON.stringify(msg));
buffer[0] = binMsg.data_type;
buffer[1] = binMsg.module;
buffer[2] = binMsg.sub_mod;
buffer[3] = 0; // Reserved byte
buffer.set(binMsg.payload, 4); // Append payload after header
const msg: ServerMsg = {
type: "binary",
data: buffer,
};
getWebsocketService().send(msg);
} }

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="m136-80-56-56 264-264H160v-80h320v320h-80v-184L136-80Zm344-400v-320h80v184l264-264 56 56-264 264h184v80H480Z"/></svg>

Before

Width:  |  Height:  |  Size: 206 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

Before

Width:  |  Height:  |  Size: 668 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.949 14.121 19.071 12a5.008 5.008 0 0 0 0-7.071 5.006 5.006 0 0 0-7.071 0l-.707.707 1.414 1.414.707-.707a3.007 3.007 0 0 1 4.243 0 3.005 3.005 0 0 1 0 4.243l-2.122 2.121a2.723 2.723 0 0 1-.844.57L13.414 12l1.414-1.414-.707-.707a4.965 4.965 0 0 0-3.535-1.465c-.235 0-.464.032-.691.066L3.707 2.293 2.293 3.707l18 18 1.414-1.414-5.536-5.536c.277-.184.538-.396.778-.636zm-6.363 3.536a3.007 3.007 0 0 1-4.243 0 3.005 3.005 0 0 1 0-4.243l1.476-1.475-1.414-1.414L4.929 12a5.008 5.008 0 0 0 0 7.071 4.983 4.983 0 0 0 3.535 1.462A4.982 4.982 0 0 0 12 19.071l.707-.707-1.414-1.414-.707.707z"></path></svg>

Before

Width:  |  Height:  |  Size: 667 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.465 11.293c1.133-1.133 3.109-1.133 4.242 0l.707.707 1.414-1.414-.707-.707c-.943-.944-2.199-1.465-3.535-1.465s-2.592.521-3.535 1.465L4.929 12a5.008 5.008 0 0 0 0 7.071 4.983 4.983 0 0 0 3.535 1.462A4.982 4.982 0 0 0 12 19.071l.707-.707-1.414-1.414-.707.707a3.007 3.007 0 0 1-4.243 0 3.005 3.005 0 0 1 0-4.243l2.122-2.121z"></path><path d="m12 4.929-.707.707 1.414 1.414.707-.707a3.007 3.007 0 0 1 4.243 0 3.005 3.005 0 0 1 0 4.243l-2.122 2.121c-1.133 1.133-3.109 1.133-4.242 0L10.586 12l-1.414 1.414.707.707c.943.944 2.199 1.465 3.535 1.465s2.592-.521 3.535-1.465L19.071 12a5.008 5.008 0 0 0 0-7.071 5.006 5.006 0 0 0-7.071 0z"></path></svg>

Before

Width:  |  Height:  |  Size: 712 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#5f6368"><path d="M120-120v-320h80v184l504-504H520v-80h320v320h-80v-184L256-200h184v80H120Z"/></svg>

Before

Width:  |  Height:  |  Size: 171 B

View File

@ -1,5 +1,5 @@
.text-layout { .text-layout {
@apply mx-auto max-w-2xl w-full sm:min-w-[640px] px-2 @apply m-auto max-w-2xl min-w-min px-2
} }
.page-title { .page-title {
@ -14,8 +14,3 @@
@apply text-blue-600 font-bold; @apply text-blue-600 font-bold;
cursor: default; cursor: default;
} }
.el-checkbox:hover {
background-color: var(--el-color-primary-light-9);
border-color: var(--el-color-primary-light-8);
}

View File

@ -18,5 +18,5 @@ export function logHelloMessage() {
" ██████████\n" + " ██████████\n" +
"\n" + "\n" +
"Logo是什么意义意义就是...没有意义。\n" + "Logo是什么意义意义就是...没有意义。\n" +
"大概是一起去整点赛博薯条吧。"); "大概是一起去整点赛博薯条吧。-来自法国鸽子");
} }

View File

@ -58,7 +58,6 @@ class OneTimeWebsocket implements IWebsocket {
/* did not receive packet "heartBeatTimeout" times, /* did not receive packet "heartBeatTimeout" times,
* connection may be lost: close the socket */ * connection may be lost: close the socket */
if (this.socket.readyState === this.socket.OPEN) { if (this.socket.readyState === this.socket.OPEN) {
console.log("No heart beat, break connection");
this.close(); this.close();
this.clear(); this.clear();
} }
@ -81,21 +80,31 @@ class OneTimeWebsocket implements IWebsocket {
return return
const msg: ServerMsg = { const msg: ServerMsg = {
data: ev.data, data: {},
type: "json", type: "json",
} }
if (typeof ev.data === "string") { if (typeof ev.data === "string") {
msg.type = "json" try {
msg.data = JSON.parse(ev.data) as ApiJsonMsg;
if ((msg.data as ApiJsonMsg).cmd === undefined ||
(msg.data as ApiJsonMsg).module === undefined
){
console.log("Server msg has no cmd or module");
return;
}
} catch (e) {
console.log(e);
return;
}
} else { } else {
msg.type = "binary"; msg.type = "binary";
msg.data = ev.data;
console.log(typeof ev.data);
} }
this.msgCallback(msg); this.msgCallback(msg);
} }
this.socket.onclose = (ev) => { this.socket.onclose = () => {
if (isDevMode()) {
console.log("ws closed", ev.reason, ev.code);
}
this.socket.onclose = null this.socket.onclose = null
this.socket.onopen = null this.socket.onopen = null
this.socket.onerror = null this.socket.onerror = null
@ -138,11 +147,13 @@ class OneTimeWebsocket implements IWebsocket {
if (this.socket.readyState !== WebSocket.OPEN) { if (this.socket.readyState !== WebSocket.OPEN) {
return; return;
} }
if (isDevMode()) {
console.log('WebSocket proxies data ', msg);
}
this.socket.send(msg.data); console.log('WebSocket proxies data ', msg);
if (msg.type === "binary") {
// this.socket.send(msg.data);
} else if (msg.type === "json") {
this.socket.send(JSON.stringify(msg.data));
}
} }
clear() { clear() {

View File

@ -4,7 +4,6 @@ import Wifi from '@/views/Wifi.vue'
import Feedback from '@/views/Feedback.vue' import Feedback from '@/views/Feedback.vue'
import About from '@/views/About.vue' import About from '@/views/About.vue'
import Uart from '@/views/Uart.vue' import Uart from '@/views/Uart.vue'
import Page404 from '@/views/404.vue'
import {translate} from "@/locales"; import {translate} from "@/locales";
@ -15,8 +14,7 @@ const router = createRouter({
path: '/', path: '/',
name: 'home', name: 'home',
meta: {title: translate("page.home")}, meta: {title: translate("page.home")},
// component: Wifi component: Home
redirect: () => '/wifi',
}, { }, {
path: '/home:ext(.*)', path: '/home:ext(.*)',
meta: {title: translate("page.home")}, meta: {title: translate("page.home")},
@ -38,10 +36,6 @@ const router = createRouter({
meta: {title: translate('page.feedback')}, meta: {title: translate('page.feedback')},
name: 'feedback', name: 'feedback',
component: Feedback, component: Feedback,
}, {
path: '/:catchAll(.*)', // This will match all paths that aren't matched by above routes
name: 'NotFound',
component: Page404,
}, },
] ]
}) })

View File

@ -1,11 +1,8 @@
import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api"; import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api";
import {isDevMode} from "@/composables/buildMode";
import {type ApiBinaryMsg, decodeHeader} from "@/api/binDataDef";
export interface IModuleCallback { export interface IModuleCallback {
ctrlCallback: (msg: ControlMsg) => void; ctrlCallback: (msg: ControlMsg) => void;
serverJsonMsgCallback: (msg: ApiJsonMsg) => void; serverMsgCallback: (msg: ServerMsg) => void;
serverBinMsgCallback: (msg: ApiBinaryMsg) => void;
} }
const moduleMap = new Map<number, IModuleCallback>(); const moduleMap = new Map<number, IModuleCallback>();
@ -24,48 +21,16 @@ export function unregisterModule(moduleId: number) {
} }
export function routeModuleServerMsg(msg: ServerMsg) { export function routeModuleServerMsg(msg: ServerMsg) {
if (msg.type === "json") { if (msg.type == "json") {
let jsonMsg: ApiJsonMsg; const module = (msg.data as ApiJsonMsg).module;
try {
jsonMsg = JSON.parse(msg.data as string) as ApiJsonMsg;
if (jsonMsg.cmd === undefined ||
jsonMsg.module === undefined
){
console.log("Server msg has no cmd or module", msg.data);
return;
}
} catch (e) {
console.log(e);
return;
}
const module = jsonMsg.module;
const moduleHandler = moduleMap.get(module); const moduleHandler = moduleMap.get(module);
if (moduleHandler) { if (moduleHandler) {
moduleHandler.serverJsonMsgCallback(jsonMsg); moduleHandler.serverMsgCallback(msg);
} else { } else {
if (isDevMode()) {
console.log("routeModuleServerMsg module not loaded", module); console.log("routeModuleServerMsg module not loaded", module);
} }
}
} else { } else {
const arr = msg.data as ArrayBuffer; console.log("routeModuleServerMsg ignored:", msg);
if (arr.byteLength < 4) {
if (isDevMode()) {
console.log("binary message too short");
}
return;
}
const binaryMsg = decodeHeader(msg.data as ArrayBuffer);
const moduleHandler = moduleMap.get(binaryMsg.module);
if (moduleHandler) {
moduleHandler.serverBinMsgCallback(binaryMsg);
} else {
if (isDevMode()) {
console.log("routeModuleServerMsg ignored:", msg, binaryMsg);
}
}
} }
} }

View File

@ -1,17 +0,0 @@
<script setup lang="ts">
</script>
<template>
<div class="text-layout">
<h1 class="page-title">404</h1>
<h2 class="text-center">页面不存在</h2>
<RouterLink to="/">
<el-card class="text-center">返回首页</el-card>
</RouterLink>
</div>
</template>
<style scoped>
</style>

View File

@ -1,66 +1,39 @@
<script setup lang="ts"> <script setup lang="ts">
const version = import.meta.env.VITE_APP_GIT_TAG || "v0.0.0"; const version = __APP_VERSION__;
const compileTime = import.meta.env.VITE_APP_LAST_COMMIT || "1970-00-00"; const compileTime = __BUILD_TIME__;
</script> </script>
<template> <template>
<div class="text-layout"> <div class="text-layout">
<el-divider></el-divider> <el-divider></el-divider>
<el-divider>关于</el-divider> <el-divider>关于上位机</el-divider>
<el-divider></el-divider> <el-divider></el-divider>
<el-collapse>
<el-collapse-item title="关于网页版上位机">
<el-descriptions border :column="1" class="mt-5 description-style"> <el-descriptions border :column="1" class="mt-5 description-style">
<el-descriptions-item label="版本">{{ version }}</el-descriptions-item> <el-descriptions-item label="上位机版本">{{version}}</el-descriptions-item>
<el-descriptions-item label="发布时间">{{compileTime}}</el-descriptions-item> <el-descriptions-item label="发布时间">{{compileTime}}</el-descriptions-item>
<el-descriptions-item label="许可证">MIT</el-descriptions-item> <el-descriptions-item label="许可证">MIT</el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-descriptions title="鸣谢" border :column="1" class="mt-5 description-style"> <el-descriptions title="鸣谢" 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 label="vuejs"><a href="https://github.com/vuejs/vue/blob/main/LICENSE">MIT</a></el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item label="typescript"><a <el-descriptions-item label="typescript"><a
href="https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt">Apache 2.0</a></el-descriptions-item> href="https://github.com/microsoft/TypeScript/blob/main/LICENSE.txt">Apache 2.0</a></el-descriptions-item>
<el-descriptions-item label="vite"><a target="_blank" href="https://github.com/vitejs/vite/blob/main/LICENSE">MIT</a> <el-descriptions-item label="vite"><a href="https://github.com/vitejs/vite/blob/main/LICENSE">MIT</a></el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="tailwindcss"><a href="https://github.com/tailwindlabs/tailwindcss/blob/master/LICENSE">MIT</a></el-descriptions-item>
<el-descriptions-item label="tailwindcss"><a
href="https://github.com/tailwindlabs/tailwindcss/blob/master/LICENSE">MIT</a></el-descriptions-item>
<el-descriptions-item label="element-plus"><a <el-descriptions-item label="element-plus"><a
href="https://github.com/element-plus/element-plus/blob/dev/LICENSE">MIT</a></el-descriptions-item> href="https://github.com/element-plus/element-plus/blob/dev/LICENSE">MIT</a></el-descriptions-item>
<el-descriptions-item label="pinia"><a target="_blank" href="https://github.com/vuejs/pinia/blob/v2/LICENSE">MIT</a> <el-descriptions-item label="pinia"><a href="https://github.com/vuejs/pinia/blob/v2/LICENSE">MIT</a></el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="mitt"><a href="https://github.com/developit/mitt/blob/main/LICENSE">MIT</a></el-descriptions-item>
<el-descriptions-item label="mitt"><a target="_blank" href="https://github.com/developit/mitt/blob/main/LICENSE">MIT</a> <el-descriptions-item label="vue-router"><a href="https://github.com/vuejs/vue-router/blob/dev/LICENSE">MIT</a></el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="vue-i18n"><a href="https://github.com/kazupon/vue-i18n?tab=MIT-1-ov-file#readme">MIT</a></el-descriptions-item>
<el-descriptions-item label="vue-router"><a <el-descriptions-item label="lightningcss"><a href="https://github.com/parcel-bundler/lightningcss/blob/master/LICENSE">MPL-2.0 license</a></el-descriptions-item>
href="https://github.com/vuejs/vue-router/blob/dev/LICENSE">MIT</a></el-descriptions-item>
<el-descriptions-item label="vue-i18n"><a target="_blank" href="https://github.com/kazupon/vue-i18n?tab=MIT-1-ov-file#readme">MIT</a>
</el-descriptions-item>
<el-descriptions-item label="lightningcss"><a
href="https://github.com/parcel-bundler/lightningcss/blob/master/LICENSE">MPL-2.0 license</a>
</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item>
<el-collapse-item title="关于下位机">
<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>
<el-descriptions title="鸣谢" 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>
</el-collapse-item>
</el-collapse>
<el-descriptions title="作者:空空(kerms)" border :column="1" class="mt-5 description-style"> <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-item label="github"><a href="https://github.com/kerms">https://github.com/kerms</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="邮箱">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 label="BiliBili"><a href="https://space.bilibili.com/38669852">UID38669852</a></el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item label="QQ群">642246000</el-descriptions-item> <el-descriptions-item label="QQ群">642246000</el-descriptions-item>
<el-descriptions-item label="备注">欢迎大家来打扰啊</el-descriptions-item> <el-descriptions-item label="备注">欢迎大家来打扰啊</el-descriptions-item>
</el-descriptions> </el-descriptions>

View File

@ -1,18 +1,15 @@
<template> <template>
<div class="text-layout"> <div class="text-layout">
<h2 class="page-title">主页</h2> <h2 class="page-title">主页</h2>
<p>空空如也暂不知道放什么</p>
<router-link to="/wifi">
<el-card>
<p class="text-center">Wi-Fi设置</p>
</el-card>
</router-link>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import InlineSvg from "@/components/InlineSvg.vue";
</script> </script>
<style scoped>
</style>

View File

@ -5,6 +5,7 @@
</h1> </h1>
<el-divider></el-divider> <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">连接Wi-Fi</h2>
<el-form label-width="auto" ref="formRef" :model="ssidValidateForm" class="m-auto"> <el-form label-width="auto" ref="formRef" :model="ssidValidateForm" class="m-auto">
<el-form-item <el-form-item
@ -44,82 +45,38 @@
clearable clearable
/> />
</el-form-item> </el-form-item>
<div class="mb-2">
<el-alert type="info" show-icon>
如果不是通过透传器的热点连接更换Wi-Fi将导致此界面与透传器断开连接
</el-alert>
</div>
<div class="flex justify-center"> <div class="flex justify-center">
<el-button @click="onConnectClick" type="primary">连接</el-button> <el-button @click="onConnectClick" type="primary">连接</el-button>
</div> </div>
</el-form> </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>
<div class="flex shrink-0">
<el-tooltip effect="light">
<template #content>
<p>热点+终端模式并存会影响稳定性且保持热点开启会增加功耗</p>
<p>
<el-text size="small">智能模式</el-text>
成功连接Wi-Fi30秒后自动关闭热点断开连接5秒后自动打开热点
</p>
<p>
<el-text size="small">热点+终端共存模式</el-text>
方便使用但是影响稳定性
</p>
<p>
<el-text size="small">单热点模式缺点</el-text>
无网络
</p>
</template>
<InlineSvg name="help" class="w-3.5 h-3.5 text-gray-500 cursor-help"></InlineSvg>
</el-tooltip>
</div>
<el-select v-model="wifiMode" :disabled="wsStore.state != ControlEvent.CONNECTED">
<el-option
v-for="item in wifiModeOptions"
:key="item.key"
:value="item.key"
:label="item.label"
/>
</el-select>
<el-button type="primary" @click="wifiChangeMode" :loading="wifiMode_loading">保存</el-button>
</div>
<el-divider></el-divider> <el-divider></el-divider>
<el-descriptions <el-descriptions
title="Wi-Fi终端(STA)信息" title="Wi-Fi终端信息"
:column="1" :column="1"
border border
class="description-style" class="description-style"
> >
<template #extra> <el-descriptions-item label="asd">
<el-switch v-model="wifiSta_On" :disabled="wsStore.state != ControlEvent.CONNECTED || !wifiAp_On"
active-text="已开启" inactive-text="未开启" :loading="wifiMode_loading"
:before-change="()=>beforeWifiModeChange('STA')"
/>
</template>
<el-descriptions-item span="4">
<template #label > <template #label >
<div> <div>
信号强度 信号强度
</div> </div>
</template> </template>
<template #default > <template #default >
<p>{{ wifiStaApInfo.rssi }}</p> {{ wifiStaApInfo.rssi }}
</template> </template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item span="4"> <el-descriptions-item span="1">
<template #label> <template #label>
<div> <div>
Wi-Fi名(SSID) SSID
</div> </div>
</template> </template>
<p>{{ wifiStaApInfo.ssid }}</p> {{ wifiStaApInfo.ssid }}
</el-descriptions-item> </el-descriptions-item>
<!-- <el-descriptions-item span="6" >--> <!-- <el-descriptions-item span="6" >-->
<!-- <template #label>--> <!-- <template #label>-->
@ -127,21 +84,23 @@
<!-- 密码--> <!-- 密码-->
<!-- </div>--> <!-- </div>-->
<!-- </template>--> <!-- </template>-->
<!-- <password-viewer :password="wifiStaApInfo.password"></password-viewer>--> <!-- <password-viewer password="asdasdasd"></password-viewer>-->
<!-- </el-descriptions-item>--> <!-- </el-descriptions-item>-->
<el-descriptions-item span="4">
<template #label>
<div>
IP
</div>
</template>
{{ wifiStaApInfo.ip }}
</el-descriptions-item>
<el-descriptions-item span="4"> <el-descriptions-item span="4">
<template #label> <template #label>
<div> <div>
MAC MAC
</div> </div>
</template> </template>
<p>{{ wifiStaApInfo.mac }}</p> {{ wifiStaApInfo.mac }}
</el-descriptions-item>
<el-descriptions-item span="4">
<template #label>
<div>IP(内网地址)</div>
</template>
<p>{{ wifiStaApInfo.ip }}</p>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item span="4"> <el-descriptions-item span="4">
<template #label> <template #label>
@ -149,139 +108,43 @@
网关 网关
</div> </div>
</template> </template>
<p>{{ wifiStaApInfo.gateway }}</p> {{ wifiStaApInfo.gateway }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item span="4"> <el-descriptions-item span="4">
<template #label> <template #label>
<div> <div>
掩码 掩码
</div> </div>
</template> </template>
<p>{{ wifiStaApInfo.netmask }}</p> {{ wifiStaApInfo.netmask }}
</el-descriptions-item>
<el-descriptions-item span="4">
<template #label>
<div>
首选DNS
</div>
</template>
<p>{{ wifiStaApInfo.dns_main }}</p>
</el-descriptions-item>
<el-descriptions-item span="4">
<template #label>
<div>
备用DNS
</div>
</template>
<p>{{ wifiStaApInfo.dns_backup }}</p>
</el-descriptions-item>
<el-descriptions-item span="4">
<template #label>
<div>
IP分配模式
</div>
</template>
<el-select v-model="wifiStaticInfo.static_ip_en" :disabled="wsStore.state != ControlEvent.CONNECTED">
<el-option
v-for="item in staIPModeOptions"
:key="item.key"
:value="item.key"
:label="item.label"
/>
</el-select>
</el-descriptions-item>
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_ip_en">
<template #label>
<div>IP(内网地址)</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>
网关
</div>
</template>
<el-input v-model="wifiStaticInfo.gateway"></el-input>
</el-descriptions-item>
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_ip_en">
<template #label>
<div>
掩码
</div>
</template>
<el-input v-model="wifiStaticInfo.netmask"></el-input>
</el-descriptions-item>
<el-descriptions-item span="4">
<template #label>
<div>
DNS模式
</div>
</template>
<el-select v-model="wifiStaticInfo.static_dns_en" :disabled="wsStore.state != ControlEvent.CONNECTED">
<el-option
v-for="item in staDNSModeOptions"
:key="item.key"
:value="item.key"
:label="item.label"
/>
</el-select>
</el-descriptions-item>
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_dns_en">
<template #label>
<div>
首选DNS
</div>
</template>
<el-input v-model="wifiStaticInfo.dns_main"></el-input>
</el-descriptions-item>
<el-descriptions-item span="4" v-if="wifiStaticInfo.static_dns_en">
<template #label>
<div>
备用DNS
</div>
</template>
<el-input v-model="wifiStaticInfo.dns_backup"></el-input>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<div class="flex justify-center mt-4">
<el-button type="primary" :loading="wifiMode_loading" @click="wifiStaSetStaticInfo">保存</el-button>
</div>
<el-divider></el-divider> <el-divider></el-divider>
<el-descriptions <el-descriptions
title="Wi-Fi自发热点(AP)信息" title="Wi-Fi热点信息"
:column="1" :column="1"
border border
class="description-style" class="description-style"
> >
<template #extra>
<el-switch v-model="wifiAp_On" :disabled="wsStore.state != ControlEvent.CONNECTED || !wifiSta_On"
:loading="wifiMode_loading" active-text="已开启" inactive-text="未开启"
:before-change="()=>beforeWifiModeChange('AP')"
/>
</template>
<el-descriptions-item span="6"> <el-descriptions-item span="6">
<template #label> <template #label>
<div> <div>
Wi-Fi名(SSID) SSID
</div> </div>
</template> </template>
<div class="flex"> {{ wifiApInfo.ssid }}
<el-input v-model="wifiApInfo.ssid"></el-input>
</div>
</el-descriptions-item>
<el-descriptions-item span="6">
<template #label>
<div>
密码
</div>
</template>
<el-input v-model="wifiApInfo.password"></el-input>
</el-descriptions-item> </el-descriptions-item>
<!-- <el-descriptions-item span="6">-->
<!-- <template #label>-->
<!-- <div>-->
<!-- 密码-->
<!-- </div>-->
<!-- </template>-->
<!-- <password-viewer password="asdasdasd"></password-viewer>-->
<!-- </el-descriptions-item>-->
<el-descriptions-item span="4"> <el-descriptions-item span="4">
<template #label> <template #label>
<div> <div>
@ -318,9 +181,7 @@
{{ wifiApInfo.netmask }} {{ wifiApInfo.netmask }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<div class="flex justify-center mt-4">
<el-button type="primary" :loading="wifiMode_loading" @click="wifiApChangeCredential">保存</el-button>
</div>
<el-divider></el-divider> <el-divider></el-divider>
</div> </div>
@ -329,32 +190,22 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, onMounted, onUnmounted, reactive, ref} from "vue"; import {computed, onMounted, onUnmounted, reactive, ref} from "vue";
import { import {
type IWifiMode,
wifi_ap_get_info,
wifi_ap_set_credential,
wifi_connect_to,
wifi_get_mode,
wifi_get_scan_list,
wifi_set_mode,
wifi_sta_get_ap_info, wifi_sta_get_ap_info,
wifi_get_scan_list,
WifiCmd, WifiCmd,
type WifiInfo, type WifiInfo,
type WifiList, type WifiList,
WifiMode, WifiModuleID,
type WifiScanInfo, wifi_ap_get_info
type WiFiCredential,
WifiStatus, wifi_sta_get_static_info,
type IWifiStaStaticInfo, wifi_sta_set_static_conf,
} from "@/api/apiWifi"; } from "@/api/apiWifi";
import type {FormInstance} from "element-plus"; import type {FormInstance} from "element-plus";
import InlineSvg from "@/components/InlineSvg.vue"; import InlineSvg from "@/components/InlineSvg.vue";
import type {ApiJsonMsg, ControlMsg} from "@/api"; import type {ApiJsonMsg, ControlMsg, ServerMsg} from "@/api";
import {ControlEvent, ControlMsgType, WtModuleID} from "@/api"; import {ControlEvent, ControlMsgType} from "@/api";
import {registerModule, unregisterModule} from "@/router/msgRouter"; import {registerModule, unregisterModule} from "@/router/msgRouter";
import {useWsStore} from "@/stores/websocket"; import {useWsStore} from "@/stores/websocket";
import {globalNotify, globalNotifyRightSide} from "@/composables/notification"; import {globalNotify, globalNotifyRightSide} from "@/composables/notification";
import {isDevMode} from "@/composables/buildMode";
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
let wifiListPlaceholder = ref("我的WIFI") let wifiListPlaceholder = ref("我的WIFI")
@ -363,29 +214,6 @@ let ssidValidateForm = reactive({
password: "", password: "",
}) })
let wifiSta_On = ref(false);
const wifiMode_loading = ref(false)
let wifiAp_On = ref(false);
let wifiMode = ref(-1);
let wifiModeOptions = [
{
label: "智能热点+常开终端 (AP+STA)",
key: WifiMode.WIFI_AP_AUTO_STA_ON,
}, {
label: "仅开启热点 (AP)",
key: WifiMode.WIFI_AP_ON_STA_OFF,
}, {
label: "[不推荐] 常开热点+常开终端 (AP+STA)",
key: WifiMode.WIFI_AP_STA_ON,
}, /* {
value: "仅开启终端STA",
key: 2,
},*/
]
let wsStore = useWsStore(); let wsStore = useWsStore();
@ -395,49 +223,17 @@ const defWifiInfo: WifiInfo = {
gateway: "未连接", gateway: "未连接",
ip: "未连接", ip: "未连接",
mac: "未连接", mac: "未连接",
dns_main: "未连接",
dns_backup: "未连接",
rssi: 0, rssi: 0,
netmask: "未连接", netmask: "未连接",
ssid: "未连接", ssid: "未连接",
password: "",
} }
const staIPModeOptions = [
{
label: "自动 (DHCP)",
key: 0,
}, {
label: "静态IP",
key: 1,
},
]
const staDNSModeOptions = [
{
label: "自动 (使用网关)",
key: 0,
}, {
label: "静态DNS",
key: 1,
},
]
let wifiStaApInfo = reactive<WifiInfo>({...defWifiInfo}); let wifiStaApInfo = reactive<WifiInfo>({...defWifiInfo});
let wifiApInfo = reactive<WifiInfo>({...defWifiInfo}); let wifiApInfo = reactive<WifiInfo>({...defWifiInfo});
let wifiStaticInfo = reactive<IWifiStaStaticInfo>({
dns_backup: "0.0.0.0",
dns_main: "0.0.0.0",
gateway: "0.0.0.0",
ip: "0.0.0.0",
netmask: "0.0.0.0",
static_dns_en: 0,
static_ip_en: 0,
});
let scanning = ref(false); let scanning = ref(false);
let scan_cb: any; let scan_cb: any;
let connectBtnClicked = 0; let options: Array<WifiInfo> = [];
let options: Array<WifiScanInfo> = [];
const scanText = computed(() => { const scanText = computed(() => {
return scanning.value ? "扫描中" : "扫描"; return scanning.value ? "扫描中" : "扫描";
}); });
@ -450,28 +246,25 @@ const querySearch = (queryString: string, cb: any) => {
} }
} }
const onClientMsg = (msg: ApiJsonMsg) => { const onClientMsg = (msg: ServerMsg) => {
switch (msg.cmd as WifiCmd) { if (msg.type !== "json") {
return;
}
let json = msg.data as ApiJsonMsg;
switch (json.cmd as WifiCmd) {
case WifiCmd.UNKNOWN: case WifiCmd.UNKNOWN:
break; break;
case WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO: { case WifiCmd.WIFI_API_JSON_STA_GET_AP_INFO: {
const info = msg as WifiInfo; const info = msg.data as WifiInfo;
if (info.rssi === 0) {
Object.assign(wifiStaApInfo, defWifiInfo);
} else {
Object.assign(wifiStaApInfo, info); Object.assign(wifiStaApInfo, info);
}
if (connectBtnClicked) {
connectBtnClicked = 0;
globalNotifyRightSide(wifiStaApInfo.ssid + " 连接成功", "success");
wifi_sta_get_static_info();
}
break; break;
} }
case WifiCmd.WIFI_API_JSON_CONNECT: case WifiCmd.WIFI_API_JSON_CONNECT:
break; break;
case WifiCmd.WIFI_API_JSON_GET_SCAN: { case WifiCmd.WIFI_API_JSON_GET_SCAN: {
const list = msg as WifiList; const list = msg.data as WifiList;
scanning.value = false; scanning.value = false;
list.scan_list.forEach(value => { list.scan_list.forEach(value => {
if (value.rssi > -50) { if (value.rssi > -50) {
@ -493,65 +286,10 @@ const onClientMsg = (msg: ApiJsonMsg) => {
case WifiCmd.WIFI_API_JSON_DISCONNECT: case WifiCmd.WIFI_API_JSON_DISCONNECT:
break; break;
case WifiCmd.WIFI_API_JSON_AP_GET_INFO: { case WifiCmd.WIFI_API_JSON_AP_GET_INFO: {
const info = msg as WifiInfo; const info = msg.data as WifiInfo;
Object.assign(wifiApInfo, info); Object.assign(wifiApInfo, info);
break; break;
} }
case WifiCmd.WIFI_API_JSON_SET_MODE:
wifi_get_mode();
/* falls through */
case WifiCmd.WIFI_API_JSON_GET_MODE: {
const modeInfo = msg as IWifiMode;
wifiMode_loading.value = false;
if (modeInfo.err !== undefined) {
globalNotifyRightSide("设置失败", "error");
return;
}
if (modeInfo.status !== undefined) {
wifiAp_On.value = modeInfo.status === WifiStatus.WIFI_MODE_AP || modeInfo.status === WifiStatus.WIFI_MODE_APSTA;
wifiSta_On.value = modeInfo.status === WifiStatus.WIFI_MODE_STA || modeInfo.status === WifiStatus.WIFI_MODE_APSTA;
}
if (modeInfo.mode !== undefined) {
if (modeInfo.mode < WifiMode.WIFI_AP_STOP) {
wifiMode.value = modeInfo.mode;
} else if (modeInfo.mode === WifiMode.WIFI_AP_START) {
wifiAp_On.value = true;
} else if (modeInfo.mode === WifiMode.WIFI_AP_STOP) {
wifiAp_On.value = false;
} else if (modeInfo.mode === WifiMode.WIFI_STA_START) {
wifiSta_On.value = true;
} else if (modeInfo.mode === WifiMode.WIFI_STA_STOP) {
wifiSta_On.value = false;
}
}
break;
}
case WifiCmd.WIFI_API_JSON_AP_SET_CRED: {
const wifiCred = msg as WiFiCredential;
if (wifiCred.err !== undefined) {
globalNotifyRightSide(wifiCred.err, "error");
} else {
globalNotifyRightSide("已保存配置", "success");
}
wifiMode_loading.value = false;
break;
}
case WifiCmd.WIFI_API_JSON_STA_GET_STATIC_INFO: {
const staticInfo = msg as IWifiStaStaticInfo & ApiJsonMsg;
console.log("@@@", staticInfo);
Object.assign(wifiStaticInfo, staticInfo);
break;
}
case WifiCmd.WIFI_API_JSON_STA_SET_STATIC_CONF:
wifiMode_loading.value = false;
break;
default:
if (isDevMode()) {
console.log(msg);
}
break;
} }
}; };
@ -568,8 +306,6 @@ const onClientCtrl = (msg: ControlMsg) => {
if (msg.data === ControlEvent.CONNECTED) { if (msg.data === ControlEvent.CONNECTED) {
wifi_sta_get_ap_info(); wifi_sta_get_ap_info();
wifi_ap_get_info(); wifi_ap_get_info();
wifi_get_mode();
wifi_sta_get_static_info();
} }
}; };
@ -587,60 +323,23 @@ function onConnectClick() {
globalNotify("调试器未连接", 'error'); globalNotify("调试器未连接", 'error');
return; return;
} }
if (ssidValidateForm.wifiSsid !== "") { console.log(ssidValidateForm.wifiSsid, ssidValidateForm.password);
wifi_connect_to(ssidValidateForm.wifiSsid, ssidValidateForm.password);
connectBtnClicked = 1;
}
}
function beforeWifiModeChange(ap_sta: "AP" | "STA" = "AP") {
if (ap_sta === "AP") {
wifiMode_loading.value = true;
wifi_set_mode(wifiAp_On.value ? WifiMode.WIFI_AP_STOP : WifiMode.WIFI_AP_START);
} else {
wifiMode_loading.value = true;
wifi_set_mode(wifiSta_On.value ? WifiMode.WIFI_STA_STOP : WifiMode.WIFI_STA_START);
}
wifi_sta_get_ap_info();
return false;
}
function wifiChangeMode() {
wifiMode_loading.value = true;
wifi_set_mode(wifiMode.value);
}
function wifiApChangeCredential() {
if (wifiApInfo.ssid === "") {
globalNotifyRightSide("请输入AP名称", "error");
return;
}
wifiMode_loading.value = true;
wifi_ap_set_credential(wifiApInfo.ssid, wifiApInfo.password);
}
function wifiStaSetStaticInfo() {
wifiMode_loading.value = true;
wifi_sta_set_static_conf(wifiStaticInfo);
wifi_sta_get_ap_info();
} }
onMounted(() => { onMounted(() => {
registerModule(WtModuleID.WIFI, { registerModule(WifiModuleID, {
ctrlCallback: onClientCtrl, ctrlCallback: onClientCtrl,
serverJsonMsgCallback: onClientMsg, serverMsgCallback: onClientMsg
serverBinMsgCallback: () => {},
}); });
wifi_sta_get_ap_info(); wifi_sta_get_ap_info();
wifi_ap_get_info(); wifi_ap_get_info();
wifi_get_mode();
wifi_sta_get_static_info();
}); });
onUnmounted(() => { onUnmounted(() => {
unregisterModule(WtModuleID.WIFI); unregisterModule(WifiModuleID);
}); });
</script> </script>

View File

@ -1,31 +1,25 @@
<template> <template>
<nav class="relative px-2 py-0.5 sm:py-1 flex justify-between items-center border-b h-full"> <nav class="relative px-2 py-1 flex justify-between items-center border-b">
<div class="flex"> <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 p-3">
<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"> <svg class="block h-4 w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<title>导航侧栏</title> <title>导航侧栏</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path> <path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
</svg> </svg>
</button> </button>
<router-link to="/" class="text-3xl px-4 font-bold leading-none hidden items-center sm:flex" title="走,去码头整点薯条"> <router-link to="/" class="text-3xl px-4 font-bold leading-none" title="走,去码头整点薯条">
<InlineSvg name="favicon" class="h-5 lg:h-8"></InlineSvg> <InlineSvg name="favicon" class="h-10"></InlineSvg>
</router-link> </router-link>
<!-- <a class="text-3xl px-4 font-bold leading-none" href="/">--> <!-- <a class="text-3xl px-4 font-bold leading-none" href="/">-->
<!-- <InlineSvg name="home" class="h-10"></InlineSvg>--> <!-- <InlineSvg name="home" class="h-10"></InlineSvg>-->
<!-- </a>--> <!-- </a>-->
<!-- <router-link to="/" class="flex items-center text-sm text-blue-600 font-bold">主页</router-link>--> <!-- <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>--> <!-- <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>
</div> </div>
<div class="flex"> <div class="flex">
<ul class="hidden absolute top-1/2 left-1/2 transform -translate-y-1/2 -translate-x-1/2 sm:flex sm:mx-auto sm:items-center sm:w-auto sm:space-x-6"> <ul class="hidden absolute top-1/2 left-1/2 transform -translate-y-1/2 -translate-x-1/2 md:flex md:mx-auto md:items-center md:w-auto md:space-x-6">
<li v-for="(item, index) in menuItems" :key="index" class="router-link"> <li v-for="(item, index) in menuItems" :key="index" class="router-link">
<router-link :to="item.href" :class="item?.class">{{item.name}}</router-link> <router-link :to="item.href" :class="item?.class">{{item.name}}</router-link>
</li> </li>
@ -33,23 +27,13 @@
</div> </div>
<!-- <a class="md:ml-auto md:mr-3"></a>--> <!-- <a class="md:ml-auto md:mr-3"></a>-->
<div class="flex h-full"> <div class="flex">
<div id="page-spec-slot" class="content-center h-full flex flex-row"></div> <el-button @click="stateMenuOpen=true" :type="wsColor" size="large" class="transition duration-1000">
<div class="lg:hidden"> <InlineSvg v-show="wsColor!=='success'" name="wifi-exclamation" class="mr-2" width="20"></InlineSvg>
<el-button :type="wsColor" size="small" class="transition duration-1000 min-h-full"> <InlineSvg v-show="wsColor==='success'" name="wifi-3" class="mr-2" width="20"></InlineSvg>
<InlineSvg v-show="wsColor!=='success'" name="link-off" class="mr-2" width="20"></InlineSvg> {{ wsState }}
<InlineSvg v-show="wsColor==='success'" name="link" class="mr-2" width="20"></InlineSvg>
<div class="text-xs sm:text-sm lg:text-base">{{ wsState }}</div>
</el-button> </el-button>
</div> </div>
<div class="hidden lg:flex">
<el-button :type="wsColor" size="large" class="transition duration-1000 min-h-full">
<InlineSvg v-show="wsColor!=='success'" name="link-off" class="mr-2" width="20"></InlineSvg>
<InlineSvg v-show="wsColor==='success'" name="link" class="mr-2" width="20"></InlineSvg>
<div class="text-base">{{ wsState }}</div>
</el-button>
</div>
</div>
</nav> </nav>
<div :class='["custom-drawer", {open: sideMenuOpen}]'> <div :class='["custom-drawer", {open: sideMenuOpen}]'>
<el-drawer <el-drawer
@ -58,7 +42,7 @@
size="" size=""
:direction="'ltr'" :direction="'ltr'"
> >
<div :class="[sideMenuItemClass]" class="pr-6 flex text-gray-500" @click="sideMenuOpen=false"> <div id="testborder" :class="[sideMenuItemClass]" class="pr-6 flex text-gray-500" @click="sideMenuOpen=false">
<InlineSvg name="cross" class="h-6"></InlineSvg> <InlineSvg name="cross" class="h-6"></InlineSvg>
<div> <div>
<p class="h-6 flex items-center">{{ $t("page.close") }}</p> <p class="h-6 flex items-center">{{ $t("page.close") }}</p>
@ -75,12 +59,9 @@
<template #footer> <template #footer>
<div> <div>
<el-button @click="toggle"> <p class="text-xs text-center text-gray-400">
<InlineSvg v-if="!isFullscreen" name="open-in-full" width="16px" fill="#000000"></InlineSvg> <span>Copyright <a href="http://github.com/kerms">kerms</a> 2024</span>
<p v-if="!isFullscreen">全屏</p> </p>
<InlineSvg v-if="isFullscreen" name="close-fullscreen" width="16px" fill="#000000"></InlineSvg>
<p v-if="isFullscreen">缩小</p>
</el-button>
</div> </div>
</template> </template>
</el-drawer> </el-drawer>
@ -109,12 +90,8 @@ import {computed, ref} from "vue";
import {useWsStore} from "@/stores/websocket"; import {useWsStore} from "@/stores/websocket";
import {translate} from "@/locales"; import {translate} from "@/locales";
import {ControlEvent} from "@/api"; import {ControlEvent} from "@/api";
import {useRoute} from "vue-router";
import { useFullscreen } from '@vueuse/core'
const wsStore = useWsStore(); const wsStore = useWsStore();
const {isFullscreen, toggle} = useFullscreen();
const route = useRoute();
const sideMenuItemClass = "block p-4 text-sm font-semibold hover:bg-blue-50 hover:text-blue-600 rounded" const sideMenuItemClass = "block p-4 text-sm font-semibold hover:bg-blue-50 hover:text-blue-600 rounded"
const sideMenuOpen = ref(false); const sideMenuOpen = ref(false);
@ -140,17 +117,12 @@ const wsState = computed(() => {
return translate(wsStore.state); return translate(wsStore.state);
}); });
type Item = { const menuItems = ([
name: string; {
href: string;
class?: string;
};
const menuItems: Item[] = ([
/* {
name: translate("page.home"), name: translate("page.home"),
href: "/", href: "/",
}, */{
}, {
name: translate("page.wifi"), name: translate("page.wifi"),
href: "/wifi", href: "/wifi",
}, { }, {
@ -159,6 +131,10 @@ const menuItems: Item[] = ([
}, { }, {
name: translate("page.feedback"), name: translate("page.feedback"),
href: "/feedback", href: "/feedback",
}, {
name: translate("page.uart"),
href: "/uart",
class: "todo-menu-item",
}, },
]); ]);
@ -170,20 +146,16 @@ const menuItems: Item[] = ([
border: solid 1px; border: solid 1px;
}*/ }*/
/* drawer */
.custom-drawer :deep(.el-drawer) {
transition: all 0.1s; /* Custom duration*/
}
/* drawer overlay */ /* drawer overlay */
.custom-drawer :deep(.el-overlay) { .custom-drawer.open :deep(.el-overlay) {
transition: all 0s; /* Custom duration*/ transition: all 0s; /* Custom duration*/
} }
.custom-drawer :deep(.el-drawer) {
transition: all 0s; /* Custom duration*/
}
.custom-drawer.open :deep(.el-drawer) {
transition: all 0.05s; /* Custom duration*/
}
.custom-drawer :deep(.el-drawer__body) { .custom-drawer :deep(.el-drawer__body) {
padding: 0; padding: 0;
} }

View File

@ -2,18 +2,15 @@ import { fileURLToPath, URL } from 'node:url'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import {ConfigEnv, defineConfig, loadEnv} from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import svgLoader from "vite-svg-loader"; import svgLoader from "vite-svg-loader";
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js"; import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
import { viteSingleFile } from 'vite-plugin-singlefile' import { viteSingleFile } from 'vite-plugin-singlefile'
import packageJson from "./package.json"
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default ({mode}: ConfigEnv) => { export default defineConfig({
process.env = {...process.env, ...loadEnv(mode, process.cwd())};
return defineConfig({
plugins: [ plugins: [
vue(), vue(),
AutoImport({ AutoImport({
@ -26,14 +23,17 @@ export default ({mode}: ConfigEnv) => {
cssInjectedByJsPlugin(), cssInjectedByJsPlugin(),
viteSingleFile(), viteSingleFile(),
], ],
define: {}, define: {
'__APP_VERSION__': JSON.stringify(packageJson.version),
'__BUILD_TIME__': JSON.stringify(new Date().toISOString()),
},
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': fileURLToPath(new URL('./src', import.meta.url))
} }
}, },
cacheDir: process.env.VITE_CACHE_DIR || undefined, cacheDir: "/tmp/zhuang/cache",
worker: { worker: {
rollupOptions: { rollupOptions: {
output: { output: {
@ -52,7 +52,7 @@ export default ({mode}: ConfigEnv) => {
}, },
build: { build: {
// target: 'es2015', // target: 'es2015',
outDir: process.env.VITE_OUTPUT_DIR || undefined, outDir: '/tmp/zhuang/dap-web-dist/',
emptyOutDir: true, emptyOutDir: true,
cssMinify: 'lightningcss', cssMinify: 'lightningcss',
@ -116,4 +116,3 @@ export default ({mode}: ConfigEnv) => {
} }
}, },
}) })
};