Files
web-fusion/src/views/YoloBrowser.vue

220 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="flex h-full w-full bg-gradient-to-br from-gray-50 to-gray-100 animate-fadeIn">
<!-- 侧边栏 -->
<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"
:sidebar-width="navSidebarWidth" />
<!-- 内容区域 -->
<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>
<!-- 更新通知 -->
<div style="display: none;">
<!-- Hack to keep UpdateNotification if needed, or move it to layout -->
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { isElectron } from '@/utils/electronBridge'
import Sidebar from '@/components/Sidebar.vue'
import ViewPlaceholder from '@/components/ViewPlaceholder.vue'
// Props passed from parent (App.vue or Layout) can replace some local state if we want to lift state up.
// For now, I'll keep local state to minimize refactor risk, assuming this component is mounted once and kept alive.
// BUT, App.vue passed some props like `accountGroups` etc.
// Wait, in App.vue these were local state. I needs to move the logic here or keep it in App.vue and pass via props.
// To keep valid functionality, I will copy the logic 1:1 here.
const props = defineProps(['accountGroups', 'rotationStatus', 'greetingStats', 'automationLogs', 'navSidebarWidth'])
const emit = defineEmits(['go-back', 'stop-all', 'request-config-load'])
// Constants
const USER_KEY = 'user_data'
const CONFIG_KEY = 'autoDm_runConfig'
// State
const currentTab = ref('A')
const isLoading = ref(false)
const automationStatus = ref({})
const selectedViewId = ref(null)
const viewAccountMap = ref({})
// We use props for these now? Or still load config here?
// The original App.vue loaded config.
// Let's use the props if provided, otherwise load locally?
// Actually App.vue code showed `loadConfig` was called on mounted.
// Since `accountGroups` is passed as prop in my new design (implied by previous thought), I should use it.
// BUT, to be safe and independent:
const internalAccountGroups = ref([])
// use props if available, else local
const effectiveAccountGroups = computed(() => props.accountGroups || internalAccountGroups.value)
// Computed
const tabs = computed(() => [
{ id: 'A', label: effectiveAccountGroups.value[0]?.name || 'Tab A', viewIds: [1, 2, 3] },
{ id: 'B', label: effectiveAccountGroups.value[1]?.name || 'Tab B', viewIds: [4, 5, 6] },
{ id: 'C', label: effectiveAccountGroups.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(() => {
// If props are not provided (standalone usage), load config
if (!props.accountGroups) {
loadConfig()
} else {
// Build viewAccountMap from props
buildViewMap(props.accountGroups)
}
// 监听本地存储变化,当配置更新时重新加载
window.addEventListener('storage', handleStorageChange)
// 监听自定义配置更新事件
window.addEventListener('config-updated', handleConfigUpdate)
// Listeners specific to browser view
// ...
})
onUnmounted(() => {
// 清理监听器
window.removeEventListener('storage', handleStorageChange)
window.removeEventListener('config-updated', handleConfigUpdate)
})
// 处理本地存储变化
const handleStorageChange = (event) => {
if (event.key === CONFIG_KEY) {
loadConfig()
// 强制重新构建 viewAccountMap确保使用最新的账号信息
if (props.accountGroups) {
buildViewMap(props.accountGroups)
}
}
}
// 处理配置更新事件
const handleConfigUpdate = () => {
console.log('[YoloBrowser] 收到配置更新事件,重新加载配置')
loadConfig()
// 强制重新构建 viewAccountMap确保使用最新的账号信息
if (props.accountGroups) {
buildViewMap(props.accountGroups)
}
}
// Original loadConfig logic
const loadConfig = () => {
try {
const savedConfig = localStorage.getItem(CONFIG_KEY)
if (savedConfig) {
const config = JSON.parse(savedConfig)
internalAccountGroups.value = config.accountGroups || []
buildViewMap(config.accountGroups)
}
} catch { }
}
const buildViewMap = (groups) => {
const map = {}
if (!groups) return
groups.forEach((group, groupIndex) => {
const viewsPerGroup = 3
group.accounts.forEach((account, accIndex) => {
const viewId = groupIndex * viewsPerGroup + accIndex + 1
if (viewId <= 9 && account.email && account.pwd) {
map[viewId] = { ...account, group: group.name }
}
})
})
viewAccountMap.value = map
}
watch(() => props.accountGroups, (newVal) => {
if (newVal) buildViewMap(newVal)
}, { deep: true })
// 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
// 重新加载配置,确保获取最新的账号信息
loadConfig()
// 强制重新构建 viewAccountMap确保使用最新的账号信息
if (props.accountGroups) {
buildViewMap(props.accountGroups)
}
if (isElectron()) {
await window.electronAPI.switchToView(viewId)
}
}
const handleGoToConfig = async () => {
emit('go-back')
}
const handleStopAll = async () => {
emit('stop-all')
}
</script>