初始化

This commit is contained in:
2025-07-01 21:36:08 +08:00
commit 5f904364bf
51 changed files with 18760 additions and 0 deletions

5
.env.development Normal file
View File

@@ -0,0 +1,5 @@
# .env.development
VUE_APP_API_URL="123123123"
# VUE_APP_FEATURE_FLAG=true

5
.env.production Normal file
View File

@@ -0,0 +1,5 @@
# .env.production
VUE_APP_API_URL="434534534"
# VUE_APP_FEATURE_FLAG=true

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
# tk-page
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

19
jsconfig.json Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

14254
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
package.json Normal file
View File

@@ -0,0 +1,46 @@
{
"name": "tk-page",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"@ffmpeg/core": "^0.12.10",
"@ffmpeg/util": "^0.12.2",
"@vueuse/core": "^13.1.0",
"axios": "^1.8.4",
"core-js": "^3.8.3",
"echarts": "^5.6.0",
"element-plus": "^2.9.7",
"ffmpeg-wasm": "^1.0.1",
"h264-converter": "^0.1.4",
"lodash": "^4.17.21",
"msgpack-lite": "^0.1.26",
"pinia": "^3.0.1",
"qwebchannel": "^6.2.0",
"socket.io-client": "^4.8.1",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"less": "^4.2.2",
"less-loader": "^12.2.0",
"postcss-preset-env": "^10.1.5",
"postcss-px-to-viewport": "^1.1.1",
"postcss-px-viewport": "^0.0.4",
"postcss-viewport-units": "^0.1.6"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

13
postcss.config.js Normal file
View File

@@ -0,0 +1,13 @@
// module.exports = {
// plugins: {
// 'postcss-px-to-viewport': {
// viewportWidth: 1600, // 视窗的宽度,对应设计稿宽度
// viewportHeight: 900, // 视窗的高度,对应设计稿高度
// unitPrecision: 3, // 指定 px 转换为视窗单位值的小数位数
// viewportUnit: 'vw', // 指定需要转换成的视窗单位vw 或者 vh
// selectorBlackList: ['.ignore', '.hairlines'], // 指定不需要转换的类
// minPixelValue: 1, // 小于或等于 1 px 不转换为视窗单位
// mediaQuery: false // 允许在媒体查询中转换 px
// }
// }
// };

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

32
public/index.html Normal file
View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= webpackConfig.name %>
</title>
<!-- <script src="qrc:///qtwebchannel/qwebchannel.js"></script> -->
</head>
<body>
<noscript>
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it
to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<style>
body {
margin: 0;
padding: 0;
/* width: 1600px;
height: 900px; */
}
</style>
</html>

71
src/App.vue Normal file
View File

@@ -0,0 +1,71 @@
<template>
<div>
<router-view v-if="isRouterAlive" />
</div>
</template>
<script>
export default {
name: 'App',
provide() {
return {
reload: this.reload
}
},
data() {
return {
isRouterAlive: true
}
},
methods: {
reload() {
// 先将组件隐藏
this.isRouterAlive = false
// 使用nextTick确保DOM更新后再重新显示组件
this.$nextTick(() => {
this.isRouterAlive = true
})
}
}
}
const debounce = (fn, delay) => {
let timer
return (...args) => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn(...args)
}, delay)
}
}
const _ResizeObserver = window.ResizeObserver
window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
constructor(callback) {
callback = debounce(callback, 200)
super(callback)
}
}
</script>
<style>
/* App.vue */
.layout {
display: grid;
grid-template-rows: auto 1fr;
height: 100vh;
}
/* #vite-error-overlay { display: none !important; } 隐藏vite错误提示 */
.control-area {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
touch-action: none;
/* 禁用默认滚动 */
}
</style>

47
src/api/account.js Normal file
View File

@@ -0,0 +1,47 @@
import { getAxios, postAxios, downFile } from '@/utils/axios.js'
export function apiGetCart() {
return getAxios({ url: '/cgi-bin/cart/latest' })
}
export function login(data) {
return postAxios({ url: 'api/account/login', data })
}
export function cheekalive(data) {
return postAxios({ url: 'api/account/cheekalive', data })
}
export function tkhostdata(data) {
return postAxios({ url: 'api/tkinfo/tkhostdata', data })
}
export function dicts(data) {
return postAxios({ url: 'api/param/dicts', data })
}
export function tkhostdetail(data) {
return postAxios({ url: 'api/tkinfo/tkhostdetail', data })
}
//导出表格
export function exporthosts(data) {
return postAxios({ url: 'api/export/hostsinfo', data })
}
export function downList(url, data) {
return downFile(url, data)
}
//查询tk账号查询次数
export function tkaccountuseinfo(data) {
return postAxios({ url: 'api/tkinfo/tkaccountuseinfo', data })
}
//查询员工
export function getStaffList(data) {
return postAxios({ url: 'api/account/list', data })
}
//分配主播
export function managerhosts(data) {
return postAxios({ url: 'api/account/managerhosts', data })
}
//编辑主播
export function upholdinfo(data) {
return postAxios({ url: 'api/tkinfo/upholdinfo', data })
}
//获取
export function getCountryinfo(data) {
return postAxios({ url: 'api/tkinfo/countryinfo', data })
}

7
src/api/adb.js Normal file
View File

@@ -0,0 +1,7 @@
import { getAxios, postAxios, downFile } from '@/utils/axios.js'
export function getPhoneSize() {
return getAxios({ url: '/api/router/scrcpy/size' })
}
export function touchclick(data) {
return getAxios({ url: `/api/router/scrcpy/click?x=${data.x}&y=${data.y}&type=${data.type}`, })
}

