线上版本更新
This commit is contained in:
1518
package-lock.json
generated
1518
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -14,16 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"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",
|
||||
"qwebchannel": "^6.2.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^5.4.11",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"vue-router": "^5.0.2"
|
||||
"vite": "^5.4.11"
|
||||
}
|
||||
}
|
||||
|
||||
104
src/App.vue
104
src/App.vue
@@ -5,7 +5,7 @@
|
||||
|
||||
<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>
|
||||
<!-- 配置页面 - 使用 v-show 保持状态 -->
|
||||
@@ -15,16 +15,57 @@
|
||||
</div>
|
||||
|
||||
<!-- 浏览器页面 -->
|
||||
<div class="h-full w-full animate-fadeIn" v-show="currentPage === 'browser'">
|
||||
<WorkbenchLayout
|
||||
:account-groups="accountGroups"
|
||||
:rotation-status="rotationStatus"
|
||||
:greeting-stats="greetingStats"
|
||||
:automation-logs="automationLogs"
|
||||
@go-back="handleGoToConfig"
|
||||
@stop-all="handleStopAll"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
<div class="flex h-full w-full bg-gradient-to-br from-gray-50 to-gray-100 animate-fadeIn"
|
||||
v-show="currentPage === 'browser'">
|
||||
<!-- 侧边栏 -->
|
||||
<Sidebar :tabs="tabs" :current-tab="currentTab" @tab-switch="handleTabSwitch"
|
||||
@go-back="handleGoToConfig" @stop-all="handleStopAll" :is-loading="isLoading"
|
||||
:account-groups="accountGroups" :rotation-status="rotationStatus" :greeting-stats="greetingStats"
|
||||
:automation-logs="automationLogs" />
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<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>
|
||||
</template>
|
||||
</template>
|
||||
@@ -36,7 +77,8 @@ import { isElectron, getAppVersion } from './utils/electronBridge'
|
||||
import LoginPage from './pages/LoginPage.vue'
|
||||
import ConfigPage from './pages/ConfigPage.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'
|
||||
|
||||
// Constants
|
||||
@@ -46,8 +88,10 @@ const CONFIG_KEY = 'autoDm_runConfig'
|
||||
// State
|
||||
const updateReady = ref(false)
|
||||
const currentPage = ref('login')
|
||||
const currentTab = ref('A')
|
||||
const isLoading = ref(false)
|
||||
const automationStatus = ref({})
|
||||
const selectedViewId = ref(null)
|
||||
const accountGroups = ref([])
|
||||
const viewAccountMap = ref({})
|
||||
const rotationStatus = ref(null)
|
||||
@@ -57,6 +101,15 @@ const automationLogs = ref([])
|
||||
const isElectronEnv = isElectron()
|
||||
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
|
||||
onMounted(() => {
|
||||
// Set Title
|
||||
@@ -72,7 +125,7 @@ onMounted(() => {
|
||||
if (userData) {
|
||||
const user = JSON.parse(userData)
|
||||
if (user && user.tokenValue) {
|
||||
currentPage.value = 'browser'
|
||||
currentPage.value = 'config'
|
||||
}
|
||||
}
|
||||
} catch { } // eslint-disable-line no-empty
|
||||
@@ -198,6 +251,31 @@ watch(currentPage, (newVal) => {
|
||||
})
|
||||
|
||||
// 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 () => {
|
||||
if (isElectron()) {
|
||||
|
||||
@@ -68,10 +68,8 @@
|
||||
<div class="space-y-2 max-h-60 overflow-auto">
|
||||
<div v-for="parent in LEVEL_OPTIONS" :key="parent.value"
|
||||
class="border border-gray-100 rounded p-2">
|
||||
<label
|
||||
class="flex items-center gap-2 cursor-pointer font-medium text-gray-700">
|
||||
<input type="checkbox"
|
||||
:checked="isParentSelected(parent).allSelected"
|
||||
<label class="flex items-center gap-2 cursor-pointer font-medium text-gray-700">
|
||||
<input type="checkbox" :checked="isParentSelected(parent).allSelected"
|
||||
:indeterminate="isParentSelected(parent).partialSelected"
|
||||
@change="toggleParentLevel(parent.value)" class="w-4 h-4" />
|
||||
{{ parent.label }} 级
|
||||
|
||||
14
src/main.js
14
src/main.js
@@ -1,17 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import './styles/index.css'
|
||||
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)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
app.use(ElementPlus)
|
||||
|
||||
app.mount('#root')
|
||||
createApp(App).mount('#root')
|
||||
|
||||
@@ -669,11 +669,14 @@ const handleStart = async (specificGroupIndex) => {
|
||||
sleepTime: config.value.sleepTime,
|
||||
inviteThreshold: config.value.inviteThreshold,
|
||||
prologueList,
|
||||
needTranslate: config.value.needTranslate, // 添加翻译开关配置
|
||||
maxAnchorCount: config.value.maxAnchorCount,
|
||||
rotationEnabled: config.value.rotateEnabled,
|
||||
rotationIntervalMinutes: config.value.switchMinutes,
|
||||
maxAnchorCount: config.value.maxAnchorCount,
|
||||
rotationEnabled: config.value.rotateEnabled,
|
||||
rotationIntervalMinutes: config.value.switchMinutes,
|
||||
currentActiveGroup: activeGroupName,
|
||||
// 这里只是保存配置到 configStore,不会直接传给 automation
|
||||
})
|
||||
|
||||
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 }) => {
|
||||
await new Promise(r => setTimeout(r, delay))
|
||||
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)
|
||||
currentGroupIndex.value = activeGroupIndex
|
||||
const status = await window.electronAPI.getRotationStatus()
|
||||
|
||||
@@ -1,28 +1,6 @@
|
||||
```
|
||||
<template>
|
||||
<div
|
||||
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 -->
|
||||
<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%)" />
|
||||
@@ -157,24 +135,14 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { isElectron, getAppVersion } from '../utils/electronBridge'
|
||||
import { setUser, setToken, setUserPass, getUserPass, setPermissions } from '@/utils/storage'
|
||||
import logo from '../assets/logo.png'
|
||||
import illustration from '../assets/illustration.png'
|
||||
|
||||
const emit = defineEmits(['loginSuccess'])
|
||||
|
||||
const { locale } = useI18n()
|
||||
|
||||
// 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 STORAGE_KEY = 'login_credentials'
|
||||
const USER_KEY = 'user_data'
|
||||
|
||||
const credentials = ref({
|
||||
tenantName: '',
|
||||
@@ -193,12 +161,13 @@ onMounted(() => {
|
||||
|
||||
// 加载保存的凭据
|
||||
try {
|
||||
const saved = getUserPass()
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
if (saved) {
|
||||
const data = JSON.parse(saved)
|
||||
credentials.value = {
|
||||
tenantName: saved.tenantName || '',
|
||||
username: saved.username || saved.userId || '',
|
||||
password: saved.password || '',
|
||||
tenantName: data.tenantName || '',
|
||||
username: data.username || data.userId || '',
|
||||
password: data.password || '',
|
||||
}
|
||||
}
|
||||
} catch { } // eslint-disable-line no-empty
|
||||
@@ -214,8 +183,8 @@ const handleSubmit = async () => {
|
||||
error.value = ''
|
||||
|
||||
try {
|
||||
// 保存凭据 (Using compatible storage helper)
|
||||
setUserPass(credentials.value)
|
||||
// 保存凭据
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(credentials.value))
|
||||
|
||||
console.log('[LoginPage] 开始登录...', credentials.value)
|
||||
|
||||
@@ -229,17 +198,7 @@ const handleSubmit = async () => {
|
||||
console.log('[LoginPage] 登录结果:', result)
|
||||
|
||||
if (result.success && result.user) {
|
||||
// Save token and user info to localStorage using legacy keys to support ported views
|
||||
setToken(result.user.tokenValue);
|
||||
setUser(result.user);
|
||||
|
||||
// 保存权限信息
|
||||
setPermissions({
|
||||
bigBrother: result.user.bigBrother,
|
||||
crawl: result.user.crawl,
|
||||
webAi: result.user.webAi,
|
||||
});
|
||||
|
||||
localStorage.setItem(USER_KEY, JSON.stringify(result.user))
|
||||
emit('loginSuccess')
|
||||
} else {
|
||||
error.value = result.error || '登录失败'
|
||||
|
||||
Reference in New Issue
Block a user