线上版本更新
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": {
|
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
104
src/App.vue
104
src/App.vue
@@ -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()) {
|
||||||
|
|||||||
@@ -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 }} 级
|
||||||
|
|||||||
14
src/main.js
14
src/main.js
@@ -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')
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 || '登录失败'
|
||||||
|
|||||||
Reference in New Issue
Block a user