稳定测试版
This commit is contained in:
@@ -13,6 +13,10 @@ export function getIdByName(name) {
|
|||||||
export function update(data) {
|
export function update(data) {
|
||||||
return postAxios({ url: 'api/save_data/update', data })
|
return postAxios({ url: 'api/save_data/update', data })
|
||||||
}
|
}
|
||||||
|
//批量修改主播建联状态
|
||||||
|
export function updates(data) {
|
||||||
|
return postAxios({ url: 'api/save_data/updates', data })
|
||||||
|
}
|
||||||
|
|
||||||
//获取话术
|
//获取话术
|
||||||
export function prologue() {
|
export function prologue() {
|
||||||
|
|||||||
@@ -12,3 +12,8 @@ export function translationToChinese(data) {
|
|||||||
export function translation(data) {
|
export function translation(data) {
|
||||||
return postAxios({ url: '/translation', data })
|
return postAxios({ url: '/translation', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function customTranslation(data) {
|
||||||
|
return postAxios({ url: '/customTranslation', data })
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ export function updateAnchorList(data) {
|
|||||||
export function aiConfig(data) {
|
export function aiConfig(data) {
|
||||||
return postAxios({ url: 'aiConfig', data })
|
return postAxios({ url: 'aiConfig', data })
|
||||||
}
|
}
|
||||||
|
//设置经纪人信息
|
||||||
|
export function getAiConfig() {
|
||||||
|
return getAxios({ url: 'getAiConfig' })
|
||||||
|
}
|
||||||
|
|
||||||
//获取消息列表
|
//获取消息列表
|
||||||
export function selectLast(data) {
|
export function selectLast(data) {
|
||||||
return getAxios({ url: 'select_last_message', data })
|
return getAxios({ url: 'select_last_message', data })
|
||||||
@@ -103,3 +108,13 @@ export function deleteLast(data) {
|
|||||||
export function stopAllTask(data) {
|
export function stopAllTask(data) {
|
||||||
return postAxios({ url: 'stopAllTask', data })
|
return postAxios({ url: 'stopAllTask', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//换号
|
||||||
|
export function changeAccount(data) {
|
||||||
|
return postAxios({ url: 'changeAccount', data })
|
||||||
|
}
|
||||||
|
//重置tk
|
||||||
|
export function restartTikTok(data) {
|
||||||
|
return postAxios({ url: 'restartTikTok', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
src/assets/wifi.png
Normal file
BIN
src/assets/wifi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -48,7 +48,7 @@
|
|||||||
<span class="country" :title="it.country">{{ it.country || '—' }}</span>
|
<span class="country" :title="it.country">{{ it.country || '—' }}</span>
|
||||||
<span class="state" :class="{ done: it.invitationType == 2 }">{{ it.invitationType == 2 ? '金票' :
|
<span class="state" :class="{ done: it.invitationType == 2 }">{{ it.invitationType == 2 ? '金票' :
|
||||||
'普票'
|
'普票'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
<span class="name">{{ item.sender || '未知用户' }}</span>
|
<span class="name">{{ item.sender || '未知用户' }}</span>
|
||||||
<span class="sep">·</span>
|
<span class="sep">·</span>
|
||||||
<span class="device">{{ item.device || '未知设备' }}</span>
|
<span class="device">{{ item.device || '未知设备' }}</span>
|
||||||
|
<span class="sep">·</span>
|
||||||
|
<span class="device">{{ item.time || '未知时间' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bubble" :class="isSeen(item) ? 'seen' : 'unread'" :style="bubbleStyle(item)"
|
<div class="bubble" :class="isSeen(item) ? 'seen' : 'unread'" :style="bubbleStyle(item)"
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
|
|
||||||
<!-- 可选:自定义提示音(静音时也会被静音) -->
|
<!-- 可选:自定义提示音(静音时也会被静音) -->
|
||||||
@@ -71,6 +74,7 @@ const normalizedMessages = computed(() => {
|
|||||||
raw: m,
|
raw: m,
|
||||||
sender: m?.sender ?? m?.name ?? m?.user ?? m?.from ?? '',
|
sender: m?.sender ?? m?.name ?? m?.user ?? m?.from ?? '',
|
||||||
device: m?.device ?? m?.deviceName ?? m?.udid ?? '',
|
device: m?.device ?? m?.deviceName ?? m?.udid ?? '',
|
||||||
|
time: m?.time ?? m?.timestamp ?? '',
|
||||||
text: m?.text ?? m?.content ?? '',
|
text: m?.text ?? m?.content ?? '',
|
||||||
// 统一转成 0/1,兼容旧的 seen/read 布尔
|
// 统一转成 0/1,兼容旧的 seen/read 布尔
|
||||||
status: Number(
|
status: Number(
|
||||||
|
|||||||
@@ -8,12 +8,30 @@
|
|||||||
{{ lineCount }}/{{ MAX_LINES_FOR_ANCHOR }} 行
|
{{ lineCount }}/{{ MAX_LINES_FOR_ANCHOR }} 行
|
||||||
</span>
|
</span>
|
||||||
<span v-if="title === '私信'">
|
<span v-if="title === '私信'">
|
||||||
|
<!-- 翻译
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="开启后,打招呼将自动翻译为主播地区使用的语言" placement="bottom">
|
||||||
|
|
||||||
|
<el-switch style="margin-right: 20px;" v-model="data.needTranslate" />
|
||||||
|
</el-tooltip> -->
|
||||||
|
|
||||||
自动回复
|
自动回复
|
||||||
<el-switch style="margin-right: 20px;" v-model="value1" />
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="开启后,检测到新私信将自动回复" placement="bottom">
|
||||||
|
|
||||||
|
<el-switch style="margin-right: 20px;" v-model="data.auto" />
|
||||||
|
</el-tooltip>
|
||||||
|
</span>
|
||||||
|
<span v-if="title === '评论' || title === '视频评论'">
|
||||||
|
评论
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="关闭后,将不在评论" placement="bottom">
|
||||||
|
|
||||||
|
<el-switch style="margin-right: 20px;" v-model="data.common" />
|
||||||
|
</el-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<el-button v-if="title !== '主播ID'" type="success" @click="exportPrologue(title)">
|
<el-button v-if="title !== '主播ID'" type="success" @click="exportPrologue(title)">
|
||||||
导入{{ title }}
|
导入{{ title }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<el-button @click="onClickCancel">取消</el-button>
|
<el-button @click="onClickCancel">取消</el-button>
|
||||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||||
</template>
|
</template>
|
||||||
@@ -26,8 +44,13 @@ import { ref, computed, watch } from 'vue';
|
|||||||
import { prologue, comment } from '@/api/account';
|
import { prologue, comment } from '@/api/account';
|
||||||
const MAX_LINES_FOR_ANCHOR = 100;
|
const MAX_LINES_FOR_ANCHOR = 100;
|
||||||
|
|
||||||
let value1 = ref(false);
|
|
||||||
|
|
||||||
|
|
||||||
|
let data = ref({
|
||||||
|
// needTranslate: false,
|
||||||
|
auto: false,
|
||||||
|
common: true,
|
||||||
|
});
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: { type: Boolean, required: true },
|
visible: { type: Boolean, required: true },
|
||||||
title: { type: String, default: '' },
|
title: { type: String, default: '' },
|
||||||
@@ -67,7 +90,7 @@ function handleConfirm() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const items = parseLines();
|
const items = parseLines();
|
||||||
emit('confirm', items, props.title, props.index, value1.value);
|
emit('confirm', items, props.title, props.index, data.value);
|
||||||
closingByConfirm.value = true;
|
closingByConfirm.value = true;
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
}
|
}
|
||||||
@@ -83,7 +106,11 @@ function onClosed() {
|
|||||||
|
|
||||||
// 重置表单状态
|
// 重置表单状态
|
||||||
rawText.value = '';
|
rawText.value = '';
|
||||||
value1.value = false;
|
// data.value = {
|
||||||
|
// needTranslate: false,
|
||||||
|
// auto: false,
|
||||||
|
// common: true,
|
||||||
|
// }
|
||||||
|
|
||||||
// 只有非“确定”关闭才对外发 cancel
|
// 只有非“确定”关闭才对外发 cancel
|
||||||
if (!byConfirm) emit('cancel');
|
if (!byConfirm) emit('cancel');
|
||||||
@@ -93,12 +120,13 @@ function onClosed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportPrologue(title) {
|
function exportPrologue(title) {
|
||||||
if (title === '评论') {
|
if (title === '私信') {
|
||||||
comment().then(res => {
|
prologue().then(res => {
|
||||||
rawText.value = res.map(item => item).join('\n\n');
|
rawText.value = res.map(item => item).join('\n\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
prologue().then(res => {
|
comment().then(res => {
|
||||||
rawText.value = res.map(item => item).join('\n\n');
|
rawText.value = res.map(item => item).join('\n\n');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
538
src/components/translationDialog.vue
Normal file
538
src/components/translationDialog.vue
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="visible" width="960px" :close-on-click-modal="false" :destroy-on-close="false"
|
||||||
|
:show-close="false" @close="onClose">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="text-lg font-semibold">多语翻译</div>
|
||||||
|
<!-- <div class="text-xs text-gray-500">可输入多句文本 → 选择语种 → 一键翻译 → 可编辑&持久化 → 确定返回某语种结果</div> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="space-y-5">
|
||||||
|
<!-- 源文本区 -->
|
||||||
|
<el-card shadow="never" class="!border rounded-xl">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-medium">源文本</span>
|
||||||
|
<div class="space-x-2">
|
||||||
|
<el-button size="small" @click="addSentence">新增一行</el-button>
|
||||||
|
<el-button size="small" @click="importFromTextarea">从多行文本导入</el-button>
|
||||||
|
<el-button size="small" type="warning" plain @click="clearAll">清空</el-button>
|
||||||
|
<el-button type="success" @click="exportPrologue()">
|
||||||
|
导入私信
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="grid grid-cols-12 gap-3">
|
||||||
|
<div class="col-span-12 lg:col-span-6">
|
||||||
|
<el-input v-model="bulkText" :rows="8" type="textarea"
|
||||||
|
placeholder="每行一条句子,可粘贴多行,然后点击『从多行文本导入』" />
|
||||||
|
</div>
|
||||||
|
<div class="col-span-12 lg:col-span-6 space-y-2">
|
||||||
|
<div v-for="(s, i) in sentences" :key="`src-${i}`" class="flex items-start gap-2"
|
||||||
|
style="display: flex;">
|
||||||
|
<el-input v-model="sentences[i]" :placeholder="'第 ' + (i + 1) + ' 句'" />
|
||||||
|
<el-button circle @click="removeSentence(i)" style="color: red;">
|
||||||
|
X
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div v-if="sentences.length === 0" class="text-xs text-gray-500">尚未添加句子</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 语种与翻译控制区 -->
|
||||||
|
<el-card shadow="never" class="!border rounded-xl">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-medium">语种与翻译</span>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<el-switch v-model="autoSave" active-text="自动保存" />
|
||||||
|
<el-button size="small" @click="saveToLocal">保存</el-button>
|
||||||
|
<el-button size="small" @click="loadFromLocal">载入</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-3">
|
||||||
|
<div class="col-span-12 lg:col-span-6 space-y-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-24 text-right text-sm text-gray-500">选择语种</div>
|
||||||
|
<el-select v-model="selectedLangs" multiple filterable placeholder="选择需要翻译到的语种"
|
||||||
|
class="w-full">
|
||||||
|
<el-option v-for="opt in languageOptions" :key="opt.value" :label="opt.label"
|
||||||
|
:value="opt.value" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="flex items-center gap-2">
|
||||||
|
<div class="w-24 text-right text-sm text-gray-500">引擎</div>
|
||||||
|
<el-select v-model="engine" class="w-full">
|
||||||
|
<el-option label="自定义回调 (推荐在父组件里接入)" value="custom" />
|
||||||
|
<el-option label="本地占位引擎 (演示)" value="local" />
|
||||||
|
</el-select>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-24"></div>
|
||||||
|
<el-button type="primary" :disabled="!canTranslate || isAnyLoading" @click="onTranslate">
|
||||||
|
<el-icon v-if="isAnyLoading" class="mr-1 is-loading">
|
||||||
|
<Loading />
|
||||||
|
</el-icon>
|
||||||
|
{{ isAnyLoading ? '翻译中...' : '翻译' }}
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<!-- <el-button :disabled="!Object.keys(translations).length"
|
||||||
|
@click="syncLengths">对齐长度</el-button> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-12 lg:col-span-6">
|
||||||
|
<el-alert title="说明" type="info" :closable="false" show-icon class="mb-3"
|
||||||
|
description="选择语种后点击『翻译』。翻译结果可在下方标签页逐条手动调整,自动保存开启时会同步到本地。" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-tabs v-if="selectedLangs.length" v-model="activeTab" class="mt-4">
|
||||||
|
<el-tab-pane v-for="lang in selectedLangs" :key="lang" :name="lang">
|
||||||
|
<template #label>
|
||||||
|
<span>{{ getLangLabel(lang) }}</span>
|
||||||
|
<el-icon v-if="loadingLangs[lang]" class="ml-1 is-loading" style="vertical-align: -2px;">
|
||||||
|
<Loading />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<!-- ✅ 加载中提示 -->
|
||||||
|
<div v-if="loadingLangs[lang]" class="text-sm text-gray-500 flex items-center gap-2">
|
||||||
|
<el-icon class="is-loading">
|
||||||
|
<Loading />
|
||||||
|
</el-icon>
|
||||||
|
正在翻译 {{ getLangLabel(lang) }} ...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ✅ 加载完成但无结果 -->
|
||||||
|
<div v-else-if="!translations[lang] || translations[lang].length === 0"
|
||||||
|
class="text-xs text-gray-500">
|
||||||
|
无数据,点击『翻译』获取。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ✅ 加载完成且有结果 -->
|
||||||
|
<div v-else v-for="(t, i) in translations[lang]" :key="`${lang}-${i}`"
|
||||||
|
class="flex items-start gap-2">
|
||||||
|
<el-input v-model="translations[lang][i]" :placeholder="'第 ' + (i + 1) + ' 句'" />
|
||||||
|
<el-popover placement="top" trigger="hover">
|
||||||
|
<template #reference>
|
||||||
|
<el-tag type="info" effect="plain">源</el-tag>
|
||||||
|
</template>
|
||||||
|
<div class="max-w-72 text-xs">{{ sentences[i] }}</div>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
共 {{ sentences.length }} 句 · 选择 {{ selectedLangs.length }} 种语言
|
||||||
|
<!-- <template v-if="activeTab"> · 当前返回:{{ getLangLabel(activeTab) }}</template> -->
|
||||||
|
</div>
|
||||||
|
<div class="space-x-2">
|
||||||
|
自动回复
|
||||||
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="开启后,检测到新私信将自动回复" placement="bottom">
|
||||||
|
|
||||||
|
<el-switch style="margin-right: 20px;" v-model="auto" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-button @click="onClose">取消</el-button>
|
||||||
|
<el-button type="primary" :disabled="!canConfirm" @click="onConfirm">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref, watch, computed, onMounted } from 'vue'
|
||||||
|
import { prologue } from '@/api/account';
|
||||||
|
import { Loading } from '@element-plus/icons-vue' // ✅ 旋转图标
|
||||||
|
const suppressCancelNext = ref(false) // ✅ 下次关闭是否屏蔽 cancel 事件
|
||||||
|
/**
|
||||||
|
* Props & Emits(JS 版)
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
// 控制弹窗显示
|
||||||
|
modelValue: { type: Boolean, required: true },
|
||||||
|
/**
|
||||||
|
* 自定义翻译回调:由父组件注入,实现真正的翻译
|
||||||
|
* 入参:sentences 原文数组、targetLang 目标语种值
|
||||||
|
* 返回:与 sentences 等长的目标语言数组
|
||||||
|
*/
|
||||||
|
translateFn: { type: Function, default: null },
|
||||||
|
type: '',
|
||||||
|
// 本地持久化 key 前缀(同一项目可自定义隔离)
|
||||||
|
storageKeyPrefix: { type: String, default: 'translationDialog' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'confirm'])
|
||||||
|
|
||||||
|
let auto = ref(true);
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (v) => emit('update:modelValue', v),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
let bulkText = ref('')
|
||||||
|
const sentences = reactive([])
|
||||||
|
const selectedLangs = ref([])
|
||||||
|
const translations = reactive({}) // 所有翻译后的内容
|
||||||
|
const loadingLangs = reactive({}) // ✅ { [lang]: boolean } 每个语种的加载中状态
|
||||||
|
const autoSave = ref(true)
|
||||||
|
const activeTab = ref('')
|
||||||
|
const engine = ref('custom') // 'custom' | 'local'
|
||||||
|
|
||||||
|
watch(selectedLangs, (langs) => {
|
||||||
|
const keep = new Set(langs)
|
||||||
|
|
||||||
|
// 删掉已取消选择的语言对应的数据
|
||||||
|
Object.keys(translations).forEach((k) => {
|
||||||
|
if (!keep.has(k)) delete translations[k]
|
||||||
|
})
|
||||||
|
Object.keys(loadingLangs).forEach((k) => {
|
||||||
|
if (!keep.has(k)) delete loadingLangs[k]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果当前激活标签不在选择里,重置到第一个
|
||||||
|
if (activeTab.value && !keep.has(activeTab.value)) {
|
||||||
|
activeTab.value = langs[0] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要的话,立刻持久化
|
||||||
|
if (autoSave.value) saveToLocal()
|
||||||
|
}, { deep: false })
|
||||||
|
/** 默认语言选项 */
|
||||||
|
const defaultLanguages = [
|
||||||
|
{ label: '英语', value: 'en' },
|
||||||
|
{ label: '简体中文', value: 'zh-CN' },
|
||||||
|
{ label: '繁体中文', value: 'zh-TW' },
|
||||||
|
{ label: '俄语', value: 'ru' },
|
||||||
|
{ label: '日语', value: 'ja' },
|
||||||
|
{ label: '韩语', value: 'ko' },
|
||||||
|
{ label: '西班牙语', value: 'es' },
|
||||||
|
{ label: '法语', value: 'fr' },
|
||||||
|
{ label: '葡萄牙语', value: 'pt' },
|
||||||
|
{ label: '德语', value: 'de' },
|
||||||
|
{ label: '意大利语', value: 'it' },
|
||||||
|
{ label: '泰语', value: 'th' },
|
||||||
|
{ label: '越南语', value: 'vi' },
|
||||||
|
{ label: '印度尼西亚语', value: 'id' },
|
||||||
|
{ label: '马来语', value: 'ms' },
|
||||||
|
{ label: '阿拉伯语', value: 'ar' },
|
||||||
|
{ label: '印地语', value: 'hi' },
|
||||||
|
{ label: '希伯来语', value: 'he' },
|
||||||
|
{ label: '缅甸语', value: 'my' },
|
||||||
|
{ label: '泰米尔语', value: 'ta' },
|
||||||
|
{ label: '乌尔都语', value: 'ur' },
|
||||||
|
{ label: '孟加拉语', value: 'bn' },
|
||||||
|
{ label: '波兰语', value: 'pl' },
|
||||||
|
{ label: '荷兰语', value: 'nl' },
|
||||||
|
{ label: '罗马尼亚语', value: 'ro' },
|
||||||
|
{ label: '土耳其语', value: 'tr' },
|
||||||
|
{ label: '高棉语', value: 'km' },
|
||||||
|
{ label: '老挝语', value: 'lo' },
|
||||||
|
{ label: '粤语', value: 'yue' },
|
||||||
|
{ label: '捷克语', value: 'cs' },
|
||||||
|
{ label: '希腊语', value: 'el' },
|
||||||
|
{ label: '瑞典语', value: 'sv' },
|
||||||
|
{ label: '匈牙利语', value: 'hu' },
|
||||||
|
{ label: '丹麦语', value: 'da' },
|
||||||
|
{ label: '芬兰语', value: 'fi' },
|
||||||
|
{ label: '乌克兰语', value: 'uk' },
|
||||||
|
{ label: '保加利亚语', value: 'bg' },
|
||||||
|
{ label: '塞尔维亚语', value: 'sr' },
|
||||||
|
{ label: '泰卢固语', value: 'te' },
|
||||||
|
{ label: '南非荷兰语', value: 'af' },
|
||||||
|
{ label: '亚美尼亚语', value: 'hy' },
|
||||||
|
{ label: '阿萨姆语', value: 'as' },
|
||||||
|
{ label: '阿斯图里亚斯语', value: 'ast' },
|
||||||
|
{ label: '巴斯克语', value: 'eu' },
|
||||||
|
{ label: '白俄罗斯语', value: 'be' },
|
||||||
|
{ label: '波斯尼亚语', value: 'bs' },
|
||||||
|
{ label: '加泰罗尼亚语', value: 'ca' },
|
||||||
|
{ label: '宿务语', value: 'ceb' },
|
||||||
|
{ label: '克罗地亚语', value: 'hr' },
|
||||||
|
{ label: '埃及阿拉伯语', value: 'arz' },
|
||||||
|
{ label: '爱沙尼亚语', value: 'et' },
|
||||||
|
{ label: '加利西亚语', value: 'gl' },
|
||||||
|
{ label: '格鲁吉亚语', value: 'ka' },
|
||||||
|
{ label: '古吉拉特语', value: 'gu' },
|
||||||
|
{ label: '冰岛语', value: 'is' },
|
||||||
|
{ label: '爪哇语', value: 'jv' },
|
||||||
|
{ label: '卡纳达语', value: 'kn' },
|
||||||
|
{ label: '哈萨克语', value: 'kk' },
|
||||||
|
{ label: '拉脱维亚语', value: 'lv' },
|
||||||
|
{ label: '立陶宛语', value: 'lt' },
|
||||||
|
{ label: '卢森堡语', value: 'lb' },
|
||||||
|
{ label: '马其顿语', value: 'mk' },
|
||||||
|
{ label: '马加希语', value: 'mai' },
|
||||||
|
{ label: '马耳他语', value: 'mt' },
|
||||||
|
{ label: '马拉地语', value: 'mr' },
|
||||||
|
{ label: '美索不达米亚阿拉伯语', value: 'acm' },
|
||||||
|
{ label: '摩洛哥阿拉伯语', value: 'ary' },
|
||||||
|
{ label: '内志阿拉伯语', value: 'ars' },
|
||||||
|
{ label: '尼泊尔语', value: 'ne' },
|
||||||
|
{ label: '北阿塞拜疆语', value: 'az' },
|
||||||
|
{ label: '北黎凡特阿拉伯语', value: 'apc' },
|
||||||
|
{ label: '北乌兹别克语', value: 'uz' },
|
||||||
|
{ label: '书面语挪威语', value: 'nb' },
|
||||||
|
{ label: '新挪威语', value: 'nn' },
|
||||||
|
{ label: '奥克语', value: 'oc' },
|
||||||
|
{ label: '奥里亚语', value: 'or' },
|
||||||
|
{ label: '邦阿西楠语', value: 'pag' },
|
||||||
|
{ label: '西西里语', value: 'scn' },
|
||||||
|
{ label: '信德语', value: 'sd' },
|
||||||
|
{ label: '僧伽罗语', value: 'si' },
|
||||||
|
{ label: '斯洛伐克语', value: 'sk' },
|
||||||
|
{ label: '斯洛文尼亚语', value: 'sl' },
|
||||||
|
{ label: '南黎凡特阿拉伯语', value: 'ajp' },
|
||||||
|
{ label: '斯瓦希里语', value: 'sw' },
|
||||||
|
{ label: '他加禄语', value: 'tl' },
|
||||||
|
{ label: '塔伊兹-亚丁阿拉伯语', value: 'acq' },
|
||||||
|
{ label: '托斯克阿尔巴尼亚语', value: 'sq' },
|
||||||
|
{ label: '突尼斯阿拉伯语', value: 'aeb' },
|
||||||
|
{ label: '威尼斯语', value: 'vec' },
|
||||||
|
{ label: '瓦莱语', value: 'war' },
|
||||||
|
{ label: '威尔士语', value: 'cy' },
|
||||||
|
{ label: '西波斯语', value: 'fa' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const languageOptions = computed(() => (defaultLanguages))
|
||||||
|
const getLangLabel = (v) => (languageOptions.value.find((x) => x.value === v)?.label || v)
|
||||||
|
|
||||||
|
/** 计算属性 */
|
||||||
|
const canTranslate = computed(() => sentences.length > 0 && selectedLangs.value.length > 0)
|
||||||
|
const isAnyLoading = computed(() => Object.values(loadingLangs).some(Boolean))
|
||||||
|
const canConfirm = computed(() => !!activeTab.value && !!(translations[activeTab.value] || []).length)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法 - 源文本
|
||||||
|
*/
|
||||||
|
function importFromTextarea() {
|
||||||
|
const lines = bulkText.value
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
sentences.splice(0, sentences.length, ...lines)
|
||||||
|
syncLengths()
|
||||||
|
if (autoSave.value) saveToLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSentence() {
|
||||||
|
sentences.push('')
|
||||||
|
syncLengths()
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSentence(i) {
|
||||||
|
sentences.splice(i, 1)
|
||||||
|
syncLengths()
|
||||||
|
if (autoSave.value) saveToLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAll() {
|
||||||
|
bulkText.value = ''
|
||||||
|
sentences.splice(0)
|
||||||
|
Object.keys(translations).forEach((k) => delete translations[k])
|
||||||
|
if (autoSave.value) saveToLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方法 - 翻译
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 执行翻译功能的主函数
|
||||||
|
* 该函数会遍历所选语言,对输入文本进行批量翻译,并处理翻译结果的存储与显示
|
||||||
|
*/
|
||||||
|
async function onTranslate() {
|
||||||
|
// 检查是否满足翻译条件,如果不满足则直接返回
|
||||||
|
if (!canTranslate.value) return
|
||||||
|
// 遍历所有选中的目标语言,依次进行翻译
|
||||||
|
// ✅ 给所有选中语种先置为 loading
|
||||||
|
const langs = selectedLangs.value.slice()
|
||||||
|
langs.forEach(l => loadingLangs[l] = true)
|
||||||
|
|
||||||
|
// ✅ 并行翻译;想要串行也可以改成 for...of + await
|
||||||
|
await Promise.all(langs.map(async (lang) => {
|
||||||
|
try {
|
||||||
|
const arr = await translate(sentences.slice(), lang)
|
||||||
|
translations[lang] = ensureLength(arr, sentences.length)
|
||||||
|
} finally {
|
||||||
|
loadingLangs[lang] = false // ✅ 不管成功/失败都清理 loading
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
// 如果未设置返回语言且存在选中的语言,则将第一个选中的语言设为返回语言
|
||||||
|
if (!activeTab.value && selectedLangs.value.length) {
|
||||||
|
activeTab.value = selectedLangs.value[0]
|
||||||
|
}
|
||||||
|
// 如果启用了自动保存功能,则将翻译结果保存到本地存储
|
||||||
|
if (autoSave.value) saveToLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function translate(src, lang) {
|
||||||
|
if (engine.value === 'custom' && typeof props.translateFn === 'function') {
|
||||||
|
return await props.translateFn(src, lang)
|
||||||
|
}
|
||||||
|
// 本地占位:仅作演示,将文本附加 [lang] 标记
|
||||||
|
return src.map((s) => (s ? `${s} [${lang}]` : ''))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对齐各语言数组长度到 sentences.length
|
||||||
|
*/
|
||||||
|
function ensureLength(arr, targetLen) {
|
||||||
|
const out = (arr || []).slice(0, targetLen)
|
||||||
|
while (out.length < targetLen) out.push('')
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLengths() {
|
||||||
|
const n = sentences.length
|
||||||
|
Object.keys(translations).forEach((k) => {
|
||||||
|
translations[k] = ensureLength(translations[k] || [], n)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 持久化(localStorage)
|
||||||
|
*/
|
||||||
|
const storagePrefix = computed(() => props.storageKeyPrefix || 'translationDialog')
|
||||||
|
|
||||||
|
const saveToLocal = debounce(() => {
|
||||||
|
const key = (k) => `${storagePrefix.value}:${k}`
|
||||||
|
localStorage.setItem(key('sentences'), JSON.stringify(sentences))
|
||||||
|
localStorage.setItem(key('selectedLangs'), JSON.stringify(selectedLangs.value))
|
||||||
|
localStorage.setItem(key('activeTab'), JSON.stringify(activeTab.value))
|
||||||
|
localStorage.setItem(key('translations'), JSON.stringify(translations))
|
||||||
|
}, 200) // 200ms 防抖
|
||||||
|
function debounce(fn, wait = 200) {
|
||||||
|
let t
|
||||||
|
return (...args) => {
|
||||||
|
clearTimeout(t)
|
||||||
|
t = setTimeout(() => fn(...args), wait)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function loadFromLocal() {
|
||||||
|
const key = (k) => `${storagePrefix.value}:${k}`
|
||||||
|
try {
|
||||||
|
const s = JSON.parse(localStorage.getItem(key('sentences')) || '[]')
|
||||||
|
const langs = JSON.parse(localStorage.getItem(key('selectedLangs')) || '[]')
|
||||||
|
const a = JSON.parse(localStorage.getItem(key('activeTab')) || '""')
|
||||||
|
const t = JSON.parse(localStorage.getItem(key('translations')) || '{}')
|
||||||
|
|
||||||
|
sentences.splice(0, sentences.length, ...(Array.isArray(s) ? s : []))
|
||||||
|
selectedLangs.value = Array.isArray(langs) ? langs : []
|
||||||
|
activeTab.value = typeof a === 'string' ? a : ''
|
||||||
|
|
||||||
|
Object.keys(translations).forEach((k) => delete translations[k])
|
||||||
|
if (t && typeof t === 'object') {
|
||||||
|
for (const k of Object.keys(t)) {
|
||||||
|
translations[k] = Array.isArray(t[k]) ? t[k] : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果本地的 activeTab 不在当前选中语言里,退回到第一个
|
||||||
|
if (activeTab.value && !selectedLangs.value.includes(activeTab.value)) {
|
||||||
|
activeTab.value = selectedLangs.value[0] || ''
|
||||||
|
}
|
||||||
|
syncLengths()
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('loadFromLocal error', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([sentences, selectedLangs, translations, activeTab], () => {
|
||||||
|
if (autoSave.value) saveToLocal()
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
function onConfirm() {
|
||||||
|
if (!activeTab.value) return
|
||||||
|
suppressCancelNext.value = true // ✅ 告诉下次关闭不要触发 cancel
|
||||||
|
// 拷贝成普通对象,避免把响应式引用直接抛给外部
|
||||||
|
const out = JSON.parse(JSON.stringify(translations))
|
||||||
|
// 追加 yolo 为原始内容(源句子数组)
|
||||||
|
out.yolo = sentences.slice()
|
||||||
|
emit('confirm', { type: props.type, strings: out, auto: auto.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
if (suppressCancelNext.value) {
|
||||||
|
suppressCancelNext.value = false // ✅ 仅关闭,不触发 cancel
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('cancel') // ✅ 正常关闭时才触发 cancel(如点击“取消”/Esc/右上角关闭)
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportPrologue() {
|
||||||
|
|
||||||
|
prologue().then(res => {
|
||||||
|
bulkText.value = res.map(item => item).join('\n\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
onMounted(() => {
|
||||||
|
loadFromLocal()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 轻量美化,依赖页面存在 Tailwind 时会更佳 */
|
||||||
|
.space-y-2>*+* {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-y-3>*+* {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-y-5>*+* {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-cols-12 {
|
||||||
|
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-3 {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-span-12 {
|
||||||
|
grid-column: span 12 / span 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lg\:col-span-6 {
|
||||||
|
grid-column: span 12 / span 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.lg\:col-span-6 {
|
||||||
|
grid-column: span 6 / span 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-xl {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -84,10 +84,14 @@ export function setContentpriList(data) {
|
|||||||
localStorage.setItem('Contentpri', JSON.stringify(data));
|
localStorage.setItem('Contentpri', JSON.stringify(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 以“换行”拼接输出,用于 textarea 等展示 */
|
||||||
|
export function getContentListMultiline() {
|
||||||
|
return getContentList() == null ? '' : getContentList().join('\n');
|
||||||
|
}
|
||||||
// 用于获取评论信息
|
// 用于获取评论信息
|
||||||
export function getContentList() {
|
export function getContentList() {
|
||||||
const arr = JSON.parse(localStorage.getItem('Content'))
|
const arr = JSON.parse(localStorage.getItem('Content'))
|
||||||
return [arr, arr, arr, arr, arr, arr, arr, arr];
|
return arr;
|
||||||
}
|
}
|
||||||
// 用于设置评论信息
|
// 用于设置评论信息
|
||||||
export function setContentList(data) {
|
export function setContentList(data) {
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ function attachInterceptors(instance) {
|
|||||||
instance.interceptors.request.use((config) => {
|
instance.interceptors.request.use((config) => {
|
||||||
// 登录/换租户接口可能不需要 token,根据你的需求放行
|
// 登录/换租户接口可能不需要 token,根据你的需求放行
|
||||||
const urlLast = sliceUrl(config.url || '')
|
const urlLast = sliceUrl(config.url || '')
|
||||||
if ((urlLast === 'prologue' || urlLast === 'comment' || urlLast === 'aiChat-logout')) {
|
if ((urlLast === 'prologue' || urlLast === 'comment' || urlLast === 'aiChat-logout' || urlLast === 'updates')) {
|
||||||
config.headers['vvtoken'] = getToken()
|
config.headers['vvtoken'] = getToken()
|
||||||
}
|
}
|
||||||
// 超时 & 通用头
|
// 超时 & 通用头
|
||||||
config.timeout = 60000
|
config.timeout = 600000
|
||||||
if (!config.headers) config.headers = {}
|
if (!config.headers) config.headers = {}
|
||||||
// 大多数 POST 走 x-www-form-urlencoded(保持你原来的行为)
|
// 大多数 POST 走 x-www-form-urlencoded(保持你原来的行为)
|
||||||
// console.log(config.method)
|
// console.log(config.method)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ import { getToken, setToken, setUser, setUserPass, getUserPass } from '@/stores/
|
|||||||
import { ElLoading, ElMessage } from 'element-plus';
|
import { ElLoading, ElMessage } from 'element-plus';
|
||||||
import { passToken } from '@/api/ios';
|
import { passToken } from '@/api/ios';
|
||||||
|
|
||||||
let version = ref('1.6.4');
|
let version = ref('2.5.0');
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ const onSubmit = () => {
|
|||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
loading.close();
|
loading.close();
|
||||||
}).finally((err) => {
|
}).finally((err) => {
|
||||||
loading.close();
|
// loading.close();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,17 @@
|
|||||||
<div class="main">
|
<div class="main">
|
||||||
<el-scrollbar class="left"> <!-- 左边栏 -->
|
<el-scrollbar class="left"> <!-- 左边栏 -->
|
||||||
<div style="position: absolute;left: 20px; top: 20px;">
|
<div style="position: absolute;left: 20px; top: 20px;">
|
||||||
<el-button style="background: linear-gradient(90deg, #60a5fa, #34d399); color: azure;"
|
<el-button style="background: linear-gradient(90deg, #60a5fa, #34d399); color: azure; "
|
||||||
@click="showMyInfo = true">人设编辑</el-button>
|
@click="showMyInfo = true">人设编辑</el-button>
|
||||||
|
|
||||||
|
<!-- <el-button style="background: linear-gradient(90deg, #60a5fa, #34d399); color: azure;" @click="MesNewList.push({
|
||||||
|
sender: 'Alice', // 或 name/user/from
|
||||||
|
device: 'iPhone 14 Pro', // 或 deviceName/udid
|
||||||
|
text: '你好呀~', // 或 content
|
||||||
|
time: '2023-10-01 12:00:00', // 可选;有些是 'time' 会被过滤或 content
|
||||||
|
type: 'msg' // 可选;有些是 'time' 会被过滤
|
||||||
|
})">新增</el-button> -->
|
||||||
|
<!-- <el-button @click="updates([{ id: 10022, aiOperation: 1 }])">更新请求</el-button> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="center-line"> <!-- 左边栏按钮 -->
|
<div class="center-line"> <!-- 左边栏按钮 -->
|
||||||
@@ -37,7 +46,7 @@
|
|||||||
@mousemove.stop="(e) => onCanvasMove(device.deviceId, e, index)" />
|
@mousemove.stop="(e) => onCanvasMove(device.deviceId, e, index)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="input-info" v-show="selectedDevice == index">
|
<div class="input-info" v-show="selectedDevice == index">
|
||||||
|
<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="getMesList(device.deviceId)">获取当前聊天记录</div>
|
||||||
<div class="app-button" @click="stopOne(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="runTask(runType, device.deviceId)">开启</div>
|
||||||
@@ -47,13 +56,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="right center-line" @click.self="selectedDevice = 999">
|
<div class="right center-line" @click.self="selectedDevice = 999">
|
||||||
<!-- <div style="margin: 30px;"></div> -->
|
<!-- <div style="margin: 30px;"></div> -->
|
||||||
|
|
||||||
<ChatDialog :visible="openShowChat" :messages="chatList" />
|
<ChatDialog :visible="openShowChat" :messages="chatList" />
|
||||||
<MessageDialogd :visible="openShowChat" :messages="MesNewList" :sound-src="ding" />
|
<MessageDialogd :visible="openShowChat" :messages="MesNewList" :sound-src="ding" />
|
||||||
</div>
|
</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"
|
<MultiLineInputDialog v-model:visible="showDialog" :initialText='initialTextStr' :title="dialogTitle"
|
||||||
:index="selectedDevice" @confirm="onDialogConfirm" @cancel="stopAll" />
|
:index="selectedDevice" @confirm="onDialogConfirm" @cancel="stopAll" />
|
||||||
<HostListManagerDialog v-model:visible="showHostDlg" @save="onHostSaved" @invitType="invitTypeFun" />
|
<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" />
|
||||||
</div>
|
</div>
|
||||||
<!-- <AgentGuildDialog v-model="showMyInfo" :model="formInit" @save="handleSave" /> -->
|
<!-- <AgentGuildDialog v-model="showMyInfo" :model="formInit" @save="handleSave" /> -->
|
||||||
<AgentGuildDialog v-model="showMyInfo" :model="{
|
<AgentGuildDialog v-model="showMyInfo" :model="{
|
||||||
@@ -92,12 +105,12 @@
|
|||||||
<div>总时长</div>
|
<div>总时长</div>
|
||||||
<div><b>{{ schedAMin + schedBMin }}</b> 分钟(必须等于 60)</div>
|
<div><b>{{ schedAMin + schedBMin }}</b> 分钟(必须等于 60)</div>
|
||||||
|
|
||||||
<!-- <div>换号</div>
|
<div>换号</div>
|
||||||
<div style="display:flex; gap:8px; align-items:center;">
|
<div style="display:flex; gap:8px; align-items:center;">
|
||||||
<el-switch v-model="interruptEnabled" active-text="开启换号" />
|
<el-switch v-model="interruptEnabled" active-text="开启换号" />
|
||||||
<el-input-number v-model="interruptEveryMin" :min="1" :max="180" />
|
<el-input-number v-model="interruptEveryMin" :min="1" :max="24" />
|
||||||
<span>分钟换一次</span>
|
<span>小时换一次</span>
|
||||||
</div> -->
|
</div>
|
||||||
<div>联盟号</div>
|
<div>联盟号</div>
|
||||||
<div style="display:flex; gap:8px; align-items:center;">
|
<div style="display:flex; gap:8px; align-items:center;">
|
||||||
<el-switch v-model="isAlliance" active-text="联盟号快速私信" />
|
<el-switch v-model="isAlliance" active-text="联盟号快速私信" />
|
||||||
@@ -121,19 +134,21 @@ import {
|
|||||||
setphoneXYinfo, getphoneXYinfo, getUser,
|
setphoneXYinfo, getphoneXYinfo, getUser,
|
||||||
getHostList, setHostList, getContentpriList,
|
getHostList, setHostList, getContentpriList,
|
||||||
setContentpriList, getContentList, setContentList,
|
setContentpriList, getContentList, setContentList,
|
||||||
setsessionId, getsessionId, getContentpriListMultiline
|
setsessionId, getsessionId, getContentListMultiline, getContentpriListMultiline
|
||||||
} from '@/stores/storage'
|
} from '@/stores/storage'
|
||||||
import { connectSSE } from '@/utils/sseUtils'
|
import { connectSSE } from '@/utils/sseUtils'
|
||||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
|
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
|
||||||
import { chat, translationToChinese, translation } from "@/api/chat";
|
import { chat, translationToChinese, translation, customTranslation } from "@/api/chat";
|
||||||
import HostListManagerDialog from '@/components/HostListManagerDialog.vue'
|
import HostListManagerDialog from '@/components/HostListManagerDialog.vue'
|
||||||
import AgentGuildDialog from '@/components/AgentGuildDialog.vue'
|
import AgentGuildDialog from '@/components/AgentGuildDialog.vue'
|
||||||
|
|
||||||
import MultiLineInputDialog from '@/components/MultiLineInputDialog.vue'; // 根据实际路径修改
|
import MultiLineInputDialog from '@/components/MultiLineInputDialog.vue'; // 根据实际路径修改
|
||||||
|
import TranslationDialog from '@/components/translationDialog.vue'; // 根据实际路径修改
|
||||||
|
|
||||||
import ChatDialog from '@/components/ChatDialog.vue'
|
import ChatDialog from '@/components/ChatDialog.vue'
|
||||||
import MessageDialogd from '@/components/MessageDialogd.vue'
|
import MessageDialogd from '@/components/MessageDialogd.vue'
|
||||||
import { pickTikTokBundleId } from '@/utils/arrUtils'
|
import { pickTikTokBundleId } from '@/utils/arrUtils'
|
||||||
import { logout } from '@/api/account';
|
import { logout, updates } from '@/api/account';
|
||||||
import {
|
import {
|
||||||
getDeviceList,
|
getDeviceList,
|
||||||
toHome,
|
toHome,
|
||||||
@@ -153,7 +168,10 @@ import {
|
|||||||
aiConfig,
|
aiConfig,
|
||||||
selectLast,
|
selectLast,
|
||||||
updatelast,
|
updatelast,
|
||||||
stopAllTask
|
changeAccount,
|
||||||
|
stopAllTask,
|
||||||
|
anchorList,
|
||||||
|
restartTikTok
|
||||||
} from '@/api/ios';
|
} from '@/api/ios';
|
||||||
import ding from '@/assets/mes.wav'
|
import ding from '@/assets/mes.wav'
|
||||||
import { set } from "lodash";
|
import { set } from "lodash";
|
||||||
@@ -163,6 +181,10 @@ const openShowChat = ref(true)
|
|||||||
const showHostDlg = ref(false)
|
const showHostDlg = ref(false)
|
||||||
//ai人设弹框
|
//ai人设弹框
|
||||||
const showMyInfo = ref(false)
|
const showMyInfo = ref(false)
|
||||||
|
//翻译弹框
|
||||||
|
const showtransDlg = ref(false)
|
||||||
|
let transDlgType = ref('')
|
||||||
|
|
||||||
// 假设这是你已有的数据
|
// 假设这是你已有的数据
|
||||||
const borkerConfig = reactive({
|
const borkerConfig = reactive({
|
||||||
agentName: '',
|
agentName: '',
|
||||||
@@ -171,12 +193,15 @@ const borkerConfig = reactive({
|
|||||||
contact: ''
|
contact: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//评论 自动化
|
||||||
|
let common = ref(true);
|
||||||
let initialTextStr = ref('') // 初始文本字符串
|
let initialTextStr = ref('') // 初始文本字符串
|
||||||
// 批次缓冲(仅用于当前“波”)
|
// 批次缓冲(仅用于当前“波”)
|
||||||
let batch = []; // [{ country, text }]
|
let batch = []; // [{ country, text }]
|
||||||
let flushTimer = null;
|
let flushTimer = null;
|
||||||
|
|
||||||
let hostList = []
|
let hostList = [] // 主播列表
|
||||||
|
let comonList = [] //评论列表
|
||||||
//查询列表轮询
|
//查询列表轮询
|
||||||
let getListtimer = null;
|
let getListtimer = null;
|
||||||
let userdata = getUser();
|
let userdata = getUser();
|
||||||
@@ -288,9 +313,15 @@ const buttons = [
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isLocked('like')) return
|
if (isLocked('like')) return
|
||||||
|
// runType.value = 'like'
|
||||||
|
// deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId }))
|
||||||
|
dialogTitle.value = '视频评论';
|
||||||
|
setTimeout(() => {
|
||||||
|
showDialog.value = true;
|
||||||
|
|
||||||
|
initialTextStr.value = getContentListMultiline();
|
||||||
|
}, 500)
|
||||||
|
|
||||||
runType.value = 'like'
|
|
||||||
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId }))
|
|
||||||
},
|
},
|
||||||
show: () => true,
|
show: () => true,
|
||||||
img: {
|
img: {
|
||||||
@@ -314,24 +345,31 @@ const buttons = [
|
|||||||
{
|
{
|
||||||
label: '监测消息',
|
label: '监测消息',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (runType.value == 'lisen') {
|
if (runType.value == 'listen') {
|
||||||
deviceInformation.value.forEach((item) => {
|
deviceInformation.value.forEach((item) => {
|
||||||
stopScript({ udid: item.deviceId })
|
stopScript({ udid: item.deviceId })
|
||||||
})
|
})
|
||||||
runType.value = ''
|
runType.value = ''
|
||||||
return
|
return
|
||||||
};
|
};
|
||||||
if (isLocked('lisen')) return
|
if (isLocked('listen')) return
|
||||||
|
//如果传评论就注释一下两行代码,解开后面代码
|
||||||
runType.value = 'lisen'
|
runType.value = 'listen'
|
||||||
deviceInformation.value.forEach((item) => monitorMessages({ udid: item.deviceId }))
|
deviceInformation.value.forEach((item) => monitorMessages({ udid: item.deviceId }))
|
||||||
|
// dialogTitle.value = '评论(无消息将刷视频)';
|
||||||
|
// setTimeout(() => {
|
||||||
|
// showDialog.value = true;
|
||||||
|
|
||||||
|
// initialTextStr.value = getContentListMultiline();
|
||||||
|
// }, 500)
|
||||||
|
|
||||||
},
|
},
|
||||||
show: () => true,
|
show: () => true,
|
||||||
img: {
|
img: {
|
||||||
normal: new URL('@/assets/video/leftBtn1.png', import.meta.url).href,
|
normal: new URL('@/assets/video/leftBtn1.png', import.meta.url).href,
|
||||||
hover: new URL('@/assets/video/leftBtn1-1.png', import.meta.url).href
|
hover: new URL('@/assets/video/leftBtn1-1.png', import.meta.url).href
|
||||||
},
|
},
|
||||||
style: () => ctrlStyle('lisen')
|
style: () => ctrlStyle('listen')
|
||||||
|
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@@ -371,9 +409,9 @@ const isAlliance = ref(false)
|
|||||||
// 是否开启
|
// 是否开启
|
||||||
const interruptEnabled = ref(false)
|
const interruptEnabled = ref(false)
|
||||||
// 每隔多少分钟打断一次(可做弹窗配置)
|
// 每隔多少分钟打断一次(可做弹窗配置)
|
||||||
const interruptEveryMin = ref(15)
|
const interruptEveryMin = ref(2)
|
||||||
// 打断器最大重试次数 & 每次超时(按需调)
|
// 打断器最大重试次数 & 每次超时(按需调)
|
||||||
const interruptMaxRetries = 3
|
const interruptMaxRetries = 1
|
||||||
const interruptCallTimeoutMs = 120_000
|
const interruptCallTimeoutMs = 120_000
|
||||||
|
|
||||||
// 运行态
|
// 运行态
|
||||||
@@ -673,44 +711,76 @@ const selectDevice = (index) => {
|
|||||||
const dragState = ref({}) // 以 index 作为 key 保存 {ox, oy, t}
|
const dragState = ref({}) // 以 index 作为 key 保存 {ox, oy, t}
|
||||||
|
|
||||||
const onCanvasDown = (udid, e, index) => {
|
const onCanvasDown = (udid, e, index) => {
|
||||||
// 记录起点(Canvas 内的 offset)
|
// 记录起点(Canvas 内 offset)和时间
|
||||||
dragState.value[index] = { ox: e.offsetX, oy: e.offsetY, t: Date.now(), udid }
|
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 onCanvasMove = (udid, e, index) => {
|
||||||
// 如需在 overlay 上画指示、十字线等,可在这里使用 e.offsetX/e.offsetY
|
// 若要实时观察滑动轨迹(可选)
|
||||||
|
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 onCanvasUp = async (udid, e, index) => {
|
||||||
const st = dragState.value[index]
|
const st = dragState.value[index]
|
||||||
if (!st) return
|
if (!st) return
|
||||||
|
|
||||||
const { ox, oy, t } = st
|
const { ox, oy, t, startDevXY, startOffsetXY } = st
|
||||||
const dx = e.offsetX - ox
|
const dx = e.offsetX - ox
|
||||||
const dy = e.offsetY - oy
|
const dy = e.offsetY - oy
|
||||||
const elapsed = Date.now() - t
|
const elapsed = Date.now() - t
|
||||||
delete dragState.value[index]
|
delete dragState.value[index]
|
||||||
|
|
||||||
// 映射到真实分辨率
|
// 终点(设备坐标 & 画布 offset)
|
||||||
const p0 = mapToDeviceXY(index, ox, oy)
|
const endDevXY = mapToDeviceXY(index, e.offsetX, e.offsetY)
|
||||||
const p1 = mapToDeviceXY(index, e.offsetX, e.offsetY)
|
const endOffsetXY = { x: e.offsetX, y: e.offsetY }
|
||||||
console.log(" x, y", p0, p1)
|
|
||||||
// 判断是“点按”还是“滑动”
|
// ✅ 这里打印:起点/终点(两套坐标)+ 耗时
|
||||||
const MOVE_THR = 5 // 像素阈值(可按需调整)
|
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
|
const isTap = Math.hypot(dx, dy) < MOVE_THR && elapsed < 500
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isTap) {
|
if (isTap) {
|
||||||
await tapAction({ udid, x: p1.x, y: p1.y })
|
await tapAction({ udid, x: endDevXY.x, y: endDevXY.y })
|
||||||
// console.log('tap', p1)
|
|
||||||
} else {
|
} else {
|
||||||
// 只需要方向:1上/2左/3下/4右
|
// //通过方向判断code 1234
|
||||||
const rotation = Number((deviceInformation.value[index] || {}).rotation || 0)
|
// const rotation = Number((deviceInformation.value[index] || {}).rotation || 0)
|
||||||
|
// const code = getSwipeCodeWithRotation(dx, dy, rotation)
|
||||||
const code = getSwipeCodeWithRotation(dx, dy, rotation) // 必定得到1~4
|
// await swipeAction({ udid, direction: code })
|
||||||
console.log("code", code)
|
|
||||||
await swipeAction({ udid, direction: code })
|
|
||||||
|
|
||||||
|
//通过自定义滑动坐标和时间传参
|
||||||
|
await swipeAction({ udid, sx: startDevXY.x, sy: startDevXY.y, ex: endDevXY.x, ey: endDevXY.y, duration: elapsed / 1000 })
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
@@ -826,64 +896,90 @@ async function stopAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//确认多行文本框内容
|
//确认多行文本框内容
|
||||||
function onDialogConfirm(result, type, index, isMon) {
|
function onDialogConfirm(result, type, index, data) {
|
||||||
// console.log(type, result, isMon);
|
console.log(type, result, data);
|
||||||
console.log(type)
|
|
||||||
if (type == '主播ID') {
|
if (type == '主播ID') {
|
||||||
hostList = (result || []).map(id => ({ id, country: '' }))
|
hostList = (result || []).map(id => ({ id, country: '' }))
|
||||||
|
|
||||||
dialogTitle.value = '私信';
|
//无需评论,注释,如果需要注释下面代码,放开后面代码
|
||||||
|
// dialogTitle.value = '私信';
|
||||||
|
// setTimeout(() => {
|
||||||
|
// showDialog.value = true;
|
||||||
|
|
||||||
|
// initialTextStr.value = getContentpriListMultiline();
|
||||||
|
// }, 500)
|
||||||
|
dialogTitle.value = '评论';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
showDialog.value = true;
|
showDialog.value = true;
|
||||||
|
|
||||||
initialTextStr.value = getContentpriListMultiline();
|
initialTextStr.value = getContentListMultiline();
|
||||||
}, 500)
|
}, 500)
|
||||||
|
} else if (type == '评论') {
|
||||||
|
comonList = result
|
||||||
|
setContentList(result)
|
||||||
|
common.value = data.common
|
||||||
|
// dialogTitle.value = '私信';
|
||||||
|
// setTimeout(() => {
|
||||||
|
// showDialog.value = true;
|
||||||
|
|
||||||
|
// initialTextStr.value = getContentpriListMultiline();
|
||||||
|
// }, 500)
|
||||||
|
transDlgType.value = '私信'
|
||||||
|
showtransDlg.value = true
|
||||||
} else if (type == '私信') {
|
} else if (type == '私信') {
|
||||||
runType.value = 'follow'
|
// runType.value = 'follow'
|
||||||
setContentpriList(result)
|
// setContentpriList(result)
|
||||||
// console.log('hostList', hostList)
|
|
||||||
|
|
||||||
if (isAlliance.value) {
|
// if (isAlliance.value) {
|
||||||
followAndGreetUnion(
|
// followAndGreetUnion(
|
||||||
{
|
// {
|
||||||
deviceList: deviceInformation.value.map(item => item.deviceId),
|
// deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||||
anchorList: hostList.map(item => ({
|
// anchorList: hostList.map(item => ({
|
||||||
anchorId: item.id,
|
// anchorId: item.id,
|
||||||
country: item.country,
|
// country: item.country,
|
||||||
invitationType: item.invitationType,
|
// invitationType: item.invitationType,
|
||||||
state: stateByInvType(item.invitationType),
|
// state: stateByInvType(item.invitationType),
|
||||||
})),
|
// })),
|
||||||
prologueList: result,
|
// prologueList: result,
|
||||||
needReply: isMon
|
// needReply: data.auto,
|
||||||
}
|
// needTranslate: data.needTranslate,
|
||||||
).then((res) => {
|
// }
|
||||||
ElMessage({ type: 'success', message: '任务开启成功' });
|
// ).then((res) => {
|
||||||
|
// ElMessage({ type: 'success', message: '任务开启成功' });
|
||||||
|
|
||||||
hostList = []
|
// hostList = []
|
||||||
})
|
// })
|
||||||
} else {
|
// } else {
|
||||||
passAnchorData(
|
// passAnchorData(
|
||||||
{
|
// {
|
||||||
deviceList: deviceInformation.value.map(item => item.deviceId),
|
// deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||||
anchorList: hostList.map(item => ({
|
// anchorList: hostList.map(item => ({
|
||||||
anchorId: item.id,
|
// anchorId: item.id,
|
||||||
country: item.country,
|
// country: item.country,
|
||||||
invitationType: item.invitationType,
|
// invitationType: item.invitationType,
|
||||||
state: stateByInvType(item.invitationType),
|
// state: stateByInvType(item.invitationType),
|
||||||
})),
|
// })),
|
||||||
prologueList: result,
|
// prologueList: result,
|
||||||
needReply: isMon
|
// comment: comonList,
|
||||||
}
|
// needReply: data.auto,
|
||||||
).then((res) => {
|
// needTranslate: data.needTranslate,
|
||||||
ElMessage({ type: 'success', message: '任务开启成功' });
|
// isComment: data.common
|
||||||
|
// }
|
||||||
hostList = []
|
// ).then((res) => {
|
||||||
})
|
// ElMessage({ type: 'success', message: '任务开启成功' });
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// hostList = []
|
||||||
|
// })
|
||||||
|
|
||||||
|
// }
|
||||||
|
} else if (type == '视频评论') {
|
||||||
|
runType.value = 'like'
|
||||||
|
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId, comment: result, isComment: data.common }))
|
||||||
|
} else if (type == '评论(无消息将刷视频)') {
|
||||||
|
runType.value = 'listen'
|
||||||
|
deviceInformation.value.forEach((item) => monitorMessages({ udid: item.deviceId, comment: result }))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -898,6 +994,7 @@ onMounted(async () => {
|
|||||||
const res = await window.electronAPI.isiproxy({
|
const res = await window.electronAPI.isiproxy({
|
||||||
intervalMs: 2000,
|
intervalMs: 2000,
|
||||||
exeName: 'iproxy.exe',
|
exeName: 'iproxy.exe',
|
||||||
|
// maxWaitMs: 3000, // 可选:5分钟超时
|
||||||
maxWaitMs: 300000, // 可选:5分钟超时
|
maxWaitMs: 300000, // 可选:5分钟超时
|
||||||
});
|
});
|
||||||
if (res.running) {
|
if (res.running) {
|
||||||
@@ -915,12 +1012,34 @@ onMounted(async () => {
|
|||||||
|
|
||||||
window.electronAPI.startMq(userdata.tenantId, userdata.id)
|
window.electronAPI.startMq(userdata.tenantId, userdata.id)
|
||||||
// 初始化时获取设备列表
|
// 初始化时获取设备列表
|
||||||
getListtimer = setInterval(() => {
|
|
||||||
|
//每3秒获取一次设备列表 消息列表 和 查询主播列表是否还有主播
|
||||||
|
getListtimer = setInterval(async () => {
|
||||||
getDeviceListFun()
|
getDeviceListFun()
|
||||||
selectLastFun()
|
selectLastFun()
|
||||||
|
const hostsList = await getStoredHostList()
|
||||||
|
// console.log(hostsList.length)
|
||||||
|
//当私信主播时,主播列表没有数据了,提示列表空了 并且关闭私信
|
||||||
|
if (runType.value == 'follow') {
|
||||||
|
if (hostsList.length <= 0) {
|
||||||
|
await stopAll()
|
||||||
|
runType.value = 'like'
|
||||||
|
deviceInformation.value.forEach((item) => growAccount({ udid: item.deviceId }))
|
||||||
|
ElMessageBox.alert('私信全部完成!(刷视频中)', '提示', {
|
||||||
|
confirmButtonText: 'OK',
|
||||||
|
callback: (action) => {
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}, 3000)
|
}, 3000)
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
await checkVPN()
|
||||||
|
}, 1000 * 20)
|
||||||
|
|
||||||
if (!await isAiConfig()) {
|
if (!await isAiConfig()) {
|
||||||
showMyInfo.value = true
|
showMyInfo.value = true
|
||||||
}
|
}
|
||||||
@@ -959,9 +1078,10 @@ onMounted(async () => {
|
|||||||
const country = data && data.country != null ? data.country : ''
|
const country = data && data.country != null ? data.country : ''
|
||||||
const text = data && (data.hostsId != null ? data.hostsId : data.text)
|
const text = data && (data.hostsId != null ? data.hostsId : data.text)
|
||||||
const invitationType = data && (data.invitationType != null ? data.invitationType : '')
|
const invitationType = data && (data.invitationType != null ? data.invitationType : '')
|
||||||
|
const id = data && data.id != null ? data.id : ''
|
||||||
if (!text) return
|
if (!text) return
|
||||||
|
|
||||||
batch.push({ country, text, invitationType })
|
batch.push({ country, text, invitationType, id })
|
||||||
|
|
||||||
scheduleFlush((items) => {
|
scheduleFlush((items) => {
|
||||||
// 批量入库
|
// 批量入库
|
||||||
@@ -971,6 +1091,10 @@ onMounted(async () => {
|
|||||||
invitationType: h.invitationType,
|
invitationType: h.invitationType,
|
||||||
state: stateByInvType(h.invitationType),
|
state: stateByInvType(h.invitationType),
|
||||||
}))
|
}))
|
||||||
|
updates(items.map(h => ({
|
||||||
|
id: h.id,
|
||||||
|
aiOperation: 1,
|
||||||
|
})))
|
||||||
addTempAnchorData(list)
|
addTempAnchorData(list)
|
||||||
}, 400)
|
}, 400)
|
||||||
})
|
})
|
||||||
@@ -1014,21 +1138,16 @@ const getDeviceListFun = () => {
|
|||||||
//获取新消息
|
//获取新消息
|
||||||
const selectLastFun = () => {
|
const selectLastFun = () => {
|
||||||
selectLast().then((res) => {
|
selectLast().then((res) => {
|
||||||
// console.log("返回", deviceInformation, res)
|
|
||||||
let mesInfoData = res
|
let mesInfoData = res
|
||||||
mesInfoData.forEach(element => {
|
mesInfoData.forEach(element => {
|
||||||
deviceInformation.value.forEach((item, index) => {
|
deviceInformation.value.forEach((item, index) => {
|
||||||
console.log(res)
|
|
||||||
console.log(item.deviceId == element.device)
|
|
||||||
if (item.deviceId == element.device) {
|
if (item.deviceId == element.device) {
|
||||||
element.device = index + 1 + '号设备'
|
element.device = index + 1 + '号设备'
|
||||||
console.log(element.device)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log('============', mesInfoData)
|
MesNewList.value = [...mesInfoData];
|
||||||
MesNewList.value = mesInfoData
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1079,7 +1198,7 @@ async function uploadLogFile() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function runTask(key, deviceId) {
|
function runTask(key, deviceId, type) {
|
||||||
console.log('[schedule] 切换到任务:', key, printCurrentTime())
|
console.log('[schedule] 切换到任务:', key, printCurrentTime())
|
||||||
|
|
||||||
forceActivate(key, async () => {
|
forceActivate(key, async () => {
|
||||||
@@ -1094,6 +1213,7 @@ function runTask(key, deviceId) {
|
|||||||
deviceList: [deviceId],
|
deviceList: [deviceId],
|
||||||
anchorList: [],
|
anchorList: [],
|
||||||
prologueList: getContentpriList(),
|
prologueList: getContentpriList(),
|
||||||
|
comment: comonList,
|
||||||
needReply: false
|
needReply: false
|
||||||
}
|
}
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
@@ -1102,7 +1222,7 @@ function runTask(key, deviceId) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
//第一个小时结束后,第二轮开始的时候,直接进入follow
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
runType.value = 'follow'
|
runType.value = 'follow'
|
||||||
|
|
||||||
@@ -1111,6 +1231,7 @@ function runTask(key, deviceId) {
|
|||||||
deviceList: deviceInformation.value.map(item => item.deviceId),
|
deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||||
anchorList: [],
|
anchorList: [],
|
||||||
prologueList: getContentpriList(),
|
prologueList: getContentpriList(),
|
||||||
|
comment: comonList,
|
||||||
needReply: false
|
needReply: false
|
||||||
}
|
}
|
||||||
).then((res) => {
|
).then((res) => {
|
||||||
@@ -1194,20 +1315,46 @@ function resumeAfterInterrupt() {
|
|||||||
pauseSnapshot = null
|
pauseSnapshot = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行一次中断器函数
|
||||||
|
* 该函数会尝试使用设备列表中的每个设备更改账户,并检查是否所有操作都成功
|
||||||
|
* @returns {Promise<boolean>} 返回一个Promise,解析为布尔值,表示操作是否全部成功
|
||||||
|
*/
|
||||||
async function runInterrupterOnce() {
|
async function runInterrupterOnce() {
|
||||||
// TODO: 换成你的真实调用,比如:替换
|
// 定义一个异步函数,用于执行设备账户更改操作
|
||||||
// const ok = await someApi({ ... })
|
|
||||||
// return !!ok
|
|
||||||
|
|
||||||
// 示例:包装一个带超时的 Promise;把你的方法放到 promiseFn 里
|
|
||||||
const promiseFn = async () => {
|
const promiseFn = async () => {
|
||||||
// 这里放你的真实逻辑
|
// 从deviceInformation中提取设备ID,并过滤掉无效值
|
||||||
changeAccount
|
const devices = (deviceInformation.value || [])
|
||||||
console.log('[换号] 正在执行...')
|
.map(d => d?.deviceId)
|
||||||
// 比如 await doDailyCheck(); 然后返回 true/false
|
.filter(Boolean);
|
||||||
return true
|
|
||||||
}
|
// 如果没有有效设备,直接返回false
|
||||||
return await withTimeout(promiseFn(), interruptCallTimeoutMs).catch(() => false)
|
if (!devices.length) return false;
|
||||||
|
console.log(devices.length + '台设备换账号')
|
||||||
|
// 为每个设备创建一个检查函数,用于尝试更改账户
|
||||||
|
const checks = devices.map(async (udid) => {
|
||||||
|
try {
|
||||||
|
// 尝试更改账户并检查返回结果
|
||||||
|
const res = await changeAccount({ udid: udid });
|
||||||
|
// 检查返回值是否表示成功
|
||||||
|
return (
|
||||||
|
res.code === 200
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// 如果出错,返回false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待所有检查完成,并获取结果
|
||||||
|
const settled = await Promise.allSettled(checks);
|
||||||
|
// 检查是否所有操作都成功完成
|
||||||
|
const allOk = settled.every(s => s.status === 'fulfilled' && s.value === true);
|
||||||
|
return allOk;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用超时机制执行promiseFn,捕获超时或其他错误并返回false
|
||||||
|
return await withTimeout(promiseFn(), interruptCallTimeoutMs).catch(() => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function withTimeout(p, ms) {
|
function withTimeout(p, ms) {
|
||||||
@@ -1222,7 +1369,7 @@ function startScheduleLoop() {
|
|||||||
lastInterruptTs = Date.now()
|
lastInterruptTs = Date.now()
|
||||||
localStorage.setItem('INT_LAST_TS', String(lastInterruptTs))
|
localStorage.setItem('INT_LAST_TS', String(lastInterruptTs))
|
||||||
// 先按当前 index 跑一次,保持“即刻对齐”
|
// 先按当前 index 跑一次,保持“即刻对齐”
|
||||||
runTask(schedulePlan[scheduleState.index].key)
|
runTask(schedulePlan[scheduleState.index].key, null, 1)
|
||||||
|
|
||||||
if (scheduleTimer) clearInterval(scheduleTimer)
|
if (scheduleTimer) clearInterval(scheduleTimer)
|
||||||
|
|
||||||
@@ -1235,12 +1382,12 @@ function startScheduleLoop() {
|
|||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (!lastInterruptTs) lastInterruptTs = now // 首次初始化
|
if (!lastInterruptTs) lastInterruptTs = now // 首次初始化
|
||||||
|
|
||||||
const due = now - lastInterruptTs >= interruptEveryMin.value * 60_000
|
const due = now - lastInterruptTs >= interruptEveryMin.value * 60_000 * 60_000
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
'due=', due,
|
'due=', due,
|
||||||
'elapsed=', now - lastInterruptTs,
|
'elapsed=', now - lastInterruptTs,
|
||||||
'threshold=', interruptEveryMin.value * 60_000
|
'threshold=', interruptEveryMin.value * 60_000 * 60_000
|
||||||
)
|
)
|
||||||
if (due) {
|
if (due) {
|
||||||
interrupting = true
|
interrupting = true
|
||||||
@@ -1251,7 +1398,8 @@ function startScheduleLoop() {
|
|||||||
pauseSnapshot = { index: scheduleState.index, elapsedBeforePause }
|
pauseSnapshot = { index: scheduleState.index, elapsedBeforePause }
|
||||||
|
|
||||||
// 停掉当前片段
|
// 停掉当前片段
|
||||||
await stopCurrentMode()
|
await stopAll()
|
||||||
|
|
||||||
|
|
||||||
// 执行中断任务(带重试)
|
// 执行中断任务(带重试)
|
||||||
let ok = false
|
let ok = false
|
||||||
@@ -1296,7 +1444,7 @@ function startScheduleLoop() {
|
|||||||
|
|
||||||
function forceActivate(key, runner) {
|
function forceActivate(key, runner) {
|
||||||
// 跳过互斥逻辑,直接切换
|
// 跳过互斥逻辑,直接切换
|
||||||
runType.value = key;
|
// runType.value = key;
|
||||||
if (typeof runner === 'function') runner();
|
if (typeof runner === 'function') runner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1402,6 +1550,131 @@ function stopOne(deviceId) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//查看主播库主播信息
|
||||||
|
async function getStoredHostList() {
|
||||||
|
const v = await anchorList()
|
||||||
|
return v ? v : []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let isWifi = ref(false);
|
||||||
|
const checkVPN = async () => {
|
||||||
|
try {
|
||||||
|
// 设置超时 5 秒钟
|
||||||
|
const timeout = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('请求超时')), 10000) // 10秒超时
|
||||||
|
);
|
||||||
|
|
||||||
|
// 使用 Promise.race 来进行超时控制
|
||||||
|
const response = await Promise.race([
|
||||||
|
fetch('https://www.google.com', { method: 'HEAD', mode: 'no-cors' }),
|
||||||
|
timeout
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 判断 fetch 请求是否成功
|
||||||
|
if (response && response.type === 'opaque') {
|
||||||
|
// ElMessage.success('VPN连接正常!');
|
||||||
|
isWifi.value = false;
|
||||||
|
} else {
|
||||||
|
ElMessage.error('VPN连接失败,无法访问网络。');
|
||||||
|
isWifi.value = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 捕获超时错误或其他错误
|
||||||
|
ElMessage.error('VPN连接失败,无法访问网络。');
|
||||||
|
isWifi.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 语言选项(也可以使用内置默认)
|
||||||
|
// const languages = [
|
||||||
|
// { label: '简体中文 (zh-CN)', value: 'zh-CN' },
|
||||||
|
// { label: 'English (en)', value: 'en' },
|
||||||
|
// { label: '日本語 (ja)', value: 'ja' },
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// 模拟翻译函数:你可以接入自己后端或第三方接口 sentences文本 targetLang语言
|
||||||
|
async function doTranslate(sentences, targetLang) {
|
||||||
|
const str = arrayToString(sentences)
|
||||||
|
try {
|
||||||
|
const response = await customTranslation({
|
||||||
|
"msg": str,
|
||||||
|
"language": targetLang
|
||||||
|
})
|
||||||
|
console.log(response)
|
||||||
|
|
||||||
|
|
||||||
|
// 1️⃣ 去掉首尾的大括号
|
||||||
|
const raw = response.replace(/^{|}$/g, '')
|
||||||
|
|
||||||
|
// 2️⃣ 按换行符切割为数组
|
||||||
|
const arr = raw.split('\n').map(s => s.trim()).filter(Boolean)
|
||||||
|
|
||||||
|
console.log(arr)
|
||||||
|
// 简单演示逻辑(真实情况应调用 API)
|
||||||
|
return arr
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Translation error:', error)
|
||||||
|
// 发生错误时返回空数组
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收“确定”事件返回结果
|
||||||
|
function onConfirm({ type, strings, auto }) {
|
||||||
|
console.log('✅ 确认返回:', type, strings, auto)
|
||||||
|
showtransDlg.value = false
|
||||||
|
runType.value = 'follow'
|
||||||
|
setContentpriList(strings)
|
||||||
|
|
||||||
|
if (isAlliance.value) {
|
||||||
|
followAndGreetUnion(
|
||||||
|
{
|
||||||
|
deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||||
|
anchorList: hostList.map(item => ({
|
||||||
|
anchorId: item.id,
|
||||||
|
country: item.country,
|
||||||
|
invitationType: item.invitationType,
|
||||||
|
state: stateByInvType(item.invitationType),
|
||||||
|
})),
|
||||||
|
prologueList: strings,
|
||||||
|
needReply: data.auto,
|
||||||
|
// needTranslate: data.needTranslate,
|
||||||
|
}
|
||||||
|
).then((res) => {
|
||||||
|
ElMessage({ type: 'success', message: '任务开启成功' });
|
||||||
|
|
||||||
|
hostList = []
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
passAnchorData(
|
||||||
|
{
|
||||||
|
deviceList: deviceInformation.value.map(item => item.deviceId),
|
||||||
|
anchorList: hostList.map(item => ({
|
||||||
|
anchorId: item.id,
|
||||||
|
country: item.country,
|
||||||
|
invitationType: item.invitationType,
|
||||||
|
state: stateByInvType(item.invitationType),
|
||||||
|
})),
|
||||||
|
prologueList: strings, //私信对象
|
||||||
|
comment: comonList, //评论列表
|
||||||
|
needReply: auto, //自动回复
|
||||||
|
// needTranslate: data.needTranslate,
|
||||||
|
isComment: common.value //是否评论
|
||||||
|
}
|
||||||
|
).then((res) => {
|
||||||
|
ElMessage({ type: 'success', message: '任务开启成功' });
|
||||||
|
console.log("启动成功")
|
||||||
|
hostList = []
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToString(arr) {
|
||||||
|
// 过滤空项并用 \n 连接
|
||||||
|
return arr.filter(Boolean).join(' \n')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ module.exports = defineConfig({
|
|||||||
additionalData: `@import "@/static/css/app.less";` // 注入全局变量文件
|
additionalData: `@import "@/static/css/app.less";` // 注入全局变量文件
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
productionSourceMap: false
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user