定制大哥指定数量,优化tk版弹窗遮罩

This commit is contained in:
2026-04-20 10:18:23 +08:00
parent 5f45cde984
commit 33586d3f80
2 changed files with 122 additions and 52 deletions

View File

@@ -9,16 +9,19 @@
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span :class="statusChipClass">{{ statusText }}</span> <span :class="statusChipClass">{{ statusText }}</span>
<button @click="handlePrepareViews" :disabled="isPreparing || !isElectronEnv" class="flex items-center gap-2 rounded-lg bg-gradient-to-r from-emerald-500 to-teal-500 px-4 py-2 text-sm text-white shadow-sm transition-all hover:from-emerald-600 hover:to-teal-600 disabled:cursor-not-allowed disabled:opacity-60"> <button @click="handlePrepareViews" :disabled="isPreparing || !isElectronEnv"
class="flex items-center gap-2 rounded-lg bg-gradient-to-r from-emerald-500 to-teal-500 px-4 py-2 text-sm text-white shadow-sm transition-all hover:from-emerald-600 hover:to-teal-600 disabled:cursor-not-allowed disabled:opacity-60">
<span>{{ isPreparing ? '预热中...' : '预热视图' }}</span> <span>{{ isPreparing ? '预热中...' : '预热视图' }}</span>
</button> </button>
<button @click="openBrowserView" class="flex items-center gap-2 rounded-lg bg-gradient-to-r from-blue-500 to-cyan-500 px-4 py-2 text-sm text-white shadow-sm transition-all hover:from-blue-600 hover:to-cyan-600"> <button @click="openBrowserView"
class="flex items-center gap-2 rounded-lg bg-gradient-to-r from-blue-500 to-cyan-500 px-4 py-2 text-sm text-white shadow-sm transition-all hover:from-blue-600 hover:to-cyan-600">
<span>打开浏览器视图</span> <span>打开浏览器视图</span>
</button> </button>
</div> </div>
</div> </div>
<div class="mb-6 flex w-fit items-center gap-2 rounded-full border border-blue-200 bg-gradient-to-r from-blue-50 to-green-50 px-4 py-2 text-sm text-gray-700"> <div
class="mb-6 flex w-fit items-center gap-2 rounded-full border border-blue-200 bg-gradient-to-r from-blue-50 to-green-50 px-4 py-2 text-sm text-gray-700">
<span class="h-2 w-2 animate-pulse rounded-full bg-green-500" /> <span class="h-2 w-2 animate-pulse rounded-full bg-green-500" />
先填写配置再预热视图登录完成后再启动脚本 先填写配置再预热视图登录完成后再启动脚本
</div> </div>
@@ -53,24 +56,40 @@
<div> <div>
<label class="mb-1 block text-sm font-medium text-gray-700">打招呼内容</label> <label class="mb-1 block text-sm font-medium text-gray-700">打招呼内容</label>
</div> </div>
<button @click="showGreetingDialog = true" :disabled="isStarting || !isElectronEnv" class="rounded-lg border border-blue-200 bg-blue-50 px-4 py-2 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-100 disabled:cursor-not-allowed disabled:opacity-60"> <button @click="showGreetingDialog = true" :disabled="isStarting || !isElectronEnv"
class="rounded-lg border border-blue-200 bg-blue-50 px-4 py-2 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-100 disabled:cursor-not-allowed disabled:opacity-60">
打招呼内容配置 打招呼内容配置
</button> </button>
</div> </div>
<div class="mt-3 flex items-center gap-3 text-xs text-gray-500"> <div class="mt-3 flex items-center gap-3 text-xs text-gray-500">
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">基础话术 {{ greetingCount }} </span> <span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">基础话术 {{ greetingCount }}
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">翻译 {{ configForm.needTranslate ? '已开启' : '未开启' }}</span> </span>
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">语种 {{ languageCount }}</span> <span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">翻译 {{
configForm.needTranslate ? '已开启' : '未开启' }}</span>
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1">语种 {{ languageCount
}}</span>
</div> </div>
</div> </div>
<div class="grid grid-cols-[minmax(0,1fr),220px] gap-4 items-end"> <div class="grid grid-cols-1 gap-4 md:grid-cols-[minmax(0,1fr),minmax(0,1fr),220px] items-end">
<div> <div>
<label class="mb-2 block text-sm font-medium text-gray-700">分组轮换间隔分钟</label> <label class="mb-2 block text-sm font-medium text-gray-700">分组轮换间隔分钟</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" /> <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> </div>
<label class="flex h-11 items-center gap-3 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-700"> <div>
<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" /> <label class="mb-2 block text-sm font-medium text-gray-700">每用户休眠秒数</label>
<input v-model.number="configForm.postUserSleepSeconds" :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="handlePostUserSleepBlur" />
</div>
<label
class="flex h-11 items-center gap-3 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3 text-sm text-gray-700">
<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> <span>开启 AI 回复未读消息</span>
</label> </label>
</div> </div>
@@ -81,24 +100,32 @@
<label class="mb-1 block text-sm font-medium text-gray-700">数据池来源</label> <label class="mb-1 block text-sm font-medium text-gray-700">数据池来源</label>
<p class="text-xs text-gray-500">启动时只需告诉后端用主播池还是大哥池</p> <p class="text-xs text-gray-500">启动时只需告诉后端用主播池还是大哥池</p>
</div> </div>
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1 text-xs text-gray-600">{{ dataPoolLabel }}</span> <span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1 text-xs text-gray-600">{{
dataPoolLabel }}</span>
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<label class="flex items-center gap-3 rounded-lg border px-4 py-3 text-sm transition-colors" :class="configForm.dataPoolSource === 'anchor_hosts' ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-gray-200 bg-gray-50 text-gray-700'"> <label class="flex items-center gap-3 rounded-lg border px-4 py-3 text-sm transition-colors"
<input v-model="configForm.dataPoolSource" :disabled="isStarting || !isElectronEnv" type="radio" value="anchor_hosts" class="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500" /> :class="configForm.dataPoolSource === 'anchor_hosts' ? 'border-blue-500 bg-blue-50 text-blue-700' : 'border-gray-200 bg-gray-50 text-gray-700'">
<input v-model="configForm.dataPoolSource" :disabled="isStarting || !isElectronEnv" type="radio"
value="anchor_hosts" class="h-4 w-4 border-gray-300 text-blue-600 focus:ring-blue-500" />
<span>主播池</span> <span>主播池</span>
</label> </label>
<label class="flex items-center gap-3 rounded-lg border px-4 py-3 text-sm transition-colors" :class="configForm.dataPoolSource === 'brother_info' ? 'border-purple-500 bg-purple-50 text-purple-700' : 'border-gray-200 bg-gray-50 text-gray-700'"> <label class="flex items-center gap-3 rounded-lg border px-4 py-3 text-sm transition-colors"
<input v-model="configForm.dataPoolSource" :disabled="isStarting || !isElectronEnv" type="radio" value="brother_info" class="h-4 w-4 border-gray-300 text-purple-600 focus:ring-purple-500" /> :class="configForm.dataPoolSource === 'brother_info' ? 'border-purple-500 bg-purple-50 text-purple-700' : 'border-gray-200 bg-gray-50 text-gray-700'">
<input v-model="configForm.dataPoolSource" :disabled="isStarting || !isElectronEnv" type="radio"
value="brother_info" class="h-4 w-4 border-gray-300 text-purple-600 focus:ring-purple-500" />
<span>大哥池</span> <span>大哥池</span>
</label> </label>
</div> </div>
<div v-if="isBrotherInfoMode" class="mt-4"> <div v-if="isBrotherInfoMode" class="mt-4">
<div class="mb-2 flex items-center justify-between gap-3"> <div class="mb-2 flex items-center justify-between gap-3">
<label class="block text-sm font-medium text-gray-700">大哥随机回复话术</label> <label class="block text-sm font-medium text-gray-700">大哥随机回复话术</label>
<span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1 text-xs text-gray-600">{{ replyMessageCount }} </span> <span class="rounded-full border border-gray-200 bg-gray-100 px-3 py-1 text-xs text-gray-600">{{
replyMessageCount }} </span>
</div> </div>
<textarea v-model="configForm.replyMessagesText" :disabled="isStarting || !isElectronEnv" rows="5" placeholder="一行一条回复话术,启动时传给后端 replyMessages" class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-sm text-gray-800 outline-none focus:border-purple-500 disabled:cursor-not-allowed disabled:opacity-60" /> <textarea v-model="configForm.replyMessagesText" :disabled="isStarting || !isElectronEnv" rows="5"
placeholder="一行一条回复话术,启动时传给后端 replyMessages"
class="w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-sm text-gray-800 outline-none focus:border-purple-500 disabled:cursor-not-allowed disabled:opacity-60" />
</div> </div>
</div> </div>
</div> </div>
@@ -113,15 +140,20 @@
<span class="text-xs text-gray-500">每组支持 0-3 个视图</span> <span class="text-xs text-gray-500">每组支持 0-3 个视图</span>
</div> </div>
<div class="grid grid-cols-3 gap-4"> <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 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 class="flex items-center justify-between gap-2">
<div> <div>
<div class="text-sm font-medium text-gray-800"> {{ index + 1 }} </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 class="mt-1 text-xs text-gray-500">{{ groupRangeLabel(index) }}</div>
</div> </div>
<span class="rounded-full bg-gray-100 px-2 py-1 text-[10px] text-gray-600">{{ groupViewText(count) }}</span> <span class="rounded-full bg-gray-100 px-2 py-1 text-[10px] text-gray-600">{{ groupViewText(count)
}}</span>
</div> </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)" /> <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>
</div> </div>
</section> </section>
@@ -130,15 +162,22 @@
<div class="space-y-6"> <div class="space-y-6">
<section class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm"> <section class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div><div class="text-sm font-medium text-gray-900">AI 人设</div></div> <div>
<span :class="['rounded-full px-3 py-1 text-xs font-medium', aiConfigured ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700']">{{ aiConfigured ? '已配置' : '未配置' }}</span> <div class="text-sm font-medium text-gray-900">AI 人设</div>
</div> </div>
<button @click="showAIDialog = true" class="mt-4 w-full rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-100">配置 / 修改 AI 人设</button> <span
:class="['rounded-full px-3 py-1 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 rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-100">配置
/ 修改 AI 人设</button>
</section> </section>
<section class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm"> <section class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm">
<div class="text-sm font-medium text-gray-900">执行主播库</div> <div class="text-sm font-medium text-gray-900">执行主播库</div>
<button @click="showHostDialog = true" class="mt-4 w-full rounded-lg border border-purple-200 bg-purple-50 px-4 py-3 text-sm font-medium text-purple-600 transition-colors hover:bg-purple-100">打开执行主播库</button> <button @click="showHostDialog = true"
class="mt-4 w-full rounded-lg border border-purple-200 bg-purple-50 px-4 py-3 text-sm font-medium text-purple-600 transition-colors hover:bg-purple-100">打开执行主播库</button>
</section> </section>
<section class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm"> <section class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm">
@@ -147,9 +186,12 @@
<div class="text-sm font-medium text-gray-900">大哥池</div> <div class="text-sm font-medium text-gray-900">大哥池</div>
<p class="mt-1 text-xs leading-5 text-gray-500">参考 brother_info 数据池维护大哥列表并支持切换为大哥池模式启动任务</p> <p class="mt-1 text-xs leading-5 text-gray-500">参考 brother_info 数据池维护大哥列表并支持切换为大哥池模式启动任务</p>
</div> </div>
<span :class="['rounded-full px-3 py-1 text-xs font-medium', isBrotherInfoMode ? 'bg-fuchsia-100 text-fuchsia-700' : 'bg-gray-100 text-gray-600']">{{ isBrotherInfoMode ? '当前使用中' : '可切换' }}</span> <span
:class="['rounded-full px-3 py-1 text-xs font-medium', isBrotherInfoMode ? 'bg-fuchsia-100 text-fuchsia-700' : 'bg-gray-100 text-gray-600']">{{
isBrotherInfoMode ? '当前使用中' : '可切换' }}</span>
</div> </div>
<button @click="showBrotherInfoDialog = true" class="mt-4 w-full rounded-lg border border-fuchsia-200 bg-fuchsia-50 px-4 py-3 text-sm font-medium text-fuchsia-600 transition-colors hover:bg-fuchsia-100">打开大哥池</button> <button @click="showBrotherInfoDialog = true"
class="mt-4 w-full rounded-lg border border-fuchsia-200 bg-fuchsia-50 px-4 py-3 text-sm font-medium text-fuchsia-600 transition-colors hover:bg-fuchsia-100">打开大哥池</button>
</section> </section>
<section class="rounded-xl bg-slate-950 p-5 shadow-sm"> <section class="rounded-xl bg-slate-950 p-5 shadow-sm">
@@ -159,7 +201,9 @@
</div> </div>
</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 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>
</div> </div>
@@ -167,24 +211,33 @@
<div v-else class="flex h-full w-full bg-gradient-to-br from-gray-50 to-gray-100 animate-fadeIn"> <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 flex-shrink-0 border-r border-gray-200 bg-white shadow-sm"> <aside :style="sidebarStyle" class="h-full flex-shrink-0 border-r border-gray-200 bg-white shadow-sm">
<div class="m-3 mb-0 flex gap-2"> <div class="m-3 mb-0 flex gap-2">
<button @click="backToConfig" class="flex-1 rounded-lg border border-gray-200 bg-gray-100 px-3 py-2 text-left text-xs text-gray-700 transition-colors hover:bg-gray-200">返回配置</button> <button @click="backToConfig"
<button @click="handleStop" :disabled="isStopping || !isElectronEnv" class="rounded-lg bg-red-500 px-3 py-2 text-xs text-white transition-colors hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50">{{ isStopping ? '停止中' : '停止任务' }}</button> class="flex-1 rounded-lg border border-gray-200 bg-gray-100 px-3 py-2 text-left text-xs text-gray-700 transition-colors hover:bg-gray-200">返回配置</button>
<button @click="handleStop" :disabled="isStopping || !isElectronEnv"
class="rounded-lg bg-red-500 px-3 py-2 text-xs text-white transition-colors hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50">{{
isStopping ? '停止中' : '停止任务' }}</button>
</div> </div>
<div class="border-b border-gray-200 p-4"> <div class="border-b border-gray-200 p-4">
<h1 class="bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-lg font-bold text-transparent">自动私信TK版</h1> <h1 class="bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-lg font-bold text-transparent">
自动私信TK版</h1>
<p class="mt-1 text-xs text-gray-500">浏览器视图页位置与 BrowserView 完全对齐</p> <p class="mt-1 text-xs text-gray-500">浏览器视图页位置与 BrowserView 完全对齐</p>
</div> </div>
<div class="space-y-4 overflow-auto p-4"> <div class="space-y-4 overflow-auto p-4">
<div class="rounded-xl border border-gray-200 bg-gray-50 p-4"> <div class="rounded-xl border border-gray-200 bg-gray-50 p-4">
<div class="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-500">视图分组</div> <div class="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-500">视图分组</div>
<div class="space-y-3"> <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 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"> <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-sm font-medium text-gray-800"> {{ group.groupIndex + 1 }} </span>
<span class="text-xs text-gray-500">{{ group.label }}</span> <span class="text-xs text-gray-500">{{ group.label }}</span>
</div> </div>
<div class="grid grid-cols-3 gap-2"> <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> <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> </div>
@@ -192,11 +245,16 @@
<div class="rounded-xl border border-gray-200 bg-gray-50 p-4"> <div class="rounded-xl border border-gray-200 bg-gray-50 p-4">
<div class="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-500">快速操作</div> <div class="mb-3 text-xs font-semibold uppercase tracking-wider text-gray-500">快速操作</div>
<div class="space-y-3"> <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"> <label
<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" /> 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> <span>当前视图已完成手动登录</span>
</label> </label>
<button class="h-9 w-full 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" @click="handleStart">{{ isStarting ? '启动中...' : '启动任务' }}</button> <button
class="h-9 w-full 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" @click="handleStart">{{ isStarting ? '启动中...' : '启动任务'
}}</button>
<div class="text-xs leading-5 text-gray-500">先打开浏览器视图在当前视图里手动登录账号确认登录完成后再勾选并启动脚本</div> <div class="text-xs leading-5 text-gray-500">先打开浏览器视图在当前视图里手动登录账号确认登录完成后再勾选并启动脚本</div>
</div> </div>
</div> </div>
@@ -206,10 +264,15 @@
<main class="relative flex min-w-0 flex-1 flex-col"> <main class="relative flex min-w-0 flex-1 flex-col">
<div class="flex h-12 items-center gap-2 border-b border-gray-200 bg-white px-4 shadow-sm"> <div class="flex h-12 items-center gap-2 border-b border-gray-200 bg-white px-4 shadow-sm">
<span class="mr-2 text-sm text-gray-500">视图:</span> <span class="mr-2 text-sm text-gray-500">视图:</span>
<button v-for="viewId in viewIds" :key="viewId" @click="handleSwitchView(viewId)" :disabled="isSwitchingView || !isElectronEnv" :class="['rounded-lg px-3 py-1.5 text-sm font-medium transition-all disabled:cursor-not-allowed disabled:opacity-60', activeViewId === viewId ? 'bg-blue-500 text-white shadow-md' : 'border border-gray-200 bg-gray-100 text-gray-600 hover:bg-gray-200']">视图 {{ viewId }}</button> <button v-for="viewId in viewIds" :key="viewId" @click="handleSwitchView(viewId)"
:disabled="isSwitchingView || !isElectronEnv"
:class="['rounded-lg px-3 py-1.5 text-sm font-medium transition-all disabled:cursor-not-allowed disabled:opacity-60', activeViewId === viewId ? 'bg-blue-500 text-white shadow-md' : 'border border-gray-200 bg-gray-100 text-gray-600 hover:bg-gray-200']">视图
{{ viewId }}</button>
<div class="flex-1" /> <div class="flex-1" />
<span class="rounded border border-gray-200 bg-gray-100 px-2 py-1 text-xs text-gray-500">{{ statusText }}</span> <span class="rounded border border-gray-200 bg-gray-100 px-2 py-1 text-xs text-gray-500">{{ statusText
<span v-if="isSwitchingView" class="rounded border border-blue-200 bg-blue-50 px-2 py-1 text-xs text-blue-600">切换中...</span> }}</span>
<span v-if="isSwitchingView"
class="rounded border border-blue-200 bg-blue-50 px-2 py-1 text-xs text-blue-600">切换中...</span>
</div> </div>
<div class="relative flex-1"> <div class="relative flex-1">
<ViewPlaceholder class="absolute inset-0" /> <ViewPlaceholder class="absolute inset-0" />
@@ -217,10 +280,12 @@
</main> </main>
</div> </div>
<AIConfigDialog :visible="showAIDialog" :config="aiConfig" @close="showAIDialog = false" @save="handleSaveAIConfig" @change="(key, value) => aiConfig[key] = value" /> <AIConfigDialog :visible="showAIDialog" :config="aiConfig" @close="showAIDialog = false" @save="handleSaveAIConfig"
<HostListDialog :visible="showHostDialog" @close="showHostDialog = false" @save="() => {}" /> @change="(key, value) => aiConfig[key] = value" />
<HostListDialog :visible="showHostDialog" @close="showHostDialog = false" @save="() => { }" />
<BrotherInfoDialog :visible="showBrotherInfoDialog" @close="showBrotherInfoDialog = false" /> <BrotherInfoDialog :visible="showBrotherInfoDialog" @close="showBrotherInfoDialog = false" />
<GreetingDialog :visible="showGreetingDialog" @close="showGreetingDialog = false" @confirm="handleGreetingConfirm" /> <GreetingDialog :visible="showGreetingDialog" @close="showGreetingDialog = false"
@confirm="handleGreetingConfirm" />
</div> </div>
</template> </template>
@@ -252,7 +317,7 @@ const isPreparing = ref(false)
const isStarting = ref(false) const isStarting = ref(false)
const isStopping = ref(false) const isStopping = ref(false)
const isSwitchingView = ref(false) const isSwitchingView = ref(false)
const configForm = reactive({ prologueList: {}, needTranslate: false, dataPoolSource: 'anchor_hosts', replyMessagesText: '', replyUnreadMessages: true, groupSwitchMinutes: 10, groupViewCounts: [...DEFAULT_GROUP_COUNTS] }) const configForm = reactive({ prologueList: {}, needTranslate: false, dataPoolSource: 'anchor_hosts', replyMessagesText: '', replyUnreadMessages: true, groupSwitchMinutes: 10, postUserSleepSeconds: 6, groupViewCounts: [...DEFAULT_GROUP_COUNTS] })
const aiConfig = ref({ agentName: '', guildName: '', contactTool: '', contact: '' }) const aiConfig = ref({ agentName: '', guildName: '', contactTool: '', contact: '' })
const sidebarStyle = computed(() => ({ width: `${props.navSidebarWidth}px`, minWidth: '96px', maxWidth: '400px' })) const sidebarStyle = computed(() => ({ width: `${props.navSidebarWidth}px`, minWidth: '96px', maxWidth: '400px' }))
const greetingMessages = computed(() => { const greetingMessages = computed(() => {
@@ -274,14 +339,16 @@ const browserViewGroups = computed(() => normalizedGroupViewCounts.value.map((en
const viewIds = computed(() => browserViewGroups.value.flatMap(group => group.views.filter(view => view.enabled).map(view => view.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 totalEnabledViews = computed(() => normalizedGroupViewCounts.value.reduce((sum, count) => sum + count, 0))
const payloadPreview = computed(() => JSON.stringify(buildStartPayload(), null, 2)) const payloadPreview = computed(() => JSON.stringify(buildStartPayload(), null, 2))
const currentPrepareKey = computed(() => JSON.stringify({ groupViewCounts: normalizedGroupViewCounts.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, replyUnreadMessages: Boolean(configForm.replyUnreadMessages), dataPoolSource: configForm.dataPoolSource, replyMessages: replyMessages.value, prologueList: configForm.prologueList || {}, needTranslate: Boolean(configForm.needTranslate) })) const currentPrepareKey = computed(() => JSON.stringify({ groupViewCounts: normalizedGroupViewCounts.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, postUserSleepSeconds: normalizedPostUserSleepSeconds.value, replyUnreadMessages: Boolean(configForm.replyUnreadMessages), dataPoolSource: configForm.dataPoolSource, replyMessages: replyMessages.value, prologueList: configForm.prologueList || {}, needTranslate: Boolean(configForm.needTranslate) }))
const hasPreparedViews = computed(() => preparedConfigKey.value === currentPrepareKey.value) const hasPreparedViews = computed(() => preparedConfigKey.value === currentPrepareKey.value)
const statusChipClass = computed(() => statusText.value.includes('运行') ? 'rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700' : statusText.value.includes('失败') ? 'rounded-full border border-rose-200 bg-rose-50 px-3 py-1 text-xs font-medium text-rose-700' : 'rounded-full border border-amber-200 bg-amber-50 px-3 py-1 text-xs font-medium text-amber-700') const statusChipClass = computed(() => statusText.value.includes('运行') ? 'rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700' : statusText.value.includes('失败') ? 'rounded-full border border-rose-200 bg-rose-50 px-3 py-1 text-xs font-medium text-rose-700' : 'rounded-full border border-amber-200 bg-amber-50 px-3 py-1 text-xs font-medium text-amber-700')
function clampMinutes(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) || numericValue <= 0 ? 10 : Math.max(1, Math.round(numericValue)) } function clampMinutes(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) || numericValue <= 0 ? 10 : Math.max(1, Math.round(numericValue)) }
function clampPositiveSeconds(value, fallback = 6) { const numericValue = Number(value); return !Number.isFinite(numericValue) || numericValue <= 0 ? fallback : Math.max(1, Math.round(numericValue)) }
function clampGroupCount(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) ? 0 : Math.min(3, Math.max(0, Math.round(numericValue))) } function clampGroupCount(value) { const numericValue = Number(value); return !Number.isFinite(numericValue) ? 0 : 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 normalizeGroupViewCounts(values) { const safeValues = Array.isArray(values) ? values.slice(0, 3) : []; while (safeValues.length < 3) safeValues.push(0); return safeValues.map(clampGroupCount) }
const normalizedPostUserSleepSeconds = computed(() => clampPositiveSeconds(configForm.postUserSleepSeconds, 6))
function buildStartPayload() { function buildStartPayload() {
const payload = { dataPoolSource: configForm.dataPoolSource, replyUnreadMessages: Boolean(configForm.replyUnreadMessages), groupSwitchMinutes: normalizedGroupSwitchMinutes.value, groupViewCounts: normalizedGroupViewCounts.value, prologueList: JSON.parse(JSON.stringify(configForm.prologueList || {})), needTranslate: Boolean(configForm.needTranslate) } const payload = { dataPoolSource: configForm.dataPoolSource, replyUnreadMessages: Boolean(configForm.replyUnreadMessages), groupSwitchMinutes: normalizedGroupSwitchMinutes.value, postUserSleepSeconds: normalizedPostUserSleepSeconds.value, groupViewCounts: normalizedGroupViewCounts.value, prologueList: JSON.parse(JSON.stringify(configForm.prologueList || {})), needTranslate: Boolean(configForm.needTranslate) }
if (greetingMessages.value.length > 0) payload.greetingMessages = greetingMessages.value if (greetingMessages.value.length > 0) payload.greetingMessages = greetingMessages.value
if (replyMessages.value.length > 0) payload.replyMessages = replyMessages.value if (replyMessages.value.length > 0) payload.replyMessages = replyMessages.value
return payload return payload
@@ -290,6 +357,7 @@ function ensureElectronCapability(methodName) { if (!isElectronEnv || !window.el
function invalidatePreparedState(nextStatus = '配置已变更,请重新预热视图') { preparedConfigKey.value = ''; loginConfirmed.value = false; if (!statusText.value.includes('运行')) statusText.value = nextStatus } function invalidatePreparedState(nextStatus = '配置已变更,请重新预热视图') { preparedConfigKey.value = ''; loginConfirmed.value = false; if (!statusText.value.includes('运行')) statusText.value = nextStatus }
function handleGreetingConfirm(data) { configForm.prologueList = { yolo: data.sentences || [], ...(data.translations || {}) }; configForm.needTranslate = Boolean(data.needTranslate); showGreetingDialog.value = false; invalidatePreparedState(); void saveSharedConfig() } function handleGreetingConfirm(data) { configForm.prologueList = { yolo: data.sentences || [], ...(data.translations || {}) }; configForm.needTranslate = Boolean(data.needTranslate); showGreetingDialog.value = false; invalidatePreparedState(); void saveSharedConfig() }
function handleSwitchMinutesBlur() { configForm.groupSwitchMinutes = clampMinutes(configForm.groupSwitchMinutes); invalidatePreparedState() } function handleSwitchMinutesBlur() { configForm.groupSwitchMinutes = clampMinutes(configForm.groupSwitchMinutes); invalidatePreparedState() }
function handlePostUserSleepBlur() { configForm.postUserSleepSeconds = clampPositiveSeconds(configForm.postUserSleepSeconds, 6); invalidatePreparedState() }
function handleGroupCountBlur(index) { configForm.groupViewCounts[index] = clampGroupCount(configForm.groupViewCounts[index]); invalidatePreparedState('视图配置已变更,请重新预热视图') } function handleGroupCountBlur(index) { configForm.groupViewCounts[index] = clampGroupCount(configForm.groupViewCounts[index]); invalidatePreparedState('视图配置已变更,请重新预热视图') }
function groupViewText(count) { return count <= 0 ? '不启用' : `开启 ${count}` } function groupViewText(count) { return count <= 0 ? '不启用' : `开启 ${count}` }
function groupRangeLabel(index) { const startViewId = TIKTOK_VIEW_IDS[index * 3]; const endViewId = TIKTOK_VIEW_IDS[index * 3 + 2]; return `视图 ${startViewId}-${endViewId}` } function groupRangeLabel(index) { const startViewId = TIKTOK_VIEW_IDS[index * 3]; const endViewId = TIKTOK_VIEW_IDS[index * 3 + 2]; return `视图 ${startViewId}-${endViewId}` }
@@ -318,6 +386,7 @@ async function loadSharedConfig() {
if (typeof saved?.needTranslate === 'boolean') configForm.needTranslate = saved.needTranslate if (typeof saved?.needTranslate === 'boolean') configForm.needTranslate = saved.needTranslate
if (saved?.standaloneTikTokDataPoolSource === 'anchor_hosts' || saved?.standaloneTikTokDataPoolSource === 'brother_info') configForm.dataPoolSource = saved.standaloneTikTokDataPoolSource if (saved?.standaloneTikTokDataPoolSource === 'anchor_hosts' || saved?.standaloneTikTokDataPoolSource === 'brother_info') configForm.dataPoolSource = saved.standaloneTikTokDataPoolSource
if (Array.isArray(saved?.standaloneTikTokReplyMessages)) configForm.replyMessagesText = saved.standaloneTikTokReplyMessages.join('\n') if (Array.isArray(saved?.standaloneTikTokReplyMessages)) configForm.replyMessagesText = saved.standaloneTikTokReplyMessages.join('\n')
if (saved?.standaloneTikTokPostUserSleepSeconds !== undefined) configForm.postUserSleepSeconds = clampPositiveSeconds(saved.standaloneTikTokPostUserSleepSeconds, 6)
} catch (error) { } catch (error) {
console.error('load shared config failed:', error) console.error('load shared config failed:', error)
} }
@@ -331,7 +400,8 @@ async function saveSharedConfig() {
prologueList: JSON.parse(JSON.stringify(configForm.prologueList || {})), prologueList: JSON.parse(JSON.stringify(configForm.prologueList || {})),
needTranslate: Boolean(configForm.needTranslate), needTranslate: Boolean(configForm.needTranslate),
standaloneTikTokDataPoolSource: configForm.dataPoolSource, standaloneTikTokDataPoolSource: configForm.dataPoolSource,
standaloneTikTokReplyMessages: replyMessages.value standaloneTikTokReplyMessages: replyMessages.value,
standaloneTikTokPostUserSleepSeconds: normalizedPostUserSleepSeconds.value
} }
await window.electronAPI.saveRunConfig(nextConfig) await window.electronAPI.saveRunConfig(nextConfig)
} catch (error) { } catch (error) {
@@ -352,7 +422,7 @@ async function handlePrepareViews() {
if (result.currentViewId) activeViewId.value = result.currentViewId if (result.currentViewId) activeViewId.value = result.currentViewId
preparedConfigKey.value = currentPrepareKey.value preparedConfigKey.value = currentPrepareKey.value
await saveSharedConfig() await saveSharedConfig()
pageMode.value = 'browser' // pageMode.value = 'browser'
await window.electronAPI.showViews() await window.electronAPI.showViews()
statusText.value = `视图已预热,请先在视图 ${activeViewId.value} 手动登录,再启动脚本` statusText.value = `视图已预热,请先在视图 ${activeViewId.value} 手动登录,再启动脚本`
ElMessage.success('视图预热完成,已切换到浏览器视图') ElMessage.success('视图预热完成,已切换到浏览器视图')
@@ -481,16 +551,16 @@ watch(viewIds, (nextViewIds) => {
}, { immediate: true }) }, { immediate: true })
watch(pageMode, async (newVal) => { watch(pageMode, async (newVal) => {
if (!isElectronEnv) return if (!isElectronEnv) return
if (newVal === 'config' && window.electronAPI?.hideViews) await window.electronAPI.hideViews().catch(() => {}) if (newVal === 'config' && window.electronAPI?.hideViews) await window.electronAPI.hideViews().catch(() => { })
}) })
onMounted(async () => { onMounted(async () => {
await checkAIConfig() await checkAIConfig()
await loadSharedConfig() await loadSharedConfig()
if (!isElectronEnv || !window.electronAPI?.hideViews) return if (!isElectronEnv || !window.electronAPI?.hideViews) return
await window.electronAPI.hideViews().catch(() => {}) await window.electronAPI.hideViews().catch(() => { })
}) })
onUnmounted(async () => { onUnmounted(async () => {
if (!isElectronEnv || !window.electronAPI?.hideViews) return if (!isElectronEnv || !window.electronAPI?.hideViews) return
await window.electronAPI.hideViews().catch(() => {}) await window.electronAPI.hideViews().catch(() => { })
}) })
</script> </script>

View File

@@ -488,7 +488,7 @@ function stopTimerfun() {
// Specify Room Logic // Specify Room Logic
// 动态计算最大行数限制tenantId=12741 为 5000 条,其他为 50 条 // 动态计算最大行数限制tenantId=12741 为 5000 条,其他为 50 条
const maxSpecifyLines = computed(() => { const maxSpecifyLines = computed(() => {
return userInfo.value.tenantId == 12384 ? 5000 : 50; return userInfo.value.tenantId == 12384 || userInfo.value.tenantId == 12741 ? 5000 : 50;
}); });
// 当前行数 // 当前行数