重构clickxy方法和屏幕滑动方法,对齐屏幕video大小

This commit is contained in:
2025-08-13 14:22:39 +08:00
parent abd52c712d
commit 64f589fe6b
2 changed files with 252 additions and 219 deletions

View File

@@ -1,13 +1,15 @@
<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>
<el-dialog draggable :title="title" v-model="visibleLocal" width="600px" @closed="onClosed">
<el-input type="textarea" v-model="rawText" :rows="10" :placeholder="placeholder" />
<template #footer>
<span v-if="title == '私信'">自动回复
<span v-if="title === '私信'">
自动回复
<el-switch style="margin-right: 20px;" v-model="value1" />
</span>
<el-button v-if="title !== '主播ID'" type="success" @click="exportPrologue(title)">导入{{ title }}</el-button>
<el-button @click="handleCancel">取消</el-button>
<el-button v-if="title !== '主播ID'" type="success" @click="exportPrologue(title)">
导入{{ title }}
</el-button>
<el-button @click="onClickCancel">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</template>
</el-dialog>
@@ -16,114 +18,81 @@
<script setup>
import { ref, computed, watch } from 'vue';
import { prologue, comment } from '@/api/account';
let value1 = ref(false);
// 定义接收的 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,
},
visible: { type: Boolean, required: true },
title: { type: String, default: '' },
initialText: { type: String, default: '' },
type: { type: Number, default: 10 },
index: { type: Number, default: 999 },
placeholder: { type: String, default: '每行一条,支持粘贴多行后自动拆分' },
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;
}
);
// 区分关闭来源true=通过“确定”关闭false=取消/遮罩/ESC/右上角关闭
const closingByConfirm = ref(false);
watch(() => props.initialText, (v) => { rawText.value = v; });
// 计算属性,用于 v-model:visible 绑定 el-dialog
const visibleLocal = computed({
get() {
return props.visible;
},
set(val) {
emit('update:visible', val);
},
get: () => 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;
.map(s => s.trim())
.filter(Boolean);
return props.dedupe ? Array.from(new Set(lines)) : lines;
}
/** 点击确定按钮 */
function handleConfirm() {
const items = parseLines();
emit('confirm', items, props.title, props.index, value1.value);
// 关闭对话框
emit('update:visible', false);
rawText.value = '';
closingByConfirm.value = true; // 标记:这次关闭来自“确定”
emit('update:visible', false); // 关弹窗
}
/** 点击取消或对话框关闭 */
function handleCancel() {
function onClickCancel() {
closingByConfirm.value = false; // 非确认关闭
emit('update:visible', false); // 关弹窗
}
// 所有关闭后的统一收尾
function onClosed() {
const byConfirm = closingByConfirm.value;
// 重置表单状态
rawText.value = '';
emit('update:visible', false);
emit('cancel');
value1.value = false;
// 只有非“确定”关闭才对外发 cancel
if (!byConfirm) emit('cancel');
// 重置标记
closingByConfirm.value = false;
}
function exportPrologue(title) {
if (title === '评论') {
comment().then((res) => {
console.log(res);
comment().then(res => {
rawText.value = res.map(item => item).join('\n\n');
})
});
} else {
prologue().then((res) => {
console.log(res);
prologue().then(res => {
rawText.value = res.map(item => item).join('\n\n');
})
});
}
}
</script>
<style scoped>
/* 根据项目风格调整 */
/* 根据需要自定义样式 */
</style>

View File

@@ -358,7 +358,7 @@ const initVideoStream = (udid, index) => {
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)
wsActions.clickCopyText(resData.device, index)
}, 1500);
} else if (resData.type == 'getmesNum') {
if (resData.message == 0) {
@@ -384,9 +384,11 @@ const initVideoStream = (udid, index) => {
//点击进入新消息页面以后,获取页面信息
wsActions.clickCopyList(deviceInformation.value[index].udid, index)
} else if (resData.type == 'clickSysMesage') {
Back('', index)
setTimeout(() => {
Back('', index)
setTimeout(() => {
Back('', index)
}, 1000)
}, 1000)
} else if (resData.type == 'isVideoAndLive') {
@@ -422,10 +424,41 @@ const initVideoStream = (udid, index) => {
istranslate.value = false
}
} 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])
// console.log(iponeCoefficient.value, '手机尺寸宽度:', resData.width, '高度:', resData.height);
const RAW_W = Math.max(1, resData.width || 0);
const RAW_H = Math.max(1, resData.height || 1);
// 目标高度固定 720按比例缩放宽
const TARGET_H = 720;
const ratio = TARGET_H / RAW_H;
const scaledWFloat = RAW_W * ratio;
const scaledH = TARGET_H;
// 只向下对齐floor。要 32 对齐就把 ALIGN 改 32
const ALIGN = 16;
const downAlign = (x, a) => Math.max(a, Math.floor(x / a) * a);
const scaledW = downAlign(scaledWFloat, ALIGN);
// 1) 给后续发送 pointer 用的 screenSize服务端就按这个坐标系解释 point
mouseData.position.screenSize.width = scaledW;
mouseData.position.screenSize.height = scaledH;
// 2) 保存“对齐后的尺寸”,用于坐标换算等
frameMeta.value[resData.device] = {
w: scaledW,
h: scaledH,
rotation: 0,
};
// 3) 你代码里很多地方用 resData.x * iponeCoefficient把它同步到 320×720 逻辑坐标
// 这样 UI 节点坐标(原始帧坐标) -> 320×720 逻辑坐标
// iponeCoefficient.value[index].width = 320 / scaledW;
// iponeCoefficient.value[index].height = 720 / scaledH;
iponeCoefficient.value[index].width = scaledW / resData.width
iponeCoefficient.value[index].height = scaledH / resData.height
console.log(
`[getSize] raw=${RAW_W}x${RAW_H} -> scaled=${scaledW}x${scaledH} (align↓${ALIGN})`
);
} else {
console.log(resData.type, '坐标返回x:', resData.x, 'y:', resData.y);
}
@@ -433,7 +466,7 @@ const initVideoStream = (udid, index) => {
phoneXYinfo.value[index].id = resData.udid
phoneXYinfo.value[index].id = resData.device
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') {
@@ -564,6 +597,9 @@ const initVideoStream = (udid, index) => {
// 用法:
(async () => {
const result = await randomDelayAndExecute();
if (runType.value[0] !== 'brushLive') {
return
}
//30%概率
if (result) {
@@ -725,66 +761,93 @@ const initCanvas = (udid) => {
};
// 鼠标按下事件处理
const handleCanvasdown = (udid, event, index) => {
const { x, y } = getCanvasCoordinate(event, udid);
console.log("鼠标按下", x, y);
const handleCanvasdown = (udid, e, index) => {
const { x, y } = getCanvasCoordinate(e, udid);
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));
sendPointer(udid, index, 0, x, y);
setTimeout(() => { selectedDevice.value = index; }, 300);
};
// 鼠标抬起事件处理
const handleCanvasup = (udid, event, index) => {
const { x, y } = getCanvasCoordinate(event, udid);
// position.endX = floorNum(x)
// position.endY = floorNum(y)
const handleCanvasup = (udid, e, index) => {
const { x, y } = getCanvasCoordinate(e, udid);
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));
sendPointer(udid, index, 1, x, y);
};
// 鼠标移动事件处理
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 handleMouseMove = (udid, e, index) => {
if (!isdown.value) return;
const { x, y } = getCanvasCoordinate(e, udid);
sendPointer(udid, index, 2, x, y);
};
// 统一发包point 用帧坐标screenSize 用帧宽高
function sendPointer(udid, index, action /* 0 down,1 up,2 move */, x, y) {
// const meta = frameMeta.value[udid] || { w: 320, h: 720 };
const meta = frameMeta.value[udid] || { w: 320, h: 720, rotation: 0 };
// console.log("frameMeta.value", frameMeta.value)
// console.log("udid", udid)
const payload = {
type: 2,
action,
pointerId: 0,
position: {
point: { x, y },
screenSize: { width: meta.w, height: meta.h },
},
pressure: action === 1 ? 0 : 1,
buttons: action === 1 ? 0 : 1,
};
console.log("发送坐标", payload)
wslist[index]?.send(toBuffer(payload));
}
// 坐标计算
const frameMeta = ref({});
// —— 坐标换算DOM -> 帧坐标
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,
};
// 鼠标在“可视区域”的比例CSS 尺寸)
const rx = (event.clientX - rect.left) / rect.width;
const ry = (event.clientY - rect.top) / rect.height;
// 当前帧尺寸(优先 videoWidth/Height兜底用 getSize 回包)
// const meta = frameMeta.value[udid] || { w: 320, h: 720, rotation: 0 };
const meta = frameMeta.value[udid] || { w: 320, h: 720, rotation: 0 }; // 统一
// 映射到“帧坐标”
let x = rx * meta.w;
let y = ry * meta.h;
// 如有旋转在此处理(你如果已经在渲染层旋转了,这里就不需要)
switch (meta.rotation ?? 0) {
case 90: [x, y] = [meta.w - y, x]; break;
case 180: [x, y] = [meta.w - x, meta.h - y]; break;
case 270: [x, y] = [y, meta.h - x]; break;
}
x = Math.max(0, Math.min(meta.w - 1, x));
y = Math.max(0, Math.min(meta.h - 1, y));
return { x: Math.round(x), y: Math.round(y), w: meta.w, h: meta.h };
};
// ======= 对齐工具 =======
const ALIGN_BASE = 16; // 改成 32 就是 32 对齐
const ROUND_MODE = 'nearest'; // 'down' | 'up' | 'nearest'
function alignTo(x, a = ALIGN_BASE, mode = ROUND_MODE) {
if (a <= 1) return Math.round(x);
const q = x / a;
if (mode === 'down') return Math.floor(q) * a;
if (mode === 'up') return Math.ceil(q) * a;
/* nearest */ return Math.round(q) * a;
}
//返回上一层
const Back = (udid, index) => {
wslist[index].send(toBufferBtn({ "type": 0, "action": 0, "keycode": 4, "metaState": 0, "repeat": 0 }));
@@ -834,7 +897,7 @@ onMounted(() => {
document.addEventListener("visibilitychange", handleVisibilityChange);
ObtainDeviceInformation();
window.addEventListener('keydown', (e) => {
console.log('触发按键了 键盘事件有效', e)
// console.log('触发按键了 键盘事件有效', e)
if (e.key == 'Backspace') {
key(selectedDevice.value, 'down', 67)
}
@@ -1128,7 +1191,9 @@ function setPrivateText(index, datatype) {
})
} else {
wslist[index].send(setClipboard(textContentpri.value[index]));
textContentpri.value[index] = getContentpriList()[index][getRandomNumber(getContentpriList()[index].length - 1)];
if (getContentpriList()[index]) {
textContentpri.value[index] = getContentpriList()[index][getRandomNumber(getContentpriList()[index].length - 1)];
}
}
@@ -1150,89 +1215,87 @@ function getComArr(index, type) {
}
}
//传入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)
// 小工具
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
async function tapAt(udid, index, x, y, holdMs = 80) {
sendPointer(udid, index, 0, x, y); // down
await sleep(holdMs);
sendPointer(udid, index, 1, x, y); // up
}
async function longPressAt(udid, index, x, y, holdMs = 1500) {
sendPointer(udid, index, 0, x, y); // down
await sleep(holdMs);
sendPointer(udid, index, 1, x, y); // up
}
/** 可用于自定义滑动/拖拽(如果以后要替代 slideDown/slideRight */
async function drag(udid, index, x1, y1, x2, y2, durationMs = 300, steps = 8) {
sendPointer(udid, index, 0, x1, y1); // down
const dx = (x2 - x1) / steps;
const dy = (y2 - y1) / steps;
const dt = Math.max(8, Math.floor(durationMs / steps));
for (let i = 1; i <= steps; i++) {
await sleep(dt);
sendPointer(udid, index, 2, Math.round(x1 + dx * i), Math.round(y1 + dy * i)); // move
}
await sleep(20);
sendPointer(udid, index, 1, x2, y2); // up
}
// 用 pointer down/up/move 改写后的 clickxy
async function clickxy(x, y, index, type) {
const udid = deviceInformation.value[index]?.udid;
if (!udid) return;
try {
if (type === 3) {
// 关注/私信后的返回和下滑
await tapAt(udid, index, x, y, 80);
await sleep(300);
Back('', index);
await sleep(300);
wsActions.slideDown(phoneXYinfo.value[index].id, index);
// 你原代码里这里还有 start(...),如果没定义会报错;需要的话保留:
// await sleep(300);
// start(phoneXYinfo.value[index].id, index);
return;
}
if (type === 2) {
// 评论后的返回和右滑
await tapAt(udid, index, x, y, 80);
await sleep(300);
Back('', index);
await sleep(300);
wsActions.slideRight(phoneXYinfo.value[index].id, index);
return;
}
if (type === 1) {
// 评论框点击 -> 粘贴发送
await tapAt(udid, index, x, y, 80);
await sleep(300);
setComText(index);
return;
}
if (type === 9) {
// 长按
await longPressAt(udid, index, x, y, 1500);
return;
}
// 默认:普通轻点
await tapAt(udid, index, x, y, 80);
} catch (e) {
console.error('clickxy error:', e);
}
}
@@ -1346,7 +1409,7 @@ function randomSeeVideo(udid, index) {
wsActions.isHost(udid, index)//检测
}, delay);
}
//延迟3-4分钟后秒后检测关注
//延迟1-4分钟后秒后检测关注
async function randomDelayAndExecute() {
const minDelay = 1 * 60 * 1000;
const maxDelay = 4 * 60 * 1000;
@@ -1390,9 +1453,10 @@ function getVideoStyle(index) {
function stop() {
// actions[index] = [];
cloesMonitor();
cloesMonitor(); //关闭监听
isStop.value = true; //停止所有任务
isMsgPop.value = false;//关闭爬虫sse任务
deviceInformation.value.forEach((item, i) => {
console.log('停止', i);
createTaskQueue(i).clear();//清除队伍中的任务
@@ -1438,7 +1502,7 @@ function openMonitor(type) {
//关闭监听
function cloesMonitor() {
isMonitorOn.value = false;//关闭监听
deviceInformation.value.forEach((device, index) => {
runType.value[index] = ''
})