initial commit

This commit is contained in:
kerms 2024-03-22 14:06:06 +08:00
commit 1bae314449
43 changed files with 1336 additions and 0 deletions

18
.eslintrc.cjs Normal file
View File

@ -0,0 +1,18 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'vue/multi-word-component-names': "off"
},
}

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/
!.vscode/extensions.json
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
package-lock.json

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

1
README.md Normal file
View File

@ -0,0 +1 @@
# 允斯调试器的内嵌网页版上位机

9
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
}

20
components.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
ElButton: typeof import('element-plus/es')['ElButton']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElInput: typeof import('element-plus/es')['ElInput']
InlineSvg: typeof import('./src/components/InlineSvg.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

46
package.json Normal file
View File

@ -0,0 +1,46 @@
{
"name": "vue-project",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"element-plus": "^2.6.1",
"mitt": "^3.0.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-i18n": "^9.10.2",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node20": "^20.1.2",
"@types/node": "^20.11.28",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"lightningcss": "^1.24.1",
"npm-run-all2": "^6.1.2",
"postcss": "^8.4.38",
"prettier": "^3.0.3",
"tailwindcss": "^3.4.1",
"typescript": "~5.4.0",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.1.6",
"vite-svg-loader": "^5.1.0",
"vue-tsc": "^2.0.6"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

64
src/App.vue Normal file
View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import {useWsStore} from "@/stores/websocket";
import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef";
import type {IWebsocketService} from "@/composables/websocket/websocketService";
import {ControlEvent, ControlMsgType} from "@/composables/broadcastChannelDef";
import {getWebsocketService} from "@/composables/websocket/websocketService";
import {onMounted, onUnmounted} from "vue";
const wsState = useWsStore();
const onClientCtrl = (msg: ControlMsg) => {
console.log("App.vue:", msg);
if (msg.type === ControlMsgType.WS_EVENT) {
wsState.$patch({state: msg.data as ControlEvent})
}
};
const onServerMsg = (msg: ServerMsg) => {
console.log("App.vue:", msg);
};
let websocketService: IWebsocketService;
onMounted(() => {
// const host = window.location
console.log("App.vue mounted")
const host = "192.168.43.61";
websocketService = getWebsocketService();
websocketService.init(host, onServerMsg, onClientCtrl);
});
onUnmounted(() => {
});
import NavBar from "@/views/navigation/NavBar.vue";
</script>
<template>
<header>
<nav-bar/>
</header>
<p class="m-0">This is body</p>
<RouterView/>
<p>end</p>
<!-- <p>{{ test }}</p>-->
<el-button type="danger"><p>test</p></el-button>
</template>
<style>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
.router-active {
background-color: #666;
cursor: default;
}
</style>

55
src/api/apiWifi.ts Normal file
View File

@ -0,0 +1,55 @@
import {sendJsonMsg} from '@/composables/broadcastChannelDef'
import {type ApiJsonMsg} from '@/api'
const WifiModuleID = 1;
enum WifiCmd {
UNKNOWN = 0,
WIFI_API_JSON_GET_AP_INFO,
WIFI_API_JSON_CONNECT,
WIFI_API_JSON_GET_SCAN,
WIFI_API_JSON_DISCONNECT,
}
interface WifiMsgOut extends ApiJsonMsg {
ssid?: string;
password?: string;
}
export function wifi_get_scan_list() {
const msg : WifiMsgOut = {
module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_GET_SCAN,
}
sendJsonMsg(msg);
}
export function wifi_get_ap_info() {
const msg : WifiMsgOut = {
module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_GET_AP_INFO,
}
sendJsonMsg(msg);
}
export function wifi_connect_to(ssid: string, password: string) {
const msg: WifiMsgOut = {
module: WifiModuleID,
cmd: WifiCmd.WIFI_API_JSON_CONNECT,
ssid: ssid,
password: password,
}
sendJsonMsg(msg);
}
export interface WifiInfo {
rssi: number;
ssid: string;
mac: string;
wifiLogo?: string;
}
export interface WifiList {
scan_list: Array<WifiInfo>;
}

4
src/api/index.ts Normal file
View File

@ -0,0 +1,4 @@
export interface ApiJsonMsg {
module: number;
cmd: number;
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"></path></svg>

After

Width:  |  Height:  |  Size: 369 B

1
src/assets/icon/home.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M240-200h120v-240h240v240h120v-360L480-740 240-560v360Zm-80 80v-480l320-240 320 240v480H520v-240h-80v240H160Zm320-350Z"/></svg>

After

Width:  |  Height:  |  Size: 201 B

3
src/assets/icon/test.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<path d="M128 192h768v128H128zm0 256h512v128H128zm0 256h768v128H128zm576-352 192 160-192 128z"></path>
</svg>

After

Width:  |  Height:  |  Size: 178 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="18" r="2"></circle></svg>

After

Width:  |  Height:  |  Size: 105 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.671 14.307C16.184 12.819 14.17 12 12 12s-4.184.819-5.671 2.307l1.414 1.414c1.11-1.11 2.621-1.722 4.257-1.722 1.636.001 3.147.612 4.257 1.722l1.414-1.414z"></path><circle cx="12" cy="18" r="2"></circle></svg>

After

Width:  |  Height:  |  Size: 280 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.671 14.307C16.184 12.819 14.17 12 12 12s-4.184.819-5.671 2.307l1.414 1.414c1.11-1.11 2.621-1.722 4.257-1.722 1.636.001 3.147.612 4.257 1.722l1.414-1.414z"></path><path d="M20.437 11.292c-4.572-4.573-12.301-4.573-16.873 0l1.414 1.414c3.807-3.807 10.238-3.807 14.045 0l1.414-1.414z"></path><circle cx="12" cy="18" r="2"></circle></svg>

After

Width:  |  Height:  |  Size: 406 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 6c3.537 0 6.837 1.353 9.293 3.809l1.414-1.414C19.874 5.561 16.071 4 12 4c-4.071.001-7.874 1.561-10.707 4.395l1.414 1.414C5.163 7.353 8.463 6 12 6zm5.671 8.307c-3.074-3.074-8.268-3.074-11.342 0l1.414 1.414c2.307-2.307 6.207-2.307 8.514 0l1.414-1.414z"></path><path d="M20.437 11.293c-4.572-4.574-12.301-4.574-16.873 0l1.414 1.414c3.807-3.807 10.238-3.807 14.045 0l1.414-1.414z"></path><circle cx="12" cy="18" r="2"></circle></svg>

After

Width:  |  Height:  |  Size: 502 B

4
src/assets/tailwind.css Normal file
View File

@ -0,0 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;

View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
const props = defineProps({
name: {
type: String,
required: true,
},
});
const icon = defineAsyncComponent(() =>
import('@/assets/icon/' + `${props.name}.svg`)
);
</script>
<template>
<component :is="icon" class="fill-current" />
</template>
<style scoped>
</style>

View File

@ -0,0 +1,43 @@
import {type ApiJsonMsg} from "@/api"
import {getWebsocketService} from "@/composables/websocket/websocketService";
export const toServer = new BroadcastChannel("toServer");
export const toClient = new BroadcastChannel("toClient");
export const toWebsocketCtrl = new BroadcastChannel("toWebsocketCtrl");
export const toClientCtrl = new BroadcastChannel("toClientCtrl");
export enum ControlMsgType {
WS_EVENT = "WS_EVENT",
WS_SET_HOST = "WS_SET_HOST",
WS_GET_STATE = "WS_GET_STATE",
}
export enum ControlEvent {
DISCONNECTED = "DISCONNECTED",
LOADED = "LOADED",
CONNECTED = "CONNECTED",
CONNECTING = "CONNECTING",
}
export interface ControlMsg {
type: ControlMsgType,
data: ControlEvent | string,
}
export interface ServerMsg {
type: "json" | "binary"
data: object
}
export function sendJsonMsg(apiJsonMsg: ApiJsonMsg) {
const msg: ServerMsg = {
type: "json",
data: apiJsonMsg,
};
getWebsocketService().send(msg);
// toServer.postMessage(msg);
}
export function sendBinMsg(msg: ApiJsonMsg) {
// toServer.postMessage(JSON.stringify(msg));
}

View File

@ -0,0 +1,116 @@
import MyWorker from '@/composables/websocket/ws.sharedworker?sharedworker&inline'
import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper";
import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef";
import {ControlMsgType, toClient, toClientCtrl} from "@/composables/broadcastChannelDef";
const toServer = new BroadcastChannel("toServer");
export interface IWebsocketService {
init(host: string,
msgCallback: (msg: ServerMsg) => any,
ctrlCallback: (msg: ControlMsg) => any)
: void;
deinit(): void;
send(msg: ServerMsg): void;
}
/**
* Websocket that run in a shared worker, shared across tabs
*/
class WebsocketShared implements IWebsocketService{
private static instance: IWebsocketService;
private worker: SharedWorker;
private msgCallback: (msg: ServerMsg) => any;
private ctrlCallback: (msg: ControlMsg) => any;
public static getInstance(): IWebsocketService {
if (!WebsocketShared.instance) {
WebsocketShared.instance = new WebsocketShared();
}
return WebsocketShared.instance;
}
private constructor() {
console.log("Shared Websocket init")
this.msgCallback = () => {}
this.ctrlCallback = () => {}
this.messageEventProxy = this.messageEventProxy.bind(this);
this.controlEventProxy = this.controlEventProxy.bind(this);
this.worker = new MyWorker();
}
init(host: string, msgCallback: (msg: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void {
console.log("webworker init")
toClient.removeEventListener("message", this.messageEventProxy);
toClientCtrl.removeEventListener("message", this.controlEventProxy);
this.msgCallback = msgCallback;
this.ctrlCallback = ctrlCallback;
toClient.addEventListener("message", this.messageEventProxy);
toClientCtrl.addEventListener("message", this.controlEventProxy);
// this.worker.port.onmessage = this.controlEventProxy;
this.worker.port.postMessage({type: ControlMsgType.WS_SET_HOST, data: host} as ControlMsg)
}
deinit(): void {
toClient.removeEventListener("message", this.messageEventProxy);
toClientCtrl.removeEventListener("message", this.controlEventProxy);
}
send(msg: ServerMsg): void {
console.log("Websocket Service send (not really)", msg)
toServer.postMessage(msg);
}
messageEventProxy(ev: MessageEvent<ServerMsg>) {
this.msgCallback(ev.data);
}
controlEventProxy(ev: MessageEvent<ControlMsg>) {
this.ctrlCallback(ev.data);
}
}
class WebsocketClassic implements IWebsocketService{
private static instance: IWebsocketService;
private socket: WebsocketWrapper;
private static count: number = 0;
public static getInstance(): IWebsocketService {
if (!WebsocketClassic.instance) {
WebsocketClassic.instance = new WebsocketClassic();
}
return WebsocketClassic.instance;
}
private constructor() {
this.socket = new WebsocketWrapper();
WebsocketClassic.count++;
}
init(host: string, msgCallback: (ev: ServerMsg) => any, ctrlCallback: (msg: ControlMsg) => any): void {
console.log("Websocket Service INIT called", WebsocketClassic.count);
this.socket.init(host, msgCallback, ctrlCallback);
}
deinit(): void {
}
send(msg: ServerMsg): void {
this.socket.send(msg);
}
}
export function getWebsocketService(): IWebsocketService {
if (typeof SharedWorker !== 'undefined') {
return WebsocketShared.getInstance();
} else {
return WebsocketClassic.getInstance();
}
}

View File

@ -0,0 +1,210 @@
import type {ServerMsg, ControlMsg} from "@/composables/broadcastChannelDef";
import {ControlEvent, ControlMsgType} from "@/composables/broadcastChannelDef";
interface IWebsocket {
close(): void;
send(msg: ServerMsg): void;
}
class WebsocketDummy implements IWebsocket {
close(): void {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
send(msg: ServerMsg) {
}
}
class OneTimeWebsocket implements IWebsocket {
private readonly heartBeatTimeout: number = 2;
private readonly host: string;
private readonly intervalId: number;
private readonly msgCallback: (ev: ServerMsg) => any;
private readonly ctrlCallback: (msg: ControlMsg) => any;
private readonly closeCallback: () => void;
private socket: WebSocket;
private heartBeatTimeCount: number;
private stoped: boolean;
constructor(host: string,
msgCallback: (ev: ServerMsg) => any,
ctrlCallback: (msg: ControlMsg) => any,
closeCallback: () => void
) {
this.host = host;
this.stoped = false;
this.msgCallback = msgCallback;
this.ctrlCallback = ctrlCallback;
this.closeCallback = closeCallback;
this.heartBeatTimeCount = 0;
this.intervalId = 0;
this.socket = new WebSocket(`ws://${this.host}/ws`);
this.setSocketProp();
this.intervalId = setInterval(() => {
if (this.heartBeatTimeCount > this.heartBeatTimeout) {
/* did not receive packet "heartBeatTimeout" times,
* connection may be lost: close the socket */
this.socket.close();
console.log("interval: ", this.heartBeatTimeCount, "state: ", this.socket.readyState);
}
this.heartBeatTimeCount++;
}, 2000);
}
private setSocketProp() {
this.socket.binaryType = 'arraybuffer'
this.heartBeatTimeCount = 0;
this.socket.onmessage = (ev) => {
this.heartBeatTimeCount = 0;
if (!ev.data)
return
const msg: ServerMsg = {
data: {},
type: "json",
}
this.msgCallback(msg);
if (typeof ev.data === "string") {
try {
msg.data = JSON.parse(ev.data);
this.msgCallback(msg);
} catch (e) {
return;
}
} else {
console.log(typeof ev.data);
}
}
this.socket.onclose = () => {
console.log('WebSocket Disconnected');
clearInterval(this.intervalId);
this.socket.onclose = null
this.socket.onopen = null
this.socket.onerror = null
this.socket.onmessage = null;
const msg: ControlMsg = {
type: ControlMsgType.WS_EVENT,
data: ControlEvent.DISCONNECTED,
}
this.ctrlCallback(msg);
this.closeCallback();
};
this.socket.onerror = (error) => {
console.error('WebSocket Error', error);
this.socket.close();
};
this.socket.onopen = ev => {
console.log('WebSocket Connected');
if (this.stoped) {
this.close();
return;
}
this.heartBeatTimeCount = 0;
const msg: ControlMsg = {
type: ControlMsgType.WS_EVENT,
data: ControlEvent.CONNECTED,
}
this.ctrlCallback(msg);
};
const msg: ControlMsg = {
type: ControlMsgType.WS_EVENT,
data: ControlEvent.CONNECTING,
}
this.ctrlCallback(msg);
}
close() {
this.stoped = true;
this.socket.close();
}
send(msg: ServerMsg) {
if (this.socket.readyState !== WebSocket.OPEN) {
return;
}
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));
}
}
}
export class WebsocketWrapper {
private socket: IWebsocket;
private msgCallback: (ev: ServerMsg) => any;
private ctrlCallback: (msg: ControlMsg) => any;
private timeoutId: number;
private host: string;
private stoped: boolean;
constructor() {
this.socket = new WebsocketDummy();
this.msgCallback = () => {};
this.ctrlCallback = () => {};
this.timeoutId = 0;
this.host = "";
this.stoped = false;
this.closeCallback = this.closeCallback.bind(this);
}
init(host: string,
msgCallback: (ev: ServerMsg) => any,
ctrlCallback: (msg: ControlMsg) => any)
{
if (this.host === host) {
return;
}
this.newConnection(host, msgCallback, ctrlCallback);
}
private newConnection(host: string,
msgCallback: (ev: ServerMsg) => any,
ctrlCallback: (msg: ControlMsg) => any)
{
this.socket.close();
this.host = host;
this.msgCallback = msgCallback;
this.ctrlCallback = ctrlCallback;
this.socket = new OneTimeWebsocket(host, this.msgCallback, this.ctrlCallback, this.closeCallback);
}
private closeCallback() {
if (this.stoped) {
return;
}
this.timeoutId = setTimeout(() =>
this.newConnection(this.host, this.msgCallback, this.ctrlCallback),
2000);
}
deinit() {
this.socket.close();
this.stoped = true;
clearTimeout(this.timeoutId);
}
send(msg: ServerMsg) {
console.log('WebSocket send: not ready', msg);
// this.socket.send(msg)
}
}

View File

@ -0,0 +1,39 @@
declare const self: SharedWorkerGlobalScope;
import {WebsocketWrapper} from "@/composables/websocket/websocketWrapper";
import type {ControlMsg, ServerMsg} from "@/composables/broadcastChannelDef";
import {ControlEvent, ControlMsgType, toClient, toClientCtrl, toServer} from "@/composables/broadcastChannelDef";
const websocket = new WebsocketWrapper();
let host = "";
function ctrlBroadcast(msg: ControlMsg) {
toClientCtrl.postMessage(msg);
}
function msgBroadcast(msg: ServerMsg) {
toClient.postMessage(msg);
}
// self.onconnect
self.onconnect = function(event) {
const port = event.ports[0];
port.onmessage = function (e: MessageEvent<ControlMsg>) {
console.log('Received message in SharedWorker:', e.data);
if (e.data.type === ControlMsgType.WS_SET_HOST) {
if (host === "" && e.data.data !== "") {
host = e.data.data;
websocket.init(host, msgBroadcast, ctrlBroadcast);
}
}
};
const msg: ControlMsg = {
type: ControlMsgType.WS_EVENT,
data: ControlEvent.LOADED,
}
port.postMessage(msg);
};
toServer.onmessage = (ev: MessageEvent<ServerMsg>) => {
websocket.send(ev.data);
};

21
src/i18n.ts Normal file
View File

@ -0,0 +1,21 @@
import { createI18n } from 'vue-i18n';
import zh from '@/locales/zh'
import en from '@/locales/en'
// const locale = localStorage.getItem('lang') || 'zh';
const locale = 'zh';
console.log("langggggg:", locale);
const i18n = createI18n({
globalInjection: true,
legacy: false,
locale: locale,
fallbackLocale: 'zh',
messages: {
zh,
// en,
}
});
export default i18n;

3
src/locales/en.ts Normal file
View File

@ -0,0 +1,3 @@
export default {
disconnected: "disconnected"
};

17
src/locales/zh.ts Normal file
View File

@ -0,0 +1,17 @@
export default {
DISCONNECTED: "未连接",
CONNECTED: "已连接",
CONNECTING: "连接中",
WS: {
DISCONNECTED: "未连接",
CONNECTED: "已连接",
CONNECTING: "连接中",
},
PAGE: {
HOME: "主页",
ABOUT: "关于",
FEEDBACK: "反馈",
},
}

16
src/main.ts Normal file
View File

@ -0,0 +1,16 @@
import '@/assets/tailwind.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import i18n from '@/i18n';
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(i18n);
app.mount('#app')

29
src/router/index.ts Normal file
View File

@ -0,0 +1,29 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Wifi from '@/views/Wifi.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: Home
}, {
path: '/home:ext(.*)',
component: Home,
}, {
path: '/wifi:ext(.*)',
component: Wifi,
}, {
path: '/about:ext(.*)',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('@/views/About.vue')
}
]
})
export default router

