初始化
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: [
|
||||
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||