新增网络状态检测

This commit is contained in:
2025-10-31 19:39:57 +08:00
parent d0ace399f1
commit fad6cdf227
3 changed files with 228 additions and 111 deletions

View File

@@ -118,3 +118,7 @@ export function restartTikTok(data) {
return postAxios({ url: 'restartTikTok', data })
}
//获取设备网络状态
export function getDeviceNetStatus(data) {
return postAxios({ url: 'getDeviceNetStatus', data })
}

View File

@@ -481,4 +481,66 @@ video {
.video-canvas .overlay {
z-index: 2;
}
/* 容器要能放伪元素 */
.video-canvas.net-bad {
position: relative;
outline: 2px solid rgba(239, 68, 68, 0.6);
/* 常亮的细红框作底线 */
border-radius: 16px;
}
/* 闪烁的红色光圈边框(不占据布局、不挡点击) */
.video-canvas.net-bad::after {
content: '';
position: absolute;
inset: -4px;
/* 往外扩一点,避免吃掉内容 */
border-radius: 20px;
pointer-events: none;
z-index: 3;
/* 盖在内部 img/canvas 之上 */
box-shadow:
0 0 0 3px rgba(239, 68, 68, 0.95),
/* 实心外圈 */
0 0 14px rgba(239, 68, 68, 0.85),
/* 强发光 */
0 0 36px rgba(239, 68, 68, 0.65);
/* 远发光 */
opacity: 0.2;
animation: alertPulse 0.9s infinite cubic-bezier(.65, .05, .36, 1);
will-change: opacity, box-shadow, transform;
}
/* 强烈的“脉冲闪烁”效果 */
@keyframes alertPulse {
0% {
opacity: 0.2;
box-shadow:
0 0 0 2px rgba(239, 68, 68, 0.6),
0 0 10px rgba(239, 68, 68, 0.55),
0 0 24px rgba(239, 68, 68, 0.45);
transform: scale(1);
}
40% {
opacity: 1;
box-shadow:
0 0 0 4px rgba(239, 68, 68, 1),
0 0 18px rgba(239, 68, 68, 0.95),
0 0 42px rgba(239, 68, 68, 0.85);
transform: scale(1.01);
/* 轻微放大,增强“警报感” */
}
100% {
opacity: 0.2;
box-shadow:
0 0 0 2px rgba(239, 68, 68, 0.6),
0 0 10px rgba(239, 68, 68, 0.55),
0 0 24px rgba(239, 68, 68, 0.45);
transform: scale(1);
}
}

View File

@@ -4,15 +4,6 @@
<div style="position: absolute;left: 20px; top: 20px;">
<el-button style="background: linear-gradient(90deg, #60a5fa, #34d399); color: azure; "
@click="showMyInfo = true">人设编辑</el-button>
<!-- <el-button style="background: linear-gradient(90deg, #60a5fa, #34d399); color: azure;" @click="MesNewList.push({
sender: 'Alice', // 或 name/user/from
device: 'iPhone 14 Pro', // 或 deviceName/udid
text: '你好呀~', // 或 content
time: '2023-10-01 12:00:00', // 可选;有些是 'time' 会被过滤或 content
type: 'msg' // 可选;有些是 'time' 会被过滤
})">新增</el-button> -->
<!-- <el-button @click="updates([{ id: 10022, aiOperation: 1 }])">更新请求</el-button> -->
</div>
<div class="center-line"> <!-- 左边栏按钮 -->
@@ -35,8 +26,10 @@
<!-- 中间手机区域 -->
<div class="content" @click.self="selectedDevice = 999">
<div class="video-container" v-for="(device, index) in deviceInformation" :key="device.deviceId">
<div class="video-canvas" :class="{ active: selectedDevice === index }" :style="getCanvasStyle(index)"
@click="selectDevice(index)">
<div class="video-canvas" :class="{
active: selectedDevice === index,
'net-bad': netStatus[device.deviceId] === false
}" :style="getCanvasStyle(index)" @click="selectDevice(index)">
<img class="stream" :src="imgSrcMap[device.deviceId] || ''" :data-id="device.deviceId"
:ref="el => (imgRefs[device.deviceId] = el)" />
@@ -171,7 +164,8 @@ import {
changeAccount,
stopAllTask,
anchorList,
restartTikTok
restartTikTok,
getDeviceNetStatus
} from '@/api/ios';
import ding from '@/assets/mes.wav'
import { set } from "lodash";
@@ -229,6 +223,23 @@ let deviceInformation = ref([])
//停止中
let stopLoading = null
// 每台设备的网络状态true=正常false=异常
const netStatus = reactive({}) // { [deviceId]: boolean }
// —— 网络波动联动(每设备暂停/恢复)——
const offlineFlags = reactive({}) // { [deviceId]: true/false } 是否因断网而暂停过
const lastNet = reactive({}) // { [deviceId]: true/false } 上一次看到的网络状态
const resumeTimers = new Map() // deviceId -> setTimeout id
const NET_RESUME_STABLE_MS = 6000 // 断网恢复后,等待网络稳定的毫秒数
function clearResumeTimer(id) {
const t = resumeTimers.get(id)
if (t) {
clearTimeout(t)
resumeTimers.delete(id)
}
}
// 当前是否被其它模式占用(四个互斥按钮专用)
const isLocked = (type) => !!runType.value && runType.value !== type
@@ -777,11 +788,6 @@ const onCanvasUp = async (udid, e, index) => {
if (isTap) {
await tapAction({ udid, x: endDevXY.x, y: endDevXY.y })
} else {
// //通过方向判断code 1234
// const rotation = Number((deviceInformation.value[index] || {}).rotation || 0)
// const code = getSwipeCodeWithRotation(dx, dy, rotation)
// await swipeAction({ udid, direction: code })
//通过自定义滑动坐标和时间传参
await swipeAction({ udid, sx: startDevXY.x, sy: startDevXY.y, ex: endDevXY.x, ey: endDevXY.y, duration: elapsed / 1000 })
}
@@ -922,60 +928,9 @@ function onDialogConfirm(result, type, index, data) {
comonList = result
setContentList(result)
common.value = data.common
// dialogTitle.value = '私信';
// setTimeout(() => {
// showDialog.value = true;
// initialTextStr.value = getContentpriListMultiline();
// }, 500)
transDlgType.value = '私信'
showtransDlg.value = true
} else if (type == '私信') {
// runType.value = 'follow'
// setContentpriList(result)
// if (isAlliance.value) {
// followAndGreetUnion(
// {
// deviceList: deviceInformation.value.map(item => item.deviceId),
// anchorList: hostList.map(item => ({
// anchorId: item.id,
// country: item.country,
// invitationType: item.invitationType,
// state: stateByInvType(item.invitationType),
// })),
// prologueList: result,
// needReply: data.auto,
// needTranslate: data.needTranslate,
// }
// ).then((res) => {
// ElMessage({ type: 'success', message: '任务开启成功' });
// hostList = []
// })
// } else {
// passAnchorData(
// {
// deviceList: deviceInformation.value.map(item => item.deviceId),
// anchorList: hostList.map(item => ({
// anchorId: item.id,
// country: item.country,
// invitationType: item.invitationType,
// state: stateByInvType(item.invitationType),
// })),
// prologueList: result,
// comment: comonList,
// needReply: data.auto,
// needTranslate: data.needTranslate,
// isComment: data.common
// }
// ).then((res) => {
// ElMessage({ type: 'success', message: '任务开启成功' });
// hostList = []
// })
// }
} else if (type == '视频评论') {
runType.value = 'like'
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId, comment: result, isComment: data.common }))
@@ -1016,11 +971,10 @@ onMounted(async () => {
window.electronAPI.startMq(userdata.tenantId, userdata.id)
// 初始化时获取设备列表
//每3秒获取一次设备列表 消息列表 和 查询主播列表是否还有主播
getListtimer = setInterval(async () => {
getDeviceListFun()
selectLastFun()
getDeviceListFun() //获取设备列表
selectLastFun() //获取手机网络状态
const hostsList = await getStoredHostList()
// console.log(hostsList.length)
//当私信主播时,主播列表没有数据了,提示列表空了 并且关闭私信
@@ -1042,8 +996,10 @@ onMounted(async () => {
setInterval(async () => {
await checkVPN()
await refreshNetStatus()
}, 1000 * 20)
if (!await isAiConfig()) {
showMyInfo.value = true
}
@@ -1114,20 +1070,14 @@ onUnmounted(() => {
let isStartLac = false
const getDeviceListFun = () => {
getDeviceList().then((res) => {
// console.log('返回', res.length)
if (res && res.length > 0 && deviceInformation.value.length !== res.length) {
console.log("设备变更")
deviceInformation.value = res
// refreshAllStopImgs()
// reloadImg()
}
if (res.length == 0) {
deviceInformation.value = []
// refreshAllStopImgs()
// reloadImg()
}
// deviceInformation.value = ['', '', '', '', '', '',]
}).catch((err) => {
if (isStartLac) {
ElMessage.error(`IOSAI服务错误`)
@@ -1210,37 +1160,68 @@ function runTask(key, deviceId, type) {
console.log("进入follow", scheduleEnabled.value)
if (scheduleEnabled.value) {
if (!deviceId) {
await stopAll(100)
await stopAll(5000)
} else {
passAnchorData(
{
deviceList: [deviceId],
anchorList: [],
prologueList: getContentpriList(),
comment: comonList,
needReply: auto.value
}
).then((res) => {
hostList = []
})
if (isAlliance.value) {
followAndGreetUnion(
{
deviceList: [deviceId],
anchorList: [],
prologueList: getContentpriList(),
needReply: auto.value,
}
).then((res) => {
hostList = []
})
} else {
passAnchorData(
{
deviceList: [deviceId],
anchorList: [],
prologueList: getContentpriList(),
comment: comonList,
needReply: auto.value,
isComment: common.value //是否评论
}
).then((res) => {
hostList = []
})
}
return
}
//第一个小时结束后,第二轮开始的时候,直接进入follow
setTimeout(() => {
runType.value = 'follow'
if (isAlliance.value) {
followAndGreetUnion(
{
deviceList: deviceInformation.value.map(item => item.deviceId),
anchorList: [],
prologueList: getContentpriList(),
needReply: auto.value
}
).then((res) => {
hostList = []
})
} else {
passAnchorData(
{
deviceList: deviceInformation.value.map(item => item.deviceId),
anchorList: [],
prologueList: getContentpriList(),
comment: comonList,
needReply: auto.value,
isComment: common.value //是否评论
}
).then((res) => {
hostList = []
})
}
passAnchorData(
{
deviceList: deviceInformation.value.map(item => item.deviceId),
anchorList: [],
prologueList: getContentpriList(),
comment: comonList,
needReply: auto.value
}
).then((res) => {
hostList = []
})
}, 1000)
return
@@ -1254,7 +1235,7 @@ function runTask(key, deviceId, type) {
} else if (key === 'like') {
if (!deviceId) {
await stopAll(100)
await stopAll(5000)
} else {
growAccount({ udid: deviceId })
return
@@ -1268,7 +1249,7 @@ function runTask(key, deviceId, type) {
} else if (key === 'brushLive') {
if (!deviceId) {
await stopAll(100)
await stopAll(5000)
} else {
watchLiveForGrowth({ udid: deviceId })
return
@@ -1282,7 +1263,7 @@ function runTask(key, deviceId, type) {
runType.value = 'brushLive'
} else if (key === 'listen') {
if (!deviceId) {
await stopAll(100)
await stopAll(5000)
} else {
monitorMessages({ udid: deviceId })
return
@@ -1402,7 +1383,7 @@ function startScheduleLoop() {
pauseSnapshot = { index: scheduleState.index, elapsedBeforePause }
// 停掉当前片段
await stopAll(100)
await stopAll(5000)
// 执行中断任务(带重试)
@@ -1590,13 +1571,6 @@ const checkVPN = async () => {
}
};
// 语言选项(也可以使用内置默认)
// const languages = [
// { label: '简体中文 (zh-CN)', value: 'zh-CN' },
// { label: 'English (en)', value: 'en' },
// { label: '日本語 (ja)', value: 'ja' },
// ]
// 模拟翻译函数:你可以接入自己后端或第三方接口 sentences文本 targetLang语言
async function doTranslate(sentences, targetLang) {
const str = arrayToString(sentences)
@@ -1680,6 +1654,83 @@ function arrayToString(arr) {
// 过滤空项并用 \n 连接
return arr.filter(Boolean).join(' \n')
}
// —— 每 20s 刷新网络状态 ——
// 写一个小函数专门刷新网络状态,方便复用
async function refreshNetStatus() {
const list = [...(deviceInformation.value || [])]
// 并发请求每台设备网络状态(安全处理错误)
const settled = await Promise.allSettled(
list.map(d => getDeviceNetStatus({ udid: d.deviceId }))
)
settled.forEach((s, i) => {
const id = list[i].deviceId
netStatus[id] = (s.status === 'fulfilled' && s.value === true)
})
console.log("设备在线状态", settled)
// 清理已下线设备的状态,避免残留
const aliveIds = new Set(list.map(d => d.deviceId))
Object.keys(netStatus).forEach(id => {
if (!aliveIds.has(id)) {
delete netStatus[id]
delete lastNet[id]
delete offlineFlags[id]
clearResumeTimer(id)
}
})
// —— 在这里做“网络→任务联动”(只在有运行模式时触发)——
if (runType.value) {
list.forEach(({ deviceId: id }) => {
const prev = lastNet[id] // 之前的网络状态
const curr = netStatus[id] // 当前的网络状态true/false/undefined)
if (typeof curr === 'undefined') return
// 边沿触发true -> false 断网false -> true 恢复
if (prev !== curr) {
// 断网:立刻停止该设备任务(只停这一台)
if (curr === false) {
clearResumeTimer(id) // 避免有遗留恢复定时器
if (!offlineFlags[id]) {
console.log(`[NET] ${id} 离线,停止该设备任务`)
offlineFlags[id] = true
// 这里调用你已有的“只停一台”的方法
stopOne(id)
}
}
// 恢复:等待网络稳定后再恢复该设备任务
if (curr === true && offlineFlags[id]) {
clearResumeTimer(id)
const timer = setTimeout(() => {
// 二次确认:还在运行模式里、该设备仍在线
if (runType.value && netStatus[id] === true) {
console.log(`[NET] ${id} 恢复在线,重新启动当前模式: ${runType.value}`)
// 仅启动这一台
runTask(runType.value, id)
// 标记已恢复
offlineFlags[id] = false
}
resumeTimers.delete(id)
}, NET_RESUME_STABLE_MS)
resumeTimers.set(id, timer)
}
}
// 更新 last
lastNet[id] = curr
})
} else {
// 不在运行模式时,清理“暂停标记”和恢复定时器,保持干净
Object.keys(offlineFlags).forEach(id => { offlineFlags[id] = false })
Array.from(resumeTimers.keys()).forEach(id => clearResumeTimer(id))
}
}
</script>
<style scoped lang="less">