12
src/stores/counter.ts Normal file
View File

@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

11
src/stores/websocket.ts Normal file
View File

@ -0,0 +1,11 @@
import {defineStore} from "pinia";
import {ControlEvent} from "@/composables/broadcastChannelDef";
export const useWsStore = defineStore('websocket', {
state: () => {
return {
state: ControlEvent.DISCONNECTED as ControlEvent,
dummy_var: 1 as number,
};
},
});

12
src/views/About.vue Normal file
View File

@ -0,0 +1,12 @@
<template>
<h2>About Page</h2>
<!-- Your about page content goes here -->
<h2>About Page</h2>
</template>
<script setup lang="ts">
let name = "about-a";
</script>

13
src/views/Gpio.vue Normal file
View File

@ -0,0 +1,13 @@
<template>
<h4>Skewed</h4>
<input class="tgl tgl-skewed" id="cb3" type="checkbox" checked/>
<label class="tgl-btn" data-tg-off="低电平" data-tg-on="高电平" for="cb3"></label>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

23
src/views/Home.vue Normal file
View File

@ -0,0 +1,23 @@
<template>
<h2>Home Page</h2>
<!-- Your home page content goes here -->
<h2>Home Page</h2>
<nav>
<!-- <RouterLink to="/">Home</RouterLink>-->
<!-- <RouterLink to="/wifi">Wifi</RouterLink>-->
<!-- <RouterLink to="/about">About</RouterLink>-->
<!-- <RouterLink to="/test">Test</RouterLink>-->
<!-- <RouterLink to="/home">home</RouterLink>-->
</nav>
</template>
<script setup lang="ts">
document.title = "Home";
</script>
<style scoped>
</style>

