爬虫联动弹窗版本备份
This commit is contained in:
@@ -4,8 +4,8 @@ VUE_APP_BASE_LOCAL=http://127.0.0.1:34567/
|
||||
# VUE_APP_BASE_LOCAL=http://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
|
||||
@@ -23,7 +23,7 @@ export function prologue() {
|
||||
export function comment() {
|
||||
return getAxios({ url: 'api/common/comment' })
|
||||
}
|
||||
//获取评论
|
||||
//登出
|
||||
export function logout(data) {
|
||||
return postAxios({ url: 'api/user/aiChat-logout', data })
|
||||
}
|
||||
|
||||
@@ -72,7 +72,28 @@ export function anchorList(data) {
|
||||
export function deleteAnchorWithIds(data) {
|
||||
return postAxios({ url: 'deleteAnchorWithIds', data })
|
||||
}
|
||||
//设置主播列表
|
||||
export function updateAnchorList(data) {
|
||||
return postAxios({ url: 'updateAnchorList', data })
|
||||
}
|
||||
|
||||
//设置经纪人信息
|
||||
export function aiConfig(data) {
|
||||
return postAxios({ url: 'aiConfig', data })
|
||||
}
|
||||
}
|
||||
//获取消息列表
|
||||
export function selectLast(data) {
|
||||
return getAxios({ url: 'select_last_message', data })
|
||||
}
|
||||
//获更新消息列表
|
||||
export function updatelast(data) {
|
||||
return postAxios({ url: 'update_last_message', data })
|
||||
}
|
||||
//删除消息列表
|
||||
export function deleteLast(data) {
|
||||
return postAxios({ url: 'delete_last_message', data })
|
||||
}
|
||||
//全部停止
|
||||
export function stopAllTask(data) {
|
||||
return postAxios({ url: 'stopAllTask', data })
|
||||
}
|
||||
|
||||
BIN
src/assets/mes.wav
Normal file
BIN
src/assets/mes.wav
Normal file
Binary file not shown.
@@ -2,14 +2,14 @@
|
||||
<div v-if="visible" class="dialog-overlay" @click.self="close">
|
||||
<div class="dialog-content">
|
||||
<h3 class="text-lg font-bold mb-4">
|
||||
<img style="margin: 0px 15px;" src="@/assets/video/chatMes.png"></img>
|
||||
消息翻译内容
|
||||
聊天翻译内容
|
||||
</h3>
|
||||
<el-scrollbar class="chat-box">
|
||||
<el-scrollbar class="chat-box" ref="scrollbarRef">
|
||||
<div v-for="(msg, index) in messages.filter(m => m.type !== 'time')" :key="index"
|
||||
:class="msg.dir === 'in' ? 'left-message' : 'right-message'">
|
||||
<div @click="fallbackCopyTextToClipboard(index, msg.text)"
|
||||
:class="['bubble', msg.dir, { 'active': activeIndex === index }]">{{ msg.text }}
|
||||
:class="['bubble', msg.dir, { 'active': activeIndex === index }]">
|
||||
{{ msg.text }}
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@@ -18,7 +18,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, ref } from 'vue'
|
||||
import { defineProps, defineEmits, ref, watch, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -31,36 +31,48 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const close = () => emit('close')
|
||||
|
||||
let activeIndex = ref(null);
|
||||
let activeIndex = ref(null)
|
||||
const scrollbarRef = ref(null) // 引用 el-scrollbar
|
||||
|
||||
// 兜底方案:传统复制方法
|
||||
// 兜底复制
|
||||
function fallbackCopyTextToClipboard(index, text) {
|
||||
activeIndex.value = index;
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
textArea.style.top = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
activeIndex.value = index
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = text
|
||||
textArea.style.position = 'fixed'
|
||||
textArea.style.left = '-999999px'
|
||||
textArea.style.top = '-999999px'
|
||||
document.body.appendChild(textArea)
|
||||
textArea.focus()
|
||||
textArea.select()
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
const successful = document.execCommand('copy')
|
||||
if (successful) {
|
||||
ElMessage.success('复制成功');
|
||||
ElMessage.success('复制成功')
|
||||
} else {
|
||||
ElMessage.error('复制失败1');
|
||||
ElMessage.error('复制失败1')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('复制失败2');
|
||||
ElMessage.error('复制失败2')
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
document.body.removeChild(textArea)
|
||||
}
|
||||
|
||||
// === 自动滚动到底部 ===
|
||||
watch(
|
||||
() => props.messages,
|
||||
async () => {
|
||||
await nextTick()
|
||||
const wrap = scrollbarRef.value?.wrapRef // el-scrollbar 内部的原生容器
|
||||
if (wrap) {
|
||||
wrap.scrollTop = wrap.scrollHeight
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
<el-button size="small" @click="selectNone">全不选</el-button>
|
||||
<el-button size="small" @click="invertSelect">反选</el-button>
|
||||
<el-button size="small" type="danger" :disabled="!selectedCount" @click="deleteSelected">删除选中</el-button>
|
||||
|
||||
<el-switch v-model="gold" :loading="goldLoading" :before-change="goldBeforeChange" inline-prompt
|
||||
active-text="金票" inactive-text="金票" size="large" style="--el-switch-on-color: #db9600; " />
|
||||
<el-switch v-model="ordinary" :loading="ordinaryLoading" :before-change="ordinaryBeforeChange" inline-prompt
|
||||
inactive-text="普票" active-text="普票" size="large" />
|
||||
<!-- ✅ 新增:一键删除已处理 -->
|
||||
<!-- <el-button size="small" type="warning" :disabled="!processedCount" @click="deleteProcessed">
|
||||
删除已处理
|
||||
@@ -36,11 +39,15 @@
|
||||
:ref="el => setCardRef(it.anchorId, el)" @click.stop="toggleSelect(it.anchorId)">
|
||||
<div class="row top">
|
||||
<span class="id" :title="it.anchorId">{{ it.anchorId }}</span>
|
||||
<button class="x" title="删除此项" @click.stop="deleteOne(it.anchorId)">×</button>
|
||||
<button v-if="it.state == 0" class="x" title="不执行">X</button>
|
||||
<button v-else class="y" title="执行">√</button>
|
||||
|
||||
</div>
|
||||
<div class="row meta">
|
||||
<span class="country" :title="it.country">{{ it.country || '—' }}</span>
|
||||
<span class="state" :class="{ done: !!it.state }">{{ it.state ? '已处理' : '未处理' }}</span>
|
||||
<span class="state" :class="{ done: it.invitationType == 2 }">{{ it.invitationType == 2 ? '金票' :
|
||||
'普票'
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,13 +68,13 @@
|
||||
import { ref, reactive, computed, watch, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getHostList, setHostList } from '@/stores/storage'
|
||||
import { anchorList, deleteAnchorWithIds } from '@/api/ios'
|
||||
import { anchorList, deleteAnchorWithIds, updateAnchorList } from '@/api/ios'
|
||||
// v-model:visible 接口
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'save'])
|
||||
const emit = defineEmits(['update:visible', 'save', 'invitType'])
|
||||
const show = computed({
|
||||
get: () => props.visible,
|
||||
set: (v) => emit('update:visible', v)
|
||||
@@ -77,6 +84,14 @@ const show = computed({
|
||||
const hosts = ref([]) // {country, text, state}
|
||||
const selected = reactive(new Set()) // 选中的 text 集合
|
||||
|
||||
//金票
|
||||
let gold = ref(true)
|
||||
let goldLoading = ref(false)
|
||||
|
||||
//普票
|
||||
let ordinary = ref(true)
|
||||
let ordinaryLoading = ref(false)
|
||||
|
||||
// 卡片 DOM 引用与位置缓存
|
||||
const gridRef = ref(null)
|
||||
const cardRefs = reactive({}) // text -> el
|
||||
@@ -102,6 +117,7 @@ async function getStoredHostList() {
|
||||
|
||||
async function onOpen() {
|
||||
hosts.value = await getStoredHostList()
|
||||
console.log(hosts.value)
|
||||
selected.clear()
|
||||
await nextTick()
|
||||
recalcRects()
|
||||
@@ -261,6 +277,51 @@ function deleteProcessed() {
|
||||
.catch(() => { })
|
||||
}
|
||||
|
||||
|
||||
const ordinaryBeforeChange = () => {
|
||||
const next = !ordinary.value // 目标值(切换后)
|
||||
ordinaryLoading.value = true
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
updateAnchorList({ invitationType: 1, state: next ? 1 : 0 })
|
||||
.then(async () => {
|
||||
ordinaryLoading.value = false
|
||||
hosts.value = await getStoredHostList()
|
||||
emit('invitType', 'ordinary', next) // 把“切换后的目标布尔”抛给父组件
|
||||
resolve(true) // 允许切换
|
||||
})
|
||||
.catch(error => {
|
||||
ordinaryLoading.value = false
|
||||
ElMessage.error('Operation failed: ' + error.message)
|
||||
resolve(false) // 阻止切换
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const goldBeforeChange = () => {
|
||||
console.log(ordinary.value, gold.value)
|
||||
const next = !gold.value
|
||||
goldLoading.value = true
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
updateAnchorList({ invitationType: 2, state: next ? 1 : 0 })
|
||||
.then(async () => {
|
||||
goldLoading.value = false
|
||||
hosts.value = await getStoredHostList()
|
||||
emit('invitType', 'gold', next)
|
||||
resolve(true)
|
||||
})
|
||||
.catch(error => {
|
||||
goldLoading.value = false
|
||||
ElMessage.error('Operation failed: ' + error.message)
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
watch(hosts, () => nextTick().then(recalcRects))
|
||||
</script>
|
||||
|
||||
@@ -354,6 +415,15 @@ watch(hosts, () => nextTick().then(recalcRects))
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-card .y {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #00a316;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-card .x:hover {
|
||||
color: #f33;
|
||||
}
|
||||
@@ -370,8 +440,8 @@ watch(hosts, () => nextTick().then(recalcRects))
|
||||
}
|
||||
|
||||
.item-card .state.done {
|
||||
color: #67c23a;
|
||||
border-color: #67c23a;
|
||||
color: #db9600;
|
||||
border-color: #db9600;
|
||||
}
|
||||
|
||||
.selection-rect {
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
<template>
|
||||
<div v-if="visible" class="dialog-overlay" @click.self="close">
|
||||
<div class="dialog-content">
|
||||
<h3 class="text-lg font-bold mb-4">
|
||||
<img style="margin: 0 15px;" src="@/assets/video/chatMes.png" />
|
||||
新消息提醒
|
||||
<!-- 右上角:一键已读 + 静音按钮 + 未读徽标 -->
|
||||
<div class="mark-all">
|
||||
|
||||
<button v-if="unreadCount > 0" class="mark-all-btn" @click.stop="markAllSeen" title="一键已读">一键已读</button>
|
||||
<button class="mark-all-btn2" @click.stop="delAllSeen" title="删除已读">删除已读</button>
|
||||
|
||||
<button class="mute-btn" @click.stop="toggleMute" :title="muted ? '取消静音' : '静音'">
|
||||
<span v-if="muted">🔕</span>
|
||||
<span v-else>🔔</span>
|
||||
</button>
|
||||
</div>
|
||||
<span v-if="unreadCount > 0" class="unread-badge">{{ unreadCount }}</span>
|
||||
|
||||
<h3 style="display: flex;" class="text-lg font-bold mb-4 ">
|
||||
<img style="margin: 0 2px;" src="@/assets/video/chatMes.png" />
|
||||
<div>新消息提醒</div>
|
||||
</h3>
|
||||
|
||||
<el-scrollbar class="chat-box">
|
||||
@@ -20,6 +33,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<!-- 可选:自定义提示音(静音时也会被静音) -->
|
||||
<audio v-if="soundSrc" ref="audioRef" :src="soundSrc" preload="auto" :muted="muted"></audio>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -27,34 +43,25 @@
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, ref, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { updatelast, deleteLast } from '@/api/ios'
|
||||
const deleting = ref(false) // 防抖:删除过程中禁用按钮(可选)
|
||||
|
||||
const props = defineProps({
|
||||
visible: Boolean,
|
||||
messages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
// 可自定义颜色
|
||||
reminderColor: {
|
||||
type: String,
|
||||
// Element Plus 警告浅色(看起来像“提醒”)
|
||||
default: 'var(--el-color-warning-light-5, #ffe58f)' // 更深一点
|
||||
},
|
||||
seenColor: {
|
||||
type: String,
|
||||
// EP 填充浅色(更“平静”的已读态)
|
||||
default: 'var(--el-fill-color-light, #f2f3f5)'
|
||||
}
|
||||
messages: { type: Array, required: true, default: () => [] },
|
||||
reminderColor: { type: String, default: 'var(--el-color-warning-light-5, #ffe58f)' },
|
||||
seenColor: { type: String, default: 'var(--el-fill-color-light, #f2f3f5)' },
|
||||
soundSrc: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'seen'])
|
||||
const emit = defineEmits(['close', 'seen', 'delete-read'])
|
||||
const close = () => emit('close')
|
||||
|
||||
// 记录哪些消息已读(用原始索引做 key,避免过滤后索引错位)
|
||||
const seenSet = ref(new Set())
|
||||
// —— 静音 —— //
|
||||
const muted = ref(false)
|
||||
function toggleMute() { muted.value = !muted.value }
|
||||
|
||||
// 规范化并带上原始索引,过滤掉 type === 'time'
|
||||
// 过滤 type==='time',并保留原始索引;容错旧字段
|
||||
const normalizedMessages = computed(() => {
|
||||
const res = []
|
||||
; (props.messages || []).forEach((m, i) => {
|
||||
@@ -64,33 +71,27 @@ const normalizedMessages = computed(() => {
|
||||
raw: m,
|
||||
sender: m?.sender ?? m?.name ?? m?.user ?? m?.from ?? '',
|
||||
device: m?.device ?? m?.deviceName ?? m?.udid ?? '',
|
||||
text: m?.text ?? m?.content ?? ''
|
||||
text: m?.text ?? m?.content ?? '',
|
||||
// 统一转成 0/1,兼容旧的 seen/read 布尔
|
||||
status: Number(
|
||||
m?.status ?? ((m?.seen === true || m?.read === true) ? 1 : 0)
|
||||
)
|
||||
})
|
||||
})
|
||||
return res
|
||||
})
|
||||
|
||||
// 初始化:如果消息本身标注了 seen/read,则置为已读
|
||||
watch(
|
||||
() => props.messages,
|
||||
(list) => {
|
||||
const s = new Set()
|
||||
list?.forEach((m, i) => {
|
||||
if (m && (m.seen === true || m.read === true)) s.add(i)
|
||||
})
|
||||
seenSet.value = s
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// —— 基于 status 判断 —— //
|
||||
function isSeen(item) {
|
||||
return seenSet.value.has(item.origIndex)
|
||||
// 优先读 item.raw.status,其次读规范化的 item.status
|
||||
return Number(item?.raw?.status ?? item?.status ?? 0) === 1
|
||||
}
|
||||
|
||||
function markSeen(item) {
|
||||
if (!isSeen(item)) {
|
||||
seenSet.value.add(item.origIndex)
|
||||
// 通知父组件(可选):你也可以在父里把 messages[item.origIndex].seen = true 持久化
|
||||
// 标记为已读
|
||||
item.raw.status = 1
|
||||
try { updatelast(item.raw) } catch (_) { }
|
||||
emit('seen', { index: item.origIndex, message: item.raw })
|
||||
}
|
||||
}
|
||||
@@ -102,36 +103,112 @@ function bubbleStyle(item) {
|
||||
}
|
||||
}
|
||||
|
||||
// 点击:标记已读 + 复制文本
|
||||
function onClickMessage(item) {
|
||||
markSeen(item)
|
||||
fallbackCopyTextToClipboard(item.text)
|
||||
// —— 未读数量(status === 0)—— //
|
||||
const unreadCount = computed(() =>
|
||||
normalizedMessages.value.reduce((acc, it) => acc + (isSeen(it) ? 0 : 1), 0)
|
||||
)
|
||||
|
||||
// —— 一键已读:把所有 status=0 的设为 1 —— //
|
||||
async function markAllSeen() {
|
||||
let changed = 0
|
||||
for (const it of normalizedMessages.value) {
|
||||
if (!isSeen(it)) {
|
||||
it.raw.status = 1
|
||||
try { await updatelast(it.raw) } catch (_) { }
|
||||
emit('seen', { index: it.origIndex, message: it.raw })
|
||||
changed++
|
||||
}
|
||||
}
|
||||
if (changed > 0) ElMessage.success(`已标记 ${changed} 条为已读`)
|
||||
else ElMessage.info('没有未读可标记')
|
||||
}
|
||||
|
||||
// 兜底复制
|
||||
function fallbackCopyTextToClipboard(text) {
|
||||
// const textArea = document.createElement('textarea')
|
||||
// textArea.value = text || ''
|
||||
// textArea.style.position = 'fixed'
|
||||
// textArea.style.left = '-999999px'
|
||||
// textArea.style.top = '-999999px'
|
||||
// document.body.appendChild(textArea)
|
||||
// textArea.focus()
|
||||
// textArea.select()
|
||||
|
||||
try {
|
||||
// const ok = document.execCommand('copy')
|
||||
// ok ? ElMessage.success('已读') : ElMessage.error('复制失败1')
|
||||
ElMessage.success('已读')
|
||||
} catch (err) {
|
||||
// ElMessage.error('复制失败2')
|
||||
} finally {
|
||||
// document.body.removeChild(textArea)
|
||||
// —— 删除已读:把所有 status=1 的抛给父组件处理 —— //
|
||||
// 子组件不直接改 props.messages,向上抛事件让父组件过滤
|
||||
// —— 一键删除(删除所有 status=1)—— //
|
||||
async function delAllSeen() {
|
||||
// 先拍一张快照,避免删除过程中索引变化
|
||||
const readItems = normalizedMessages.value.filter(it => isSeen(it))
|
||||
if (readItems.length === 0) {
|
||||
ElMessage.info('没有已读可删除')
|
||||
return
|
||||
}
|
||||
|
||||
if (deleting.value) return
|
||||
deleting.value = true
|
||||
try {
|
||||
// 并发删除(每条把“已读的消息体”传给后端)
|
||||
const results = await Promise.allSettled(
|
||||
readItems.map(it => deleteLast(it.raw))
|
||||
)
|
||||
|
||||
// 统计删除成功/失败
|
||||
const okIndexes = []
|
||||
const okMessages = []
|
||||
let ok = 0, fail = 0
|
||||
results.forEach((r, i) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
ok++
|
||||
okIndexes.push(readItems[i].origIndex)
|
||||
okMessages.push(readItems[i].raw)
|
||||
} else {
|
||||
fail++
|
||||
console.warn('[deleteLast] 失败:', readItems[i].raw, r.reason)
|
||||
}
|
||||
})
|
||||
|
||||
// 通知父组件把成功删除的从列表移除
|
||||
if (okIndexes.length > 0) {
|
||||
emit('delete-read', { indexes: okIndexes, messages: okMessages })
|
||||
}
|
||||
|
||||
if (ok > 0) ElMessage.success(`已删除 ${ok} 条${fail ? `,失败 ${fail} 条` : ''}`)
|
||||
else ElMessage.error('删除失败')
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// —— 提示音 —— //
|
||||
const audioRef = ref(null)
|
||||
function playNotice() {
|
||||
if (muted.value) return
|
||||
if (props.soundSrc && audioRef.value) {
|
||||
audioRef.value.currentTime = 0
|
||||
audioRef.value.play().catch(() => { })
|
||||
} else {
|
||||
try {
|
||||
const Ctx = window.AudioContext || window.webkitAudioContext
|
||||
if (!Ctx) return
|
||||
const ctx = new Ctx()
|
||||
const o = ctx.createOscillator()
|
||||
const g = ctx.createGain()
|
||||
o.type = 'sine'; o.frequency.value = 880
|
||||
o.connect(g); g.connect(ctx.destination)
|
||||
const t0 = ctx.currentTime
|
||||
g.gain.setValueAtTime(0.0001, t0)
|
||||
g.gain.exponentialRampToValueAtTime(0.12, t0 + 0.02)
|
||||
g.gain.exponentialRampToValueAtTime(0.00001, t0 + 0.28)
|
||||
o.start(t0); o.stop(t0 + 0.3)
|
||||
o.onended = () => ctx.close().catch(() => { })
|
||||
} catch (_) { }
|
||||
}
|
||||
}
|
||||
|
||||
// 未读数增加时才提示
|
||||
watch(unreadCount, (now, old) => {
|
||||
if (old !== undefined && now > old) playNotice()
|
||||
})
|
||||
|
||||
// 点击条目 => 单条已读
|
||||
function onClickMessage(item) {
|
||||
markSeen(item)
|
||||
try { ElMessage.success('已读') } catch (_) { }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
<style scoped lang="less">
|
||||
.dialog-overlay {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -139,6 +216,7 @@ function fallbackCopyTextToClipboard(text) {
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
position: relative;
|
||||
margin-top: 3vh;
|
||||
background: rgb(246, 246, 246);
|
||||
padding: 0 20px 20px 20px;
|
||||
@@ -146,6 +224,91 @@ function fallbackCopyTextToClipboard(text) {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.mark-all {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 5px;
|
||||
|
||||
button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 一键已读按钮(靠右,位于静音按钮左侧) */
|
||||
.mark-all-btn {
|
||||
|
||||
/* 未读徽标(10) + 静音按钮(28+间距) 预留 */
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: none;
|
||||
background: var(--el-color-primary, #409eff);
|
||||
color: #fff;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mark-all-btn2 {
|
||||
|
||||
/* 未读徽标(10) + 静音按钮(28+间距) 预留 */
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: none;
|
||||
background: #ff4040;
|
||||
color: #fff;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mark-all-btn:hover {
|
||||
filter: brightness(0.98);
|
||||
}
|
||||
|
||||
/* 静音按钮 */
|
||||
.mute-btn {
|
||||
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
border: none;
|
||||
background: var(--el-fill-color, #f5f7fa);
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
|
||||
cursor: pointer;
|
||||
/* display: grid; */
|
||||
place-items: center;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.mute-btn:hover {
|
||||
filter: brightness(0.98);
|
||||
}
|
||||
|
||||
/* 未读徽标 */
|
||||
.unread-badge {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
min-width: 22px;
|
||||
height: 22px;
|
||||
padding: 0 6px;
|
||||
background: var(--el-color-danger, #f56c6c);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
border-radius: 9999px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, .12);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -190,7 +353,7 @@ function fallbackCopyTextToClipboard(text) {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 颜色通过内联样式控制(.unread/.seen 仅用于语义钩子) */
|
||||
/* 颜色通过内联样式控制(.unread/.seen 仅语义钩子) */
|
||||
.bubble.unread {}
|
||||
|
||||
.bubble.seen {}
|
||||
|
||||
@@ -47,7 +47,7 @@ function attachInterceptors(instance) {
|
||||
}
|
||||
|
||||
// 业务失败:提示 + reject
|
||||
const msg = `${data?.code ?? ''} ${data?.msg ?? '请求失败'}`
|
||||
const msg = `${data?.code ?? ''} ${data?.message ?? '请求失败'}`
|
||||
ElMessage.error(msg)
|
||||
|
||||
const err = new Error(msg)
|
||||
|
||||
@@ -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('1.1.4');
|
||||
let version = ref('1.5.4');
|
||||
onMounted(() => {
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
<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
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
})">新增</el-button> -->
|
||||
</div>
|
||||
|
||||
<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"
|
||||
@@ -16,15 +24,21 @@
|
||||
<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>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<!-- 中间手机区域 -->
|
||||
<div class="content" @click.self="selectedDevice = 999">
|
||||
<div v-if="isImg" class="video-container" v-for="(device, index) in deviceInformation" :key="device.deviceId">
|
||||
<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)">
|
||||
<img class="stream" :src="'http://localhost:' + device.screenPort" />
|
||||
<img class="stream" :key="device.deviceId + '-' + imgKeyTick"
|
||||
:src="`http://localhost:${device.screenPort}/?t=${Date.now() || 0}`" :data-id="device.deviceId"
|
||||
:ref="el => (imgRefs[device.deviceId] = el)" />
|
||||
|
||||
<canvas v-show="selectedDevice === index" class="overlay"
|
||||
@mousedown.stop="(e) => onCanvasDown(device.deviceId, e, index)"
|
||||
@mouseup.stop="(e) => onCanvasUp(device.deviceId, e, index)"
|
||||
@@ -61,11 +75,11 @@
|
||||
<div class="right center-line" @click.self="selectedDevice = 999">
|
||||
<!-- <div style="margin: 30px;"></div> -->
|
||||
<ChatDialog :visible="openShowChat" :messages="chatList" />
|
||||
<MessageDialogd :visible="openShowChat" :messages="MesNewList" />
|
||||
<MessageDialogd :visible="openShowChat" :messages="MesNewList" :sound-src="ding" />
|
||||
</div>
|
||||
<MultiLineInputDialog v-model:visible="showDialog" :initialText='""' :title="dialogTitle" :index="selectedDevice"
|
||||
@confirm="onDialogConfirm" @cancel="stopAll" />
|
||||
<HostListManagerDialog v-model:visible="showHostDlg" @save="onHostSaved" />
|
||||
<HostListManagerDialog v-model:visible="showHostDlg" @save="onHostSaved" @invitType="invitTypeFun" />
|
||||
|
||||
</div>
|
||||
<!-- <AgentGuildDialog v-model="showMyInfo" :model="formInit" @save="handleSave" /> -->
|
||||
@@ -120,7 +134,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onUnmounted, onBeforeUnmount, watch, inject, computed } from "vue";
|
||||
import { ref, reactive, onMounted, onUnmounted, onBeforeUnmount, watch, inject, computed, nextTick } from "vue";
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
setphoneXYinfo, getphoneXYinfo, getUser,
|
||||
@@ -155,11 +169,14 @@ import {
|
||||
getChatTextInfo,
|
||||
setLoginInfo,
|
||||
aiConfig,
|
||||
|
||||
selectLast,
|
||||
updatelast,
|
||||
stopAllTask
|
||||
} from '@/api/ios';
|
||||
import ding from '@/assets/mes.wav'
|
||||
import { set } from "lodash";
|
||||
const router = useRouter();
|
||||
const openShowChat = ref([true])
|
||||
const openShowChat = ref(true)
|
||||
//主播库
|
||||
const showHostDlg = ref(false)
|
||||
//ai人设
|
||||
@@ -172,66 +189,22 @@ const borkerConfig = reactive({
|
||||
contact: ''
|
||||
})
|
||||
|
||||
// 批次缓冲(仅用于当前“波”)
|
||||
let batch = []; // [{ country, text }]
|
||||
let flushTimer = null;
|
||||
|
||||
let hostList = []
|
||||
//查询列表轮询
|
||||
let getListtimer = null;
|
||||
let userdata = getUser();
|
||||
let chatList = ref([])
|
||||
|
||||
let MesNewList = ref([{
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
}, {
|
||||
sender: 'Alice', // 或 name/user/from
|
||||
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||
text: '你好呀~', // 或 content
|
||||
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||
},])
|
||||
let isImg = ref(true)
|
||||
let MesNewList = ref([])
|
||||
|
||||
// 引入刷新方法
|
||||
const reload = inject("reload")
|
||||
const reloadImg = () => {
|
||||
isImg.value = false
|
||||
setTimeout(() => {
|
||||
isImg.value = true
|
||||
}, 1000)
|
||||
refreshAllImgs()
|
||||
}
|
||||
//start弹窗
|
||||
let isMsgPop = ref(false)
|
||||
@@ -252,6 +225,12 @@ let stopLoading = null
|
||||
// 当前是否被其它模式占用(四个互斥按钮专用)
|
||||
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))
|
||||
})
|
||||
|
||||
// 互斥按钮的样式:激活=红,锁定=半透明且禁点
|
||||
const ctrlStyle = (type) => ({
|
||||
backgroundColor: runType.value === type ? 'red' : '',
|
||||
@@ -421,7 +400,9 @@ const buttons = [
|
||||
{
|
||||
label: '登出',
|
||||
onClick: () => {
|
||||
refreshAllStopImgs() // 停止所有视频流
|
||||
logout({ userId: userdata.id, tenantId: userdata.tenantId })
|
||||
|
||||
router.push('/')
|
||||
|
||||
},
|
||||
@@ -502,6 +483,51 @@ function saveSchedule() {
|
||||
|
||||
const selectedDevice = ref(null)
|
||||
|
||||
|
||||
// 每台设备的 <img> 引用
|
||||
const imgRefs = ref({}) // { [id]: HTMLImageElement }
|
||||
const imgTs = ref({}) // { [id]: number } // cache-bust
|
||||
const imgKeyTick = ref(0) // 强制重建 <img>
|
||||
|
||||
function refreshOneImg(id, port) {
|
||||
const el = imgRefs.value[id]
|
||||
console.log("终止", id)
|
||||
|
||||
if (el) {
|
||||
// 硬中断旧请求
|
||||
el.src = ''
|
||||
el.removeAttribute('src')
|
||||
}
|
||||
// 下一拍再重建
|
||||
nextTick(() => {
|
||||
imgTs.value[id] = Date.now()
|
||||
imgKeyTick.value++ // 触发 key 变化 -> 销毁并新建 <img>
|
||||
})
|
||||
}
|
||||
|
||||
// —— 关键:登出/离开时“批量硬中断”所有图片流 ——
|
||||
async function hardStopAllImgStreams(id, port) {
|
||||
const el = imgRefs.value[id]
|
||||
console.log("终止", id)
|
||||
|
||||
if (el) {
|
||||
// 硬中断旧请求
|
||||
el.src = ''
|
||||
el.removeAttribute('src')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function refreshAllImgs() {
|
||||
Object.keys(imgRefs.value).forEach(id => refreshOneImg(id))
|
||||
}
|
||||
function refreshAllStopImgs() {
|
||||
Object.keys(imgRefs.value).forEach(id => hardStopAllImgStreams(id))
|
||||
}
|
||||
|
||||
|
||||
// —— 显示尺寸固定为 320x720;未选中缩略为 THUMB_SCALE 倍 ——
|
||||
// 尺寸与排布
|
||||
const BASE_W = 320
|
||||
@@ -725,29 +751,25 @@ async function stopAll() {
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
});
|
||||
// if (!runType.value) return
|
||||
// 所有操作完成后执行以下代码
|
||||
scheduleEnabled.value = false
|
||||
runType.value = ''
|
||||
batchMode.value = 'init' // 初始化状态 关注状态
|
||||
isMsgPop.value = false; //弹窗状态(不是弹窗)
|
||||
dropCurrentWave() // 丢弃当前波的残留缓冲
|
||||
|
||||
try {
|
||||
// 使用 Promise.all 并行处理所有设备的停止操作
|
||||
await Promise.all(deviceInformation.value.map(async (item) => {
|
||||
try {
|
||||
const res = await stopScript({ udid: item.deviceId })
|
||||
console.log(`停止成功:${item.deviceId}`, res, printCurrentTime())
|
||||
ElMessage.success(`停止成功:${item.deviceId}`)
|
||||
stopLoading.close()
|
||||
} catch (error) {
|
||||
console.log(`停止失败`, printCurrentTime())
|
||||
ElMessage.error(`脚本已停止`)
|
||||
stopLoading.close()
|
||||
}
|
||||
}))
|
||||
|
||||
// 所有操作完成后执行以下代码
|
||||
scheduleEnabled.value = false
|
||||
runType.value = ''
|
||||
batchMode.value = 'init'
|
||||
const res = await stopAllTask(deviceInformation.value.map((item) => item.deviceId))
|
||||
console.log(`全部停止成功`, printCurrentTime())
|
||||
ElMessage.success(`全部停止成功`)
|
||||
stopLoading.close()
|
||||
} catch (error) {
|
||||
console.error('批量停止过程中发生错误:', error)
|
||||
console.log(`停止失败`, printCurrentTime())
|
||||
ElMessage.error(`脚本已停止`)
|
||||
stopLoading.close()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//确认多行文本框内容
|
||||
@@ -763,12 +785,15 @@ function onDialogConfirm(result, type, index, isMon) {
|
||||
} else if (type == '私信') {
|
||||
runType.value = 'follow'
|
||||
setContentpriList(result)
|
||||
console.log('hostList', hostList)
|
||||
passAnchorData(
|
||||
{
|
||||
deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||
anchorList: hostList.map(item => ({
|
||||
anchorId: item.id,
|
||||
country: item.country
|
||||
country: item.country,
|
||||
invitationType: item.invitationType,
|
||||
state: stateByInvType(item.invitationType),
|
||||
})),
|
||||
prologueList: result,
|
||||
needReply: isMon
|
||||
@@ -783,7 +808,7 @@ function onDialogConfirm(result, type, index, isMon) {
|
||||
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: 'Loading',
|
||||
@@ -795,14 +820,16 @@ onMounted(() => {
|
||||
getListtimer = setInterval(() => {
|
||||
loading.close();
|
||||
getDeviceListFun()
|
||||
selectLastFun()
|
||||
|
||||
|
||||
}, 3000)
|
||||
if (!await isAiConfig()) {
|
||||
showMyInfo.value = true
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 批次缓冲(仅用于当前“波”)
|
||||
let batch = []; // [{ country, text }]
|
||||
let flushTimer = null;
|
||||
|
||||
function scheduleFlush(handler, delay = 400) {
|
||||
if (flushTimer) clearTimeout(flushTimer);
|
||||
flushTimer = setTimeout(() => {
|
||||
@@ -821,11 +848,26 @@ onMounted(() => {
|
||||
|
||||
// —— SSE 接收 ——
|
||||
const es = connectSSE('http://localhost:3312/events', (data) => {
|
||||
console.log('来自服务端:', data);
|
||||
// console.log('来自服务端:', data);
|
||||
// console.log(1)
|
||||
//总开关
|
||||
// 总开关关闭:不弹窗、丢弃所有消息
|
||||
if (!sseEnabled.value) {
|
||||
if (data === 'start') {
|
||||
// 开始一个新波时顺便清空(保险起见)
|
||||
dropCurrentWave()
|
||||
}
|
||||
return // 其余任何数据也直接丢弃
|
||||
}
|
||||
|
||||
|
||||
if (data === 'start') {
|
||||
console.log(2)
|
||||
|
||||
// 新一波开始:根据当前状态决定“本波 flush 用谁”
|
||||
if (!isMsgPop.value) {
|
||||
console.log(3)
|
||||
|
||||
// 还没开始过 -> 首次弹框,确认后使用处理本波
|
||||
isMsgPop.value = true;
|
||||
batchMode.value = 'init';
|
||||
@@ -841,7 +883,8 @@ onMounted(() => {
|
||||
// 不在这里立刻提交;让后续主播数据先进 batch,再由防抖统一 flush
|
||||
// 不直接发;把这“一波”的主播先塞进 hostList,然后弹出“私信”输入框
|
||||
scheduleFlush((items) => {
|
||||
hostList = (items || []).map(h => ({ id: h.text, country: h.country || '' }))
|
||||
//1普票 2金票 invitationType
|
||||
hostList = (items || []).map(h => ({ id: h.text, country: h.country || '', invitationType: h.invitationType }))
|
||||
|
||||
})
|
||||
setTimeout(() => {
|
||||
@@ -859,19 +902,27 @@ onMounted(() => {
|
||||
batchMode.value = 'follow';
|
||||
// 立刻安排一次“尾随防抖”flush,等本波数据齐了再送
|
||||
scheduleFlush((items) => {
|
||||
addTempAnchorData(items.map(h => ({ anchorId: h.text, country: h.country || '' })));
|
||||
// 这里 items 元素是 h
|
||||
const list = items.map(h => ({
|
||||
anchorId: h.text,
|
||||
country: h.country || '',
|
||||
invitationType: h.invitationType,
|
||||
state: stateByInvType(h.invitationType)
|
||||
}))
|
||||
addTempAnchorData(list)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 非 start:本波主播数据进入批次
|
||||
const country = data && data.country != null ? data.country : '';
|
||||
const text = data && (data.hostsId != null ? data.hostsId : data.text);
|
||||
const invitationType = data && (data.invitationType != null ? data.invitationType : '');
|
||||
if (text == null) {
|
||||
// 数据格式不对,丢弃或打印
|
||||
console.warn('[SSE] 非法数据,缺少 hostsId/text:', data);
|
||||
return;
|
||||
}
|
||||
batch.push({ country, text });
|
||||
batch.push({ country, text, invitationType });
|
||||
|
||||
// 根据当前模式,刷新防抖(让“最后一条到来后”延迟几百毫秒再统一提交)
|
||||
if (batchMode.value === 'init') {
|
||||
@@ -881,7 +932,7 @@ onMounted(() => {
|
||||
if (runType.value === 'follow') {
|
||||
passAnchorData({
|
||||
deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||
anchorList: items.map(h => ({ anchorId: h.text, country: h.country || '' })),
|
||||
anchorList: items.map(h => ({ anchorId: h.text, country: h.country || '', invitationType: h.invitationType, state: stateByInvType(h.invitationType) })),
|
||||
needReply: false
|
||||
});
|
||||
} else {
|
||||
@@ -892,7 +943,14 @@ onMounted(() => {
|
||||
} else {
|
||||
// 已在关注:走追加逻辑
|
||||
scheduleFlush((items) => {
|
||||
addTempAnchorData(items.map(h => ({ anchorId: h.text, country: h.country || '' })));
|
||||
// 这里 items 元素是 h
|
||||
const list = items.map(h => ({
|
||||
anchorId: h.text,
|
||||
country: h.country || '',
|
||||
invitationType: h.invitationType,
|
||||
state: stateByInvType(h.invitationType)
|
||||
}))
|
||||
addTempAnchorData(list)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -909,16 +967,18 @@ onUnmounted(() => {
|
||||
let isStartLac = false
|
||||
const getDeviceListFun = () => {
|
||||
getDeviceList().then((res) => {
|
||||
console.log('返回', res.length)
|
||||
// console.log('返回', res.length)
|
||||
if (res && res.length > 0 && deviceInformation.value.length !== res.length) {
|
||||
console.log("设备变更")
|
||||
deviceInformation.value = res
|
||||
reloadImg()
|
||||
// refreshAllStopImgs()
|
||||
// reloadImg()
|
||||
}
|
||||
|
||||
if (res.length == 0) {
|
||||
deviceInformation.value = []
|
||||
reloadImg()
|
||||
// refreshAllStopImgs()
|
||||
// reloadImg()
|
||||
}
|
||||
// deviceInformation.value = ['', '', '', '', '', '',]
|
||||
}).catch((err) => {
|
||||
@@ -932,6 +992,27 @@ const getDeviceListFun = () => {
|
||||
})
|
||||
}
|
||||
|
||||
//获取新消息
|
||||
const selectLastFun = () => {
|
||||
selectLast().then((res) => {
|
||||
// console.log("返回", deviceInformation, res)
|
||||
let mesInfoData = res
|
||||
mesInfoData.forEach(element => {
|
||||
deviceInformation.value.forEach((item, index) => {
|
||||
console.log(res)
|
||||
console.log(item.deviceId == element.device)
|
||||
if (item.deviceId == element.device) {
|
||||
element.device = index + 1 + '号设备'
|
||||
console.log(element.device)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// console.log('============', mesInfoData)
|
||||
MesNewList.value = mesInfoData
|
||||
})
|
||||
}
|
||||
|
||||
async function uploadLogFile() {
|
||||
let loading = null
|
||||
try {
|
||||
@@ -1156,6 +1237,50 @@ function onSave(payload) {
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function isAiConfig() {
|
||||
const res = await window.electronAPI.fileExists("resources/iOSAI/data/aiConfig.json");
|
||||
console.log(res);
|
||||
return res.exists;
|
||||
}
|
||||
|
||||
//金票
|
||||
const gold = ref(true) // ON=执行
|
||||
//普票
|
||||
const ordinary = ref(true) // ON=执行
|
||||
|
||||
|
||||
function invitTypeFun(invitType, nextEnabled) {
|
||||
// 子组件已传来“切换后的布尔值”
|
||||
if (invitType === 'gold') {
|
||||
gold.value = nextEnabled
|
||||
} else if (invitType === 'ordinary') {
|
||||
ordinary.value = nextEnabled
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 由邀请类型 + 当前全局开关,得到 state(0/1) */
|
||||
function stateByInvType(invitationType) {
|
||||
// 2=金票,其他=普票(按你的写法)
|
||||
const enabled = invitationType == 2 ? gold.value : ordinary.value
|
||||
return enabled ? 1 : 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//清空当前批次的小工具
|
||||
function dropCurrentWave() {
|
||||
// 丢弃当前这“波”已缓冲的数据 & 取消待 flush
|
||||
batch.length = 0
|
||||
if (flushTimer) { clearTimeout(flushTimer); flushTimer = null }
|
||||
// 复位本波的弹窗与模式标记
|
||||
isMsgPop.value = false
|
||||
batchMode.value = 'init'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
Reference in New Issue
Block a user