tk版私信出版

This commit is contained in:
2026-04-16 17:31:45 +08:00
parent 1f8b830d27
commit c0125a5a9f
4 changed files with 808 additions and 117 deletions

View File

@@ -0,0 +1,642 @@
<template>
<div class="h-full w-full overflow-hidden bg-gradient-to-br from-slate-100 to-slate-200">
<div v-if="pageMode === 'config'" class="h-full overflow-auto p-6">
<div class="max-w-5xl mx-auto pb-8">
<div class="bg-white rounded-2xl shadow-xl p-8">
<div class="flex items-end justify-between mb-4 gap-6">
<div>
<h1 class="text-2xl font-semibold text-gray-900">自动私信工作台TK版</h1>
<p class="text-sm text-gray-500 mt-1">
先配置发送话术AI 回复和分组视图再进入浏览器视图页执行任务
</p>
</div>
<div class="flex items-center gap-3">
<span :class="statusChipClass">
{{ statusText }}
</span>
<button @click="handlePrepareViews" :disabled="isPreparing || !isElectronEnv"
class="px-4 py-2 text-sm bg-gradient-to-r from-emerald-500 to-teal-500 text-white rounded-lg hover:from-emerald-600 hover:to-teal-600 transition-all shadow-sm flex items-center gap-2 disabled:cursor-not-allowed disabled:opacity-60">
<span>{{ isPreparing ? '预热中...' : '预热视图' }}</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M12 5l7 7-7 7" />
</svg>
</button>
<button @click="openBrowserView"
class="px-4 py-2 text-sm bg-gradient-to-r from-blue-500 to-cyan-500 text-white rounded-lg hover:from-blue-600 hover:to-cyan-600 transition-all shadow-sm flex items-center gap-2">
<span>打开浏览器视图</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
</button>
</div>
</div>
<div
class="flex items-center gap-2 mb-6 px-4 py-2 rounded-full text-sm text-gray-700 bg-gradient-to-r from-blue-50 to-green-50 border border-blue-200 w-fit">
<span class="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
`groupViewCounts` 固定传 3 个元素分别对应第一组第二组第三组开启的视图数
</div>
<div class="grid grid-cols-3 gap-6">
<div class="col-span-2 space-y-6">
<section class="bg-gradient-to-b from-white to-gray-50 rounded-xl border border-gray-200 shadow-sm p-6">
<div class="flex items-center justify-between mb-6">
<div class="flex items-center gap-2">
<span class="w-1 h-4 rounded-full bg-gradient-to-b from-blue-500 to-green-500" />
<span class="font-medium text-gray-900">运行参数</span>
</div>
<div class="grid grid-cols-3 gap-3 text-center">
<div class="rounded-lg border border-gray-200 bg-white px-3 py-2 min-w-[88px]">
<div class="text-[10px] text-gray-500">话术数</div>
<div class="mt-1 text-base font-semibold text-gray-800">{{ greetingCount }}</div>
</div>
<div class="rounded-lg border border-gray-200 bg-white px-3 py-2 min-w-[88px]">
<div class="text-[10px] text-gray-500">AI回复</div>
<div class="mt-1 text-base font-semibold text-gray-800">{{ configForm.replyUnreadMessages ? '开启' :
'关闭' }}</div>
</div>
<div class="rounded-lg border border-gray-200 bg-white px-3 py-2 min-w-[88px]">
<div class="text-[10px] text-gray-500">开启视图</div>
<div class="mt-1 text-base font-semibold text-gray-800">{{ totalEnabledViews }}</div>
</div>
</div>
</div>
<div class="space-y-5">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">发送话术</label>
<textarea v-model="configForm.greetingText" :disabled="isStarting || !isElectronEnv" rows="6"
placeholder="一行一条话术"
class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-sm text-gray-800 outline-none focus:border-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
@blur="handleGreetingBlur" />
<p class="mt-2 text-xs text-gray-500">`greetingMessages` 会按一行一条组装提交</p>
</div>
<div class="grid grid-cols-[minmax(0,1fr),220px] gap-4 items-end">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">分组轮换间隔分钟</label>
<input v-model.number="configForm.groupSwitchMinutes" :disabled="isStarting || !isElectronEnv"
type="number" min="1" step="1"
class="h-11 w-full rounded-lg border border-gray-300 bg-white px-4 text-sm text-gray-800 outline-none focus:border-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
@blur="handleSwitchMinutesBlur" />
</div>
<label
class="flex items-center gap-3 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-700 h-11">
<input v-model="configForm.replyUnreadMessages" :disabled="isStarting || !isElectronEnv"
type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
<span>开启 AI 回复未读消息</span>
</label>
</div>
</div>
</section>
<section class="bg-gradient-to-b from-white to-gray-50 rounded-xl border border-gray-200 shadow-sm p-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<span class="w-1 h-4 rounded-full bg-gradient-to-b from-purple-500 to-blue-500" />
<span class="font-medium text-gray-900">视图分组</span>
</div>
<span class="text-xs text-gray-500">每组支持 0-3 个视图</span>
</div>
<div class="grid grid-cols-3 gap-4">
<div v-for="(count, index) in configForm.groupViewCounts" :key="index"
class="rounded-xl border border-gray-200 bg-white p-4">
<div class="flex items-center justify-between gap-2">
<div>
<div class="text-sm font-medium text-gray-800"> {{ index + 1 }} </div>
<div class="mt-1 text-xs text-gray-500">{{ groupRangeLabel(index) }}</div>
</div>
<span class="px-2 py-1 rounded-full text-[10px] bg-gray-100 text-gray-600">
{{ groupViewText(count) }}
</span>
</div>
<input v-model.number="configForm.groupViewCounts[index]" :disabled="isStarting || !isElectronEnv"
type="number" min="0" max="3" step="1"
class="mt-4 h-11 w-full rounded-lg border border-gray-300 bg-gray-50 px-4 text-sm text-gray-800 outline-none focus:border-blue-500 disabled:cursor-not-allowed disabled:opacity-60"
@blur="handleGroupCountBlur(index)" />
</div>
</div>
</section>
</div>
<div class="space-y-6">
<section class="bg-white rounded-xl border border-gray-200 shadow-sm p-5">
<div class="flex items-center justify-between">
<div>
<div class="text-sm font-medium text-gray-900">AI 人设</div>
<div class="mt-1 text-xs text-gray-500">沿用自动私信工作台的 AI 配置入口</div>
</div>
<span :class="[
'px-3 py-1 rounded-full text-xs font-medium',
aiConfigured ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'
]">
{{ aiConfigured ? '已配置' : '未配置' }}
</span>
</div>
<button @click="showAIDialog = true"
class="mt-4 w-full px-4 py-3 text-sm font-medium text-blue-600 border border-blue-200 bg-blue-50 hover:bg-blue-100 rounded-lg transition-colors">
配置 / 修改 AI 人设
</button>
</section>
<section class="bg-white rounded-xl border border-gray-200 shadow-sm p-5">
<div class="text-sm font-medium text-gray-900">执行主播库</div>
<div class="mt-1 text-xs text-gray-500">复用主播库弹窗单独维护执行名单</div>
<button @click="showHostDialog = true"
class="mt-4 w-full px-4 py-3 text-sm font-medium text-purple-600 border border-purple-200 bg-purple-50 hover:bg-purple-100 rounded-lg transition-colors">
打开执行主播库
</button>
</section>
<section class="bg-slate-950 rounded-xl shadow-sm p-5">
<div class="text-sm font-medium text-slate-100">本次提交参数</div>
<pre class="mt-3 overflow-x-auto text-xs leading-6 text-slate-300">{{ payloadPreview }}</pre>
</section>
</div>
</div>
<div v-if="!isElectronEnv"
class="mt-6 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-xs leading-5 text-amber-700">
当前是 Web 环境无法调用 Electron TikTok 自动私信能力
</div>
</div>
</div>
</div>
<div v-else class="flex h-full w-full bg-gradient-to-br from-gray-50 to-gray-100 animate-fadeIn">
<aside :style="sidebarStyle"
class="h-full bg-white border-r border-gray-200 flex flex-col shadow-sm flex-shrink-0">
<div class="m-3 mb-0 flex gap-2">
<button @click="backToConfig"
class="flex-1 px-3 py-2 text-xs bg-gray-100 text-gray-700 border border-gray-200 rounded-lg hover:bg-gray-200 transition-colors text-left">
返回配置
</button>
<button @click="handleStop" :disabled="isStopping || !isElectronEnv"
class="px-3 py-2 text-xs bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="停止独立版自动私信任务">
{{ isStopping ? '停止中' : '停止任务' }}
</button>
</div>
<div class="p-4 border-b border-gray-200">
<h1 class="text-lg font-bold bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-transparent">
自动私信TK版
</h1>
<p class="text-xs text-gray-500 mt-1">浏览器视图页位置与 BrowserView 完全对齐</p>
</div>
<div class="p-4 space-y-4 overflow-auto">
<div v-if="false" class="rounded-xl border border-gray-200 bg-gray-50 p-4">
<div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">任务摘要</div>
<div class="grid grid-cols-2 gap-3 text-sm">
<div class="rounded-lg bg-white border border-gray-200 px-3 py-2">
<div class="text-[10px] text-gray-500">当前视图</div>
<div class="mt-1 font-semibold text-gray-800">{{ activeViewId }}</div>
</div>
<div class="rounded-lg bg-white border border-gray-200 px-3 py-2">
<div class="text-[10px] text-gray-500">任务状态</div>
<div class="mt-1 font-semibold text-gray-800">{{ statusText }}</div>
</div>
<div class="rounded-lg bg-white border border-gray-200 px-3 py-2">
<div class="text-[10px] text-gray-500">发送话术</div>
<div class="mt-1 font-semibold text-gray-800">{{ greetingCount }} </div>
</div>
<div class="rounded-lg bg-white border border-gray-200 px-3 py-2">
<div class="text-[10px] text-gray-500">AI 回复</div>
<div class="mt-1 font-semibold text-gray-800">{{ configForm.replyUnreadMessages ? '开启' : '关闭' }}</div>
</div>
</div>
</div>
<div class="rounded-xl border border-gray-200 bg-gray-50 p-4">
<div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">视图分组</div>
<div class="space-y-3">
<div v-for="group in browserViewGroups" :key="group.groupIndex"
class="rounded-lg border border-gray-200 bg-white p-3">
<div class="mb-2 flex items-center justify-between">
<span class="text-sm font-medium text-gray-800"> {{ group.groupIndex + 1 }} </span>
<span class="text-xs text-gray-500">{{ group.label }}</span>
</div>
<div class="grid grid-cols-3 gap-2">
<button v-for="view in group.views" :key="view.viewId"
:disabled="!view.enabled || isSwitchingView || !isElectronEnv"
@click="handleSwitchView(view.viewId)" :class="[
'h-10 rounded-lg border text-sm font-medium transition-all disabled:cursor-not-allowed',
view.active
? 'border-blue-500 bg-blue-500 text-white shadow-sm'
: view.enabled
? 'border-gray-200 bg-gray-50 text-gray-700 hover:border-blue-300 hover:bg-blue-50'
: 'border-dashed border-gray-200 bg-gray-100 text-gray-400 opacity-70'
]">
{{ view.viewId }}
</button>
</div>
</div>
</div>
</div>
<div class="rounded-xl border border-gray-200 bg-gray-50 p-4">
<div class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">快速操作</div>
<div class="space-y-3">
<label
class="flex items-center gap-3 rounded-lg border border-gray-200 bg-white px-3 py-3 text-sm text-gray-700">
<input v-model="loginConfirmed" :disabled="isStarting || !isElectronEnv" type="checkbox"
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
<span>当前视图已完成手动登录</span>
</label>
<button
class="w-full h-9 rounded-lg border border-blue-600 bg-blue-600 text-sm text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60"
:disabled="isStarting || !isElectronEnv || !loginConfirmed" @click="handleStart">
{{ isStarting ? '启动中...' : '启动任务' }}
</button>
<div class="text-xs leading-5 text-gray-500">
先打开浏览器视图在当前视图里手动登录账号确认登录完成后再勾选并启动脚本
</div>
</div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col relative min-w-0">
<div class="h-12 bg-white border-b border-gray-200 flex items-center px-4 gap-2 shadow-sm">
<span class="text-gray-500 text-sm mr-2">视图:</span>
<button v-for="viewId in viewIds" :key="viewId" @click="handleSwitchView(viewId)"
:disabled="isSwitchingView || !isElectronEnv" :class="[
'px-3 py-1.5 rounded-lg text-sm font-medium transition-all disabled:opacity-60 disabled:cursor-not-allowed',
activeViewId === viewId
? 'bg-blue-500 text-white shadow-md'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 border border-gray-200'
]">
视图 {{ viewId }}
</button>
<div class="flex-1" />
<span class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded border border-gray-200">
{{ statusText }}
</span>
<span v-if="isSwitchingView"
class="text-xs text-blue-600 bg-blue-50 px-2 py-1 rounded border border-blue-200">
切换中...
</span>
</div>
<div class="flex-1 relative">
<ViewPlaceholder class="absolute inset-0" />
</div>
</main>
</div>
<AIConfigDialog :visible="showAIDialog" :config="aiConfig" @close="showAIDialog = false" @save="handleSaveAIConfig"
@change="(key, value) => aiConfig[key] = value" />
<HostListDialog :visible="showHostDialog" @close="showHostDialog = false" @save="() => { }" />
</div>
</template>
<script setup>
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { isElectron } from '@/utils/electronBridge'
import ViewPlaceholder from '@/components/ViewPlaceholder.vue'
import HostListDialog from '@/components/HostListDialog.vue'
import AIConfigDialog from '@/components/AIConfigDialog.vue'
const props = defineProps({
navSidebarWidth: {
type: Number,
default: 144
}
})
const TIKTOK_VIEW_IDS = Array.from({ length: 9 }, (_, index) => index + 10)
const DEFAULT_GREETING_TEXT = 'hello\nhi'
const DEFAULT_GROUP_COUNTS = [3, 3, 3]
const isElectronEnv = isElectron()
const pageMode = ref('config')
const activeViewId = ref(TIKTOK_VIEW_IDS[0])
const statusText = ref('待启动')
const showAIDialog = ref(false)
const showHostDialog = ref(false)
const aiConfigured = ref(false)
const loginConfirmed = ref(false)
const isPreparing = ref(false)
const isStarting = ref(false)
const isStopping = ref(false)
const isSwitchingView = ref(false)
const configForm = reactive({
greetingText: DEFAULT_GREETING_TEXT,
replyUnreadMessages: true,
groupSwitchMinutes: 10,
groupViewCounts: [...DEFAULT_GROUP_COUNTS]
})
const aiConfig = ref({
agentName: '',
guildName: '',
contactTool: '',
contact: ''
})
const sidebarStyle = computed(() => ({
width: `${props.navSidebarWidth}px`,
minWidth: '96px',
maxWidth: '400px'
}))
const greetingMessages = computed(() => {
return configForm.greetingText
.split(/\r?\n/)
.map(item => item.trim())
.filter(Boolean)
})
const greetingCount = computed(() => greetingMessages.value.length)
const normalizedGroupSwitchMinutes = computed(() => clampMinutes(configForm.groupSwitchMinutes))
const normalizedGroupViewCounts = computed(() => normalizeGroupViewCounts(configForm.groupViewCounts))
const browserViewGroups = computed(() =>
normalizedGroupViewCounts.value.map((enabledCount, groupIndex) => {
const baseViewId = TIKTOK_VIEW_IDS[groupIndex * 3]
return {
groupIndex,
label: `视图 ${baseViewId}-${baseViewId + 2}`,
views: Array.from({ length: 3 }, (_, offset) => {
const viewId = baseViewId + offset
return {
viewId,
enabled: offset < enabledCount,
active: activeViewId.value === viewId
}
})
}
})
)
const viewIds = computed(() =>
browserViewGroups.value.flatMap(group =>
group.views.filter(view => view.enabled).map(view => view.viewId)
)
)
const totalEnabledViews = computed(() => normalizedGroupViewCounts.value.reduce((sum, count) => sum + count, 0))
const payloadPreview = computed(() => JSON.stringify(buildStartPayload(), null, 2))
const statusChipClass = computed(() => {
if (statusText.value.includes('运行')) {
return 'px-3 py-1 rounded-full text-xs font-medium border bg-emerald-50 text-emerald-700 border-emerald-200'
}
if (statusText.value.includes('失败')) {
return 'px-3 py-1 rounded-full text-xs font-medium border bg-rose-50 text-rose-700 border-rose-200'
}
return 'px-3 py-1 rounded-full text-xs font-medium border bg-amber-50 text-amber-700 border-amber-200'
})
function clampMinutes(value) {
const numericValue = Number(value)
if (!Number.isFinite(numericValue) || numericValue <= 0) {
return 10
}
return Math.max(1, Math.round(numericValue))
}
function clampGroupCount(value) {
const numericValue = Number(value)
if (!Number.isFinite(numericValue)) {
return 0
}
return Math.min(3, Math.max(0, Math.round(numericValue)))
}
function normalizeGroupViewCounts(values) {
const safeValues = Array.isArray(values) ? values.slice(0, 3) : []
while (safeValues.length < 3) {
safeValues.push(0)
}
return safeValues.map(clampGroupCount)
}
function buildStartPayload() {
const payload = {
replyUnreadMessages: Boolean(configForm.replyUnreadMessages),
groupSwitchMinutes: normalizedGroupSwitchMinutes.value,
groupViewCounts: normalizedGroupViewCounts.value
}
if (greetingMessages.value.length > 0) {
payload.greetingMessages = greetingMessages.value
}
return payload
}
function ensureElectronCapability(methodName) {
if (!isElectronEnv || !window.electronAPI?.[methodName]) {
ElMessage.error('当前环境不支持该功能')
return false
}
return true
}
function handleGreetingBlur() {
configForm.greetingText = greetingMessages.value.join('\n')
}
function handleSwitchMinutesBlur() {
configForm.groupSwitchMinutes = clampMinutes(configForm.groupSwitchMinutes)
}
function handleGroupCountBlur(index) {
configForm.groupViewCounts[index] = clampGroupCount(configForm.groupViewCounts[index])
}
function groupViewText(count) {
if (count <= 0) {
return '不启用'
}
return `开启 ${count}`
}
function groupRangeLabel(index) {
const startViewId = TIKTOK_VIEW_IDS[index * 3]
const endViewId = TIKTOK_VIEW_IDS[index * 3 + 2]
return `视图 ${startViewId}-${endViewId}`
}
async function checkAIConfig() {
if (!isElectronEnv || !window.electronAPI?.loadAIConfig) return
try {
const saved = await window.electronAPI.loadAIConfig()
if (saved && (saved.agentName || saved.guildName || saved.contactTool || saved.contact)) {
aiConfig.value = saved
aiConfigured.value = true
return
}
aiConfigured.value = false
} catch {
aiConfigured.value = false
}
}
async function handleSaveAIConfig() {
if (isElectronEnv && window.electronAPI?.saveAIConfig) {
await window.electronAPI.saveAIConfig(JSON.parse(JSON.stringify(aiConfig.value)))
}
aiConfigured.value = true
showAIDialog.value = false
}
async function handlePrepareViews() {
if (!ensureElectronCapability('prepareStandaloneTikTokViews')) return
loginConfirmed.value = false
isPreparing.value = true
try {
const result = await window.electronAPI.prepareStandaloneTikTokViews({
groupViewCounts: normalizedGroupViewCounts.value,
targetViewId: activeViewId.value
})
if (!result?.success) {
throw new Error(result?.error || 'prepare standalone tiktok views failed')
}
if (result.currentViewId) {
activeViewId.value = result.currentViewId
}
pageMode.value = 'browser'
await window.electronAPI.showViews()
statusText.value = `请先在视图 ${activeViewId.value} 手动登录,完成后再启动脚本`
ElMessage.success('视图预热完成,已切换到浏览器视图')
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
ElMessage.error(`预热视图失败:${message}`)
} finally {
isPreparing.value = false
}
}
async function openBrowserView() {
if (!ensureElectronCapability('switchToView')) return
pageMode.value = 'browser'
try {
await window.electronAPI.switchToView(activeViewId.value)
await window.electronAPI.showViews()
if (!statusText.value.includes('运行')) {
statusText.value = `已打开视图 ${activeViewId.value},如未预热请先返回工作台执行预热视图`
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
ElMessage.error(`打开浏览器视图失败:${message}`)
}
}
async function backToConfig() {
loginConfirmed.value = false
pageMode.value = 'config'
if (!isElectronEnv || !window.electronAPI?.hideViews) return
try {
await window.electronAPI.hideViews()
} catch (error) {
console.error('hide views failed:', error)
}
}
async function handleSwitchView(viewId) {
if (!ensureElectronCapability('switchToView')) return
isSwitchingView.value = true
try {
await window.electronAPI.switchToView(viewId)
loginConfirmed.value = false
activeViewId.value = viewId
statusText.value = `已切换到视图 ${viewId},请先手动登录再启动脚本`
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
statusText.value = '切换失败'
ElMessage.error(`切换视图失败:${message}`)
} finally {
isSwitchingView.value = false
}
}
async function handleStart() {
if (!ensureElectronCapability('startStandaloneTikTokAutomationAll')) return
if (!loginConfirmed.value) {
ElMessage.warning('请先在当前视图手动登录,并勾选“已完成登录”后再启动脚本')
return
}
isStarting.value = true
try {
await window.electronAPI.switchToView(activeViewId.value)
await window.electronAPI.showViews()
const result = await window.electronAPI.startStandaloneTikTokAutomationAll(buildStartPayload())
if (!result?.success) {
statusText.value = '启动失败'
ElMessage.error(result?.error || '启动自动私信TK版失败')
return
}
statusText.value = `运行中(视图 ${activeViewId.value}`
ElMessage.success('自动私信TK版已启动')
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
statusText.value = '启动失败'
ElMessage.error(`启动失败:${message}`)
} finally {
isStarting.value = false
}
}
async function handleStop() {
if (!ensureElectronCapability('stopStandaloneTikTokAutomationAll')) return
isStopping.value = true
try {
const result = await window.electronAPI.stopStandaloneTikTokAutomationAll()
if (!result?.success) {
statusText.value = '停止失败'
ElMessage.error(result?.error || '停止自动私信TK版失败')
return
}
statusText.value = '已停止'
ElMessage.success('自动私信TK版已停止')
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
statusText.value = '停止失败'
ElMessage.error(`停止失败:${message}`)
} finally {
isStopping.value = false
}
}
watch(viewIds, (nextViewIds) => {
if (nextViewIds.length === 0) {
activeViewId.value = TIKTOK_VIEW_IDS[0]
return
}
if (!nextViewIds.includes(activeViewId.value)) {
activeViewId.value = nextViewIds[0]
}
}, { immediate: true })
watch(pageMode, async (newVal) => {
if (!isElectronEnv) return
if (newVal === 'config' && window.electronAPI?.hideViews) {
await window.electronAPI.hideViews().catch(() => { })
}
})
onMounted(async () => {
await checkAIConfig()
if (!isElectronEnv || !window.electronAPI?.hideViews) return
await window.electronAPI.hideViews().catch(() => { })
})
onUnmounted(async () => {
if (!isElectronEnv || !window.electronAPI?.hideViews) return
await window.electronAPI.hideViews().catch(() => { })
})
</script>