初始化
5
.env.development
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# .env.development
|
||||||
|
|
||||||
|
VUE_APP_API_URL="123123123"
|
||||||
|
|
||||||
|
# VUE_APP_FEATURE_FLAG=true
|
||||||
5
.env.production
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# .env.production
|
||||||
|
|
||||||
|
VUE_APP_API_URL="434534534"
|
||||||
|
|
||||||
|
# VUE_APP_FEATURE_FLAG=true
|
||||||
23
.gitignore
vendored
Normal 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
@@ -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
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
19
jsconfig.json
Normal 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
46
package.json
Normal 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
@@ -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
|
After Width: | Height: | Size: 4.2 KiB |
32
public/index.html
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/Home.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/assets/Overview.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/filter.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src/assets/list.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/listAction.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
src/assets/logo1.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/assets/logoBg.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/logoBg1.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/assets/logotext.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
src/assets/logotext1.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
src/assets/logotext12.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
src/assets/navAction.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
src/assets/open.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/assets/password.png
Normal file
|
After Width: | Height: | Size: 806 B |
BIN
src/assets/username.png
Normal file
|
After Width: | Height: | Size: 945 B |
BIN
src/assets/work.png
Normal file
|
After Width: | Height: | Size: 993 B |
BIN
src/assets/workAction.png
Normal file
|
After Width: | Height: | Size: 993 B |
BIN
src/assets/worklogo.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
100
src/components/ChatDialog.vue
Normal 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>
|
||||||
107
src/components/MultiLineInputDialog.vue.vue
Normal 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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,14 @@
|
|||||||
|
import { createStore } from 'vuex'
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
state: {
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
}
|
||||||
|
})
|
||||||
12
src/stores/notice.js
Normal 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
@@ -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
@@ -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=true,0=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
@@ -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
@@ -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
@@ -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
@@ -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
1509
src/views/VideoStream.vue
Normal file
24
vue.config.js
Normal 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: [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||