7
src/api/chat.js Normal file
View File

@@ -0,0 +1,7 @@
import { getAxios, postAxios } from '@/utils/axios.js'
export function chat(data) {
return postAxios({ url: '/chat', data })
}

BIN
src/assets/Back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
src/assets/Home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src/assets/Overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
src/assets/filter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/assets/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/assets/listAction.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
src/assets/logo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
src/assets/logoBg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
src/assets/logoBg1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
src/assets/logotext.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
src/assets/logotext1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/assets/logotext12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src/assets/navAction.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/assets/open.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
src/assets/password.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

BIN
src/assets/username.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

BIN
src/assets/work.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

BIN
src/assets/workAction.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

BIN
src/assets/worklogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,100 @@
<template>
<div v-if="visible" class="dialog-overlay" @click.self="close">
<div class="dialog-content">
<h3 class="text-lg font-bold mb-4">最近一条的消息翻译</h3>
<div class="chat-box">
<div class="left-message">
<div class="bubble left">{{ leftMsg }}</div>
</div>
<div class="right-message">
<div class="bubble right">{{ rightMsg }}</div>
</div>
</div>
<!-- <button class="close-btn" @click="close">关闭</button> -->
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
leftMsg: String,
rightMsg: String,
visible: Boolean
})
// const emit = defineEmits(['close'])
// const close = () => emit('close')
</script>
<style scoped>
.dialog-overlay {
/* position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0; */
/* background: rgba(0, 0, 0, 0.5); */
display: flex;
justify-content: center;
align-items: center;
}
.dialog-content {
background: rgb(246, 246, 246);
padding: 0px 20px 20px 20px;
border-radius: 12px;
width: 300px;
}
.chat-box {
display: flex;
flex-direction: column;
gap: 10px;
}
.left-message {
align-self: flex-start;
}
.right-message {
align-self: flex-end;
text-align: right;
}
.bubble {
padding: 10px 14px;
border-radius: 18px;
max-width: 70%;
display: inline-block;
/* 👈 使气泡宽度自适应内容 */
padding: 10px 14px;
border-radius: 18px;
max-width: 70%;
word-break: break-word;
/* 防止英文单词太长撑爆 */
}
.bubble.left {
background-color: #ffffff;
}
.bubble.right {
background-color: rgb(0, 169, 214);
color: white;
}
.close-btn {
margin-top: 20px;
padding: 8px 12px;
background-color: #409EFF;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<el-dialog draggable :title="title" v-model="visibleLocal" width="600px" @close="handleCancel">
<el-input type="textarea" v-model="rawText" :rows="10" :placeholder="placeholder" clearable></el-input>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
// 定义接收的 props
const props = defineProps({
/** 对话框可见性,外部通过 v-model:visible 绑定 */
visible: {
type: Boolean,
required: true,
},
/** 对话框标题 */
title: {
type: String,
default: '',
},
/** 文本框初始内容,可选 */
initialText: {
type: String,
default: '',
},
/** 文本框行数 */
type: {
type: Number,
default: 10,
},
/** index */
index: {
type: Number,
default: 999,
},
/** 文本框占位符 */
placeholder: {
type: String,
default: '每行一条,支持粘贴多行后自动拆分',
},
/** 是否去重,默认 true */
dedupe: {
type: Boolean,
default: true,
},
});
// 定义 emits
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
// 本地 rawText用于绑定 textarea
const rawText = ref(props.initialText);
// 当 props.initialText 变化时,更新 rawText
watch(
() => props.initialText,
(newVal) => {
rawText.value = newVal;
}
);
// 计算属性,用于 v-model:visible 绑定 el-dialog
const visibleLocal = computed({
get() {
return props.visible;
},
set(val) {
emit('update:visible', val);
},
});
/** 解析 rawText 为数组按行拆分trim去空去重如 dedupe=true */
function parseLines() {
const lines = rawText.value
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.length > 0);
if (props.dedupe) {
return Array.from(new Set(lines));
}
return lines;
}
/** 点击确定按钮 */
function handleConfirm() {
const items = parseLines();
emit('confirm', items, props.title, props.index);
// 关闭对话框
emit('update:visible', false);
rawText.value = '';
}
/** 点击取消或对话框关闭 */
function handleCancel() {
emit('update:visible', false);
emit('cancel');
}
</script>
<style scoped>
/* 可根据项目风格调整 */
</style>

24
src/main.js Normal file
View File

@@ -0,0 +1,24 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { createPinia } from 'pinia';
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import { Buffer } from 'buffer';
// createApp(App).use(store).use(router).mount('#app')
const app = createApp(App);
app.use(ElementPlus) // 注册 ElementPlus
app.use(createPinia()); // 注册 Pinia
app.use(store); // 注册 store
app.use(router);
app.config.globalProperties.Buffer = Buffer; // 注册 Buffer
window.addEventListener('unhandledrejection', event => event.preventDefault());
window.addEventListener('error', event => event.preventDefault()); // 阻止错误和未处overlay理的 Promise 拒绝事件冒泡到 window 对象
app.mount('#app');

24
src/router/index.js Normal file
View File

