大哥 主播 即时消息 三合一
This commit is contained in:
180
src/views/YoloBrowser.vue
Normal file
180
src/views/YoloBrowser.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user