diff --git a/src/pages/ConfigPage.vue b/src/pages/ConfigPage.vue index 6f10e22..27b3742 100644 --- a/src/pages/ConfigPage.vue +++ b/src/pages/ConfigPage.vue @@ -404,7 +404,7 @@ const languageList = [ { name: '中文 (繁體)', code: 'zh-Hant-TW' }, { name: 'Deutsch', code: 'de-DE' }, { name: 'Italiano', code: 'it-IT' }, - { name: 'Français', code: 'fr-FR' }, + { name: 'Français', code: 'fr' }, { name: 'Română', code: 'ro-RO' }, { name: 'Polski', code: 'pl-PL' }, { name: 'Nederlands', code: 'nl-NL' }, diff --git a/src/views/auto-dm/AutoDmTkWorkbench.vue b/src/views/auto-dm/AutoDmTkWorkbench.vue index fdb3b0c..8278683 100644 --- a/src/views/auto-dm/AutoDmTkWorkbench.vue +++ b/src/views/auto-dm/AutoDmTkWorkbench.vue @@ -9,7 +9,7 @@
{{ statusText }} - @@ -26,6 +26,11 @@ 先填写配置,再预热视图,登录完成后再启动脚本。
+
+ 当前配置已锁定。停止任务成功后,才可以修改配置、重新预热并再次启动。 +
+
@@ -56,7 +61,7 @@
- @@ -67,28 +72,28 @@ 翻译 {{ configForm.needTranslate ? '已开启' : '未开启' }} 语种 {{ languageCount - }} + }}
-
-
- @@ -106,27 +111,27 @@
-
+
@@ -148,9 +153,9 @@
{{ groupRangeLabel(index) }}
{{ groupViewText(count) - }} + }} - @@ -169,7 +174,7 @@ :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 ? '已配置' : '未配置' }} - @@ -270,7 +275,7 @@ {{ viewId }}
{{ statusText - }} + }} 切换中...
@@ -313,6 +318,7 @@ const showBrotherInfoDialog = ref(false) const aiConfigured = ref(false) const loginConfirmed = ref(false) const preparedConfigKey = ref('') +const workbenchPhase = ref('idle') const isPreparing = ref(false) const isStarting = ref(false) const isStopping = ref(false) @@ -329,6 +335,7 @@ const greetingCount = computed(() => greetingMessages.value.length) const replyMessageCount = computed(() => replyMessages.value.length) const languageCount = computed(() => Object.keys(configForm.prologueList || {}).length) const isBrotherInfoMode = computed(() => configForm.dataPoolSource === 'brother_info') +const effectiveReplyUnreadMessages = computed(() => !isBrotherInfoMode.value && Boolean(configForm.replyUnreadMessages)) const dataPoolLabel = computed(() => (isBrotherInfoMode.value ? '大哥池' : '主播池')) const normalizedGroupSwitchMinutes = computed(() => clampMinutes(configForm.groupSwitchMinutes)) const normalizedGroupViewCounts = computed(() => normalizeGroupViewCounts(configForm.groupViewCounts)) @@ -339,8 +346,11 @@ 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 totalEnabledViews = computed(() => normalizedGroupViewCounts.value.reduce((sum, count) => sum + count, 0)) const payloadPreview = computed(() => JSON.stringify(buildStartPayload(), null, 2)) -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 currentPrepareKey = computed(() => JSON.stringify({ groupViewCounts: normalizedGroupViewCounts.value, groupSwitchMinutes: normalizedGroupSwitchMinutes.value, postUserSleepSeconds: normalizedPostUserSleepSeconds.value, replyUnreadMessages: effectiveReplyUnreadMessages.value, dataPoolSource: configForm.dataPoolSource, replyMessages: replyMessages.value, prologueList: configForm.prologueList || {}, needTranslate: Boolean(configForm.needTranslate) })) const hasPreparedViews = computed(() => preparedConfigKey.value === currentPrepareKey.value) +const isConfigLocked = computed(() => workbenchPhase.value !== 'idle') +const isConfigControlDisabled = computed(() => !isElectronEnv || isPreparing.value || isStarting.value || isStopping.value || isConfigLocked.value) +const isPrepareButtonDisabled = computed(() => !isElectronEnv || isPreparing.value || isStarting.value || isStopping.value || isConfigLocked.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') 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)) } @@ -348,13 +358,32 @@ function clampGroupCount(value) { const numericValue = Number(value); return !Nu 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() { - 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) } + const payload = { dataPoolSource: configForm.dataPoolSource, replyUnreadMessages: effectiveReplyUnreadMessages.value, 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 (replyMessages.value.length > 0) payload.replyMessages = replyMessages.value return payload } function ensureElectronCapability(methodName) { if (!isElectronEnv || !window.electronAPI?.[methodName]) { ElMessage.error('当前环境不支持该功能'); return false } return true } function invalidatePreparedState(nextStatus = '配置已变更,请重新预热视图') { preparedConfigKey.value = ''; loginConfirmed.value = false; if (!statusText.value.includes('运行')) statusText.value = nextStatus } +async function syncWorkbenchState() { + if (!isElectronEnv || !window.electronAPI?.getStandaloneTikTokWorkbenchState) return + try { + const state = await window.electronAPI.getStandaloneTikTokWorkbenchState() + workbenchPhase.value = state?.phase || 'idle' + if (state?.locked) { + preparedConfigKey.value = currentPrepareKey.value + if (state.phase === 'running') { + statusText.value = '运行中' + } else if (!statusText.value.includes('运行')) { + statusText.value = '视图已预热,当前配置已锁定' + } + } else { + preparedConfigKey.value = '' + } + } catch (error) { + console.error('sync standalone tiktok workbench state failed:', error) + } +} async function showBrowserWorkspace(viewId, fallbackStatusText) { pageMode.value = 'browser' await window.electronAPI.switchToView(viewId) @@ -416,6 +445,10 @@ async function saveSharedConfig() { } async function handlePrepareViews() { if (!ensureElectronCapability('prepareStandaloneTikTokViews')) return + if (isConfigLocked.value) { + ElMessage.warning('当前配置已锁定,请先停止任务后再修改配置或重新预热') + return + } if (viewIds.value.length === 0) { ElMessage.warning('请至少启用一个视图后再预热') return @@ -426,6 +459,7 @@ async function handlePrepareViews() { 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 + workbenchPhase.value = 'prepared' preparedConfigKey.value = currentPrepareKey.value await saveSharedConfig() if (pageMode.value === 'config') { @@ -487,6 +521,10 @@ async function handleSwitchView(viewId) { } async function handleStart() { if (!ensureElectronCapability('startStandaloneTikTokAutomationAll')) return + if (workbenchPhase.value === 'running') { + ElMessage.warning('当前任务已在运行中,请先停止后再重新启动') + return + } if (!hasPreparedViews.value) { ElMessage.warning('请先完成当前配置对应的视图预热,再启动脚本') return @@ -509,6 +547,7 @@ async function handleStart() { ElMessage.error(result?.error || '启动自动私信(TK版)失败') return } + workbenchPhase.value = 'running' statusText.value = `运行中(视图 ${activeViewId.value})` ElMessage.success('自动私信(TK版)已启动') } catch (error) { @@ -529,9 +568,10 @@ async function handleStop() { ElMessage.error(result?.error || '停止自动私信(TK版)失败') return } + workbenchPhase.value = 'idle' preparedConfigKey.value = '' loginConfirmed.value = false - statusText.value = '已停止' + statusText.value = result?.message?.includes('取消预热状态') ? '已解锁' : '已停止' ElMessage.success('自动私信(TK版)已停止') } catch (error) { const message = error instanceof Error ? error.message : String(error) @@ -546,6 +586,7 @@ watch(currentPrepareKey, (nextKey, prevKey) => { if (preparedConfigKey.value && preparedConfigKey.value !== nextKey) invalidatePreparedState() }) watch(() => configForm.dataPoolSource, () => { + if (isBrotherInfoMode.value && configForm.replyUnreadMessages) configForm.replyUnreadMessages = false invalidatePreparedState() void saveSharedConfig() }) @@ -567,6 +608,7 @@ watch(pageMode, async (newVal) => { onMounted(async () => { await checkAIConfig() await loadSharedConfig() + await syncWorkbenchState() if (!isElectronEnv || !window.electronAPI?.hideViews) return await window.electronAPI.hideViews().catch(() => { }) })