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

181 lines
6.7 KiB
Vue
Raw Normal View History

2026-02-04 19:56:19 +08:00
<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" />
<!-- 内容区域 -->
<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'])
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)
}
// Listeners specific to browser view
// ...
})
// 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)
})
// 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 handleGoToConfig = async () => {
emit('go-back')
}
const handleStopAll = async () => {
emit('stop-all')
}
</script>