@@ -0,0 +1,24 @@
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/Video',
name: 'Video',
component: () => import(/* webpackChunkName: "about" */ '@/views/VideoStream.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

54
src/services/websocket.js Normal file
View File

@@ -0,0 +1,54 @@
// src/services/websocket.js
let ws = null;
let reconnectTimer = null;
export const connectWebSocket = (port = 8000) => {
// 关闭已有连接
if (ws) ws.close();
// 创建新连接
ws = new WebSocket(`ws://localhost:${port}`);
// 连接事件处理
ws.onopen = () => {
console.log('WS connected');
clearInterval(reconnectTimer);
};
ws.onerror = (error) => {
console.error('WS error:', error);
handleReconnect();
};
ws.onclose = (event) => {
console.log('WS closed:', event.reason);
handleReconnect();
};
ws.onmessage = (event) => {
console.log(12312312, event.data)
if (event.data instanceof Blob) {
// createImageBitmap(event.data).then(img => {
// ctx.drawImage(img, 0, 0);
// });
}
};
return ws;
};
// 自动重连机制
const handleReconnect = () => {
clearInterval(reconnectTimer);
// reconnectTimer = setInterval(() => {
// connectWebSocket(8000);
// }, 3000);
};
// 发送控制指令
export const sendControl = (data) => {
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
};

14
src/store/index.js Normal file
View File

@@ -0,0 +1,14 @@
import { createStore } from 'vuex'
export default createStore({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})

12
src/stores/notice.js Normal file
View File

@@ -0,0 +1,12 @@
import { defineStore } from 'pinia';
export const noticeStore = defineStore('noticeNum', {
state: () => {
return { data: { num: 0 } };
},
actions: {
increment() {
this.data.num++;
},
},
});

172
src/utils/axios.js Normal file
View File

@@ -0,0 +1,172 @@
/**
* axios请求封装
* https://rudon.blog.csdn.net/
*/
import axios from 'axios'
import { getToken, getUser } from '@/utils/storage'
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
const router = useRouter();
// axios.defaults.withCredentials = true;
// 请求地址前缀
let baseURL = ''
if (process.env.NODE_ENV === 'development') {
// 生产环境
// baseURL = "http://120.26.251.180:8085/"
baseURL = "http://120.26.251.180:15330"
} else {
// 开发环境
baseURL = "http://120.26.251.180:8085/"
}
// 请求拦截器
axios.interceptors.request.use((config) => {
// if (getToken()) {
// config.headers['token'] = getToken();
// }
// 请求超时时间 - 毫秒
config.timeout = 60000
config.baseURL = baseURL
// 自定义Content-type
config.headers['Content-type'] = 'application/json'
return config;
}, (error) => {
return Promise.reject(error)
})
// 响应拦截器
axios.interceptors.response.use((response) => {
return response
}, (error) => {
// 可添加请求失败后的处理逻辑
return Promise.reject(error)
})
// axios的get请求
export function getAxios({ url, params }) {
// 使用axios发送GET请求
return new Promise((resolve, reject) => {
axios.get(url, {
params
// 请求成功将返回的数据传递给resolve函数
}).then(res => {
resolve(res.data)
// 请求失败将错误信息传递给reject函数
}).catch(err => {
console.log(err)
reject(err)
})
})
}
// axios的post请求
export function postAxios({ url, data }) {
// if (url != 'api/account/login') {
// throttledCheekalive();
// }
return new Promise((resolve, reject) => {
axios.post(
url,
data,
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
).then(res => {
resolve(res.data)
}).catch(err => {
if (err.message == "Network Error") {
// alert("网络错误,请检查网络连接")
// ElMessage.error('网络连接错误');
reject('网络连接错误')
} else {
ElMessage.error(err.message);
reject(err.message)
}
// console.log(err)
// reject(err)
})
})
}
export const downFile = async (urlstr, data) => {
// 发送请求,获取文件流
const response = await axios.post(urlstr, data, { responseType: 'blob' });
// 获取文件名(如果后端设置了 Content-Disposition
const contentDisposition = response.headers['content-disposition'];
let fileName = 'default-file-name'; // 默认文件名
console.log(contentDisposition)
console.log(response)
if (contentDisposition) {
// 从响应头中提取文件名
const fileNameMatch = contentDisposition.match(/filename="(.+)"/);
if (fileNameMatch && fileNameMatch.length > 1) {
fileName = fileNameMatch[1];
}
}
// 创建一个临时的下载链接
const blob = new Blob([response.data], { type: response.headers['content-type'] });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName; // 设置下载的文件名
a.click();
// 释放 URL 对象
window.URL.revokeObjectURL(url);
}
//请求前验证
function cheekalive() {
axios.post('api/account/cheekalive', {
userId: getUser().userId,
currcode: getToken(),
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
).then(res => {
console.log(res.data)
if (res.data) {
} else {
alert("账号在其他地方登录!")
window.location.href = '/';
}
})
}
//节流函数
function throttle(func, limit) {
let inThrottle;
return function () {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
}
}
const throttledCheekalive = throttle(cheekalive, 5000);
export default axios

262
src/utils/bufferUtils.js Normal file
View File

@@ -0,0 +1,262 @@
import { Buffer } from 'buffer'; // 如果在浏览器环境需要引入buffer包
// PAYLOAD_LENGTHs 统一定义,实际根据你的需求调整
export const PAYLOAD_LENGTHs = 13;
export const MAX_PRESSURE_VALUE = 100; // 假设最大压力值
export const PAYLOAD_LENGTH = 33; // 根据实际计算确定
// 按钮消息对象转 buffer 返回-主页-视图 和 键盘按键的事件 转 buffer
export function toBufferBtn(message) {
const buffer = new Uint8Array(PAYLOAD_LENGTHs + 1);
let offset = 0;
// 写入类型 (1 字节)
buffer[offset++] = message.type;
// 写入动作 (1 字节)
buffer[offset++] = message.action;
// 写入键码 (4 字节大端序)
const keycodeBytes = new DataView(new ArrayBuffer(4));
keycodeBytes.setUint32(0, message.keycode, false);
buffer.set(new Uint8Array(keycodeBytes.buffer), offset);
offset += 4;
// 写入重复次数 (4 字节大端序)
const repeatBytes = new DataView(new ArrayBuffer(4));
repeatBytes.setUint32(0, message.repeat, false);
buffer.set(new Uint8Array(repeatBytes.buffer), offset);
offset += 4;
// 写入修饰键状态 (4 字节大端序)
const metaStateBytes = new DataView(new ArrayBuffer(4));
metaStateBytes.setUint32(0, message.metaState, false);
buffer.set(new Uint8Array(metaStateBytes.buffer), offset);
return buffer;
}
//字符串转buffer
export function stringToUtf8ByteArray(text) {
return new TextEncoder().encode(text)
}
//发送获取手机粘贴板内容的buffer
export function getClipboard() {
// 1. 定义消息类型常量(根据实际项目值设置)
const TYPE_SET_CLIPBOARD = 8 // 假设控制消息类型值为 1
// 2. 创建消息对象
const event = {
buffer: null,
action: TYPE_SET_CLIPBOARD
}
// 4. 计算缓冲区大小并分配空间
// 结构1字节类型 + 1字节粘贴标志 + 4字节文本长度 + N字节文本内容
const buffer = Buffer.alloc(1)
// 5. 写入缓冲区数据
let offset = 0
// 写入消息类型1字节
offset = buffer.writeUInt8(event.action, offset)
// 6. 将缓冲区挂载到消息对象
event.buffer = buffer
return buffer
}
//用于发送文字到手机上把文字砖换成scrcpy接收的buffer格式
export function setClipboard(text, paste = true) {
// 1. 定义消息类型常量(根据实际项目值设置)
const TYPE_SET_CLIPBOARD = 9 // 假设控制消息类型值为 1
// 2. 创建消息对象
const event = {
buffer: null,
action: TYPE_SET_CLIPBOARD
}
// 3. 处理文本内容
let textBytes = null
let textLength = 0
if (text) {
textBytes = stringToUtf8ByteArray(text)
textLength = textBytes.length
}
// 4. 计算缓冲区大小并分配空间
// 结构1字节类型 + 1字节粘贴标志 + 4字节文本长度 + N字节文本内容
const buffer = Buffer.alloc(1 + 1 + 4 + textLength)
// 5. 写入缓冲区数据
let offset = 0
// 写入消息类型1字节
offset = buffer.writeUInt8(event.action, offset)
// 写入粘贴标志1字节1=true0=false
offset = buffer.writeUInt8(paste ? 1 : 0, offset)
// 写入文本长度4字节大端序
offset = buffer.writeUInt32BE(textLength, offset)
// 写入文本内容(如果有)
if (textBytes) {
textBytes.forEach((byte, index) => {
buffer.writeUInt8(byte, offset + index)
})
offset += textBytes.length
}
// 6. 将缓冲区挂载到消息对象
event.buffer = buffer
return buffer
}
//buffer 转中文
export function bufferToString(buffer) {
// 创建解码器(默认 UTF-8
const decoder = new TextDecoder('utf-8');
// 直接解码 ArrayBuffer 或 Uint8Array
return decoder.decode(buffer);
}
//传入两个二进制数组判断buffer是否包含指定标识头
export function startsWithHeader(header, longArray) {
// 边界检查:如果任一参数不是 Uint8Array 则抛出错误
if (!(header instanceof Uint8Array) || !(longArray instanceof Uint8Array)) {
throw new TypeError('参数必须是 Uint8Array 类型');
}
// 快速失败:如果标识比长数组还长,直接返回 false
if (header.length > longArray.length) {
return false;
}
// 逐个字节比对
for (let i = 0; i < header.length; i++) {
if (header[i] !== longArray[i]) {
return false;
}
}
return true;
}
//长数组 减去 短数组 返回新数组 验证buffer是否包含指定标识头
export function trimLongArray(arr1, arr2) {
// 1. 确定长短数组
const [longArray, shortArray] =
arr1.length > arr2.length ? [arr1, arr2] : [arr2, arr1];
// 2. 创建新的 ArrayBuffer长度 = 长数组长度 - 短数组长度)
const resultBuffer = new ArrayBuffer(longArray.length - shortArray.length);
// 3. 将长数组后半部分拷贝到新缓冲区
const resultView = new Uint8Array(resultBuffer);
const startIndex = shortArray.length;
resultView.set(longArray.subarray(startIndex));
// 4. 去除前5个元素
const trimmedResultView = resultView.slice(5);
return trimmedResultView;
}
//base64转ArrayBuffer的方法
export function base64ToBinary(base64String) {
// 以 Base64 字符串创建一个 binary string
const binaryString = window.atob(base64String);
// 将 binary string 转换为字节数组
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer; // 返回 ArrayBuffer
}
//鼠标抬起按下 移动的事件处理 对象转buffer
export const toBuffer = (message) => {
const buffer = new Uint8Array(PAYLOAD_LENGTH + 1);
let offset = 0;
// 写入类型 (1 字节)
buffer[offset++] = message.type;
// 写入动作 (1 字节)
buffer[offset++] = message.action;
// 模拟 Java 的 long 类型 (8 字节)
// 这里简化为写入 0 和 pointerId (共 8字节)
const zeroLong = new Uint8Array(4).fill(0); // 前 4字节填 0
buffer.set(zeroLong, offset);
offset += 4;
const pointerIdBytes = new DataView(new ArrayBuffer(4));
pointerIdBytes.setUint32(0, message.pointerId, false); // 大端序
buffer.set(new Uint8Array(pointerIdBytes.buffer), offset);
offset += 4;
// 写入坐标 (各 4字节)
const xBytes = new DataView(new ArrayBuffer(4));
xBytes.setUint32(0, message.position.point.x, false);
buffer.set(new Uint8Array(xBytes.buffer), offset);
offset += 4;
const yBytes = new DataView(new ArrayBuffer(4));
yBytes.setUint32(0, message.position.point.y, false);
buffer.set(new Uint8Array(yBytes.buffer), offset);
offset += 4;
// 屏幕尺寸 (各 2字节)
const widthBytes = new DataView(new ArrayBuffer(2));
widthBytes.setUint16(0, message.position.screenSize.width, false);
buffer.set(new Uint8Array(widthBytes.buffer), offset);
offset += 2;
const heightBytes = new DataView(new ArrayBuffer(2));
heightBytes.setUint16(0, message.position.screenSize.height, false);
buffer.set(new Uint8Array(heightBytes.buffer), offset);
offset += 2;
// 压力值 (2 字节)
const pressureValue = Math.floor(message.pressure * MAX_PRESSURE_VALUE);
const pressureBytes = new DataView(new ArrayBuffer(2));
pressureBytes.setUint16(0, pressureValue, false);
buffer.set(new Uint8Array(pressureBytes.buffer), offset);
offset += 2;
// 按钮状态 (4 字节)
const buttonsBytes = new DataView(new ArrayBuffer(4));
buttonsBytes.setUint32(0, message.buttons, false);
buffer.set(new Uint8Array(buttonsBytes.buffer), offset);
return buffer;
};
export function toBuffercomm(params) {
// 假设 PAYLOAD_LENGTH 是一个预定义的常量值例如4根据实际情况调整
const PAYLOAD_LENGTH = 4;
// 创建指定长度的 Buffer
const buffer = Buffer.alloc(PAYLOAD_LENGTH + 1);
// 从参数对象中获取 type 值并写入 Buffer
buffer.writeUInt8(params.type, 0);
console.log("转换comm", buffer);
return buffer;
}

261
src/utils/countryUtil.js Normal file
View File

@@ -0,0 +1,261 @@
// country-utils.js
export const CountryCode = {
AD: "安道尔",
AE: "阿拉伯联合酋长国",
AF: "阿富汗",
AG: "安提瓜和巴布达",
AI: "安圭拉",
AL: "阿尔巴尼亚",
AM: "亚美尼亚",
AO: "安哥拉",
AQ: "南极洲",
AR: "阿根廷",
AS: "美属萨摩亚",
AT: "奥地利",
AU: "澳大利亚",
AU1: "澳大利亚",
AW: "阿鲁巴",
AX: "奥兰群岛",
AZ: "阿塞拜疆",
BA: "波斯尼亚和黑塞哥维那",
BB: "巴巴多斯",
BD: "孟加拉国",
BE: "比利时",
BF: "布基纳法索",
BG: "保加利亚",
BH: "巴林",
BI: "布隆迪",
BJ: "贝宁",
BL: "圣巴泰勒米",
BM: "百慕大群岛",
BN: "文莱达鲁萨兰国",
BO: "玻利维亚",
BQ: "博奈尔、圣尤斯特歇斯和萨巴",
BR: "巴西",
BS: "巴哈马",
BT: "不丹",
BV: "布韦岛",
BW: "博茨瓦纳",
BY: "白俄罗斯",
BZ: "伯利兹",
CA: "加拿大",
CA1: "加拿大",
CC: "科科斯(基林)群岛",
CD: "刚果民主共和国",
CF: "中非共和国",
CG: "刚果共和国",
CH: "瑞士",
CI: "科特迪瓦",
CK: "库克群岛",
CL: "智利",
CM: "喀麦隆",
CN: "中国",
CO: "哥伦比亚",
CR: "哥斯达黎加",
CU: "古巴",
CV: "佛得角",
CW: "库拉索",
CX: "圣诞岛",
CY: "塞浦路斯",
CZ: "捷克共和国",
DE: "德国",
DG: "迪戈加西亚岛",
DJ: "吉布提",
DK: "丹麦",
DM: "多米尼克",
DO: "多米尼加共和国",
DZ: "阿尔及利亚",
EC: "厄瓜多尔",
EE: "爱沙尼亚",
EG: "埃及",
EH: "西撒哈拉",
ER: "厄立特里亚",
ES: "西班牙",
ET: "埃塞俄比亚",
FI: "芬兰",
FJ: "斐济",
FK: "福克兰群岛",
FM: "密克罗尼西亚",
FO: "法罗群岛",
FR: "法国",
GA: "加蓬",
GB: "英国",
GD: "格林纳达",
GE: "格鲁吉亚",
GF: "法属圭亚那",
GG: "根西岛",
GH: "加纳",
GI: "直布罗陀",
GL: "格陵兰",
GM: "冈比亚",
GN: "几内亚",
GP: "瓜德罗普",
GQ: "赤道几内亚",
GR: "希腊",
GS: "南乔治亚和南桑德威奇群岛",
GT: "危地马拉",
GU: "关岛",
GW: "几内亚比绍",
GY: "圭亚那",
HK: "中国香港特别行政区",
HM: "赫德岛和麦克唐纳群岛",
HN: "洪都拉斯",
HR: "克罗地亚",
HT: "海地",
HU: "匈牙利",
ID: "印度尼西亚",
IE: "爱尔兰",
IL: "以色列",
IM: "马恩岛",
IN: "印度",
IO: "英属印度洋领地",
IQ: "伊拉克",
IR: "伊朗",
IS: "冰岛",
IT: "意大利",
JE: "泽西岛",
JM: "牙买加",
JO: "约旦",
JP: "日本",
JP1: "日本",
KE: "肯尼亚",
KG: "吉尔吉斯斯坦",
KH: "柬埔寨",
KI: "基里巴斯",
KM: "科摩罗",
KN: "圣基茨和尼维斯",
KP: "朝鲜",
KR: "韩国",
KR1: "韩国",
KR1_UXWAUDIT: "韩国",
KW: "科威特",
KY: "开曼群岛",
KZ: "哈萨克斯坦",
LA: "老挝",
LB: "黎巴嫩",
LC: "圣卢西亚",
LI: "列支敦士登",
LK: "斯里兰卡",
LR: "利比里亚",
LS: "莱索托",
LT: "立陶宛",
LU: "卢森堡",
LV: "拉脱维亚",
LY: "利比亚",
MA: "摩洛哥",
MC: "摩纳哥",
MD: "摩尔多瓦",
ME: "黑山",
MF: "圣马丁",
MG: "马达加斯加",
MH: "马绍尔群岛",
MK: "北马其顿",
ML: "马里",
MM: "缅甸",
MN: "蒙古",
MO: "中国澳门特别行政区",
MP: "北马里亚纳群岛",
MQ: "马提尼克",
MR: "毛里塔尼亚",
MS: "蒙特塞拉特",
MT: "马耳他",
MU: "毛里求斯",
MV: "马尔代夫",
MW: "马拉维",
MX: "墨西哥",
MY: "马来西亚",
MZ: "莫桑比克",
NA: "纳米比亚",
NC: "新喀里多尼亚",
NE: "尼日尔",
NF: "诺福克岛",
NG: "尼日利亚",
NI: "尼加拉瓜",
NL: "荷兰",
NO: "挪威",
NP: "尼泊尔",
NR: "瑙鲁",
NU: "纽埃",
NZ: "新西兰",
OM: "阿曼",
PA: "巴拿马",
PE: "秘鲁",
PF: "法属玻利尼西亚",
PG: "巴布亚新几内亚",
PH: "菲律宾",
PK: "巴基斯坦",
PL: "波兰",
PM: "圣皮埃尔和密克隆群岛",
PN: "皮特凯恩群岛",
PR: "波多黎各",
PS: "巴勒斯坦",
PT: "葡萄牙",
PW: "帕劳",
PY: "巴拉圭",
QA: "卡塔尔",
RE: "留尼汪",
RO: "罗马尼亚",
RS: "塞尔维亚",
RU: "俄罗斯",
RW: "卢旺达",
SA: "沙特阿拉伯",
SB: "索罗门群岛",
SC: "塞舌尔",
SD: "苏丹",
SE: "瑞典",
SG: "新加坡",
SI: "斯洛文尼亚",
SJ: "斯瓦尔巴和扬马延",
SK: "斯洛伐克",
SL: "塞拉利昂",
SM: "圣马利诺",
SN: "塞内加尔",
SO: "索马里",
SR: "苏里南",
SS: "南苏丹",
ST: "圣多美和普林西比",
SV: "萨尔瓦多",
SX: "荷属圣马丁",
SY: "叙利亚",
SZ: "斯威士兰",
TC: "特克斯和凯科斯群岛",
TD: "乍得",
TF: "法属南部领地",
TG: "多哥",
TH: "泰国",
TJ: "塔吉克斯坦",
TK: "托克劳群岛",
TL: "东帝汶",
TM: "土库曼斯坦",
TN: "突尼斯",
TO: "汤加",
TR: "土耳其",
TT: "特立尼达和多巴哥",
TV: "图瓦卢",
TW: "台湾",
TZ: "坦桑尼亚",
UA: "乌克兰",
UG: "乌干达",
UM: "美国本土外小岛屿",
US: "美国",
UY: "乌拉圭",
UZ: "乌兹别克斯坦",
VA: "梵蒂冈",
VC: "圣文森特",
VE: "委内瑞拉",
VG: "英属维尔京群岛",
VI: "美属维尔京群岛",
VN: "越南",
VN1: "越南",
VU: "瓦努阿图",
WS: "萨摩亚",
YE: "也门",
YT: "马约特岛",
ZA: "南非",
ZM: "赞比亚",
ZW: "津巴布韦"
};
export function getCountryName(code) {
return CountryCode[code] || null;
}

45
src/utils/storage.js Normal file
View File

@@ -0,0 +1,45 @@
export function setToken(token) {
localStorage.setItem('token', token);
}
export function getToken() {
return localStorage.getItem('token');
}
export function removeToken() {
localStorage.removeItem('token');
}
export function setUser(user) {
localStorage.setItem('user', JSON.stringify(user));
}
export function getUser() {
return JSON.parse(localStorage.getItem('user'));
}
export function setNumData(numData) {
localStorage.setItem('num', JSON.stringify(numData));
}
export function getNumData() {
return JSON.parse(localStorage.getItem('num'));
}
// 导出一个函数,用于设置用户密码
export function setUserPass(userdata) {
localStorage.setItem('userPass', JSON.stringify(userdata));
}
// 导出一个函数,用于获取用户密码
export function getUserPass() {
return JSON.parse(localStorage.getItem('userPass'));
}
// 用于设置设备坐标信息
export function setphoneXYinfo(data) {
localStorage.setItem('XYinfo', JSON.stringify(data));
}
// 用于获取设备坐标信息
export function getphoneXYinfo() {
return JSON.parse(localStorage.getItem('XYinfo'));
}

47
src/utils/wsActions.js Normal file
View File

@@ -0,0 +1,47 @@
// src/utils/wsActions.js
// 传入 wslist、isStopLike 等需要依赖的外部变量
export function createWsActions(wslist, isStopLike = null) {
// 通用 ws 发送方法
function send(index, payload) {
if (wslist[index]) {
setTimeout(() => {
mouseData.action = 1;
wslist[index].send(toBuffer(mouseData));
}, 100)
wslist[index].send(JSON.stringify(payload));
}
}
// 所有业务操作方法
return {
open: (udid, index) => send(index, { udid, action: 'openDY' }), //打开tk
slideDown: (udid, index) => send(index, { udid, action: 'slideDown' }),//下滑动视频
slideUp: (udid, index) => send(index, { udid, action: 'slideUp' }),//上滑动视频
slideRight: (udid, index) => send(index, { udid, action: 'slideRight' }),//右滑动视频
getSize: (udid, index) => send(index, { udid, action: 'getSize', index }),//右滑动视频
clickLikes: (udid, index) => send(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' }),//点赞
clickComment: (udid, index) => send(index, { udid, action: 'click', type: 'Comment', index, resourceId: 'com.zhiliaoapp.musically:id/cvd' }),//打开评论
clickComtext: (udid, index) => send(index, { udid, action: 'click', type: 'Comtext', index, resourceId: 'com.zhiliaoapp.musically:id/cs0' }),//点开输入框
clickComPush: (udid, index) => send(index, { udid, action: 'click', type: 'ComPush', index, resourceId: 'com.zhiliaoapp.musically:id/bqg' }),//发布评论
clicktomy: (udid, index) => send(index, { udid, action: 'click', type: 'tomy', index, resourceId: 'com.zhiliaoapp.musically:id/ts9' }),//进入个人资料
clickAttention: (udid, index) => send(index, { udid, action: 'click', type: 'Attention', index, resourceId: 'com.zhiliaoapp.musically:id/dhx' }),//关注
clickPrivatetext: (udid, index) => send(index, { udid, action: 'click', type: 'Privatetex', index, resourceId: 'com.zhiliaoapp.musically:id/hob' }), //私信输入框
clickPrivatePush: (udid, index) => send(index, { udid, action: 'click', type: 'PrivatePush', index, resourceId: 'com.zhiliaoapp.musically:id/hog' }),//私信发送
clickCopy: (udid, index) => send(index, { udid, action: 'click', type: 'clickCopy', index, resourceId: 'com.zhiliaoapp.musically:id/kg4' }),//查询最后一条信息并返回位置
clickCopyList: (udid, index) => send(index, { udid, action: 'click', type: 'clickCopyList', index, resourceId: 'com.zhiliaoapp.musically:id/kg4' }),//查询最后一条信息并返回位置
clickCopyText: (udid, index) => send(index, { udid, action: 'click', type: 'clickCopyText', index, resourceId: 'com.zhiliaoapp.musically:id/ui' }),//点击复制文字
getmesNum: (udid, index) => send(index, { udid, action: 'click', type: 'getmesNum', index, resourceId: 'com.zhiliaoapp.musically:id/jyv' }), //获取收件箱消息数量
clickMesage: (udid, index) => send(index, { udid, action: 'click', type: 'clickMesage', index, resourceId: 'com.zhiliaoapp.musically:id/e3_' }), //点击有消息的私信
isVideoAndLive: (udid, index) => send(index, { udid, action: 'click', type: 'isVideoAndLive', index, resourceId: 'com.zhiliaoapp.musically:id/long_press_layout' }), //获取是视频还是直播
addHost: (udid, index) => send(index, { udid, action: 'click', type: 'addHost', index, resourceId: 'com.zhiliaoapp.musically:id/fuq' }), //视频页面的关注
isHost: (udid, index) => send(index, { udid, action: 'click', type: 'isHost', index, resourceId: 'com.zhiliaoapp.musically:id/fuq' }), //判断视频页面的关注
search: (udid, index) => send(index, { udid, action: 'click', type: 'search', index, resourceId: 'com.zhiliaoapp.musically:id/gtz' }), //搜索页面
searchHost: (udid, index) => send(index, { udid, action: 'click', type: 'searchHost', index, resourceId: 'com.zhiliaoapp.musically:id/t6f' }), //搜索主播
toHost: (udid, index) => send(index, { udid, action: 'click', type: 'toHost', index, resourceId: 'com.zhiliaoapp.musically:id/iso' }), //进入主页二
hostVideo: (udid, index, num) => send(index, { udid, action: 'click', type: 'hostVideo', index, resourceId: 'com.zhiliaoapp.musically:id/d3u', num: num }), //主播视频
test: (udid, index) => send(index, { udid, action: 'click', type: 'test', index, resourceId: 'com.zhiliaoapp.musically:id/j7s' }), //截屏测试
};
}

327
src/views/HomeView.vue Normal file
View File

@@ -0,0 +1,327 @@
<template>
<div class="main">
<div class="container">
<div class="right">
<img src="../assets/logoBg.png" class="background-video" alt="">
<!-- 设置 -->
<div class="center-align">
<div></div>
<div class="setup">
<div class="setup-item center-justify">
<div></div>
<span>
网络设置
</span>
</div>
<div class="setup-item center-justify">
<div></div>
<span>
简体中文
</span>
</div>
</div>
</div>
<div class="center-line" style="margin-top: 40px;">
<!-- logo -->
<div class="logo">
<div class="center-justify" style="height: 80px; width: 300px;">
<!-- <img style="margin-right: 20px;height: 100%;" src="@/assets/logo.png"> -->
<img style="height: 100%;" src="@/assets/logotext.png">
</div>
</div>
<!-- From -->
<div class="from">
<div class="from-title center-justify">
<div>账号登陆</div>
</div>
<div class="from-input">
<el-form label-position="left" label-width="100px" :model="formData">
<div class="from-input-item1">
<img src="@/assets/username.png" alt="">
<el-input style="height: 25px;" v-model="formData.userId" placeholder="账号" clearable
@keyup.enter="onSubmit" />
</div>
<div class="from-input-item1">
<img src="@/assets/password.png" alt="">
<el-input style="height: 25px; " v-model="formData.password" type="password"
placeholder="密码" show-password @keyup.enter="onSubmit" />
</div>
<div class="from-input-item">
<el-button class="loginButton" color="#8f7ee7" type="primary"
@click="onSubmit">登录</el-button>
</div>
</el-form>
</div>
</div>
</div>
<div class="version center-justify ">版本号{{ version }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { login } from '@/api/account';
import { getToken, setToken, setUser, setUserPass, getUserPass } from '@/utils/storage';
import { ElLoading } from 'element-plus';
let version = ref('0.0.0');
onMounted(() => {
})
const router = useRouter();
const formData = ref({
userId: getUserPass() == null ? '' : getUserPass().userId,
password: getUserPass() == null ? '' : getUserPass().password,
});
const onSubmit = () => {
router.push('/Video');
return
const loading = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)',
});
setUserPass(formData.value);
login({
userId: formData.value.userId,
password: formData.value.password,
source: 'app'
}).then((res) => {
loading.close();
console.log(res)
if (res.code == 200) {
if (res.data.activeYn == 'Y') {
setToken(res.data.currcode);
setUser(res.data);
router.push('/nav');
} else {
alert('账号未启用');
}
} else {
alert(res.mes);
}
}).catch((err) => {
loading.close();
});
};
</script>
<style lang="less">
.main {
width: 100vw;
height: 100vh;
overflow: hidden;
box-sizing: border-box;
/* 页面无法选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.container {
display: flex;
box-sizing: border-box;
width: 100vw;
height: 100vh;
.right {
box-sizing: border-box;
position: relative;
width: 100vw;
height: 100vh;
padding: 20px 40px 20px 50px;
border-left: 3px solid #23516e;
position: relative;
/* 添加 position: relative */
overflow: hidden;
/* 防止内容溢出 */
.version {
color: #fff;
position: absolute;
font-size: 20px;
bottom: 20px;
left: calc(50% - 50px);
// box-sizing: border-box;
// width: 1600px;
}
.background-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
/* 确保视频在内容之下 */
}
.setup {
display: flex;
color: #fff;
.setup-item {
padding: 10px 6px;
display: flex;
div {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgb(255, 255, 255);
margin-right: 5px;
}
}
}
.logo {
padding: 20px 0;
}
.from {
width: 420px;
height: 320px;
color: #022b4e;
background-color: #ffffff44;
border-radius: 20px;
border: 1px solid #fff;
padding: 32px;
box-sizing: border-box;
.from-title {
font-family: Source Han Sans SC;
font-weight: 500;
font-size: 24px;
color: #022b4e;
line-height: 37px;
div {
font-size: 20px;
font-weight: 600;
// border-bottom: 4px solid #1db97d;
}
}
.from-input {
width: 100%;
padding: 15px 0;
.from-input-item {
display: flex;
padding: 8px 0;
.from-input-item-title {
color: #022b4e;
font-size: 18px;
font-weight: 500;
width: 80px;
height: 50px;
}
.loginButton {
width: 359px;
height: 50px;
background: #FFFFFF;
border-radius: 24px;
border: 1px solid #FFFFFF;
font-family: Source Han Sans SC;
font-weight: 500;
font-size: 18px;
color: #022b4e;
line-height: 37px;
}
}
.from-input-item1 {
display: flex;
width: 359px;
height: 50px;
background: #022b4e1c;
border-radius: 24px;
border: 1px solid #FFFFFF;
padding: 12px 25px 13px 25px;
box-sizing: border-box;
margin-bottom: 16px;
}
}
}
}
}
}
.center-line {
display: flex;
flex-direction: column;
align-items: center;
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
}
.center-align {
display: flex;
justify-content: space-between;
}
.center-flex {
display: flex;
justify-content: center;
align-items: center;
}
.el-input__wrapper {
--el-input-focus-border-color: rgba(255, 255, 0, 0);
--el-menu-hover-bg-color: rgba(255, 255, 0, 0);
}
</style>
<style scoped lang="less">
::v-deep(.el-input__wrapper) {
background-color: rgba(255, 0, 0, 0);
box-shadow: none;
}
::v-deep(.el-input__inner) {
color: #fff;
}
::v-deep(.el-input__inner::placeholder) {
color: #022b4e;
}
</style>

1225
src/views/Untitled-1.vue Normal file

File diff suppressed because it is too large Load Diff

1509
src/views/VideoStream.vue Normal file

File diff suppressed because it is too large Load Diff

24
vue.config.js Normal file
View File

@@ -0,0 +1,24 @@
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
devServer: {
client: {
overlay: false, // 重点:新版写法
},
historyApiFallback: true,
},
transpileDependencies: true,
publicPath: './', // 必须是相对路径,否则打包后图片路径会出错
css: {
loaderOptions: {
postcss: {
postcssOptions: {
plugins: [
]
}
}
}
}
});