initial commit
|
@ -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"
|
||||
},
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
|
@ -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']
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
|
@ -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>
|
|
@ -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>;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface ApiJsonMsg {
|
||||
module: number;
|
||||
cmd: number;
|
||||
}
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -0,0 +1,4 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind variants;
|
|
@ -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>
|
|
@ -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));
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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;
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
disconnected: "disconnected"
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
export default {
|
||||
DISCONNECTED: "未连接",
|
||||
CONNECTED: "已连接",
|
||||
CONNECTING: "连接中",
|
||||
|
||||
WS: {
|
||||
DISCONNECTED: "未连接",
|
||||
CONNECTED: "已连接",
|
||||
CONNECTING: "连接中",
|
||||
},
|
||||
|
||||
PAGE: {
|
||||
HOME: "主页",
|
||||
ABOUT: "关于",
|
||||
FEEDBACK: "反馈",
|
||||
},
|
||||
}
|
|
@ -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')
|
|
@ -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
|
|
@ -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 }
|
||||
})
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,12 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|