2.9.1最终稳定版
This commit is contained in:
@@ -8,11 +8,13 @@
|
||||
|
||||
<div class="center-line"> <!-- 左边栏按钮 -->
|
||||
<div v-for="(btn, index) in buttons" :key="index" style="width: 100%;">
|
||||
<div v-if="btn.show?.()" class="left-button" :style="btn.style ? btn.style() : {}" @click="btn.onClick"
|
||||
@mouseenter="hoverIndex = index" @mouseleave="hoverIndex = null">
|
||||
<div v-if="btn.show?.()" class="left-button" :style="btn.style ? btn.style() : {}"
|
||||
:title="btn.tooltip ? btn.tooltip() : ''" @click="btn.onClick" @mouseenter="hoverIndex = index"
|
||||
@mouseleave="hoverIndex = null">
|
||||
<img :src="hoverIndex === index ? btn.img.hover : btn.img.normal" alt="">
|
||||
{{ btn.label }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div style="position: absolute;left: 20px; bottom: 20px;">
|
||||
<el-button @click="showHostDlg = true">执行主播库</el-button>
|
||||
@@ -42,7 +44,9 @@
|
||||
<div class="app-button" @click="restartTikTok({ udid: device.deviceId })">重置tiktok</div>
|
||||
<div class="app-button" @click="getMesList(device.deviceId)">获取当前聊天记录</div>
|
||||
<div class="app-button" @click="stopOne(device.deviceId)">停止任务</div>
|
||||
<div class="app-button" @click="runTask(runType, device.deviceId)">开启</div>
|
||||
<div class="app-button" @click="scheduleEnabled = true; runTask(runType, device.deviceId);">
|
||||
开启
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,11 +59,11 @@
|
||||
</div>
|
||||
<img v-if="isWifi" style="position: absolute; right: 20px; top: 10px; height: 30px;" src="@/assets/wifi.png"></img>
|
||||
<MultiLineInputDialog v-model:visible="showDialog" :initialText='initialTextStr' :title="dialogTitle"
|
||||
:index="selectedDevice" @confirm="onDialogConfirm" @cancel="stopAll(100)" />
|
||||
:index="selectedDevice" @confirm="onDialogConfirm" @cancel="stopAll(2000)" />
|
||||
<HostListManagerDialog v-model:visible="showHostDlg" @save="onHostSaved" @invitType="invitTypeFun" />
|
||||
|
||||
<TranslationDialog v-model="showtransDlg" :type="transDlgType" :translateFn="doTranslate"
|
||||
storage-key-prefix="demo-translation" @confirm="onConfirm" @cancel="stopAll(100)" />
|
||||
storage-key-prefix="demo-translation" @confirm="onConfirm" @cancel="stopAll(2000)" />
|
||||
</div>
|
||||
<!-- <AgentGuildDialog v-model="showMyInfo" :model="formInit" @save="handleSave" /> -->
|
||||
<AgentGuildDialog v-model="showMyInfo" :model="{
|
||||
@@ -135,13 +139,13 @@ import { chat, translationToChinese, translation, customTranslation } from "@/ap
|
||||
import HostListManagerDialog from '@/components/HostListManagerDialog.vue'
|
||||
import AgentGuildDialog from '@/components/AgentGuildDialog.vue'
|
||||
|
||||
import MultiLineInputDialog from '@/components/MultiLineInputDialog.vue'; // 根据实际路径修改
|
||||
import TranslationDialog from '@/components/translationDialog.vue'; // 根据实际路径修改
|
||||
import MultiLineInputDialog from '@/components/MultiLineInputDialog.vue';
|
||||
import TranslationDialog from '@/components/translationDialog.vue';
|
||||
|
||||
import ChatDialog from '@/components/ChatDialog.vue'
|
||||
import MessageDialogd from '@/components/MessageDialogd.vue'
|
||||
import { pickTikTokBundleId } from '@/utils/arrUtils'
|
||||
import { logout, updates } from '@/api/account';
|
||||
import { logout, updates, health } from '@/api/account';
|
||||
import {
|
||||
getDeviceList,
|
||||
toHome,
|
||||
@@ -191,7 +195,7 @@ const borkerConfig = reactive({
|
||||
let common = ref(true);
|
||||
// 自动化
|
||||
let auto = ref(true);
|
||||
|
||||
let isTranslate = ref(false)
|
||||
let initialTextStr = ref('') // 初始文本字符串
|
||||
// 批次缓冲(仅用于当前“波”)
|
||||
let batch = []; // [{ country, text }]
|
||||
@@ -199,8 +203,9 @@ let flushTimer = null;
|
||||
|
||||
let hostList = [] // 主播列表
|
||||
let comonList = [] //评论列表
|
||||
//查询列表轮询
|
||||
//查询 列表 新消息轮询
|
||||
let getListtimer = null;
|
||||
let getNetworkListtimer = null;
|
||||
let userdata = getUser();
|
||||
let chatList = ref([])
|
||||
|
||||
@@ -223,6 +228,10 @@ let deviceInformation = ref([])
|
||||
//停止中
|
||||
let stopLoading = null
|
||||
|
||||
// 是否未开通 AI 自动回复(= 监测消息不可用)
|
||||
const isListenLockedByPlan = computed(() => String(userdata?.aiReplay ?? '0') === '0')
|
||||
|
||||
|
||||
// 每台设备的网络状态:true=正常,false=异常
|
||||
const netStatus = reactive({}) // { [deviceId]: boolean }
|
||||
|
||||
@@ -240,6 +249,11 @@ function clearResumeTimer(id) {
|
||||
}
|
||||
}
|
||||
|
||||
// —— 在线判定/过滤 ——
|
||||
// 红框=netStatus[id] === false 视为离线;其它(true/undefined)都当作可用
|
||||
const isOnline = (id) => netStatus[id] !== false
|
||||
const onlineOnly = (ids) => ids.filter(isOnline)
|
||||
|
||||
// 当前是否被其它模式占用(四个互斥按钮专用)
|
||||
const isLocked = (type) => !!runType.value && runType.value !== type
|
||||
|
||||
@@ -251,12 +265,21 @@ watch(sseEnabled, v => {
|
||||
})
|
||||
|
||||
// 互斥按钮的样式:激活=红,锁定=半透明且禁点
|
||||
const ctrlStyle = (type) => ({
|
||||
backgroundColor: runType.value === type ? 'red' : '',
|
||||
opacity: isLocked(type) ? 0.5 : 1,
|
||||
pointerEvents: isLocked(type) ? 'none' : 'auto',
|
||||
cursor: isLocked(type) ? 'not-allowed' : 'pointer',
|
||||
})
|
||||
const ctrlStyle = (type) => {
|
||||
const lockedByMode = isLocked(type)
|
||||
const disableListen = (type === 'listen') && isListenLockedByPlan.value
|
||||
|
||||
const style = {
|
||||
backgroundColor: runType.value === type ? 'red' : '',
|
||||
opacity: (lockedByMode || disableListen) ? 0.5 : 1,
|
||||
// 允许事件,以便显示浏览器原生 title 提示 & 点击弹出 ElMessage
|
||||
pointerEvents: lockedByMode ? 'none' : 'auto',
|
||||
cursor: (lockedByMode || disableListen) ? 'not-allowed' : 'pointer',
|
||||
filter: disableListen ? 'grayscale(1)' : ''
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
@@ -358,33 +381,31 @@ const buttons = [
|
||||
},
|
||||
{
|
||||
label: '监测消息',
|
||||
// 点击前先判断是否未开通,未开通只提示不执行
|
||||
onClick: () => {
|
||||
if (isListenLockedByPlan.value) {
|
||||
ElMessage.warning('未开通 AI 自动回复功能,请联系管理员开通');
|
||||
return
|
||||
}
|
||||
if (runType.value == 'listen') {
|
||||
deviceInformation.value.forEach((item) => {
|
||||
stopScript({ udid: item.deviceId })
|
||||
})
|
||||
runType.value = ''
|
||||
return
|
||||
};
|
||||
}
|
||||
if (isLocked('listen')) return
|
||||
//如果传评论就注释一下两行代码,解开后面代码
|
||||
runType.value = 'listen'
|
||||
deviceInformation.value.forEach((item) => monitorMessages({ udid: item.deviceId }))
|
||||
// dialogTitle.value = '评论(无消息将刷视频)';
|
||||
// setTimeout(() => {
|
||||
// showDialog.value = true;
|
||||
|
||||
// initialTextStr.value = getContentListMultiline();
|
||||
// }, 500)
|
||||
|
||||
},
|
||||
// 悬停提示文案
|
||||
tooltip: () => (isListenLockedByPlan.value ? '未开通:AI 自动回复' : ''),
|
||||
show: () => true,
|
||||
img: {
|
||||
normal: new URL('@/assets/video/leftBtn1.png', import.meta.url).href,
|
||||
hover: new URL('@/assets/video/leftBtn1-1.png', import.meta.url).href
|
||||
},
|
||||
style: () => ctrlStyle('listen')
|
||||
|
||||
},
|
||||
// {
|
||||
// label: '定时调度',
|
||||
@@ -397,7 +418,7 @@ const buttons = [
|
||||
// },
|
||||
{
|
||||
label: '全部停止',
|
||||
onClick: () => stopAll(100),
|
||||
onClick: () => stopAll(2000),
|
||||
show: () => true,
|
||||
img: {
|
||||
normal: new URL('@/assets/video/leftBtn8.png', import.meta.url).href,
|
||||
@@ -521,6 +542,7 @@ const loops = new Map(); // deviceId -> { timer, stopped, lastUrl }
|
||||
|
||||
/** 生成一次地址 */
|
||||
const makeUrl = (port) => `http://localhost:${port}/?t=${Date.now()}`;
|
||||
// const makeUrl = (port) => `http://192.168.1.209:${port}/?t=${Date.now()}`;
|
||||
|
||||
/** 真正断开某台设备当前连接并清理 */
|
||||
function hardCloseImg(deviceId) {
|
||||
@@ -880,31 +902,59 @@ async function stopAll(time) {
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
});
|
||||
|
||||
// 🔒 先强制关闭所有提示弹窗
|
||||
ElMessageBox.close();
|
||||
|
||||
scheduleEnabled.value = false;
|
||||
runType.value = '';
|
||||
isMsgPop.value = false;
|
||||
dropCurrentWave();
|
||||
|
||||
// ========== ⏰ 新增超时逻辑 ==========
|
||||
const STOP_TASK_TIMEOUT_MS = 120000; // 120 秒超时,可调整
|
||||
|
||||
// 封装一个带超时保护的 Promise
|
||||
const withTimeout = (p, ms) => {
|
||||
return Promise.race([
|
||||
p,
|
||||
new Promise((resolve) =>
|
||||
setTimeout(() => {
|
||||
console.warn(`[stopAllTask] 超时 ${ms}ms`);
|
||||
resolve('timeout');
|
||||
}, ms)
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
||||
try {
|
||||
// 1) 等待接口完成
|
||||
await stopAllTask(deviceInformation.value.map(item => item.deviceId));
|
||||
// 2) 等待 2 秒(和你原逻辑一致)
|
||||
// 1️⃣ 带超时保护地调用接口
|
||||
const res = await withTimeout(
|
||||
stopAllTask(deviceInformation.value.map(item => item.deviceId)),
|
||||
STOP_TASK_TIMEOUT_MS
|
||||
);
|
||||
|
||||
if (res === 'timeout') {
|
||||
console.warn('stopAllTask 请求超时,自动继续后续逻辑');
|
||||
}
|
||||
|
||||
// 2️⃣ 等待指定时间(原逻辑保留)
|
||||
await new Promise(r => setTimeout(r, time));
|
||||
|
||||
stopLoading.close();
|
||||
console.log('全部停止成功', printCurrentTime());
|
||||
ElMessage.success('全部停止成功');
|
||||
|
||||
// 3) 明确返回(可选)
|
||||
// 3️⃣ 无论接口成功或超时都返回 true
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log('停止失败', printCurrentTime(), e);
|
||||
console.error('停止失败', printCurrentTime(), e);
|
||||
ElMessage.error('脚本已停止');
|
||||
stopLoading.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//确认多行文本框内容
|
||||
function onDialogConfirm(result, type, index, data) {
|
||||
console.log(type, result, data);
|
||||
@@ -967,37 +1017,54 @@ onMounted(async () => {
|
||||
ElMessage.error(`未检测到设备`)
|
||||
|
||||
}
|
||||
|
||||
|
||||
//MQ链接
|
||||
window.electronAPI.startMq(userdata.tenantId, userdata.id)
|
||||
|
||||
|
||||
// 初始化时获取设备列表
|
||||
//每3秒获取一次设备列表 消息列表 和 查询主播列表是否还有主播
|
||||
getListtimer = setInterval(async () => {
|
||||
getDeviceListFun() //获取设备列表
|
||||
selectLastFun() //获取手机网络状态
|
||||
const hostsList = await getStoredHostList()
|
||||
selectLastFun() //获取新消息
|
||||
const hostsList = await getStoredHostList() //获取主播列表
|
||||
// console.log(hostsList.length)
|
||||
//当私信主播时,主播列表没有数据了,提示列表空了 并且关闭私信
|
||||
if (runType.value == 'follow') {
|
||||
if (hostsList.length <= 0) {
|
||||
await stopAll(5000)
|
||||
await stopAll(2000)
|
||||
runType.value = 'like'
|
||||
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId }))
|
||||
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId, comment: getContentList(), isComment: common.value }))
|
||||
|
||||
ElMessageBox.alert('私信全部完成!(刷视频中)', '提示', {
|
||||
confirmButtonText: 'OK',
|
||||
callback: (action) => {
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}, 3000)
|
||||
|
||||
setInterval(async () => {
|
||||
getNetworkListtimer = setInterval(async () => {
|
||||
await checkVPN()
|
||||
await refreshNetStatus()
|
||||
}, 1000 * 20)
|
||||
health().then((res) => {
|
||||
|
||||
}).catch((err) => {
|
||||
if (err.code === 40400) {
|
||||
//关闭获取设备列表的方法
|
||||
clearInterval(getListtimer)
|
||||
getListtimer = null
|
||||
//关闭获取网络状态的方法
|
||||
clearInterval(getNetworkListtimer)
|
||||
getNetworkListtimer = null
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
}, 1000 * 60 * 3)
|
||||
|
||||
|
||||
if (!await isAiConfig()) {
|
||||
@@ -1063,8 +1130,13 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
//关闭获取设备列表的方法
|
||||
clearInterval(getListtimer)
|
||||
getListtimer = null
|
||||
//关闭获取网络状态的方法
|
||||
clearInterval(getNetworkListtimer)
|
||||
getNetworkListtimer = null
|
||||
|
||||
})
|
||||
|
||||
let isStartLac = false
|
||||
@@ -1081,7 +1153,7 @@ const getDeviceListFun = () => {
|
||||
}).catch((err) => {
|
||||
if (isStartLac) {
|
||||
ElMessage.error(`IOSAI服务错误`)
|
||||
isStartLac = true
|
||||
// isStartLac = true
|
||||
} else {
|
||||
|
||||
}
|
||||
@@ -1153,15 +1225,21 @@ async function uploadLogFile() {
|
||||
}
|
||||
}
|
||||
function runTask(key, deviceId, type) {
|
||||
|
||||
// —— 若指定单设备,且该设备当前离线,则直接跳过 ——
|
||||
if (deviceId && !isOnline(deviceId)) {
|
||||
markPendingForOffline([deviceId]); // ⭐ 新增:让它后续来网能自动跟上
|
||||
ElMessage.warning('该设备当前网络不可用,已跳过');
|
||||
return;
|
||||
}
|
||||
console.log('[schedule] 切换到任务:', key, printCurrentTime())
|
||||
|
||||
forceActivate(key, async () => {
|
||||
if (key === 'follow') {
|
||||
console.log("进入follow", scheduleEnabled.value)
|
||||
if (scheduleEnabled.value) {
|
||||
if (!deviceId) {
|
||||
await stopAll(5000)
|
||||
} else {
|
||||
//如果有id 就只执行一个 并且return出去
|
||||
if (deviceId) {
|
||||
if (isAlliance.value) {
|
||||
followAndGreetUnion(
|
||||
{
|
||||
@@ -1169,9 +1247,11 @@ function runTask(key, deviceId, type) {
|
||||
anchorList: [],
|
||||
prologueList: getContentpriList(),
|
||||
needReply: auto.value,
|
||||
needTranslate: isTranslate.value,
|
||||
}
|
||||
).then((res) => {
|
||||
hostList = []
|
||||
return
|
||||
})
|
||||
} else {
|
||||
passAnchorData(
|
||||
@@ -1181,75 +1261,103 @@ function runTask(key, deviceId, type) {
|
||||
prologueList: getContentpriList(),
|
||||
comment: comonList,
|
||||
needReply: auto.value,
|
||||
needTranslate: isTranslate.value,
|
||||
isComment: common.value //是否评论
|
||||
|
||||
}
|
||||
).then((res) => {
|
||||
hostList = []
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
} else {
|
||||
//没有id 直接停止所有设备
|
||||
await stopAll(2000)
|
||||
|
||||
//第一个小时结束后,第二轮开始的时候,直接进入follow
|
||||
setTimeout(() => {
|
||||
runType.value = 'follow'
|
||||
//过滤无网络设备
|
||||
const allIds = deviceInformation.value.map(item => item.deviceId)
|
||||
// 在线要启动的
|
||||
const deviceIds = onlineOnly(allIds)
|
||||
if (!deviceIds.length) {
|
||||
ElMessage.warning('没有在线设备可执行任务');
|
||||
// 也别忘了把离线的标成待恢复
|
||||
markPendingForOffline(allIds.filter(id => netStatus[id] === false))
|
||||
return
|
||||
}
|
||||
// ⭐ 给这次被跳过的离线设备打标记(后续来网自动补上)
|
||||
markPendingForOffline(allIds.filter(id => !deviceIds.includes(id)))
|
||||
|
||||
if (isAlliance.value) {
|
||||
|
||||
followAndGreetUnion(
|
||||
{
|
||||
deviceList: deviceIds,
|
||||
anchorList: [],
|
||||
prologueList: getContentpriList(),
|
||||
needReply: auto.value,
|
||||
needTranslate: isTranslate.value,
|
||||
}
|
||||
).then((res) => {
|
||||
hostList = []
|
||||
return
|
||||
})
|
||||
} else {
|
||||
passAnchorData(
|
||||
{
|
||||
deviceList: deviceIds,
|
||||
anchorList: [],
|
||||
prologueList: getContentpriList(),
|
||||
comment: comonList,
|
||||
needReply: auto.value,
|
||||
needTranslate: isTranslate.value,
|
||||
isComment: common.value //是否评论
|
||||
|
||||
}
|
||||
).then((res) => {
|
||||
hostList = []
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
}, 1000)
|
||||
|
||||
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 = []
|
||||
})
|
||||
}
|
||||
|
||||
}, 1000)
|
||||
|
||||
return
|
||||
} else {
|
||||
// 如果是暂停状态,则打开弹窗进行开启第一轮任务
|
||||
scheduleEnabled.value = true
|
||||
initialTextStr.value = '';
|
||||
dialogTitle.value = '主播ID';
|
||||
showDialog.value = true;
|
||||
}
|
||||
|
||||
scheduleEnabled.value = true
|
||||
initialTextStr.value = '';
|
||||
dialogTitle.value = '主播ID';
|
||||
showDialog.value = true;
|
||||
|
||||
|
||||
} else if (key === 'like') {
|
||||
|
||||
if (!deviceId) {
|
||||
await stopAll(5000)
|
||||
await stopAll(2000)
|
||||
} else {
|
||||
growAccount({ udid: deviceId })
|
||||
growAccount({ udid: deviceId, comment: getContentList(), isComment: common.value })
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
scheduleEnabled.value = true
|
||||
runType.value = 'like'
|
||||
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId }))
|
||||
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId, comment: getContentList(), isComment: common.value }))
|
||||
}, 1000)
|
||||
|
||||
} else if (key === 'brushLive') {
|
||||
if (!deviceId) {
|
||||
await stopAll(5000)
|
||||
await stopAll(2000)
|
||||
} else {
|
||||
watchLiveForGrowth({ udid: deviceId })
|
||||
return
|
||||
@@ -1263,7 +1371,7 @@ function runTask(key, deviceId, type) {
|
||||
runType.value = 'brushLive'
|
||||
} else if (key === 'listen') {
|
||||
if (!deviceId) {
|
||||
await stopAll(5000)
|
||||
await stopAll(2000)
|
||||
} else {
|
||||
monitorMessages({ udid: deviceId })
|
||||
return
|
||||
@@ -1282,7 +1390,7 @@ function runTask(key, deviceId, type) {
|
||||
|
||||
async function stopCurrentMode() {
|
||||
// 如果你希望“只停当前片段的设备”,也可以用 stopScript 针对设备循环
|
||||
await stopAll(100)
|
||||
await stopAll(2000)
|
||||
}
|
||||
|
||||
/** 恢复:回到 scheduleState.index 对应片段,并让 startTime 回到“暂停前进度” */
|
||||
@@ -1296,6 +1404,7 @@ function resumeAfterInterrupt() {
|
||||
localStorage.setItem('SCHEDULE_STATE', JSON.stringify(scheduleState))
|
||||
|
||||
// 确保任务是该片段
|
||||
scheduleEnabled.value = true
|
||||
runTask(schedulePlan[scheduleState.index].key)
|
||||
pauseSnapshot = null
|
||||
}
|
||||
@@ -1383,7 +1492,7 @@ function startScheduleLoop() {
|
||||
pauseSnapshot = { index: scheduleState.index, elapsedBeforePause }
|
||||
|
||||
// 停掉当前片段
|
||||
await stopAll(5000)
|
||||
await stopAll(2000)
|
||||
|
||||
|
||||
// 执行中断任务(带重试)
|
||||
@@ -1421,6 +1530,7 @@ function startScheduleLoop() {
|
||||
scheduleState.index = (scheduleState.index + 1) % schedulePlan.length
|
||||
scheduleState.startTime = now
|
||||
localStorage.setItem('SCHEDULE_STATE', JSON.stringify(scheduleState))
|
||||
scheduleEnabled.value = true
|
||||
runTask(schedulePlan[scheduleState.index].key)
|
||||
}
|
||||
}, scheduleTickMs)
|
||||
@@ -1519,18 +1629,9 @@ async function doLogout() {
|
||||
}
|
||||
}
|
||||
function stopOne(deviceId) {
|
||||
stopLoading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '停止中',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
});
|
||||
stopScript({ udid: deviceId }).then((res) => {
|
||||
stopLoading.close()
|
||||
}).catch((err) => {
|
||||
stopLoading.close()
|
||||
|
||||
}).finally((err) => {
|
||||
stopLoading.close()
|
||||
stopScript({ udid: deviceId }).then((res) => {
|
||||
// ElMessage.success('停止成功');
|
||||
|
||||
})
|
||||
}
|
||||
@@ -1599,17 +1700,31 @@ async function doTranslate(sentences, targetLang) {
|
||||
}
|
||||
|
||||
// 接收“确定”事件返回结果
|
||||
function onConfirm({ type, strings, autoBlo }) {
|
||||
console.log('✅ 确认返回:', type, strings, autoBlo)
|
||||
function onConfirm({ type, strings, autoBlo, needTranslate }) {
|
||||
console.log('✅ 确认返回:', type, strings, autoBlo, needTranslate)
|
||||
auto.value = autoBlo
|
||||
isTranslate.value = needTranslate
|
||||
showtransDlg.value = false
|
||||
runType.value = 'follow'
|
||||
setContentpriList(strings)
|
||||
|
||||
//过滤无网络设备
|
||||
const allIds = deviceInformation.value.map(item => item.deviceId)
|
||||
// 在线要启动的
|
||||
const deviceIds = onlineOnly(allIds)
|
||||
if (!deviceIds.length) {
|
||||
ElMessage.warning('没有在线设备可执行任务');
|
||||
// 也别忘了把离线的标成待恢复
|
||||
markPendingForOffline(allIds.filter(id => netStatus[id] === false))
|
||||
return
|
||||
}
|
||||
// ⭐ 给这次被跳过的离线设备打标记(后续来网自动补上)
|
||||
markPendingForOffline(allIds.filter(id => !deviceIds.includes(id)))
|
||||
|
||||
if (isAlliance.value) {
|
||||
followAndGreetUnion(
|
||||
{
|
||||
deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||
deviceList: deviceIds,
|
||||
anchorList: hostList.map(item => ({
|
||||
anchorId: item.id,
|
||||
country: item.country,
|
||||
@@ -1618,7 +1733,7 @@ function onConfirm({ type, strings, autoBlo }) {
|
||||
})),
|
||||
prologueList: strings,
|
||||
needReply: autoBlo,
|
||||
// needTranslate: data.needTranslate,
|
||||
needTranslate: needTranslate,
|
||||
}
|
||||
).then((res) => {
|
||||
ElMessage({ type: 'success', message: '任务开启成功' });
|
||||
@@ -1628,7 +1743,7 @@ function onConfirm({ type, strings, autoBlo }) {
|
||||
} else {
|
||||
passAnchorData(
|
||||
{
|
||||
deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||
deviceList: deviceIds,
|
||||
anchorList: hostList.map(item => ({
|
||||
anchorId: item.id,
|
||||
country: item.country,
|
||||
@@ -1638,7 +1753,7 @@ function onConfirm({ type, strings, autoBlo }) {
|
||||
prologueList: strings, //私信对象
|
||||
comment: comonList, //评论列表
|
||||
needReply: autoBlo, //自动回复
|
||||
// needTranslate: data.needTranslate,
|
||||
needTranslate: needTranslate, //翻译
|
||||
isComment: common.value //是否评论
|
||||
}
|
||||
).then((res) => {
|
||||
@@ -1656,23 +1771,50 @@ function arrayToString(arr) {
|
||||
}
|
||||
|
||||
|
||||
// —— 每 20s 刷新网络状态 ——
|
||||
// 写一个小函数专门刷新网络状态,方便复用
|
||||
// 每台设备请求间隔(毫秒)
|
||||
const REQ_GAP_MS = 5000
|
||||
|
||||
async function refreshNetStatus() {
|
||||
const list = [...(deviceInformation.value || [])]
|
||||
if (!list.length) return
|
||||
|
||||
// 并发请求每台设备网络状态(安全处理错误)
|
||||
const settled = await Promise.allSettled(
|
||||
list.map(d => getDeviceNetStatus({ udid: d.deviceId }))
|
||||
const firstPass = {}
|
||||
|
||||
// ✅ 串行请求:每台设备间隔 5 秒
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const d = list[i]
|
||||
try {
|
||||
const res = await getDeviceNetStatus({ udid: d.deviceId })
|
||||
firstPass[d.deviceId] = (res === true)
|
||||
} catch (err) {
|
||||
firstPass[d.deviceId] = false
|
||||
}
|
||||
|
||||
// 不是最后一台才等待 5s
|
||||
if (i < list.length - 1) {
|
||||
await new Promise(r => setTimeout(r, REQ_GAP_MS))
|
||||
}
|
||||
}
|
||||
|
||||
// 二次复核
|
||||
const falseIds = Object.keys(firstPass).filter(id => firstPass[id] === false)
|
||||
const confirmResults = await Promise.all(
|
||||
falseIds.map(async id => {
|
||||
const stableOffline = await confirmStableOffline(id)
|
||||
return { id, stableOffline }
|
||||
})
|
||||
)
|
||||
|
||||
settled.forEach((s, i) => {
|
||||
const id = list[i].deviceId
|
||||
netStatus[id] = (s.status === 'fulfilled' && s.value === true)
|
||||
const effective = { ...firstPass }
|
||||
confirmResults.forEach(({ id, stableOffline }) => {
|
||||
effective[id] = !stableOffline
|
||||
})
|
||||
console.log("设备在线状态", settled)
|
||||
|
||||
// 清理已下线设备的状态,避免残留
|
||||
// 写回 UI
|
||||
Object.keys(effective).forEach(id => { netStatus[id] = effective[id] })
|
||||
console.log('设备在线状态(有效值)', effective)
|
||||
|
||||
// 清理已下线设备
|
||||
const aliveIds = new Set(list.map(d => d.deviceId))
|
||||
Object.keys(netStatus).forEach(id => {
|
||||
if (!aliveIds.has(id)) {
|
||||
@@ -1683,54 +1825,91 @@ async function refreshNetStatus() {
|
||||
}
|
||||
})
|
||||
|
||||
// —— 在这里做“网络→任务联动”(只在有运行模式时触发)——
|
||||
// 任务联动(保持原逻辑)
|
||||
if (runType.value) {
|
||||
list.forEach(({ deviceId: id }) => {
|
||||
const prev = lastNet[id] // 之前的网络状态
|
||||
const curr = netStatus[id] // 当前的网络状态(true/false/undefined)
|
||||
const prev = lastNet[id]
|
||||
const curr = netStatus[id]
|
||||
if (typeof curr === 'undefined') return
|
||||
|
||||
// 边沿触发:true -> false 断网,false -> true 恢复
|
||||
if (prev !== curr) {
|
||||
// 断网:立刻停止该设备任务(只停这一台)
|
||||
if (curr === false) {
|
||||
clearResumeTimer(id) // 避免有遗留恢复定时器
|
||||
clearResumeTimer(id)
|
||||
if (!offlineFlags[id]) {
|
||||
console.log(`[NET] ${id} 离线,停止该设备任务`)
|
||||
console.log(`[NET] ${id} 稳定离线,停止该设备任务`)
|
||||
offlineFlags[id] = true
|
||||
// 这里调用你已有的“只停一台”的方法
|
||||
stopOne(id)
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复:等待网络稳定后再恢复该设备任务
|
||||
if (curr === true && offlineFlags[id]) {
|
||||
clearResumeTimer(id)
|
||||
const timer = setTimeout(() => {
|
||||
// 二次确认:还在运行模式里、该设备仍在线
|
||||
const t = setTimeout(() => {
|
||||
if (runType.value && netStatus[id] === true) {
|
||||
console.log(`[NET] ${id} 恢复在线,重新启动当前模式: ${runType.value}`)
|
||||
// 仅启动这一台
|
||||
scheduleEnabled.value = true
|
||||
runTask(runType.value, id)
|
||||
// 标记已恢复
|
||||
offlineFlags[id] = false
|
||||
}
|
||||
resumeTimers.delete(id)
|
||||
}, NET_RESUME_STABLE_MS)
|
||||
resumeTimers.set(id, timer)
|
||||
resumeTimers.set(id, t)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新 last
|
||||
lastNet[id] = curr
|
||||
})
|
||||
} else {
|
||||
// 不在运行模式时,清理“暂停标记”和恢复定时器,保持干净
|
||||
Object.keys(offlineFlags).forEach(id => { offlineFlags[id] = false })
|
||||
Array.from(resumeTimers.keys()).forEach(id => clearResumeTimer(id))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ===== 新增:二次复核断网所需的小工具 =====
|
||||
const verifyingOffline = {} // { [deviceId]: Promise<boolean> } 去重并发
|
||||
|
||||
const RETRY_COUNT = 2 // 额外再查几次
|
||||
const RETRY_GAP_MS = 1000 // 每次间隔(可调)
|
||||
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
||||
|
||||
async function safeGetStatus(udid) {
|
||||
try {
|
||||
return await getDeviceNetStatus({ udid }) === true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** 返回 true=稳定离线;false=误报/抖动(有一次为 true 就当没离线) */
|
||||
async function confirmStableOffline(udid) {
|
||||
if (verifyingOffline[udid]) return verifyingOffline[udid]
|
||||
|
||||
verifyingOffline[udid] = (async () => {
|
||||
for (let i = 0; i < RETRY_COUNT; i++) {
|
||||
await sleep(RETRY_GAP_MS)
|
||||
const ok = await safeGetStatus(udid) // true=在线
|
||||
if (ok) return false // 有一次在线 => 抖动
|
||||
}
|
||||
return true // 全部都是 false => 稳定离线
|
||||
})().finally(() => { delete verifyingOffline[udid] })
|
||||
|
||||
return verifyingOffline[udid]
|
||||
}
|
||||
|
||||
//小工具:标记离线待恢复
|
||||
const markPendingForOffline = (ids) => {
|
||||
ids.forEach(id => {
|
||||
if (netStatus[id] === false) {
|
||||
offlineFlags[id] = true; // 标记成“暂停/待恢复”
|
||||
// 可选:初始化上次状态,便于日志判断
|
||||
if (typeof lastNet[id] === 'undefined') lastNet[id] = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
Reference in New Issue
Block a user