158
src/views/Wifi.vue Normal file
View File

@ -0,0 +1,158 @@
<template>
<div class="wifiView">
<h2>Wifi View</h2>
<el-form label-width="auto" ref="formRef" :model="ssidValidateForm">
<el-form-item
label="Wi-Fi名"
prop="wifiSsid"
:rules="[
{ required: true, message: '请输入WIFI名'},
]"
>
<div class="flex w-full">
<el-autocomplete
v-model="ssidValidateForm.wifiSsid"
clearable
:placeholder="wifiListPlaceholder"
class="inline-input w-full"
:fetch-suggestions="querySearch"
value-key="ssid"
>
<template #default="{ item }">
<div class="flex">
<InlineSvg :name="item.wifiLogo" class="h-6 pr-4"></InlineSvg>
<!-- <span class="w-10">{{ item.rssi }}</span>-->
<div>{{ item.ssid }}</div>
</div>
</template>
</el-autocomplete>
<div class="h-8">
<el-button class="h-8" @click="onScanClick">扫描</el-button>
</div>
</div>
</el-form-item>
<el-form-item label="密码">
<el-input
v-model="password"
show-password
type="password"
clearable
/>
</el-form-item>
<div class="flex justify-center">
<el-button @click="onConnect" type="primary">连接</el-button>
</div>
</el-form>
</div>
<el-button class="h-8">扫描</el-button>
<el-button type="primary">连接</el-button>
</template>
<script setup lang="ts">
import {inject, onMounted, onUnmounted, reactive, ref} from "vue";
import {wifi_get_ap_info, wifi_get_scan_list, type WifiInfo, type WifiList} from "@/api/apiWifi";
import type {FormInstance} from "element-plus";
import type {ServerMsg,ControlMsg} from "@/composables/broadcastChannelDef";
import {
ControlEvent,
ControlMsgType
} from "@/composables/broadcastChannelDef";
import InlineSvg from "@/components/InlineSvg.vue";
import {getWebsocketService} from "@/composables/websocket/websocketService";
const formRef = ref<FormInstance>()
let wifiListPlaceholder = ref("我的WIFI")
let ssidValidateForm = reactive({
wifiSsid: ""
})
const password = ref('')
let scanning = false;
let scan_cb: any;
let options: Array<WifiInfo> = [
]
const querySearch = (queryString: string, cb: any) => {
if (scanning) {
scan_cb = cb;
} else {
cb(options);
}
}
const onClientMsg = (ev: MessageEvent<ServerMsg>) => {
if (ev.data.type !== "json") {
return;
}
const json: object = ev.data.data;
let wifiList: WifiList;
try {
wifiList = ev.data.data as WifiList;
console.log(wifiList);
} catch (e) {
return;
}
scanning = false;
wifiList.scan_list.forEach(value => {
if (value.rssi > -50) {
value.wifiLogo = "wifi-3";
} else if (value.rssi > -65) {
value.wifiLogo = "wifi-2";
} else {
value.wifiLogo = "wifi-1";
}
});
options = wifiList.scan_list;
if (scan_cb) {
scan_cb(options);
scan_cb = null;
}
};
const onClientCtrl = (ev: MessageEvent<ControlMsg>) => {
if (ev.data.type !== ControlMsgType.WS_EVENT) {
return
}
if (ev.data.data === ControlEvent.CONNECTED) {
wifi_get_ap_info();
}
};
function onScanClick() {
scanning = true;
wifi_get_scan_list();
}
function onConnect() {
console.log(ssidValidateForm.wifiSsid, password.value);
}
onMounted(() => {
});
onUnmounted(() => {
});
</script>
<style scoped>
.wifiView {
background-color: bisque;
border-radius: 5px;
padding: 20px;
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<nav class="relative px-4 py-1 flex justify-between items-center border-b">
<div class="flex">
<button @click.prevent="sideMenuOpen=true" class="flex items-center hover:text-blue-600 p-3">
<svg class="block h-4 w-4 fill-current" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<title>Mobile menu</title>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
</svg>
</button>
<router-link to="/" class="text-3xl px-4 font-bold leading-none">
<InlineSvg name="home" class="h-10"></InlineSvg>
</router-link>
<!-- <a class="text-3xl px-4 font-bold leading-none" href="/">-->
<!-- <InlineSvg name="home" class="h-10"></InlineSvg>-->
<!-- </a>-->
<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>
<div class="flex">
<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><router-link to="/wifi" title="Wifi" class="text-sm text-gray-400 hover:text-gray-800">wifi</router-link></li>
<!-- <li><a class="text-sm text-gray-400 hover:text-gray-500" href="#">Home</a></li>-->
<!-- <li><a class="text-sm text-blue-600 font-bold">About Us</a></li>-->
<!-- <li><a class="text-sm text-gray-400 hover:text-gray-500" href="#">Services</a></li>-->
</ul>
</div>
<!-- <a class="md:ml-auto md:mr-3"></a>-->
<div class="flex">
<button @click="stateMenuOpen=true"
class="py-2 px-6 bg-blue-500 hover:bg-blue-600 text-sm text-white font-bold rounded-xl transition duration-200">
<span class="flex">
<svg class="mr-2" width="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 19.4998H12.01M2 8.81929C3.69692 7.30051 5.74166 6.16236 8 5.53906M5 12.8584C5.86251 12.0129 6.87754 11.3223 8 10.8319M16 5.53906C18.2583 6.16236 20.3031 7.30051 22 8.81929M16 10.8319C17.1225 11.3223 18.1375 12.0129 19 12.8584M12 4.5V15.4998" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>{{ wsState }}</span>
</span>
</button>
<!-- <span>{{ $t("Disconnected") }}</span>-->
</div>
</nav>
<div :class='["custom-drawer", {open: sideMenuOpen}]'>
<el-drawer
v-model="sideMenuOpen"
:with-header="false"
size=""
:direction="'ltr'">
<div :class="[sideMenuItemClass]" class="px-6" @click="sideMenuOpen=false">
<InlineSvg name="cross" class="w-6"></InlineSvg>
</div>
<div class="flex-col justify-between m-4 mt-0">
<div>
<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.name }}</router-link>
<!-- <a :href="item.href" :class="sideMenuItemClass">{{ item.name }}</a>-->
</li>
</ul>
</div>
<div class="mt-auto">
<p class="my-4 text-xs text-center text-gray-400">
<span>Copyright kerms 2024</span>
</p>
</div>
</div>
</el-drawer>
</div>
<div :class='["custom-drawer", {open: stateMenuOpen}]'>
<el-drawer
v-model="stateMenuOpen"
:with-header="false"
size=""
modal-class="bg-amber-400 bg-opacity-0"
:direction="'rtl'">
<div class="flex-col justify-between m-4 bg-white">
<div class="mt-auto">
<div class="pt-6">
<a class="block px-4 py-3 mb-3 leading-loose text-xs text-center font-semibold bg-gray-50 hover:bg-gray-100 rounded-xl"
href="#">Sign in</a>
<a class="block px-4 py-3 mb-2 leading-loose text-xs text-center text-white font-semibold bg-blue-600 hover:bg-blue-700 rounded-xl"
href="#">Sign Up</a>
</div>
</div>
</div>
</el-drawer>
</div>
</template>
<script lang="ts" setup>
import InlineSvg from "@/components/InlineSvg.vue";
import {computed, ref, toRef} from "vue";
import {useWsStore} from "@/stores/websocket";
import {useI18n} from "vue-i18n";
const { t } = useI18n()
const wsStore = useWsStore();
const sideMenuItemClass = "block p-4 text-sm font-semibold text-gray-400 hover:bg-blue-50 hover:text-blue-600 rounded"
const sideMenuOpen = ref(false);
const stateMenuOpen = ref(false)
const wsState = computed(() => {
return t(wsStore.state);
});
const menuItems = ([
{
name: "Home",
href: "/",
}, {
name: "About Us",
href: "/about",
}, {
name: "Services",
href: "/",
}, {
name: "Wifi",
href: "/wifi",
}, {
name: "Contact",
href: "/",
}, {
name: "6",
href: "/",
},
]);
</script>
<style scoped>
/*nav *{
border: solid 1px;
}*/
/* drawer */
.custom-drawer :deep(.el-drawer) {
transition: all 0.1s; /* Custom duration*/
}
/* drawer overlay */
.custom-drawer.open :deep(.el-overlay) {
transition: all 0s; /* Custom duration*/
}
.custom-drawer :deep(.el-drawer__body) {
padding: 0;
}
</style>

