分包
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# iOS 控制服务
|
||||
# VUE_APP_BASE_LOCAL=http://192.168.1.218:34567/
|
||||
VUE_APP_BASE_LOCAL=http://127.0.0.1:34567/
|
||||
# VUE_APP_BASE_LOCAL=http://192.168.1.209:34567/
|
||||
VUE_APP_BASE_LOCAL=https://192.168.1.231:34567/
|
||||
# VUE_APP_BASE_LOCAL=https://127.0.0.1:34567/
|
||||
# VUE_APP_BASE_LOCAL=https://192.168.1.209:34567/
|
||||
|
||||
# 业务后端(开发用内网地址)
|
||||
# VUE_APP_BASE_REMOTE=https://crawlclient.api.yolozs.com
|
||||
VUE_APP_BASE_REMOTE=http://192.168.1.144:8101/
|
||||
VUE_APP_BASE_REMOTE=https://crawlclient.api.yolozs.com
|
||||
# VUE_APP_BASE_REMOTE=http://192.168.1.144:8101/
|
||||
|
||||
# AI 服务
|
||||
VUE_APP_BASE_SPECIAL=https://ai.yolozs.com
|
||||
105
src/composables/useDevices.js
Normal file
105
src/composables/useDevices.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// src/composables/useDevices.js
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
getDeviceList,
|
||||
stopScript,
|
||||
deviceAppList,
|
||||
launchApp,
|
||||
} from '@/api/ios'
|
||||
import { pickTikTokBundleId } from '@/utils/arrUtils'
|
||||
|
||||
export function useDevices() {
|
||||
const deviceInformation = ref([]) // 设备列表
|
||||
const getListTimer = ref(null) // 定时器句柄(方便在外面清理)
|
||||
|
||||
// 是否已经提示过 IOSAI 服务错误
|
||||
let isStartLac = false
|
||||
|
||||
// 拉设备列表
|
||||
const getDeviceListFun = async () => {
|
||||
try {
|
||||
const res = await getDeviceList()
|
||||
if (res && res.length > 0 && deviceInformation.value.length !== res.length) {
|
||||
console.log('设备变更')
|
||||
deviceInformation.value = res
|
||||
}
|
||||
if (!res || res.length === 0) {
|
||||
deviceInformation.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
if (isStartLac) {
|
||||
ElMessage.error('IOSAI 服务错误')
|
||||
} else {
|
||||
// 第一次忽略,等下次再报(保持你原来的行为)
|
||||
isStartLac = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打开 Tiktok
|
||||
const openTk = async () => {
|
||||
if (!deviceInformation.value?.length) {
|
||||
ElMessage.warning('暂无在线设备')
|
||||
return
|
||||
}
|
||||
|
||||
const { ElLoading } = await import('element-plus')
|
||||
const loading = ElLoading.service({
|
||||
text: '正在打开 TikTok …',
|
||||
background: 'rgba(0,0,0,.35)'
|
||||
})
|
||||
|
||||
const results = []
|
||||
|
||||
try {
|
||||
for (const dev of deviceInformation.value) {
|
||||
const udid = dev.deviceId
|
||||
try {
|
||||
const apps = await deviceAppList({ udid })
|
||||
const bundleId = pickTikTokBundleId(apps)
|
||||
|
||||
if (!bundleId) {
|
||||
results.push({ udid, ok: false, msg: '未找到 TikTok' })
|
||||
continue
|
||||
}
|
||||
|
||||
await launchApp({ udid, bundleId })
|
||||
results.push({ udid, ok: true, msg: `已启动 TikTok (${bundleId})` })
|
||||
} catch (e) {
|
||||
console.error('openTk error', udid, e)
|
||||
results.push({ udid, ok: false, msg: '请求失败' })
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.close()
|
||||
}
|
||||
|
||||
const okCount = results.filter(r => r.ok).length
|
||||
const fail = results.filter(r => !r.ok)
|
||||
if (okCount) ElMessage.success(`已在 ${okCount} 台设备启动 TikTok`)
|
||||
if (fail.length) {
|
||||
const udids = fail.map(f => f.udid).join(', ')
|
||||
ElMessage.error(`以下设备未能启动:${udids}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 停止单台任务
|
||||
const stopOne = async (deviceId) => {
|
||||
try {
|
||||
await stopScript({ udid: deviceId })
|
||||
// 你原来这里没有提示,我也保持不弹
|
||||
// ElMessage.success('停止成功')
|
||||
} catch (e) {
|
||||
console.error('stopOne error', e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deviceInformation,
|
||||
getDeviceListFun,
|
||||
getListTimer,
|
||||
openTk,
|
||||
stopOne,
|
||||
}
|
||||
}
|
||||
0
src/composables/useNetStatus.js
Normal file
0
src/composables/useNetStatus.js
Normal file
0
src/composables/useSSEAnchors.js
Normal file
0
src/composables/useSSEAnchors.js
Normal file
0
src/composables/useSchedule.js
Normal file
0
src/composables/useSchedule.js
Normal file
255
src/composables/useScreenStreams.js
Normal file
255
src/composables/useScreenStreams.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// src/composables/useScreenStreams.js
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { tapAction, swipeAction } from '@/api/ios'
|
||||
|
||||
const BASE_W = 320
|
||||
const BASE_H = 720
|
||||
const THUMB_SCALE = 0.6
|
||||
const PER_ROW = 3
|
||||
const BOTTOM_SHIFT = Math.round(BASE_H * (1 - THUMB_SCALE)) // 288
|
||||
|
||||
export function useScreenStreams(deviceInformation) {
|
||||
// 选中的设备索引
|
||||
const selectedDevice = ref(null)
|
||||
|
||||
// 每台设备的 <img> 引用
|
||||
const imgRefs = ref({}) // { [id]: HTMLImageElement }
|
||||
|
||||
// 每台设备当前展示的 URL
|
||||
const imgSrcMap = reactive({}) // { [deviceId]: string }
|
||||
|
||||
// 每台设备循环状态
|
||||
const loops = new Map() // deviceId -> { timer, stopped }
|
||||
|
||||
const makeUrl = (port) => `http://192.168.1.231:${port}/?t=${Date.now()}`
|
||||
// const makeUrl = (port) => `http://localhost:${port}/?t=${Date.now()}`
|
||||
|
||||
function hardCloseImg(deviceId) {
|
||||
const el = imgRefs.value[deviceId]
|
||||
if (el) {
|
||||
el.src = ''
|
||||
el.removeAttribute('src')
|
||||
}
|
||||
imgSrcMap[deviceId] = ''
|
||||
}
|
||||
|
||||
function stopLoop(deviceId) {
|
||||
const s = loops.get(deviceId)
|
||||
if (!s) {
|
||||
hardCloseImg(deviceId)
|
||||
return
|
||||
}
|
||||
s.stopped = true
|
||||
if (s.timer) clearTimeout(s.timer)
|
||||
loops.delete(deviceId)
|
||||
hardCloseImg(deviceId)
|
||||
}
|
||||
|
||||
function startLoop(dev, idx = 0) {
|
||||
stopLoop(dev.deviceId)
|
||||
const state = { timer: null, stopped: false }
|
||||
loops.set(dev.deviceId, state)
|
||||
|
||||
const tick = () => {
|
||||
if (state.stopped) return
|
||||
const url = makeUrl(dev.screenPort)
|
||||
imgSrcMap[dev.deviceId] = url
|
||||
state.timer = window.setTimeout(tick, 3000)
|
||||
}
|
||||
|
||||
state.timer = window.setTimeout(tick, idx * 200)
|
||||
}
|
||||
|
||||
function reconcileLoopsByDevices(list) {
|
||||
const keep = new Set(list.map(d => d.deviceId))
|
||||
|
||||
// 停掉没了的设备
|
||||
for (const id of Array.from(loops.keys())) {
|
||||
if (!keep.has(id)) stopLoop(id)
|
||||
}
|
||||
// 为新增设备启动循环
|
||||
list.forEach((d, i) => {
|
||||
if (!loops.has(d.deviceId)) startLoop(d, i)
|
||||
})
|
||||
}
|
||||
|
||||
function refreshOneImg(deviceId) {
|
||||
const dev = deviceInformation.value.find(d => d.deviceId === deviceId)
|
||||
if (dev) {
|
||||
stopLoop(deviceId)
|
||||
startLoop(dev, 0)
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAllImgs() {
|
||||
deviceInformation.value.forEach((d, i) => {
|
||||
stopLoop(d.deviceId)
|
||||
startLoop(d, i)
|
||||
})
|
||||
}
|
||||
|
||||
function refreshAllStopImgs() {
|
||||
Object.keys(imgRefs.value).forEach(id => hardCloseImg(id))
|
||||
}
|
||||
|
||||
// 设备变动时自动对齐循环
|
||||
watch(deviceInformation, (list) => {
|
||||
reconcileLoopsByDevices(list || [])
|
||||
}, { deep: true })
|
||||
|
||||
// ——— 选中 + 缩放/上移样式 ———
|
||||
const hasTwoRows = computed(() => deviceInformation.value.length > PER_ROW)
|
||||
|
||||
const isBottomRow = (index) => {
|
||||
if (!hasTwoRows.value) return false
|
||||
const lastRow = Math.floor((deviceInformation.value.length - 1) / PER_ROW)
|
||||
return Math.floor(index / PER_ROW) === lastRow
|
||||
}
|
||||
|
||||
function getCanvasStyle(index) {
|
||||
const isSelected = selectedDevice.value === index
|
||||
if (!isSelected) {
|
||||
return { transform: `scale(${THUMB_SCALE})` }
|
||||
}
|
||||
return isBottomRow(index)
|
||||
? { transform: `translateY(-${BOTTOM_SHIFT}px) scale(1)` }
|
||||
: { transform: 'scale(1)' }
|
||||
}
|
||||
|
||||
const imgWH = (index) => {
|
||||
const scale = (selectedDevice.value === index) ? 1 : THUMB_SCALE
|
||||
return {
|
||||
width: `${BASE_W * scale}px`,
|
||||
height: `${BASE_H * scale}px`,
|
||||
transition: 'all 0.3s ease',
|
||||
}
|
||||
}
|
||||
|
||||
const displaySize = (index) => {
|
||||
const scale = (selectedDevice.value === index) ? 1 : THUMB_SCALE
|
||||
return { w: BASE_W * scale, h: BASE_H * scale }
|
||||
}
|
||||
|
||||
const selectDevice = (index) => {
|
||||
selectedDevice.value = index
|
||||
}
|
||||
|
||||
// ——— 坐标映射 + 鼠标交互 ———
|
||||
const dragState = ref({}) // index -> { ox, oy, t, udid, ... }
|
||||
|
||||
const mapToDeviceXY = (index, offsetX, offsetY) => {
|
||||
const dev = deviceInformation.value[index] || {}
|
||||
const realW = Number(dev.width) || BASE_W
|
||||
const realH = Number(dev.height) || BASE_H
|
||||
const rotation = Number(dev.rotation || 0)
|
||||
|
||||
const { w: dispW, h: dispH } = displaySize(index)
|
||||
let nx = Math.min(Math.max(offsetX / dispW, 0), 1)
|
||||
let ny = Math.min(Math.max(offsetY / dispH, 0), 1)
|
||||
|
||||
let x, y
|
||||
switch (rotation % 360) {
|
||||
case 90:
|
||||
case -270:
|
||||
x = Math.round(ny * realW)
|
||||
y = Math.round((1 - nx) * realH)
|
||||
break
|
||||
case 180:
|
||||
case -180:
|
||||
x = Math.round((1 - nx) * realW)
|
||||
y = Math.round((1 - ny) * realH)
|
||||
break
|
||||
case 270:
|
||||
case -90:
|
||||
x = Math.round((1 - ny) * realW)
|
||||
y = Math.round(nx * realH)
|
||||
break
|
||||
default:
|
||||
x = Math.round(nx * realW)
|
||||
y = Math.round(ny * realH)
|
||||
}
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
const onCanvasDown = (udid, e, index) => {
|
||||
const startDev = mapToDeviceXY(index, e.offsetX, e.offsetY)
|
||||
dragState.value[index] = {
|
||||
ox: e.offsetX,
|
||||
oy: e.offsetY,
|
||||
t: Date.now(),
|
||||
udid,
|
||||
startDevXY: startDev,
|
||||
startOffsetXY: { x: e.offsetX, y: e.offsetY }
|
||||
}
|
||||
}
|
||||
|
||||
const onCanvasMove = (udid, e, index) => {
|
||||
const st = dragState.value[index]
|
||||
if (!st) return
|
||||
// const curDev = mapToDeviceXY(index, e.offsetX, e.offsetY)
|
||||
// 调试需要再打印
|
||||
}
|
||||
|
||||
const onCanvasUp = async (udid, e, index) => {
|
||||
const st = dragState.value[index]
|
||||
if (!st) return
|
||||
|
||||
const { ox, oy, t, startDevXY, startOffsetXY } = st
|
||||
const dx = e.offsetX - ox
|
||||
const dy = e.offsetY - oy
|
||||
const elapsed = Date.now() - t
|
||||
delete dragState.value[index]
|
||||
|
||||
const endDevXY = mapToDeviceXY(index, e.offsetX, e.offsetY)
|
||||
const endOffsetXY = { x: e.offsetX, y: e.offsetY }
|
||||
|
||||
console.log('[鼠标滑动,起点/终点)+ 耗时]', {
|
||||
udid,
|
||||
start: {
|
||||
offsetXY: startOffsetXY,
|
||||
deviceXY: startDevXY
|
||||
},
|
||||
end: {
|
||||
offsetXY: endOffsetXY,
|
||||
deviceXY: endDevXY
|
||||
},
|
||||
deltaOffset: { dx, dy },
|
||||
durationMs: elapsed,
|
||||
})
|
||||
|
||||
const MOVE_THR = 5
|
||||
const isTap = Math.hypot(dx, dy) < MOVE_THR && elapsed < 500
|
||||
|
||||
try {
|
||||
if (isTap) {
|
||||
await tapAction({ udid, x: endDevXY.x, y: endDevXY.y })
|
||||
} else {
|
||||
await swipeAction({
|
||||
udid,
|
||||
sx: startDevXY.x,
|
||||
sy: startDevXY.y,
|
||||
ex: endDevXY.x,
|
||||
ey: endDevXY.y,
|
||||
duration: elapsed / 1000
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectedDevice,
|
||||
imgRefs,
|
||||
imgSrcMap,
|
||||
refreshAllImgs,
|
||||
refreshOneImg,
|
||||
refreshAllStopImgs,
|
||||
getCanvasStyle,
|
||||
imgWH,
|
||||
selectDevice,
|
||||
onCanvasDown,
|
||||
onCanvasMove,
|
||||
onCanvasUp,
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ import { getToken, setToken, setUser, setUserPass, getUserPass } from '@/stores/
|
||||
import { ElLoading, ElMessage } from 'element-plus';
|
||||
import { passToken } from '@/api/ios';
|
||||
|
||||
let version = ref('2.9.1');
|
||||
let version = ref('3.0.0');
|
||||
onMounted(() => {
|
||||
|
||||
|
||||
|
||||
@@ -19,9 +19,16 @@
|
||||
<div style="position: absolute;left: 20px; bottom: 20px;">
|
||||
<el-button @click="showHostDlg = true">执行主播库</el-button>
|
||||
<el-button type="info" @click="uploadLogFile">上传日志</el-button>
|
||||
<!-- 新增:SSE 弹窗总开关 -->
|
||||
<el-switch v-model="sseEnabled" inline-prompt active-text="监听爬虫" inactive-text="监听爬虫"
|
||||
style="margin-left: 8px;" />
|
||||
<div>
|
||||
<!-- 监听 q.tenant.*(爬虫) -->
|
||||
<el-switch v-model="sseCrawlerEnabled" inline-prompt active-text="监听爬虫" inactive-text="监听爬虫"
|
||||
style="margin-left: 8px;" @change="onToggleCrawler" />
|
||||
|
||||
<!-- 监听 b.tenant.*(大哥) -->
|
||||
<el-switch v-model="sseBossEnabled" inline-prompt active-text="监听大哥" inactive-text="监听大哥"
|
||||
style="margin-left: 8px;" @change="onToggleBoss" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@@ -125,13 +132,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onUnmounted, onBeforeUnmount, watch, inject, computed, nextTick } from "vue";
|
||||
import { ref, reactive, onMounted, onUnmounted, watch, computed } from "vue";
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
setphoneXYinfo, getphoneXYinfo, getUser,
|
||||
getHostList, setHostList, getContentpriList,
|
||||
setContentpriList, getContentList, setContentList,
|
||||
setsessionId, getsessionId, getContentListMultiline, getContentpriListMultiline
|
||||
getContentListMultiline, getContentpriListMultiline
|
||||
} from '@/stores/storage'
|
||||
import { connectSSE } from '@/utils/sseUtils'
|
||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
|
||||
@@ -144,13 +151,9 @@ 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, health } from '@/api/account';
|
||||
import {
|
||||
getDeviceList,
|
||||
toHome,
|
||||
swipeAction,
|
||||
tapAction,
|
||||
growAccount,
|
||||
stopScript,
|
||||
watchLiveForGrowth,
|
||||
@@ -158,13 +161,10 @@ import {
|
||||
passAnchorData,
|
||||
followAndGreetUnion,
|
||||
addTempAnchorData,
|
||||
deviceAppList,
|
||||
launchApp,
|
||||
getChatTextInfo,
|
||||
setLoginInfo,
|
||||
aiConfig,
|
||||
selectLast,
|
||||
updatelast,
|
||||
changeAccount,
|
||||
stopAllTask,
|
||||
anchorList,
|
||||
@@ -172,7 +172,10 @@ import {
|
||||
getDeviceNetStatus
|
||||
} from '@/api/ios';
|
||||
import ding from '@/assets/mes.wav'
|
||||
import { set } from "lodash";
|
||||
//引入两个分包方法
|
||||
import { useDevices } from '@/composables/useDevices'
|
||||
import { useScreenStreams } from '@/composables/useScreenStreams'
|
||||
|
||||
const router = useRouter();
|
||||
const openShowChat = ref(true)
|
||||
//主播库
|
||||
@@ -204,17 +207,12 @@ let flushTimer = null;
|
||||
let hostList = [] // 主播列表
|
||||
let comonList = [] //评论列表
|
||||
//查询 列表 新消息轮询
|
||||
let getListtimer = null;
|
||||
let getNetworkListtimer = null;
|
||||
let userdata = getUser();
|
||||
let chatList = ref([])
|
||||
|
||||
let MesNewList = ref([])
|
||||
|
||||
// 刷新方法
|
||||
const reloadImg = () => {
|
||||
refreshAllImgs()
|
||||
}
|
||||
//start弹窗
|
||||
let isMsgPop = ref(false)
|
||||
|
||||
@@ -223,7 +221,6 @@ let isMonitorOn = ref(false)
|
||||
const hoverIndex = ref(null) //选中
|
||||
let showDialog = ref(false);//弹窗是否显示
|
||||
let dialogTitle = ref('');//当前弹窗类型
|
||||
let deviceInformation = ref([])
|
||||
// 你可以用这种方式声明按钮们
|
||||
//停止中
|
||||
let stopLoading = null
|
||||
@@ -232,6 +229,31 @@ let stopLoading = null
|
||||
const isListenLockedByPlan = computed(() => String(userdata?.aiReplay ?? '0') === '0')
|
||||
|
||||
|
||||
// === 1. 设备相关 ===
|
||||
const {
|
||||
deviceInformation,
|
||||
getDeviceListFun,
|
||||
getListTimer,
|
||||
openTk,
|
||||
stopOne,
|
||||
} = useDevices()
|
||||
|
||||
// === 2. 屏幕流相关 ===
|
||||
const {
|
||||
selectedDevice,
|
||||
imgRefs,
|
||||
imgSrcMap,
|
||||
refreshAllImgs,
|
||||
refreshOneImg,
|
||||
refreshAllStopImgs,
|
||||
getCanvasStyle,
|
||||
imgWH,
|
||||
selectDevice,
|
||||
onCanvasDown,
|
||||
onCanvasMove,
|
||||
onCanvasUp,
|
||||
} = useScreenStreams(deviceInformation)
|
||||
|
||||
// 每台设备的网络状态:true=正常,false=异常
|
||||
const netStatus = reactive({}) // { [deviceId]: boolean }
|
||||
|
||||
@@ -257,13 +279,21 @@ const onlineOnly = (ids) => ids.filter(isOnline)
|
||||
// 当前是否被其它模式占用(四个互斥按钮专用)
|
||||
const isLocked = (type) => !!runType.value && runType.value !== type
|
||||
|
||||
// —— SSE 弹窗/接收总开关(持久化)——
|
||||
const sseEnabled = ref(JSON.parse(localStorage.getItem('SSE_ENABLED') ?? 'true'))
|
||||
watch(sseEnabled, v => {
|
||||
localStorage.setItem('SSE_ENABLED', JSON.stringify(v))
|
||||
if (!v) dropCurrentWave() // 关掉总开关时,立刻丢弃本波缓冲并取消待flush
|
||||
// —— SSE 开关(分别控制爬虫队列 & 大哥队列)——
|
||||
const sseCrawlerEnabled = ref(JSON.parse(localStorage.getItem('SSE_CRAWLER_ENABLED') ?? 'true')) // q.tenant.*
|
||||
const sseBossEnabled = ref(JSON.parse(localStorage.getItem('SSE_BOSS_ENABLED') ?? 'false')) // b.tenant.*
|
||||
|
||||
watch(sseCrawlerEnabled, v => {
|
||||
localStorage.setItem('SSE_CRAWLER_ENABLED', JSON.stringify(v))
|
||||
})
|
||||
|
||||
watch(sseBossEnabled, v => {
|
||||
localStorage.setItem('SSE_BOSS_ENABLED', JSON.stringify(v))
|
||||
})
|
||||
|
||||
// 至少有一个开着才算启用 SSE
|
||||
const sseAnyEnabled = computed(() => sseCrawlerEnabled.value || sseBossEnabled.value)
|
||||
|
||||
// 互斥按钮的样式:激活=红,锁定=半透明且禁点
|
||||
const ctrlStyle = (type) => {
|
||||
const lockedByMode = isLocked(type)
|
||||
@@ -284,7 +314,7 @@ const ctrlStyle = (type) => {
|
||||
const buttons = [
|
||||
{
|
||||
label: '刷新',
|
||||
onClick: () => reloadImg(),
|
||||
onClick: () => refreshAllImgs(),
|
||||
show: () => true,
|
||||
img: {
|
||||
normal: new URL('@/assets/video/leftBtn1.png', import.meta.url).href,
|
||||
@@ -528,363 +558,6 @@ function saveSchedule() {
|
||||
ElMessage.success('已保存定时调度')
|
||||
}
|
||||
|
||||
const selectedDevice = ref(null)
|
||||
|
||||
|
||||
// 每台设备的 <img> 引用
|
||||
const imgRefs = ref({}) // { [id]: HTMLImageElement }
|
||||
|
||||
// 新增:每台设备当前展示的 URL
|
||||
const imgSrcMap = reactive({}) // { [deviceId]: string }
|
||||
|
||||
// 新增:每台设备循环状态(timer/abort 等)
|
||||
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) {
|
||||
const el = imgRefs.value[deviceId];
|
||||
if (el) {
|
||||
el.src = '';
|
||||
el.removeAttribute('src');
|
||||
}
|
||||
// 如果你用的是 blob/objectURL,这里应该 revokeObjectURL;我们当前直接 URL,不需要。
|
||||
imgSrcMap[deviceId] = '';
|
||||
}
|
||||
|
||||
/** 只启动某台设备的 3 秒循环(第 idx 台错峰 idx*200ms) */
|
||||
function startLoop(dev, idx = 0) {
|
||||
stopLoop(dev.deviceId); // 防止重复开
|
||||
const state = { timer: null, stopped: false, lastUrl: '' };
|
||||
loops.set(dev.deviceId, state);
|
||||
|
||||
const tick = () => {
|
||||
if (state.stopped) return;
|
||||
|
||||
// 1) 先把老连接硬断开
|
||||
// hardCloseImg(dev.deviceId);
|
||||
|
||||
// 2) 立刻换新地址
|
||||
const url = makeUrl(dev.screenPort);
|
||||
imgSrcMap[dev.deviceId] = url;
|
||||
|
||||
// 3) 3 秒后再来一轮(串行,不并发)
|
||||
state.timer = window.setTimeout(tick, 3000);
|
||||
};
|
||||
|
||||
// 错峰启动,避免 N 台同时一口气重连
|
||||
state.timer = window.setTimeout(tick, idx * 200);
|
||||
}
|
||||
|
||||
/** 停止某台设备的循环并断开连接 */
|
||||
function stopLoop(deviceId) {
|
||||
const s = loops.get(deviceId);
|
||||
if (!s) {
|
||||
hardCloseImg(deviceId); // 也确保断一次
|
||||
return;
|
||||
}
|
||||
s.stopped = true;
|
||||
if (s.timer) clearTimeout(s.timer);
|
||||
loops.delete(deviceId);
|
||||
hardCloseImg(deviceId);
|
||||
}
|
||||
|
||||
|
||||
// —— 设备列表变化时,同步循环 ——
|
||||
// 注意:你的 deviceInformation 每 3 秒会被重新赋值。
|
||||
// 这里不每次都重启,而是只做“增删对齐”,避免不必要中断。
|
||||
watch(deviceInformation, (list) => {
|
||||
reconcileLoopsByDevices(list || []);
|
||||
}, { deep: true });
|
||||
|
||||
|
||||
/** 批量控制:根据 deviceInformation 启停循环(新增启动,移除停止) */
|
||||
function reconcileLoopsByDevices(list) {
|
||||
const keep = new Set(list.map(d => d.deviceId));
|
||||
|
||||
// 停掉已不存在的设备
|
||||
for (const id of Array.from(loops.keys())) {
|
||||
if (!keep.has(id)) stopLoop(id);
|
||||
}
|
||||
// 为新增设备启动循环(带错峰)
|
||||
list.forEach((d, i) => {
|
||||
if (!loops.has(d.deviceId)) startLoop(d, i);
|
||||
});
|
||||
}// 强制重建 <img>
|
||||
|
||||
// 如果你还有“手动刷新”按钮,改成只刷新当前/所有设备的一轮
|
||||
function refreshOneImg(deviceId) {
|
||||
// 立即强制进入下一轮:先停后启
|
||||
const dev = deviceInformation.value.find(d => d.deviceId === deviceId);
|
||||
if (dev) {
|
||||
stopLoop(deviceId);
|
||||
startLoop(dev, 0);
|
||||
}
|
||||
}
|
||||
function refreshAllImgs() {
|
||||
deviceInformation.value.forEach((d, i) => {
|
||||
stopLoop(d.deviceId);
|
||||
startLoop(d, i);
|
||||
});
|
||||
}
|
||||
|
||||
// —— 关键:登出/离开时“批量硬中断”所有图片流 ——
|
||||
async function hardStopAllImgStreams(id, port) {
|
||||
const el = imgRefs.value[id]
|
||||
console.log("终止", id)
|
||||
|
||||
if (el) {
|
||||
// 硬中断旧请求
|
||||
el.src = ''
|
||||
el.removeAttribute('src')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function refreshAllStopImgs() {
|
||||
Object.keys(imgRefs.value).forEach(id => hardStopAllImgStreams(id))
|
||||
}
|
||||
|
||||
|
||||
// —— 显示尺寸固定为 320x720;未选中缩略为 THUMB_SCALE 倍 ——
|
||||
// 尺寸与排布
|
||||
const BASE_W = 320
|
||||
const BASE_H = 720
|
||||
const THUMB_SCALE = 0.6
|
||||
const PER_ROW = 3
|
||||
|
||||
// 底行上移的位移量:720*(1-0.6)=288
|
||||
const BOTTOM_SHIFT = Math.round(BASE_H * (1 - THUMB_SCALE)) // 288
|
||||
|
||||
// 是否至少有两行
|
||||
const hasTwoRows = computed(() => deviceInformation.value.length > PER_ROW)
|
||||
|
||||
// 真正的“底行”判定:必须有两行以上才成立
|
||||
const isBottomRow = (index) => {
|
||||
if (!hasTwoRows.value) return false
|
||||
const lastRow = Math.floor((deviceInformation.value.length - 1) / PER_ROW)
|
||||
return Math.floor(index / PER_ROW) === lastRow
|
||||
}
|
||||
|
||||
// 统一给 .video-canvas 返回 transform(缩略/放大/底行上移)
|
||||
function getCanvasStyle(index) {
|
||||
const isSelected = selectedDevice.value === index
|
||||
if (!isSelected) {
|
||||
return { transform: `scale(${THUMB_SCALE})` }
|
||||
}
|
||||
// 选中:默认正常放大;若在底行且至少两行 -> 先上移再放大
|
||||
return isBottomRow(index)
|
||||
? { transform: `translateY(-${BOTTOM_SHIFT}px) scale(1)` }
|
||||
: { transform: 'scale(1)' }
|
||||
}
|
||||
// 当前选中的卡片:选中=1倍,未选中=缩略比例
|
||||
const imgWH = (index) => {
|
||||
const scale = (selectedDevice.value === index) ? 1 : THUMB_SCALE
|
||||
return {
|
||||
width: `${BASE_W * scale}px`,
|
||||
height: `${BASE_H * scale}px`,
|
||||
transition: 'all 0.3s ease',
|
||||
}
|
||||
}
|
||||
|
||||
// 计算某索引当前展示宽高(用于坐标换算)
|
||||
const displaySize = (index) => {
|
||||
const scale = (selectedDevice.value === index) ? 1 : THUMB_SCALE
|
||||
return { w: BASE_W * scale, h: BASE_H * scale }
|
||||
}
|
||||
|
||||
// 从 Canvas offset 坐标 → 真实手机分辨率坐标
|
||||
const mapToDeviceXY = (index, offsetX, offsetY) => {
|
||||
const dev = deviceInformation.value[index] || {}
|
||||
const realW = Number(dev.width) || BASE_W // 后端返回的真实分辨率
|
||||
const realH = Number(dev.height) || BASE_H
|
||||
const rotation = Number(dev.rotation || 0) // 若后端有提供旋转角,可用 0/90/180/270
|
||||
|
||||
const { w: dispW, h: dispH } = displaySize(index)
|
||||
|
||||
// 归一化到 0~1
|
||||
let nx = Math.min(Math.max(offsetX / dispW, 0), 1)
|
||||
let ny = Math.min(Math.max(offsetY / dispH, 0), 1)
|
||||
|
||||
// 处理旋转(如果你的服务器坐标基于设备原生朝向)
|
||||
// 0: 直接映射;90: 顺时针;180、270 同理
|
||||
let x, y
|
||||
switch (rotation % 360) {
|
||||
case 90:
|
||||
case -270:
|
||||
x = Math.round(ny * realW)
|
||||
y = Math.round((1 - nx) * realH)
|
||||
break
|
||||
case 180:
|
||||
case -180:
|
||||
x = Math.round((1 - nx) * realW)
|
||||
y = Math.round((1 - ny) * realH)
|
||||
break
|
||||
case 270:
|
||||
case -90:
|
||||
x = Math.round((1 - ny) * realW)
|
||||
y = Math.round(nx * realH)
|
||||
break
|
||||
default: // 0°
|
||||
x = Math.round(nx * realW)
|
||||
y = Math.round(ny * realH)
|
||||
}
|
||||
return { x, y }
|
||||
}
|
||||
|
||||
// 选中:恢复到 320x720 并显示盖层
|
||||
const selectDevice = (index) => {
|
||||
selectedDevice.value = index
|
||||
}
|
||||
|
||||
// ——— 鼠标交互:按下/移动/抬起 ———
|
||||
const dragState = ref({}) // 以 index 作为 key 保存 {ox, oy, t}
|
||||
|
||||
const onCanvasDown = (udid, e, index) => {
|
||||
// 记录起点(Canvas 内 offset)和时间
|
||||
const startDev = mapToDeviceXY(index, e.offsetX, e.offsetY) // 也记录“设备坐标”起点,便于直接打印
|
||||
dragState.value[index] = {
|
||||
ox: e.offsetX,
|
||||
oy: e.offsetY,
|
||||
t: Date.now(),
|
||||
udid,
|
||||
startDevXY: startDev,
|
||||
startOffsetXY: { x: e.offsetX, y: e.offsetY }
|
||||
}
|
||||
}
|
||||
|
||||
const onCanvasMove = (udid, e, index) => {
|
||||
// 若要实时观察滑动轨迹(可选)
|
||||
const st = dragState.value[index]
|
||||
if (!st) return
|
||||
const curDev = mapToDeviceXY(index, e.offsetX, e.offsetY)
|
||||
// 建议:调试阶段打开,稳定后可注释或做节流
|
||||
// console.log('[MOVE]', {
|
||||
// udid,
|
||||
// offsetXY: { x: e.offsetX, y: e.offsetY },
|
||||
// deviceXY: curDev,
|
||||
// elapsedMs: Date.now() - st.t
|
||||
// })
|
||||
}
|
||||
|
||||
const onCanvasUp = async (udid, e, index) => {
|
||||
const st = dragState.value[index]
|
||||
if (!st) return
|
||||
|
||||
const { ox, oy, t, startDevXY, startOffsetXY } = st
|
||||
const dx = e.offsetX - ox
|
||||
const dy = e.offsetY - oy
|
||||
const elapsed = Date.now() - t
|
||||
delete dragState.value[index]
|
||||
|
||||
// 终点(设备坐标 & 画布 offset)
|
||||
const endDevXY = mapToDeviceXY(index, e.offsetX, e.offsetY)
|
||||
const endOffsetXY = { x: e.offsetX, y: e.offsetY }
|
||||
|
||||
// ✅ 这里打印:起点/终点(两套坐标)+ 耗时
|
||||
console.log('[鼠标滑动,起点/终点)+ 耗时]', {
|
||||
udid,
|
||||
start: {
|
||||
offsetXY: startOffsetXY, // 画布内起点
|
||||
deviceXY: startDevXY // 设备坐标起点
|
||||
},
|
||||
end: {
|
||||
offsetXY: endOffsetXY, // 画布内终点
|
||||
deviceXY: endDevXY // 设备坐标终点
|
||||
},
|
||||
deltaOffset: { dx, dy },
|
||||
durationMs: elapsed,
|
||||
})
|
||||
|
||||
// === 你原有逻辑:tap or swipe ===
|
||||
const MOVE_THR = 5
|
||||
const isTap = Math.hypot(dx, dy) < MOVE_THR && elapsed < 500
|
||||
|
||||
try {
|
||||
if (isTap) {
|
||||
await tapAction({ udid, x: endDevXY.x, y: endDevXY.y })
|
||||
} else {
|
||||
//通过自定义滑动坐标和时间传参
|
||||
await swipeAction({ udid, sx: startDevXY.x, sy: startDevXY.y, ex: endDevXY.x, ey: endDevXY.y, duration: elapsed / 1000 })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
/** 方向码:1=上, 2=左, 3=下, 4=右(不返回0,始终给出一个方向) */
|
||||
function getSwipeCode(dx, dy) {
|
||||
// 哪个轴位移更大就取哪个轴;边界≈45°
|
||||
if (Math.abs(dx) >= Math.abs(dy)) {
|
||||
return dx < 0 ? 2 : 4 // 左/右
|
||||
} else {
|
||||
return dy < 0 ? 1 : 3 // 上/下(DOM坐标里向上是负)
|
||||
}
|
||||
}
|
||||
|
||||
/** 带设备旋转(0/90/180/270):先把画布向量(dx,dy)旋回设备坐标系再判方向 */
|
||||
function getSwipeCodeWithRotation(dx, dy, rotation = 0) {
|
||||
let dxD = dx, dyD = dy
|
||||
switch ((rotation % 360 + 360) % 360) {
|
||||
case 90: dxD = dy; dyD = -dx; break
|
||||
case 180: dxD = -dx; dyD = -dy; break
|
||||
case 270: dxD = -dy; dyD = dx; break
|
||||
default: break
|
||||
}
|
||||
return getSwipeCode(dxD, dyD)
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function openTk() {
|
||||
if (!deviceInformation.value?.length) {
|
||||
ElMessage.warning('暂无在线设备')
|
||||
return
|
||||
}
|
||||
|
||||
const loading = ElLoading.service({ text: '正在打开 TikTok …', background: 'rgba(0,0,0,.35)' })
|
||||
const results = []
|
||||
|
||||
try {
|
||||
// 为了稳妥,逐台串行(如果你希望更快,可改 Promise.all 并注意并发数)
|
||||
for (const dev of deviceInformation.value) {
|
||||
const udid = dev.deviceId
|
||||
try {
|
||||
const apps = await deviceAppList({ udid }) // 期望返回示例中的数组
|
||||
const bundleId = pickTikTokBundleId(apps)
|
||||
|
||||
if (!bundleId) {
|
||||
results.push({ udid, ok: false, msg: '未找到 TikTok' })
|
||||
continue
|
||||
}
|
||||
|
||||
await launchApp({ udid, bundleId })
|
||||
results.push({ udid, ok: true, msg: `已启动 TikTok (${bundleId})` })
|
||||
} catch (e) {
|
||||
console.error('openTk error', udid, e)
|
||||
results.push({ udid, ok: false, msg: '请求失败' })
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.close()
|
||||
}
|
||||
|
||||
// 汇总提示(成功/失败各一条)
|
||||
const okCount = results.filter(r => r.ok).length
|
||||
const fail = results.filter(r => !r.ok)
|
||||
if (okCount) ElMessage.success(`已在 ${okCount} 台设备启动 TikTok`)
|
||||
if (fail.length) {
|
||||
const udids = fail.map(f => f.udid).join(', ')
|
||||
ElMessage.error(`以下设备未能启动:${udids}`)
|
||||
}
|
||||
}
|
||||
|
||||
function getMesList(deviceId) {
|
||||
getChatTextInfo({ udid: deviceId }).then((res) => {
|
||||
if (res) {
|
||||
@@ -1029,7 +702,7 @@ onMounted(async () => {
|
||||
|
||||
// 初始化时获取设备列表
|
||||
//每3秒获取一次设备列表 消息列表 和 查询主播列表是否还有主播
|
||||
getListtimer = setInterval(async () => {
|
||||
getListTimer.value = setInterval(async () => {
|
||||
getDeviceListFun() //获取设备列表
|
||||
selectLastFun() //获取新消息
|
||||
const hostsList = await getStoredHostList() //获取主播列表
|
||||
@@ -1061,8 +734,8 @@ onMounted(async () => {
|
||||
}).catch((err) => {
|
||||
if (err.code === 40400) {
|
||||
//关闭获取设备列表的方法
|
||||
clearInterval(getListtimer)
|
||||
getListtimer = null
|
||||
clearInterval(getListTimer.value)
|
||||
getListTimer.value = null
|
||||
//关闭获取网络状态的方法
|
||||
clearInterval(getNetworkListtimer)
|
||||
getNetworkListtimer = null
|
||||
@@ -1070,13 +743,12 @@ onMounted(async () => {
|
||||
|
||||
|
||||
})
|
||||
}, 1000 * 60 * 3)
|
||||
}, 1000 * 90)
|
||||
|
||||
|
||||
if (!await isAiConfig()) {
|
||||
showMyInfo.value = true
|
||||
}
|
||||
reconcileLoopsByDevices(deviceInformation.value || []);
|
||||
|
||||
function scheduleFlush(handler, delay = 400) {
|
||||
if (flushTimer) clearTimeout(flushTimer);
|
||||
@@ -1096,10 +768,8 @@ onMounted(async () => {
|
||||
|
||||
// —— SSE 接收 ——
|
||||
const es = connectSSE('http://localhost:3312/events', (data) => {
|
||||
// console.log('来自服务端:', data);
|
||||
// console.log(1)
|
||||
//总开关
|
||||
if (!sseEnabled.value) return
|
||||
// 所有 SSE 关掉的话,直接不处理
|
||||
if (!sseAnyEnabled.value) return
|
||||
|
||||
if (data === 'start') {
|
||||
// 新一波开始:清空上一波的缓冲,重置防抖
|
||||
@@ -1107,15 +777,49 @@ onMounted(async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 非 start:正常入缓冲 → 防抖批量 addTempAnchorData
|
||||
const country = data && data.country != null ? data.country : data.region
|
||||
const text = data && (data.hostsId != null ? data.hostsId : data.displayId)
|
||||
const invitationType = data && (data.invitationType != null ? data.invitationType : '')
|
||||
const id = data && data.id != null ? data.id : ''
|
||||
// 1️⃣ 判断来源:_mqMeta = 1(q.tenant.*) / 2(b.tenant.*)
|
||||
const metaCode = data && data._mqMeta
|
||||
const fromCrawler = metaCode === 1 || metaCode === '1' // 爬虫队列 q.tenant.*
|
||||
const fromBoss = metaCode === 2 || metaCode === '2' // 大哥队列 b.tenant.*
|
||||
|
||||
// 没有标记的老数据,一律按爬虫处理(可选)
|
||||
const isUnknown = !fromCrawler && !fromBoss
|
||||
|
||||
// 2️⃣ 按开关过滤
|
||||
if (fromCrawler && !sseCrawlerEnabled.value) return
|
||||
if (fromBoss && !sseBossEnabled.value) return
|
||||
if (isUnknown && !sseCrawlerEnabled.value) return // 老数据当爬虫看
|
||||
|
||||
// 3️⃣ 按来源取不同字段
|
||||
let country = ''
|
||||
let text = ''
|
||||
let invitationType = ''
|
||||
let id = ''
|
||||
|
||||
if (fromBoss) {
|
||||
// 大哥队列 b.tenant.* 进来的那条 JSON 结构:
|
||||
// {"id":5681,"displayId":"80",...,"region":"西南",...,"_mqMeta":2}
|
||||
country = data.region || ''
|
||||
// 主播ID 优先 displayId → hostDisplayId → userIdStr → userId
|
||||
text = data.displayId
|
||||
|| data.hostDisplayId
|
||||
|| data.userIdStr
|
||||
|| (data.userId != null ? String(data.userId) : '')
|
||||
invitationType = data.invitationType != null ? data.invitationType : 2 // 金票默认 2
|
||||
id = data.id != null ? data.id : ''
|
||||
} else {
|
||||
// 爬虫队列 q.tenant.*(以前的老逻辑)
|
||||
country = data && data.country != null ? data.country : ''
|
||||
text = data && (data.hostsId != null ? data.hostsId : data.text)
|
||||
invitationType = data && (data.invitationType != null ? data.invitationType : '')
|
||||
id = data && data.id != null ? data.id : ''
|
||||
}
|
||||
|
||||
if (!text) return
|
||||
// console.log(text)
|
||||
|
||||
batch.push({ country, text, invitationType, id })
|
||||
|
||||
//400ms内如果没有数据了 进行入库,如果400ms内进入了数据 重置计时器 一波一波入库
|
||||
scheduleFlush((items) => {
|
||||
// 批量入库
|
||||
const list = items.map(h => ({
|
||||
@@ -1133,39 +837,24 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
//关闭获取设备列表的方法
|
||||
clearInterval(getListtimer)
|
||||
getListtimer = null
|
||||
clearInterval(getListTimer.value)
|
||||
getListTimer.value = null
|
||||
//关闭获取网络状态的方法
|
||||
clearInterval(getNetworkListtimer)
|
||||
getNetworkListtimer = null
|
||||
|
||||
//关闭调度定时器
|
||||
if (scheduleTimer) {
|
||||
clearInterval(scheduleTimer)
|
||||
scheduleTimer = null
|
||||
}
|
||||
})
|
||||
|
||||
let isStartLac = false
|
||||
const getDeviceListFun = () => {
|
||||
getDeviceList().then((res) => {
|
||||
if (res && res.length > 0 && deviceInformation.value.length !== res.length) {
|
||||
console.log("设备变更")
|
||||
deviceInformation.value = res
|
||||
}
|
||||
|
||||
if (res.length == 0) {
|
||||
deviceInformation.value = []
|
||||
}
|
||||
}).catch((err) => {
|
||||
if (isStartLac) {
|
||||
ElMessage.error(`IOSAI服务错误`)
|
||||
// isStartLac = true
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
//获取新消息
|
||||
const selectLastFun = () => {
|
||||
@@ -1285,6 +974,7 @@ function runTask(key, deviceId, type) {
|
||||
|
||||
//第一个小时结束后,第二轮开始的时候,直接进入follow
|
||||
setTimeout(() => {
|
||||
scheduleEnabled.value = true
|
||||
runType.value = 'follow'
|
||||
//过滤无网络设备
|
||||
const allIds = deviceInformation.value.map(item => item.deviceId)
|
||||
@@ -1349,17 +1039,29 @@ function runTask(key, deviceId, type) {
|
||||
} else if (key === 'like') {
|
||||
|
||||
if (!deviceId) {
|
||||
await stopAll(2000)
|
||||
if (scheduleEnabled.value) {
|
||||
await stopAll(2000)
|
||||
setTimeout(() => {
|
||||
scheduleEnabled.value = true
|
||||
runType.value = 'like'
|
||||
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId, comment: getContentList(), isComment: common.value }))
|
||||
}, 1000)
|
||||
} else {
|
||||
dialogTitle.value = '视频评论';
|
||||
setTimeout(() => {
|
||||
showDialog.value = true;
|
||||
|
||||
initialTextStr.value = getContentListMultiline();
|
||||
}, 500)
|
||||
}
|
||||
|
||||
} else {
|
||||
growAccount({ udid: deviceId, comment: getContentList(), isComment: common.value })
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
scheduleEnabled.value = true
|
||||
runType.value = 'like'
|
||||
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId, comment: getContentList(), isComment: common.value }))
|
||||
}, 1000)
|
||||
|
||||
|
||||
|
||||
} else if (key === 'brushLive') {
|
||||
if (!deviceId) {
|
||||
@@ -1394,10 +1096,6 @@ function runTask(key, deviceId, type) {
|
||||
})
|
||||
}
|
||||
|
||||
async function stopCurrentMode() {
|
||||
// 如果你希望“只停当前片段的设备”,也可以用 stopScript 针对设备循环
|
||||
await stopAll(2000)
|
||||
}
|
||||
|
||||
/** 恢复:回到 scheduleState.index 对应片段,并让 startTime 回到“暂停前进度” */
|
||||
function resumeAfterInterrupt() {
|
||||
@@ -1629,20 +1327,14 @@ async function doLogout() {
|
||||
dropCurrentWave()
|
||||
// es?.close?.() // 如果 connectSSE 返回 EventSource,调用 close
|
||||
refreshAllStopImgs() // 你已有:把所有 <img> src 清空
|
||||
clearInterval(getListtimer)
|
||||
getListtimer = null
|
||||
clearInterval(getListTimer.value)
|
||||
getListTimer.value = null
|
||||
await logout({ userId: userdata.id, tenantId: userdata.tenantId })
|
||||
} finally {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
function stopOne(deviceId) {
|
||||
|
||||
stopScript({ udid: deviceId }).then((res) => {
|
||||
// ElMessage.success('停止成功');
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
//查看主播库主播信息
|
||||
async function getStoredHostList() {
|
||||
@@ -1795,7 +1487,7 @@ async function refreshNetStatus() {
|
||||
const res = await getDeviceNetStatus({ udid: d.deviceId })
|
||||
firstPass[d.deviceId] = (res === true)
|
||||
} catch (err) {
|
||||
firstPass[d.deviceId] = false
|
||||
firstPass[d.deviceId] = true
|
||||
}
|
||||
|
||||
// 不是最后一台才等待 5s
|
||||
@@ -1888,7 +1580,7 @@ async function safeGetStatus(udid) {
|
||||
try {
|
||||
return await getDeviceNetStatus({ udid }) === true
|
||||
} catch {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1918,6 +1610,13 @@ const markPendingForOffline = (ids) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onToggleCrawler = (val) => {
|
||||
if (val) sseBossEnabled.value = false
|
||||
}
|
||||
const onToggleBoss = (val) => {
|
||||
if (val) sseCrawlerEnabled.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
Reference in New Issue
Block a user