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
- }}
+ }}
@@ -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(() => { })
})