12
tailwind.config.js Normal file
View File

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

27
tsconfig.app.json Normal file
View File

@ -0,0 +1,27 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [
"env.d.ts",
"src/**/*",
"src/**/*.vue",
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"node_modules/@vue/runtime-core/dist/runtime-core.d.ts"
],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"lib": [
"DOM", /* for document.* */
"WebWorker", /* for *WorkerGlobalScope */
"ESNext"
]
}
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
tsconfig.node.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

77
vite.config.ts Normal file
View File

@ -0,0 +1,77 @@
import { fileURLToPath, URL } from 'node:url'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import svgLoader from "vite-svg-loader";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
svgLoader(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
build: {
outDir: '/tmp/zhuang/dap-web-dist/',
emptyOutDir: true,
cssMinify: 'lightningcss',
rollupOptions: {
output: {
assetFileNames: (assetInfo) => {
if (!assetInfo || !assetInfo.name) {
return 'default-filename.ext';
}
const info = assetInfo.name.split(".");
let extType = info[info.length - 1];
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = "img";
} else if (/woff|woff2/.test(extType)) {
extType = "css";
} else if (/css/.test(extType)) {
extType = "css";
return "style.css"
}
// return `[name]-[hash][extname]`;
return `[name][extname]`;
},
// chunkFileNames: "[name]-[hash].js",
chunkFileNames: "[name].js",
// entryFileNames: "[name]-[hash].js",
entryFileNames: (chunkInfo) => {
// console.log(chunkInfo)
return "script.js"
},
sourcemapFileNames: "map-[name].js",
// sanitizeFileName: "anit-[name].js",
// entryFileNames: (chunkInfo) => {
// console.log(chunkInfo)
// return `${chunkInfo.name}.js`
// },
manualChunks(id) {
/* if (id.match('.*!/src/.*shared[a-zA-Z0-9-_]*[.](ts|js).*')) {
// Prevent bundling node_modules into common chunks
return 'bundle-shared';
}
else */{
// Prevent bundling node_modules into common chunks
return 'script'
}
},
},
}
},
})