Files
tkAiPage/src/views/VideoStream.vue

1510 lines
53 KiB
Vue
Raw Normal View History

2025-07-01 21:36:08 +08:00
<template>
<div class="main">
<el-scrollbar class="left">
<div class=" center-line">
<!-- <el-button class="open" @click="wsActions.open(device.udid, index)"></el-button>
<el-button class="Back" @click="Back(device.udid, index)"></el-button>
<el-button class="Home" @click="Home(device.udid, index)"></el-button>
<el-button class="Overview" @click="Overview(device.udid, index)"></el-button> -->
<!-- <el-button @click="play(device.udid, index)">play</el-button> -->
<!-- <el-button class="open" @click="slideDown(device.udid, index)"></el-button>
<el-button class="open" @click="slideUp(device.udid, index)"></el-button>
<el-button class="open" @click="clickLike(device.udid, index)"></el-button> -->
<div></div>
<!-- <el-button @click="clickLikes(device.udid, index)">点赞</el-button> -->
<!-- <el-button @click="stopLike(index)">停止点赞</el-button> -->
<!-- <div></div>
<el-button @click="clickComment(device.udid, index)">评论</el-button>
<el-button @click="clickComtext(device.udid, index)">评论框</el-button>
<el-button @click="clickComPush(device.udid, index)">发布评论</el-button>
<div></div>
<el-button @click="clicktomy(device.udid, index)">主页</el-button> -->
<!-- <el-button @click="wsActions.clickAttention(device.udid, index)">关注/私信</el-button> -->
<!-- <div></div>
<el-button @click="clickPrivatetext(device.udid, index)">私信输入</el-button>
<el-button @click="clickPrivatePush(device.udid, index)">私信发送</el-button> -->
<el-button type="warning" @click="reloadfun()">刷新</el-button>
<div></div>
<!-- <el-button @click="wsActions.clickCopy(device.udid, index)">找私信</el-button> -->
<!-- <el-button @click="wsActions.clickCopyList(device.udid, index)">私信列表</el-button> -->
<div></div>
<!-- <el-button @click="wsActions.getmesNum(device.udid, index)">获取收件箱</el-button> -->
<!-- <el-button @click="wsActions.getSize(device.udid, index)">获取屏幕尺寸</el-button> -->
<!-- <el-button @click="wsActions.slideRight(device.udid, index)">右滑</el-button> -->
<!-- <el-button @click="wsActions.isHost(device.udid, index)">一键养号</el-button> -->
<el-button type="success" @click="openTk()">打开tiktok</el-button>
<el-button type="success" @click="showDialog = true; dialogTitle = '评论'; selectedDevice = 999">一键养号</el-button>
<el-button type="success"
@click="showDialog = true; dialogTitle = '主播ID'; selectedDevice = 999">一键关注</el-button>
<el-button type="success" v-if="!isShowMes" @click="openMonitor()">开启监测消息</el-button>
<el-button type="danger" v-else @click="cloesMonitor()">关闭监测消息</el-button>
<el-button type="danger" @click="stop()">全部停止</el-button>
<el-button type="danger" @click="router.push('/')">登出</el-button>
<div></div>
<div></div>
<!-- <el-button @click="wsActions.test(device.udid, index)">截屏</el-button> -->
<div></div>
<!-- <el-button @click="wsActions.search(device.udid, index)">搜索</el-button>
<div></div>
<el-button @click="wsActions.searchHost(device.udid, index)">搜主播</el-button>
<div></div>
<el-button @click="wsActions.toHost(device.udid, index)">主页</el-button>
<div></div>
<el-button @click="LikesToLikesToLikes(device.udid, index)">批量关注</el-button> -->
<div></div>
<!-- <div style="display: flex;">
<div style="width: 150px;">主播id</div>
<el-input style="border: 1px solid #000;" v-model="hostIdContent[index]" type="text"></el-input>
<el-button @click="setHostId(index)">发送</el-button>
</div>
<div style="display: flex;">
<div style="width: 150px;">评论内容</div>
<el-input style="border: 1px solid #000;" v-model="textContent[index]" type="text"></el-input>
<el-button @click="setComText(index)">发送</el-button>
</div> -->
<!-- <div style="display: flex;">
<div style="width: 150px;">私信内容</div>
<el-input style="border: 1px solid #000;" v-model="textContentpri[index]" type="text"></el-input>
<el-button @click="setPrivateText(index)">发送</el-button>
</div> -->
<div></div>
<!-- <el-button @click="clickxy(160, 360, index)">开始/暂停</el-button> -->
<div></div>
<!-- <el-button @click="clickxy(284, 392, index, 9)">长按</el-button> -->
<!-- <el-button @click="getText(index)">获取粘贴板内容</el-button> -->
<div></div>
<!-- <el-button @click="LikesToCommentToComPush(device.udid, index)">设置自动化坐标</el-button> -->
<!-- <el-button @click="start(device.udid, index)">开始执行</el-button> -->
<!-- <el-button @click="stop(device.udid, index)">停止</el-button> -->
<div></div>
<!-- <el-button @click="wsActions.isVideoAndLive(device.udid, index)">判断视频还是直播</el-button> -->
</div>
</el-scrollbar>
<div class="content" @click.self="selectedDevice = 999">
<div class="video-container" @click.self="selectedDevice = 999" v-for="(device, index) in deviceInformation"
:key="device.udid" @mouseup="(e) => handleCanvasup(device.udid, e, index)">
<div class="video-canvas">
<video :ref="(el) => (videoElement[device.udid] = el)" autoplay muted playsinline
:style="getVideoStyle(index)" @click.stop="selectedDeviceFun(index)"></video>
<canvas class="canvas" v-show="selectedDevice == index" :ref="(el) => (canvasRef[device.udid] = el)"
@mousemove.stop="(e) => handleMouseMove(device.udid, e, index)"
@mousedown.stop="(e) => handleCanvasdown(device.udid, e, index)">
</canvas>
</div>
<div class="input-info" v-show="selectedDevice == index" :style="{ left: phone.width * 1.4 + 4 + '0px' }">
<!-- <div class="input-info" v-show="false"> -->
<el-button @click="showDialog = true; dialogTitle = '主播ID';">批量关注</el-button>
<div></div>
<el-button @click="showDialog = true; dialogTitle = '评论';">导入评论</el-button>
<div></div>
<el-button @click="getComArr(index, '评论')">查看评论</el-button>
<div></div>
<el-button @click="showDialog = true; dialogTitle = '私信';">导入私信</el-button>
<div></div>
<el-button @click="getComArr(index, '私信')">查看私信</el-button>
<el-button @click="wsActions.getmesNum(device.udid, index)">检测消息</el-button>
<el-button @click="wsActions.clickMesage(device.udid, index)">进入消息</el-button>
<el-button @click="wsActions.clickCopyList(device.udid, index)">获取聊天记录</el-button>
<el-button @click="wsActions.getSize(device.udid, index)">获取屏幕尺寸</el-button>
<div style="display: flex;">
<el-input style="border: 1px solid #000;" v-model="textContent[index]" type="text"></el-input>
<el-button @click="setComText(index)">发送</el-button>
</div>
<el-button @click="wsActions.test(device.udid, index)">截屏</el-button>
<!-- <el-button @click="wsActions.isHost(device.udid, index)">一键养号</el-button> -->
</div>
</div>
<div class="video-container" @click.self="selectedDevice = 999" v-for="item in 4" :style="getVideoStyle()"
:class="{ 'bottom-row': item >= 2 }">
<div style="position: relative;box-sizing: border-box; ">
</div>
</div>
</div>
<div class="right">
<el-button @click="openShowChat = !openShowChat">聊天</el-button>
<ChatDialog :visible="openShowChat" leftMsg="123123" rightMsg="safdgasdfasdfasgasedyr5q3245214632452345tsdfgasdf"
@close="show = false" />
<!-- <ChatDialog :visible="openShowChat" :leftMsg="chatContent.left" :rightMsg="chatContent.right"
@close="show = false" /> -->
</div>
<MultiLineInputDialog v-model:visible="showDialog" :initialText='""' :title="dialogTitle" :index="selectedDevice"
@confirm="onDialogConfirm" @cancel="onDialogCancel" />
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, onBeforeUnmount, watch, inject } from "vue";
import VideoConverter from "h264-converter";
import { Buffer } from 'buffer'; // 如果在浏览器环境需要引入buffer包
import { useRouter } from 'vue-router';
import { setphoneXYinfo, getphoneXYinfo } from '@/utils/storage'
import { set } from "lodash";
import { toBufferBtn, stringToUtf8ByteArray, getClipboard, setClipboard, bufferToString, startsWithHeader, trimLongArray, base64ToBinary, toBuffer } from '@/utils/bufferUtils';
import { createWsActions } from '@/utils/wsActions';
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
import { chat } from "@/api/chat";
import MultiLineInputDialog from '@/components/MultiLineInputDialog.vue'; // 根据实际路径修改
import ChatDialog from '@/components/ChatDialog.vue'
const router = useRouter();
let wsActions = null;
// 引入刷新方法
const reload = inject("reload")
let refresh = ref(true);
let phone = ref({ width: 207, height: 470 });
const openStr = base64ToBinary("ZQBwAAAAAAA8CgLQAtAAAAAAAAAAAAD/AAAAAAAAAAAAAAAA"); //开启视频流的启动命令
const eitwo = base64ToBinary("BAIAAABHVFJD"); //开启设备信息的命令
let isshow = ref(true);
let playTimer = ref([{}, {}, {}, {}, {}, {}, {}, {}]); // 播放定时器
let isdown = ref(false);
const videoElement = ref({});
let deviceInformation = ref([]);
//当前弹窗类型
let dialogTitle = ref('');
//评论文本内容
let textContent = ref(['', '', '', '', '', '', '', '']);
let textContentArr = ref([[], [], [], [], [], [], [], [], []]);
//主播id内容
let hostIdContent = ref(['', '', '', '', '', '', '', '']);
let hostIdContentArr = ref([[], [], [], [], [], [], [], [], []]);
//私信文本内容
let textContentpri = ref(['', '', '', '', '', '', '', '']);
let textContentpriArr = ref([[], [], [], [], [], [], [], [], []]);
//保存聊天内容
let chatList = ref([{}, {}, {}, {}, {}, {}, {}, {}]);
//传入弹窗的聊天内容
let chatContent = ref({
left: '',
right: ''
});
//选中设备
let selectedDevice = ref(999);
//ws列表
let wslist = [];
// 是否停止点赞
let isStopLike = ref([false, false, false, false, false, false, false, false]);
//播放器列表
let instanceList = ref([{}, {}, {}, {}, {}, {}, {}, {}]);
//是否是在关注主播
let runType = ref(['', '', '', '', '', '', '', '']);
//屏幕尺寸系数
let iponeCoefficient = ref([{ width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }, { width: 1, height: 1 }]);
//是否是发送的内容
let isSend = ref(false)
//adb任务队列
const taskQueues = new Map(); // 每个设备一个队列
//弹窗是否显示
let showDialog = ref(false);
//监听消息定时器
let isShowMes = ref();
//给ws-scrcpy发送的数据格式
const mouseData = {
type: 2,
action: 0,
pointerId: 0,
position: {
point: { x: 0, y: 0 },
screenSize: { width: 320, height: 720 },
},
pressure: 1,
buttons: 1,
};
//打开聊天窗口的状态
let openShowChat = ref(false);
let phoneXYinfo = ref(getphoneXYinfo() == null ? [{}, {}, {}, {}, {}, {}, {}, {}] : getphoneXYinfo());
console.log('phoneXYinfo.value', phoneXYinfo.value)
const wsCache = new Map();
//``````````````````````````````````````````````````````````````````````````````````
// 初始化 手机显示WebSocket 和视频流
const initVideoStream = (udid, index) => {
//``````````````````````````````````````````````````````````````````````````````````
// 1. 检查缓存中是否已有实例
if (wsCache.has(udid)) {
const cachedWs = wsCache.get(udid);
if (cachedWs.readyState === WebSocket.OPEN) {
return cachedWs;
}
// 如果连接已关闭,清除缓存并重新创建
wsCache.delete(udid);
}
// 2. 创建专用实例容器
instanceList.value[index] = {
wsVideo: null,
converter: null,
timer: null
};
//``````````````````````````````````````````````````````````````````````````````````
if (!videoElement.value) return;
// 1. 创建 h264-converter 实例
instanceList.value[index].converter = new VideoConverter(videoElement.value[udid], 60, 1);
// instanceList.value[index].converter.play();
// 2. 连接 WebSocket
wslist[index] = new WebSocket(
`ws://127.0.0.1:8000/?action=proxy-adb&remote=tcp%3A8886&udid=${udid}`
);
wslist[index].binaryType = "arraybuffer";
wslist[index].onopen = () => {
console.log("手机显示ws已开启");
wsActions = createWsActions(wslist, isStopLike);
// 发送 开启 视频流数据
setTimeout(() => {
wslist[index].send(openStr);
}, 300);
playTimer.value[index] = setTimeout(() => {
instanceList.value[index].converter.play();
}, 3000);
//``````````````````````````````````````````````````````````````````````````````````
wsCache.set(udid, instanceList.value[index]);
//``````````````````````````````````````````````````````````````````````````````````
};
const magicSize = stringToUtf8ByteArray('scrcpy_message');
// 3. 处理接收到的二进制数据
wslist[index].onmessage = (event) => {
const data = new Uint8Array(event.data);
//判断返回的如果是字符串为自定义返回
if (typeof event.data == 'string') {
if (isStopLike.value[index]) {
createTaskQueue(index).clear();//清除队伍中的任务
return;
}
const resData = JSON.parse(event.data)
//成功处理
console.log('自定义返回', resData)
if (resData.status === 'success') {
//触发长按该位置
if (resData.type == 'clickCopy') {
console.log('长按', resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index)
clickxy(resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index, 9) //index为9的时候长按
setTimeout(() => {
wsActions.clickCopyText(resData.udid, index)
}, 1500);
} else if (resData.type == 'getmesNum') {
if (resData.message == 0) {
console.log('没有消息')
} else if (resData.message == 1) {
console.log('有消息')
} else if (resData.message == '点击成功') {
console.log('双击', iponeCoefficient.value[index].width, iponeCoefficient.value[index].height)
setTimeout(() => {
clickxy(resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index)
setTimeout(() => {
clickxy(resData.x * iponeCoefficient.value[index].width, resData.y * iponeCoefficient.value[index].height, index) //index为9的时候长按
}, 100)
wsActions.clickMesage(deviceInformation.value[index].udid, index) //点击消息进入对话框
}, 1500)
}
} else if (resData.type == 'clickMesage') {
wsActions.clickCopyList(deviceInformation.value[index].udid, index)
} else if (resData.type == 'isVideoAndLive') {
console.log(resData.message)
} else if (resData.type == 'clickCopyList') { //获取到的消息列表
//打印所有信息
console.log('消息列表', resData.message)
const mesBox = resData.message[resData.message.length - 1]
//打印最新一条信息
console.log('最新消息', mesBox)
chat({ msg: mesBox.text }).then(res => {
console.log(res)
chatList.value[index].leftMsg = res.data.translationResult
chatList.value[index].rightMsg = res.data.chineseResult
// textContentpri.value[index] = res.data.finalResult
// PrivatetexToPrivatePush(deviceInformation.value[index].udid, index)
})
} else if (resData.action == 'getSize') {
console.log(iponeCoefficient.value, '手机尺寸宽度:', resData.width, '高度:', resData.height);
iponeCoefficient.value[index].width = 320 / resData.width
iponeCoefficient.value[index].height = 720 / resData.height
console.log('尺寸系数', iponeCoefficient.value[index])
} else {
console.log(resData.type, '坐标返回x:', resData.x, 'y:', resData.y);
// clickxy(resData.x * 0.3, resData.y * 0.3, index)
}
phoneXYinfo.value[index].id = resData.udid
if (resData.type == 'Likes') {//判断是否是 点赞
phoneXYinfo.value[index].Likes = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
if (runType.value[index] == 'follow') {
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
}
} else if (resData.type == 'Comment') {//打开评论
phoneXYinfo.value[index].Comment = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'Comtext') {//评论输入
phoneXYinfo.value[index].Comtext = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
// setTimeout(() => {
// setComText(index)//粘贴内容
// }, 1000)
} else if (resData.type == 'ComPush') {//评论发送
phoneXYinfo.value[index].ComPush = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
setTimeout(() => {
Back('', index)
if (runType.value[index] == 'follow') {
setTimeout(() => {
Back('', index)
}, 1000)
} else {
setTimeout(() => {
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
setTimeout(() => {
console.log('观看视频中')
randomSeeVideo(deviceInformation.value[index].udid, index) //50秒后继续循环任务
}, 1000);
}, 1000)
}
}, 800)
} else if (resData.type == 'tomy') {//打开主页
phoneXYinfo.value[index].tomy = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'Attention') {//关注、打开私信
phoneXYinfo.value[index].Attention = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
// LikesToCommentToComPush(deviceInformation.value[index].udid, index) //是否继续循环任务
} else if (resData.type == 'return') {//私信评论
phoneXYinfo.value[index].return = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
// wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
// setTimeout(() => {
// LikesToCommentToComPush(deviceInformation.value[index].udid, index)
// }, 1000);
} else if (resData.type == 'addHost') {//添加关注
phoneXYinfo.value[index].addHost = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
setTimeout(() => {
wsActions.isHost(deviceInformation.value[index].udid, index)//检测
}, 1000);
} else if (resData.type == 'Privatetex') {//私信评论
phoneXYinfo.value[index].Privatetex = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'PrivatePush') {//私信发送
phoneXYinfo.value[index].PrivatePush = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
//如果是在关注主播的任务中
if (runType.value[index] == 'follow') {
runType.value[index] = ''
}
setTimeout(() => {
Back('', index);
setTimeout(() => {
Back('', index);
if (runType.value[index] == 'listen') {
} else {
LikesToLikesToLikes(deviceInformation.value[index].udid, index, 2)
}
}, 1000);
}, 1000);
} else if (resData.type == 'clickCopy') {//点击复制
phoneXYinfo.value[index].clickCopy = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'hostVideo') {//点击复制
phoneXYinfo.value[index].hostVideo = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
} else if (resData.type == 'isHost') {//视频关注主播
phoneXYinfo.value[index].isHost = { x: resData.x * iponeCoefficient.value[index].width, y: resData.y * iponeCoefficient.value[index].height }
if (resData.message == 0) {
console.log('无关注', index)
Back(deviceInformation.value[index].udid, index)
setTimeout(() => {
console.log('观看视频中', index)
randomSeeVideo(deviceInformation.value[index].udid, index) //30-50秒后继续循环任务
}, 1000);
} else if (resData.message == 1) {
console.log('有关注', index)
const randomNum = Math.random(); // 生成一个0到1之间的随机数
if (randomNum < 0.5) {
console.log('进行点赞评论', index)
LikesToCommentToComPush(deviceInformation.value[index].udid, index)
} else {
console.log('下一个', index)
wsActions.slideDown(deviceInformation.value[index].udid, index)//是否继续下一个视频
setTimeout(() => {
console.log('观看视频中', index)
randomSeeVideo(deviceInformation.value[index].udid, index) //30-50秒后继续循环任务
}, 1000);
}
}
}
setphoneXYinfo(phoneXYinfo.value)
if (resData.type != 'isHost') {
if (resData.type == 'hostVideo' || resData.type == 'Likes') {
setTimeout(() => {
createTaskQueue(index).next(); // 继续队列中下一个任务
}, 5000)
} else {
createTaskQueue(index).next(); // 继续队列中下一个任务
}
}
} else {
// --------------------------------------------------------------------------报错处理
// console.error(resData.type, resData)
//如果该视频无法被评论,返回刷下一条视频
if (resData.type == 'Comtext') {
if (runType.value[index] == 'follow') {
createTaskQueue(index).clear();//清除队伍中的任务
Back('', index)
setTimeout(() => {
Back('', index)
setTimeout(() => {
Back('', index)
LikesToLikesToLikes(deviceInformation.value[index].udid, index, 2)
}, 1000)
}, 1000)
} else {
createTaskQueue(index).clear();//清除队伍中的任务
Back('', index)
setTimeout(() => {
Back('', index)
setTimeout(() => {
wsActions.isHost(deviceInformation.value[index].udid, index)
}, 1000)
}, 1000)
}
//如果该视频无法被私信,返回刷下一条视频
} else if (resData.type == 'Privatetex') {
createTaskQueue(index).clear();//清除队伍中的任务
Back('', index)
setTimeout(() => {
Back('', index)
LikesToLikesToLikes(deviceInformation.value[index].udid, index, 2)
}, 1000)
} else if (resData.type == 'hostVideo') {
createTaskQueue(index).clear();//清除队伍中的任务
Back('', index)
setTimeout(() => {
LikesToLikesToLikes(deviceInformation.value[index].udid, index, 2)
}, 1000)
} else if (resData.type == 'getmesNum' || resData.type == 'clickMesage') {
} else if (resData.type == 'clickCopyList') {
//如果无法获取到聊天记录,返回继续检测
Back('', index)
} else {
console.error(resData.message); // 错误处理
ElMessage.error(resData.message);
createTaskQueue(index).clear();//清除队伍中的任务
}
// createTaskQueue(index).next(); // 继续队列中下一个任务
}
}
//返回粘贴板内容
if (startsWithHeader(magicSize, data)) {
if (!isSend.value) {
const buffer = trimLongArray(data, magicSize);
const paste = bufferToString(buffer);
console.log('获取粘贴板内容', paste)
}
}
//视频流处理
if (instanceList.value[index].converter) {
if (isshow.value) {
instanceList.value[index].converter.appendRawData(data);
}
}
};
// 4. 错误处理
wslist[index].onerror = (error) => {
wsCache.delete(udid); // 错误时清理缓存
};
//``````````````````````````````````````````````````````````````````````````````````
wslist[index].onclose = (event) => {
wsCache.delete(udid)// 自动清理缓存
clearInterval(instanceList.value[index].timer); // 清理定时器// 移除缓存
};
//``````````````````````````````````````````````````````````````````````````````````
};
// 配置参数
let canvasRef = ref({});
// 初始化画布
const initCanvas = (udid) => {
const canvas = canvasRef.value[udid];
const dpr = window.devicePixelRatio || 1;
canvas.style.width = `${phone.value.width * 1.4}px`;
canvas.style.height = `${phone.value.height * 1.4}px`;
canvas.width = phone.value.width * 1.4 * dpr;
canvas.height = phone.value.height * 1.4 * dpr;
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
// 绘制参考网格(可选)
ctx.strokeStyle = "#ffffff00";
for (let x = 0; x <= phone.value.width; x += 100) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, phone.value.height);
ctx.stroke();
}
};
// 鼠标按下事件处理
const handleCanvasdown = (udid, event, index) => {
const { x, y } = getCanvasCoordinate(event, udid);
console.log("鼠标按下", x, y);
isdown.value = true;
mouseData.action = 0;
mouseData.pressure = 1;
mouseData.buttons = 1;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
// console.log(mouseData);
console.log(index)
setTimeout(() => {
selectedDevice.value = index;
}, 300);
console.log(wslist)
wslist[index].send(toBuffer(mouseData));
};
// 鼠标抬起事件处理
const handleCanvasup = (udid, event, index) => {
const { x, y } = getCanvasCoordinate(event, udid);
// position.endX = floorNum(x)
// position.endY = floorNum(y)
isdown.value = false;
mouseData.action = 1;
mouseData.pressure = 0;
mouseData.buttons = 0;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
// console.log(mouseData);
wslist[index].send(toBuffer(mouseData));
};
// 鼠标移动事件处理
const handleMouseMove = (udid, event, index) => {
if (isdown.value) {
const { x, y } = getCanvasCoordinate(event, udid);
// position.startX = floorNum(x)
// position.startY = floorNum(y)
mouseData.action = 2;
mouseData.pressure = 1;
mouseData.buttons = 1;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
// console.log(mouseData);
wslist[index].send(toBuffer(mouseData));
}
};
// 坐标计算
const getCanvasCoordinate = (event, udid) => {
const canvas = canvasRef.value[udid];
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
return {
x: (event.clientX - rect.left) / 0.9138 * dpr,
y: (event.clientY - rect.top) / 0.9138 * dpr,
};
};
//返回上一层
const Back = (udid, index) => {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": 4, "metaState": 0, "repeat": 0 }));
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": 4, "metaState": 0, "repeat": 0 }));
}
//返回首页
const Home = (udid, index) => {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": 3, "metaState": 0, "repeat": 0 }));
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": 3, "metaState": 0, "repeat": 0 }));
}
//任务视图
const Overview = (udid, index) => {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": 187, "metaState": 0, "repeat": 0 }));
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": 187, "metaState": 0, "repeat": 0 }));
}
//回车 index手机下标 state up按下 down抬起 keycode 键
const key = (index, state, keycode) => {
// console.log("退格", index)
if (index === 999) {
return
} else {
if (state == 'up') {
wslist[index].send(toBufferBtn({ "type": 0, "action": 1, "keycode": keycode, "metaState": 0, "repeat": 0 }));
} else if (state == 'down') {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": keycode, "metaState": 0, "repeat": 0 }));
}
}
}
const handleVisibilityChange = () => {
if (document.visibilityState === "hidden") {
if (playTimer.value) {
console.log("清除定时器");
playTimer.value.forEach(Timer => {
clearInterval(Timer);
});
playTimer.value = [{}, {}, {}, {}, {}, {}, {}, {}];
}
console.log("页面被隐藏");
isshow.value = false;
// 页面被隐藏时执行的逻辑
} else if (document.visibilityState === "visible") {
console.log("页面变为可见");
setTimeout(() => {
isshow.value = true;
// ObtainDeviceInformation();
}, 500);
}
};
onMounted(() => {
document.addEventListener("visibilitychange", handleVisibilityChange);
ObtainDeviceInformation();
window.addEventListener('keydown', (e) => {
console.log('触发按键了 键盘事件有效', e)
if (e.key == 'Backspace') {
key(selectedDevice.value, 'down', 67)
}
})
window.addEventListener('keyup', (e) => {
// console.log('触发按键了 键盘事件有效抬起', e.keyCode, e.key)
if (e.key == 'Backspace') {
key(selectedDevice.value, 'up', 67)
}
})
const loading = ElLoading.service({
lock: true,
text: '初始化中...',
background: 'rgba(0, 0, 0, 0.7)',
})
setTimeout(() => {
loading.close()
}, 2000)
});
onBeforeUnmount(() => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
});
// 组件卸载时清理
onUnmounted(() => {
console.log("卸载组件");
cloesMonitor(); //关闭检测消息
});
//获取设备信息
const ObtainDeviceInformation = () => {
// 2. 连接 WebSocket
const ws = new WebSocket("ws://127.0.0.1:8000/?action=multiplex");
ws.binaryType = "arraybuffer";
ws.onopen = () => {
ws.send(eitwo);
};
// 3. 处理接收到的二进制数据
ws.onmessage = (event) => {
const data = JSON.parse(new TextDecoder('utf-8').decode(event.data).replace(/[^\x20-\x7F]/g, ''));
try {
console.log('数组', data)
if (data.type == "devicelist") {
deviceInformation.value = [];
const filteredList = data.data.list.filter(item => item.state === 'device');
//检测到设备列表时,渲染所有设备
filteredList.forEach((item, index) => {
console.log(item);
if (item.state === "device") {
deviceInformation.value.push(item);
console.log("deviceInformation", deviceInformation.value);
setTimeout(() => {
initVideoStream(item.udid, index);
initCanvas(item.udid);
setTimeout(() => {
wsActions.getSize(item.udid, index)
}, 2000)
}, 300);
}
})
} else if (data.type == "device") {
if (data.data.device.state === "offline") {
//监听设备信息,出现离线设备,则删除设备信息
deviceInformation.value.forEach((item, index) => {
if (item.udid === data.data.device.udid) {
deviceInformation.value.splice(index, 1);
if (index < wslist.length - 1) {
deviceInformation.value.forEach((item, index1) => {
//关闭websocket连接
wslist.forEach((item, index) => {
item.close();
})
//重新连接websocket
new Promise((resolve, reject) => {
setTimeout(() => {
try {
initVideoStream(item.udid, index1);
initCanvas(item.udid);
} catch (error) {
reject(error);
}
}, 300);
});
})
}
console.log("deviceInformation", deviceInformation.value);
}
})
} else if (data.data.device.state === "device") {
let isrepeat = false;
//监听设备信息,出现新设备,则添加设备信息
deviceInformation.value.forEach((item, index) => {
if (item.udid == data.data.device.udid) {
isrepeat = true;
}
})
if (!isrepeat) {
deviceInformation.value.push(data.data.device);
// setTimeout(() => {
// initVideoStream(data.data.device.udid, deviceInformation.value.length - 1);
// initCanvas(data.data.device.udid);
// }, 300);
new Promise((resolve, reject) => {
setTimeout(() => {
try {
initVideoStream(data.data.device.udid, deviceInformation.value.length - 1);
initCanvas(data.data.device.udid);
} catch (error) {
// reject(error);
}
}, 1000);
});
}
};
}
} catch (e) {
console.error(e);
}
};
}
//喜欢-输入评论
async function LikesToCommentToComPush(udid, index) {
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Comment', index, resourceId: 'com.zhiliaoapp.musically:id/cvd' });
await sendWsTask(index, { udid, action: 'click', type: 'Comtext', index, resourceId: 'com.zhiliaoapp.musically:id/cs0' });
await sendWsTask(index, { udid, action: 'click', type: 'ComPush', index, resourceId: 'com.zhiliaoapp.musically:id/bqg' });
}
//点赞主页4个作品+评论最后一个作品并返回
async function LikesToLikesToLikes(udid, index, type) {
runType.value[index] = 'follow';
if (type == 2) {
await sendWsTask(index, { udid, action: 'click', type: 'searchTxt', index, resourceId: 'com.zhiliaoapp.musically:id/f0l' });
await sendWsTask(index, { udid, action: 'click', type: 'close', index, resourceId: 'com.zhiliaoapp.musically:id/blw' });
} else if (type == 1) {
await sendWsTask(index, { udid, action: 'click', type: 'search', index, resourceId: 'com.zhiliaoapp.musically:id/gtz' });
}
await sendWsTask(index, { udid, action: 'click', type: 'searchHost', index, resourceId: 'com.zhiliaoapp.musically:id/t6f' });
await sendWsTask(index, { udid, action: 'click', type: 'toHost', index, resourceId: 'com.zhiliaoapp.musically:id/iso' });
await sendWsTask(index, { udid, action: 'click', type: 'hostVideo', index, resourceId: 'com.zhiliaoapp.musically:id/u3o', num: 0 });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Likes', index, resourceId: 'com.zhiliaoapp.musically:id/dy6' });
await sendWsTask(index, { udid, action: 'click', type: 'Comment', index, resourceId: 'com.zhiliaoapp.musically:id/cvd' });
await sendWsTask(index, { udid, action: 'click', type: 'Comtext', index, resourceId: 'com.zhiliaoapp.musically:id/cs0' });
await sendWsTask(index, { udid, action: 'click', type: 'ComPush', index, resourceId: 'com.zhiliaoapp.musically:id/bqg' });
await sendWsTask(index, { udid, action: 'click', type: 'Attention', index, resourceId: 'com.zhiliaoapp.musically:id/dhx' });
await sendWsTask(index, { udid, action: 'click', type: 'Attention', index, resourceId: 'com.zhiliaoapp.musically:id/dhx' });
await sendWsTask(index, { udid, action: 'click', type: 'Privatetex', index, resourceId: 'com.zhiliaoapp.musically:id/hob' });
await sendWsTask(index, { udid, action: 'click', type: 'PrivatePush', index, resourceId: 'com.zhiliaoapp.musically:id/hog' });
// await sendWsTask(index, { udid, action: 'click', type: 'fanhui', index, resourceId: 'com.zhiliaoapp.musically:id/awi' });
}
//私信
async function PrivatetexToPrivatePush(udid, index) {
await sendWsTask(index, { udid, action: 'click', type: 'Privatetex', index, resourceId: 'com.zhiliaoapp.musically:id/hob' });
await sendWsTask(index, { udid, action: 'click', type: 'PrivatePush', index, resourceId: 'com.zhiliaoapp.musically:id/hog' });
}
//发送评论字符到手机方法
function setComText(index) {
isSend.value = true;
setTimeout(() => {
isSend.value = false;
}, 300);
console.log('发送评论内容', index, textContent.value[index])
wslist[index].send(setClipboard(textContent.value[index]));
textContent.value[index] = textContentArr.value[index][getRandomNumber(textContentArr.value[index].length - 1)];
}
//发送主播ID字符到手机方法
function setHostId(index) {
isSend.value = true;
setTimeout(() => {
isSend.value = false;
}, 300);
console.log('发送主播id内容', index, hostIdContent.value[index])
wslist[index].send(setClipboard(hostIdContent.value[index])); //发送内容
//获取当前主播ID的下标
const indexBom = hostIdContentArr.value[index].indexOf(hostIdContent.value[index]) + 1;
console.log(hostIdContentArr.value[index].indexOf(hostIdContent.value[index]) + 1, '当前主播下标');
console.log(hostIdContentArr.value[index], '主播列表');
//如果是最后一个ID 则重置
if (indexBom == hostIdContentArr.value[index].length) {
// isStopLike.value[index] = true;
hostIdContent.value[index] = ''
return
}
hostIdContent.value[index] = hostIdContentArr.value[index][indexBom];
console.log(hostIdContent.value[index], '下次搜索')
}
//发送私信字符到手机方法
function setPrivateText(index) {
isSend.value = true;
setTimeout(() => {
isSend.value = false;
}, 300);
console.log('发送私信内容', index, textContentpri.value[index])
wslist[index].send(setClipboard(textContentpri.value[index]));
textContentpri.value[index] = textContentpriArr.value[index][getRandomNumber(textContentpriArr.value[index].length - 1)];
}
//获取手机粘贴板方法
function getText(index) {
wslist[index].send(getClipboard());
}
function getComArr(index, type) {
if (type == '评论') {
ElMessageBox.alert(textContentArr.value[index], `当前${type}内容`, {
confirmButtonText: 'OK',
})
} else {
ElMessageBox.alert(textContentpriArr.value[index], `当前${type}内容`, {
confirmButtonText: 'OK',
})
}
}
//传入xy坐标 进行点击
function clickxy(x, y, index, type) {
console.log('clickxy方法', x, y)
if (type == 3) { //关注/私信 后的返回和下滑
mouseData.action = 0;
mouseData.pressure = 1;
mouseData.buttons = 1;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
wslist[index].send(toBuffer(mouseData));
setTimeout(() => {
mouseData.action = 1;
wslist[index].send(toBuffer(mouseData));
}, 100)
//返回和下滑
setTimeout(() => {
Back('', index)
setTimeout(() => {
wsActions.slideDown(phoneXYinfo.value[index].id, index)
setTimeout(() => {
start(phoneXYinfo.value[index].id, index)
}, 300)
}, 300)
}, 300)
} else if (type == 2) { //评论过后的返回和右滑
mouseData.action = 0;
mouseData.pressure = 1;
mouseData.buttons = 1;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
wslist[index].send(toBuffer(mouseData));
setTimeout(() => {
mouseData.action = 1;
wslist[index].send(toBuffer(mouseData));
}, 100)
//返回和右滑
setTimeout(() => {
Back('', index)
setTimeout(() => {
wsActions.slideRight(phoneXYinfo.value[index].id, index)
}, 300)
}, 300)
} else if (type == 1) {//评论框点击后的发送内容
mouseData.action = 0;
mouseData.pressure = 1;
mouseData.buttons = 1;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
wslist[index].send(toBuffer(mouseData));
setTimeout(() => {
mouseData.action = 1;
wslist[index].send(toBuffer(mouseData));
}, 100)
//发送内容
setTimeout(() => {
console.log('点击了')
setComText(index)
}, 300)
} else if (type == 9) { //长按
mouseData.action = 0;
mouseData.pressure = 1;
mouseData.buttons = 1;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
wslist[index].send(toBuffer(mouseData));
console.log('鼠标按下')
setTimeout(() => {
mouseData.action = 1;
wslist[index].send(toBuffer(mouseData));
console.log('抬起按下')
}, 1500)
} else {
mouseData.action = 0;
mouseData.pressure = 1;
mouseData.buttons = 1;
mouseData.position.point.x = x;
mouseData.position.point.y = y;
wslist[index].send(toBuffer(mouseData));
setTimeout(() => {
mouseData.action = 1;
wslist[index].send(toBuffer(mouseData));
}, 100)
}
}
//创建任务实例
function createTaskQueue(index) {
if (!taskQueues.has(index)) {
taskQueues.set(index, []);
}
return {
enqueue(task) {
taskQueues.get(index).push(task);
if (taskQueues.get(index).length === 1) {
task(); // 执行第一个任务
}
},
next() {
const queue = taskQueues.get(index);
queue.shift(); // 移除已完成任务
if (queue.length > 0) {
queue[0](); // 执行下一个任务
}
},
clear() {
taskQueues.set(index, []); // 清除所有任务
}
};
}
//发送检查该手机的xy坐标任务
function sendWsTask(index, data) {
console.log('发送任务', data.type);
return new Promise((resolve) => {
const queue = createTaskQueue(index);
const task = () => {
//发送评论的文本粘贴事件
if (data.type == 'Likes') {
clickxy(160, 360, index)
}
if (data.type == 'isHost') {
// clickxy(160, 360, index)
}
if (data.type == 'Comment') {
if (runType.value[index] == 'follow') {
clickxy(160, 360, index)
}
}
if (data.type == 'ComPush') {
setTimeout(() => {
setComText(index)//粘贴内容
}, 500)
}
if (data.type == 'searchHost') {
setTimeout(() => {
setHostId(index)//粘贴内容
}, 500)
}
//发送私信的文本粘贴事件
if (data.type == 'PrivatePush') {
setPrivateText(index)
}
//关注前的返回和右滑
if (data.type == 'Attention') {
// setTimeout(() => {
// Back('', index)
// setTimeout(() => {
// wsActions.slideRight(data.udid, index)
// }, 500);
// }, 500);
}
//发送关注之前的返回
if (data.type == 'addHost') {
// setTimeout(() => {
// Back('', index)
// setTimeout(() => {
// clickxy(160, 360, index)
// wsActions.isHost(data.udid, index)
// }, 1000);
// }, 500);
}
//发送任务
console.log('发送任务', data);
if (data.type == 'Attention') {
setTimeout(() => {
wslist[index].send(JSON.stringify(data));
resolve();
}, 1000)
} else {
wslist[index].send(JSON.stringify(data));
resolve();
}
// 表示当前任务“已发出”,但不是“已完成”
// 实际完成由 onmessage 中的 success 决定并继续执行队列
};
queue.enqueue(task);
});
}
// 生成start秒到end秒的随机延迟
function randomDelay(start, end) {
return Math.floor(Math.random() * end * 1000) + start * 1000;
}
//一个0到n之间的随机整数
function getRandomNumber(n) {
return Math.floor(Math.random() * (n + 1));
}
//延迟30-50秒后检测关注
function randomSeeVideo(udid, index) {
const delay = Math.floor(Math.random() * (50 - 30 + 1) + 30) * 1000;
setTimeout(() => {
console.log('观看结束', index);
wsActions.isHost(udid, index)//检测
}, delay);
}
//选中样式
function getVideoStyle(index) {
const isSelected = selectedDevice.value === index;
const baseWidth = phone.value.width;
const baseHeight = phone.value.height;
// console.log(isSelected, '是否相等')
return {
width: isSelected ? baseWidth * 1.4 + 'px' : baseWidth + 'px',
height: isSelected ? baseHeight * 1.4 + 'px' : baseHeight + 'px',
border: isSelected ? '2px solid blue' : '1px solid blue',
position: isSelected ? 'absolute' : 'relative',
top: isSelected ? '0' : 'unset',
left: isSelected ? '0' : 'unset',
zIndex: isSelected ? 1000 : 1,
pointerEvents: isSelected ? 'none' : 'auto',
};
}
function stop(udid, index) {
// actions[index] = [];
cloesMonitor();
isStopLike.value.forEach((item, i) => {
isStopLike.value[i] = true;
})
console.log('停止', isStopLike.value);
}
//刷新页面方法
function reloadfun() {
reload()
}
// //返回次数
// function repeatBack(index, times, delay = 1000) {
// if (typeof times !== 'number' || times <= 0) return;
// for (let i = 1; i <= times; i++) {
// setTimeout(() => {
// Back('', index);
// }, i * delay);
// }
// }
function openTk() {
deviceInformation.value.forEach((device, index) => {
wsActions.open(device.udid, index)
})
}
//监听所有手机是否有消息
function openMonitor() {
deviceInformation.value.forEach((device, index) => {
wsActions.getmesNum(device.udid, index)
isStopLike.value[index] = false;
runType.value[index] = 'listen'
})
isShowMes.value = setInterval(() => {
deviceInformation.value.forEach((device, index) => {
wsActions.getmesNum(device.udid, index)
isStopLike.value[index] = false;
})
}, 10000)
}
//关闭监听
function cloesMonitor() {
deviceInformation.value.forEach((device, index) => {
runType.value[index] = ''
})
clearInterval(isShowMes.value)
isShowMes.value = ''
}
//一键养号
function parentNum() {
deviceInformation.value.forEach((device, index) => {
isStopLike.value[index] = false;
wsActions.isHost(device.udid, index)
})
}
//取消弹窗
function onDialogCancel() {
}
//确认多行文本框内容
function onDialogConfirm(result, type, index) {
console.log(result, type, index);
if (type == '评论') {
//index ==999 表示全部
if (index == 999) {
textContentArr.value.forEach((item, indexA) => {
textContentArr.value[indexA] = result;
textContent.value[indexA] = result[getRandomNumber(result.length - 1)];
})
parentNum()
} else if (index == 998) {
textContentArr.value.forEach((item, indexA) => {
textContentArr.value[indexA] = result;
textContent.value[indexA] = result[getRandomNumber(result.length - 1)];
})
//打开私信弹窗
selectedDevice.value = 999;
dialogTitle.value = '私信';
setTimeout(() => {
showDialog.value = true;
}, 600)
} else {
textContentArr.value[index] = result;
textContent.value[index] = result[getRandomNumber(result.length - 1)];
}
} else if (type == '私信') {
//index ==999 表示全部
if (index == 999) {
textContentpriArr.value.forEach((item, indexA) => {
textContentpriArr.value[indexA] = result;
textContentpri.value[indexA] = result[getRandomNumber(result.length - 1)];
})
deviceInformation.value.forEach((device, indexB) => {
LikesToLikesToLikes(device.udid, indexB, 1)
})
} else {
textContentpriArr.value[index] = result;
textContentpri.value[index] = result[getRandomNumber(result.length - 1)];
}
} else if (type == '主播ID') {
//index ==999 表示全部
if (index == 999) {
//分割数组 几台设备 分为几个数组
const hostIdArrList = splitArray(result, deviceInformation.value.length)
deviceInformation.value.forEach((device, indexA) => {
hostIdContentArr.value[indexA] = hostIdArrList[indexA];
hostIdContent.value[indexA] = hostIdContentArr.value[indexA][0];
})
//打开评论弹窗
selectedDevice.value = 998;
dialogTitle.value = '评论';
setTimeout(() => {
showDialog.value = true;
}, 600)
} else {
hostIdContentArr.value[index] = result;
hostIdContent.value[index] = result[0];
deviceInformation.value.forEach((device, indexA) => {
if (index == indexA) {
LikesToLikesToLikes(device.udid, index, 1)
}
})
}
}
}
//分割数组
function splitArray(array, parts) {
if (!Array.isArray(array)) {
console.warn('splitArray: 第一个参数应为数组');
return [];
}
const len = array.length;
const n = parseInt(parts, 10);
if (isNaN(n) || n <= 0) {
console.warn('splitArray: 份数应为大于 0 的整数');
return [];
}
const result = [];
for (let i = 0; i < n; i++) {
// 计算第 i 份的起始和结束索引
const start = Math.floor(i * len / n);
const end = Math.floor((i + 1) * len / n);
result.push(array.slice(start, end));
}
return result;
}
function selectedDeviceFun(index) {
selectedDevice.value = index
chatContent.value.left = chatList.value[index].leftMsg
chatContent.value.right = chatList.value[index].rightMsg
console.log(chatList.value);
console.log(chatContent.value);
}
</script>
<style scoped lang="less">
body {
user-select: none;
}
.main {
display: flex;
width: 100vw;
height: 100vh;
background: #222;
}
.left {
background: #191c23;
width: 20vw;
box-sizing: content-box;
}
.content {
width: 50vw;
display: grid;
grid-template-columns: repeat(3, minmax(160px, 320px));
/* 每行 3 个,宽度自适应 */
/* gap: 10px; */
}
.right {
width: 30vw;
background-color: #272727;
}
.video-container {
position: relative;
// padding: 10px;
width: 100%;
width: auto;
/* 取消 100%,避免换行 */
display: inline-block;
display: flex;
transition: all 0.3s ease-in-out;
.video-canvas {
position: absolute;
box-sizing: border-box;
}
.input-info {
color: rgb(255, 255, 255);
background-color: #424242;
position: absolute;
z-index: 10;
}
}
video {
transition: all 0.3s ease-in-out;
/* 关键:让 video 不拦截鼠标事件 */
/* 添加动画 */
}
.top-row {
align-items: flex-start;
/* 上对齐 */
}
.bottom-row {
align-items: flex-end;
/* 下对齐 */
margin-top: auto;
}
video {
/* position: relative; */
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
/* object-fit: contain; */
/* transform: scale(0.27); */
/* 缩小到原始尺寸的50% */
/* transform-origin: top left; */
/* 控制缩放原点 */
}
.canvas {
position: absolute;
top: 0;
left: 0;
z-index: 9;
/* transform: scale(0.27); */
/* 缩小到原始尺寸的50% */
/* transform-origin: top left; */
/* 控制缩放原点 */
}
.open {
background: url(../assets/open.png) no-repeat center center;
background-size: 60% 60%;
}
.Back {
background: url(../assets/Back.png) no-repeat center center;
background-size: 60% 60%;
}
.Home {
background: url(../assets/Home.png) no-repeat center center;
background-size: 60% 60%;
}
.Overview {
background: url(../assets/Overview.png) no-repeat center center;
background-size: 60% 60%;
}
.el-button {
width: 200px;
margin: 20px;
// background-color: darkcyan;
// color: white;
position: relative;
z-index: 999;
}
.center-justify {
display: flex;
justify-content: space-around;
align-items: center;
}
.center-line {
display: flex;
flex-direction: column;
align-items: center;
// justify-content: center;
}
</style>