线上版本更新

This commit is contained in:
2026-02-05 18:50:40 +08:00
parent fce706198a
commit 0dd02a13f6
7 changed files with 118 additions and 1629 deletions

1518
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,16 +14,8 @@
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^5.2.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^1.13.4",
"element-plus": "^2.13.2",
"less": "^4.5.1",
"less-loader": "^12.3.0",
"pinia": "^3.0.4",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"qwebchannel": "^6.2.0",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"vite": "^5.4.11", "vite": "^5.4.11"
"vue-i18n": "^11.2.8",
"vue-router": "^5.0.2"
} }
} }

View File

@@ -5,7 +5,7 @@
<template v-else> <template v-else>
<!-- 登录页面 --> <!-- 登录页面 -->
<LoginPage v-if="currentPage === 'login'" @login-success="currentPage = 'browser'" class="animate-fadeIn" /> <LoginPage v-if="currentPage === 'login'" @login-success="currentPage = 'config'" class="animate-fadeIn" />
<template v-else> <template v-else>
<!-- 配置页面 - 使用 v-show 保持状态 --> <!-- 配置页面 - 使用 v-show 保持状态 -->
@@ -15,16 +15,57 @@
</div> </div>
<!-- 浏览器页面 --> <!-- 浏览器页面 -->
<div class="h-full w-full animate-fadeIn" v-show="currentPage === 'browser'"> <div class="flex h-full w-full bg-gradient-to-br from-gray-50 to-gray-100 animate-fadeIn"
<WorkbenchLayout v-show="currentPage === 'browser'">
:account-groups="accountGroups" <!-- 侧边栏 -->
:rotation-status="rotationStatus" <Sidebar :tabs="tabs" :current-tab="currentTab" @tab-switch="handleTabSwitch"
:greeting-stats="greetingStats" @go-back="handleGoToConfig" @stop-all="handleStopAll" :is-loading="isLoading"
:automation-logs="automationLogs" :account-groups="accountGroups" :rotation-status="rotationStatus" :greeting-stats="greetingStats"
@go-back="handleGoToConfig" :automation-logs="automationLogs" />
@stop-all="handleStopAll"
@logout="handleLogout" <!-- 内容区域 -->
/> <main class="flex-1 flex flex-col relative">
<!-- 顶部视图切换栏 -->
<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 currentTabConfig.viewIds" :key="viewId" @click="handleViewSwitch(viewId)"
:class="[
'px-3 py-1.5 rounded-lg text-sm font-medium transition-all',
(selectedViewId || currentTabConfig.viewIds[0]) === viewId
? 'bg-blue-500 text-white shadow-md'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 border border-gray-200'
]">
视图 {{ viewId }}
<span v-if="viewAccountMap[viewId]" class="ml-1.5 text-xs opacity-70">
({{ viewAccountMap[viewId].email.split('@')[0] }})
</span>
</button>
<div class="flex-1" />
<!-- 状态指示 -->
<span v-if="automationStatus[selectedViewId || currentTabConfig.viewIds[0]]"
class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded border border-gray-200">
{{ automationStatus[selectedViewId || currentTabConfig.viewIds[0]] }}
</span>
</div>
<!-- 单个视图显示区域 -->
<div class="flex-1 relative">
<ViewPlaceholder class="absolute inset-0" />
</div>
<div v-if="isLoading"
class="absolute inset-0 bg-slate-900/80 flex items-center justify-center z-50">
<div class="flex flex-col items-center gap-3">
<div class="w-10 h-10 border-3 border-t-primary-400 border-slate-600 rounded-full animate-spin" />
<span class="text-slate-400 text-sm">切换中...</span>
</div>
</div>
</main>
<!-- 更新通知 -->
<UpdateNotification />
</div> </div>
</template> </template>
</template> </template>
@@ -36,7 +77,8 @@ import { isElectron, getAppVersion } from './utils/electronBridge'
import LoginPage from './pages/LoginPage.vue' import LoginPage from './pages/LoginPage.vue'
import ConfigPage from './pages/ConfigPage.vue' import ConfigPage from './pages/ConfigPage.vue'
import UpdateChecker from './pages/UpdateChecker.vue' import UpdateChecker from './pages/UpdateChecker.vue'
import WorkbenchLayout from './layout/WorkbenchLayout.vue' import Sidebar from './components/Sidebar.vue'
import ViewPlaceholder from './components/ViewPlaceholder.vue'
import UpdateNotification from './components/UpdateNotification.vue' import UpdateNotification from './components/UpdateNotification.vue'
// Constants // Constants
@@ -46,8 +88,10 @@ const CONFIG_KEY = 'autoDm_runConfig'
// State // State
const updateReady = ref(false) const updateReady = ref(false)
const currentPage = ref('login') const currentPage = ref('login')
const currentTab = ref('A')
const isLoading = ref(false) const isLoading = ref(false)
const automationStatus = ref({}) const automationStatus = ref({})
const selectedViewId = ref(null)
const accountGroups = ref([]) const accountGroups = ref([])
const viewAccountMap = ref({}) const viewAccountMap = ref({})
const rotationStatus = ref(null) const rotationStatus = ref(null)
@@ -57,6 +101,15 @@ const automationLogs = ref([])
const isElectronEnv = isElectron() const isElectronEnv = isElectron()
const isDev = window.location.port === '5173' const isDev = window.location.port === '5173'
// Computed
const tabs = computed(() => [
{ id: 'A', label: accountGroups.value[0]?.name || 'Tab A', viewIds: [1, 2, 3] },
{ id: 'B', label: accountGroups.value[1]?.name || 'Tab B', viewIds: [4, 5, 6] },
{ id: 'C', label: accountGroups.value[2]?.name || 'Tab C', viewIds: [7, 8, 9] }
])
const currentTabConfig = computed(() => tabs.value.find(t => t.id === currentTab.value) || tabs.value[0])
// Lifecycle // Lifecycle
onMounted(() => { onMounted(() => {
// Set Title // Set Title
@@ -72,7 +125,7 @@ onMounted(() => {
if (userData) { if (userData) {
const user = JSON.parse(userData) const user = JSON.parse(userData)
if (user && user.tokenValue) { if (user && user.tokenValue) {
currentPage.value = 'browser' currentPage.value = 'config'
} }
} }
} catch { } // eslint-disable-line no-empty } catch { } // eslint-disable-line no-empty
@@ -198,6 +251,31 @@ watch(currentPage, (newVal) => {
}) })
// Actions // Actions
const handleTabSwitch = async (tab) => {
if (tab === currentTab.value) return
if (isElectron()) {
try {
const result = await window.electronAPI.switchTab(tab)
if (result.success) {
currentTab.value = tab
selectedViewId.value = null
}
} catch (error) {
console.error('切换标签失败:', error)
}
} else {
currentTab.value = tab
selectedViewId.value = null
}
}
const handleViewSwitch = async (viewId) => {
selectedViewId.value = viewId
if (isElectron()) {
await window.electronAPI.switchToView(viewId)
}
}
const handleGoToBrowser = async () => { const handleGoToBrowser = async () => {
if (isElectron()) { if (isElectron()) {

View File

@@ -68,10 +68,8 @@
<div class="space-y-2 max-h-60 overflow-auto"> <div class="space-y-2 max-h-60 overflow-auto">
<div v-for="parent in LEVEL_OPTIONS" :key="parent.value" <div v-for="parent in LEVEL_OPTIONS" :key="parent.value"
class="border border-gray-100 rounded p-2"> class="border border-gray-100 rounded p-2">
<label <label class="flex items-center gap-2 cursor-pointer font-medium text-gray-700">
class="flex items-center gap-2 cursor-pointer font-medium text-gray-700"> <input type="checkbox" :checked="isParentSelected(parent).allSelected"
<input type="checkbox"
:checked="isParentSelected(parent).allSelected"
:indeterminate="isParentSelected(parent).partialSelected" :indeterminate="isParentSelected(parent).partialSelected"
@change="toggleParentLevel(parent.value)" class="w-4 h-4" /> @change="toggleParentLevel(parent.value)" class="w-4 h-4" />
{{ parent.label }} {{ parent.label }}

View File

@@ -1,17 +1,5 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import './styles/index.css' import './styles/index.css'
import App from './App.vue' import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import i18n from './locales'
import router from './router'
import { createPinia } from 'pinia'
const app = createApp(App) createApp(App).mount('#root')
app.use(createPinia())
app.use(router)
app.use(i18n)
app.use(ElementPlus)
app.mount('#root')

View File

@@ -669,11 +669,14 @@ const handleStart = async (specificGroupIndex) => {
sleepTime: config.value.sleepTime, sleepTime: config.value.sleepTime,
inviteThreshold: config.value.inviteThreshold, inviteThreshold: config.value.inviteThreshold,
prologueList, prologueList,
needTranslate: config.value.needTranslate, // 添加翻译开关配置 maxAnchorCount: config.value.maxAnchorCount,
rotationEnabled: config.value.rotateEnabled,
rotationIntervalMinutes: config.value.switchMinutes,
maxAnchorCount: config.value.maxAnchorCount, maxAnchorCount: config.value.maxAnchorCount,
rotationEnabled: config.value.rotateEnabled, rotationEnabled: config.value.rotateEnabled,
rotationIntervalMinutes: config.value.switchMinutes, rotationIntervalMinutes: config.value.switchMinutes,
currentActiveGroup: activeGroupName, currentActiveGroup: activeGroupName,
// 这里只是保存配置到 configStore不会直接传给 automation
}) })
const groupsToStart = config.value.rotateEnabled const groupsToStart = config.value.rotateEnabled
@@ -706,40 +709,13 @@ const handleStart = async (specificGroupIndex) => {
} }
} }
const results = await Promise.allSettled( await Promise.allSettled(
startTasks.map(async ({ viewId, account, delay }) => { startTasks.map(async ({ viewId, account, delay }) => {
await new Promise(r => setTimeout(r, delay)) await new Promise(r => setTimeout(r, delay))
return window.electronAPI.startTikTokAutomation(viewId, account) return window.electronAPI.startTikTokAutomation(viewId, account)
}) })
) )
// 检查启动结果
const failedResults = results
.map((r, i) => ({ result: r, task: startTasks[i] }))
.filter(({ result }) => {
if (result.status === 'rejected') return true
if (result.status === 'fulfilled' && !result.value.success) return true
return false
})
// 如果全部失败,显示错误并不跳转
if (failedResults.length === startTasks.length) {
const firstError = failedResults[0]
let errorMsg = '启动失败'
if (firstError.result.status === 'rejected') {
errorMsg = firstError.result.reason?.message || '启动失败'
} else if (firstError.result.status === 'fulfilled') {
errorMsg = firstError.result.value.error || '启动失败'
}
alert(`启动失败:${errorMsg}`)
return
}
// 如果部分失败,显示警告但继续
if (failedResults.length > 0) {
console.warn(`部分账号启动失败: ${failedResults.length}/${startTasks.length}`)
}
setIsRunning(true) setIsRunning(true)
currentGroupIndex.value = activeGroupIndex currentGroupIndex.value = activeGroupIndex
const status = await window.electronAPI.getRotationStatus() const status = await window.electronAPI.getRotationStatus()

View File

@@ -1,28 +1,6 @@
```
<template> <template>
<div <div
class="min-h-screen bg-[#F0F4F8] flex items-center justify-center font-sans antialiased relative overflow-hidden transition-colors duration-300"> class="min-h-screen bg-[#F0F4F8] flex items-center justify-center font-sans antialiased relative overflow-hidden transition-colors duration-300">
<div class="absolute top-8 right-8 flex gap-4 z-20">
<!-- Network Settings (Placeholder/Mock) -->
<!-- <div class="bg-white/95 border border-slate-200 rounded-2xl px-3 py-2 shadow-lg cursor-pointer hover:-translate-y-px transition-all flex items-center gap-2">
<span class="text-sm font-semibold text-slate-700">{{ $t('login.network') }}</span>
</div> -->
<!-- Language Selector -->
<el-dropdown>
<div
class="bg-white/95 border border-slate-200 rounded-2xl px-4 py-2 shadow-lg cursor-pointer hover:-translate-y-px transition-all flex items-center gap-2">
<span class="text-sm font-semibold text-slate-700">{{ locale === 'zh' ? '中文' : 'English' }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="switchLanguage('zh')">中文</el-dropdown-item>
<el-dropdown-item @click="switchLanguage('en')">English</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- Background Shapes --> <!-- Background Shapes -->
<div class="absolute top-[-200px] right-[-200px] w-[800px] h-[800px] rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-pulse" <div class="absolute top-[-200px] right-[-200px] w-[800px] h-[800px] rounded-full mix-blend-multiply filter blur-3xl opacity-70 animate-pulse"
style="background: radial-gradient(circle, rgba(79, 129, 230, 0.2) 0%, rgba(79, 129, 230, 0) 70%)" /> style="background: radial-gradient(circle, rgba(79, 129, 230, 0.2) 0%, rgba(79, 129, 230, 0) 70%)" />
@@ -157,24 +135,14 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { isElectron, getAppVersion } from '../utils/electronBridge' import { isElectron, getAppVersion } from '../utils/electronBridge'
import { setUser, setToken, setUserPass, getUserPass, setPermissions } from '@/utils/storage'
import logo from '../assets/logo.png' import logo from '../assets/logo.png'
import illustration from '../assets/illustration.png' import illustration from '../assets/illustration.png'
const emit = defineEmits(['loginSuccess']) const emit = defineEmits(['loginSuccess'])
const { locale } = useI18n() const STORAGE_KEY = 'login_credentials'
const USER_KEY = 'user_data'
// Language Switcher
const switchLanguage = (lang) => {
locale.value = lang
localStorage.setItem('lang', lang)
}
// const STORAGE_KEY = 'login_credentials' // Deprecated in favor of getUserPass
// const USER_KEY = 'user_data' // Deprecated in favor of setUser
const credentials = ref({ const credentials = ref({
tenantName: '', tenantName: '',
@@ -193,12 +161,13 @@ onMounted(() => {
// 加载保存的凭据 // 加载保存的凭据
try { try {
const saved = getUserPass() const saved = localStorage.getItem(STORAGE_KEY)
if (saved) { if (saved) {
const data = JSON.parse(saved)
credentials.value = { credentials.value = {
tenantName: saved.tenantName || '', tenantName: data.tenantName || '',
username: saved.username || saved.userId || '', username: data.username || data.userId || '',
password: saved.password || '', password: data.password || '',
} }
} }
} catch { } // eslint-disable-line no-empty } catch { } // eslint-disable-line no-empty
@@ -214,8 +183,8 @@ const handleSubmit = async () => {
error.value = '' error.value = ''
try { try {
// 保存凭据 (Using compatible storage helper) // 保存凭据
setUserPass(credentials.value) localStorage.setItem(STORAGE_KEY, JSON.stringify(credentials.value))
console.log('[LoginPage] 开始登录...', credentials.value) console.log('[LoginPage] 开始登录...', credentials.value)
@@ -229,17 +198,7 @@ const handleSubmit = async () => {
console.log('[LoginPage] 登录结果:', result) console.log('[LoginPage] 登录结果:', result)
if (result.success && result.user) { if (result.success && result.user) {
// Save token and user info to localStorage using legacy keys to support ported views localStorage.setItem(USER_KEY, JSON.stringify(result.user))
setToken(result.user.tokenValue);
setUser(result.user);
// 保存权限信息
setPermissions({
bigBrother: result.user.bigBrother,
crawl: result.user.crawl,
webAi: result.user.webAi,
});
emit('loginSuccess') emit('loginSuccess')
} else { } else {
error.value = result.error || '登录失败' error.value = result.error || '登录失败'