Files
web-fusion/src/layout/WorkbenchLayout.vue
2026-04-22 13:53:42 +08:00

312 lines
13 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-screen w-screen overflow-hidden bg-white">
<div ref="sidebarRef" class="flex flex-col items-center py-4 border-r z-50"
style="flex: 0 0 calc(100vw * 2 / 19); min-width: 96px; max-width: 400px; background-color: #F8F9FA;">
<div class="mb-6" style="border-bottom: 1px solid #A0AEC023; padding: 10%;">
<div>
<img :src="yoloIcon" class="yolo-logo" />
</div>
</div>
<div class="flex-1 flex flex-col w-full px-2" style="gap: 2vh;">
<button @click="currentView = 'tk'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'tk' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<img :src="currentView === 'tk' ? nav11 : nav1" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium truncate">TK 工作台</span>
</button>
<button @click="currentView = 'hosts'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'hosts' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<img :src="currentView === 'hosts' ? nav22 : nav2" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium truncate">主播列表</span>
</button>
<button @click="currentView = 'auto_dm'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'auto_dm' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<img :src="currentView === 'auto_dm' ? nav33 : nav3" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium truncate">自动私信</span>
</button>
<button @click="currentView = 'auto_dm_tk'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'auto_dm_tk' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<img :src="currentView === 'auto_dm_tk' ? nav33 : nav3" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium truncate">自动私信TK版</span>
</button>
<button @click="currentView = 'FanWorkbench'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'FanWorkbench' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<img :src="currentView === 'FanWorkbench' ? nav44 : nav4" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium truncate">大哥工作台</span>
</button>
<button @click="currentView = 'pk_mini'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'pk_mini' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<img :src="currentView === 'pk_mini' ? nav55 : nav5" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium truncate">PK 工作台</span>
</button>
<button @click="currentView = 'shop'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'shop' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<img :src="currentView === 'shop' ? nav66 : nav6" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium truncate">TK商店</span>
</button>
<button
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200 text-slate-400 hover:bg-[rgba(21,96,250,0.06)]"
style="height: 6vh;">
<span class="text-base font-medium truncate">敬请期待...</span>
</button>
</div>
<div class="mt-auto w-full px-2">
<button @click="$emit('logout')"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 text-slate-400 bg-white shadow shadow-blue-900/20 transition-all">
<img :src="backIcon" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="text-base font-medium" style="color: #ED4949;">退出登录</span>
</button>
</div>
</div>
<div class="flex-1 h-full relative">
<div v-show="currentView === 'auto_dm'" class="absolute inset-0 z-10 h-full w-full">
<PermissionMask permission-key="webAi" title="自动私信工作台未开通" description="您当前没有使用自动私信功能的权限"
:placeholder-image="placeholderWebAi" :contacts="serviceContacts">
<div v-if="autoDmMode === 'config'" class="h-full w-full bg-slate-50 overflow-auto">
<ConfigPage @go-to-browser="handleGoToBrowser" @logout="$emit('logout')"
@config-updated="handleConfigUpdated" />
</div>
<div v-show="autoDmMode === 'browser'" class="h-full w-full">
<YoloBrowser v-bind="$attrs" :nav-sidebar-width="navSidebarWidth" @go-back="handleBackToConfig"
@stop-all="handleStopAll" />
</div>
</PermissionMask>
</div>
<div v-show="currentView === 'auto_dm_tk'" class="absolute inset-0 z-20 h-full w-full">
<PermissionMask permission-key="autotk" title="自动私信TK版工作台未开通" description="您当前没有使用自动私信TK版功能的权限"
:placeholder-image="placeholderWebAi" :contacts="serviceContacts">
<AutoDmTkWorkbench :nav-sidebar-width="navSidebarWidth" />
</PermissionMask>
</div>
<div v-show="currentView === 'tk'" class="absolute inset-0 z-20 bg-gray-50 h-full overflow-hidden">
<PermissionMask permission-key="crawl" title="TK工作台未开通" description="您当前没有使用TK工作台功能的权限"
:placeholder-image="placeholderTk" :contacts="serviceContacts">
<TkWorkbenches :key="tkWorkbenchKey" />
</PermissionMask>
</div>
<div v-show="currentView === 'hosts'" class="absolute inset-0 z-20 bg-gray-50 h-full overflow-hidden">
<PermissionMask permission-key="crawl" title="主播列表未开通" description="您当前没有使用主播列表功能的权限"
:placeholder-image="placeholderHosts" :contacts="serviceContacts">
<HostsList />
</PermissionMask>
</div>
<div v-show="currentView === 'FanWorkbench'" class="absolute inset-0 z-20 bg-gray-50 h-full overflow-hidden">
<PermissionMask permission-key="bigBrother" title="大哥工作台未开通" description="您当前没有使用大哥工作台功能的权限"
:placeholder-image="placeholderBigBrother" :contacts="serviceContacts">
<FanWorkbench />
</PermissionMask>
</div>
<div v-show="currentView === 'pk_mini'" class="absolute inset-0 z-20 h-full overflow-hidden">
<PkMiniWorkbench />
</div>
<div v-show="currentView === 'shop'" class="absolute inset-0 z-20 h-full overflow-hidden">
<div v-if="isElectron()"
class="w-full h-full flex items-center justify-center text-base text-slate-500 bg-white">
正在进入商店...
</div>
<iframe v-else-if="adminLoaded" :src="shopUrl" class="w-full h-full border-0"
allow="clipboard-read; clipboard-write"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-downloads" />
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted, useTemplateRef } from 'vue'
import { isElectron } from '@/utils/electronBridge'
import YoloBrowser from '@/views/YoloBrowser.vue'
import TkWorkbenches from '@/views/tk/Workbenches.vue'
import HostsList from '@/views/tk/HostsList.vue'
import ConfigPage from '@/pages/ConfigPage.vue'
import FanWorkbench from '@/views/tk/FanWorkbench.vue'
import PkMiniWorkbench from '@/views/pk-mini/PkMiniWorkbench.vue'
import AutoDmTkWorkbench from '@/views/auto-dm/AutoDmTkWorkbench.vue'
import PermissionMask from '@/components/PermissionMask.vue'
import { ENV } from '@/config'
import { getCustomServiceInfo } from '@/api/account'
import yoloIcon from '@/assets/nav/yolo.png'
import nav1 from '@/assets/nav/nav1.png'
import nav11 from '@/assets/nav/nav11.png'
import nav2 from '@/assets/nav/nav2.png'
import nav22 from '@/assets/nav/nav22.png'
import nav3 from '@/assets/nav/nav3.png'
import nav33 from '@/assets/nav/nav33.png'
import nav4 from '@/assets/nav/nav4.png'
import nav44 from '@/assets/nav/nav44.png'
import nav5 from '@/assets/nav/nav5.png'
import nav55 from '@/assets/nav/nav55.png'
import nav6 from '@/assets/nav/nav6.png'
import nav66 from '@/assets/nav/nav66.png'
import backIcon from '@/assets/nav/back.png'
import placeholderTk from '@/assets/placeholder-tk.png'
import placeholderHosts from '@/assets/placeholder-hosts.png'
import placeholderWebAi from '@/assets/placeholder-webai.png'
import placeholderBigBrother from '@/assets/placeholder-bigbrother.png'
const emit = defineEmits(['logout', 'go-back', 'stop-all'])
const currentView = ref('tk')
const autoDmMode = ref('config')
const adminLoaded = ref(false)
const shopOpened = ref(false)
const shopUrl = ENV.SHOP_URL
const sidebarRef = useTemplateRef('sidebarRef')
const navSidebarWidth = ref(200)
const tkWorkbenchKey = ref(0)
const reloadTkWorkbench = () => {
tkWorkbenchKey.value++
console.log('TK 工作台已重新加载')
}
window.reloadTkWorkbench = reloadTkWorkbench
const serviceContacts = ref([])
const loadServiceContacts = async () => {
try {
const res = await getCustomServiceInfo()
if (res) {
serviceContacts.value = res.map(item => ({
avatar: item.avater,
name: item.name,
desc: item.description,
qrcode: item.concat,
phone: item.phone
}))
}
} catch (e) {
console.error('获取客服名片失败:', e)
}
}
let resizeObserver = null
const notifySidebarWidth = (width) => {
navSidebarWidth.value = Math.round(width)
if (isElectron()) {
window.electronAPI.setSidebarWidth(Math.round(width)).catch(() => { })
}
}
onMounted(() => {
loadServiceContacts()
if (!isElectron()) return
resizeObserver = new ResizeObserver((entries) => {
const width = entries[0]?.contentRect.width
if (width) notifySidebarWidth(width)
})
if (sidebarRef.value) {
resizeObserver.observe(sidebarRef.value)
notifySidebarWidth(sidebarRef.value.getBoundingClientRect().width)
}
})
onUnmounted(() => {
resizeObserver?.disconnect()
})
const handleGoToBrowser = async () => {
autoDmMode.value = 'browser'
if (isElectron()) {
await window.electronAPI.showViews()
}
}
const handleBackToConfig = async () => {
autoDmMode.value = 'config'
if (isElectron()) {
await window.electronAPI.hideViews()
}
}
const handleStopAll = () => {
emit('stop-all')
}
const handleConfigUpdated = () => {
window.dispatchEvent(new CustomEvent('config-updated'))
}
watch(currentView, async (newVal, oldVal) => {
if (newVal === 'shop' && !adminLoaded.value && !isElectron()) {
adminLoaded.value = true
}
if (!isElectron()) return
if (newVal === 'shop') {
try {
shopOpened.value = true
await window.electronAPI.openShop(shopUrl)
} catch (e) {
console.error('打开商店失败:', e)
}
} else if (oldVal === 'shop') {
try {
await window.electronAPI.hideShop()
} catch (e) {
console.error('隐藏商店失败:', e)
}
}
const shouldShowAutoDmViews =
newVal === 'auto_dm' && autoDmMode.value === 'browser'
if (shouldShowAutoDmViews) {
try {
await window.electronAPI.showViews()
} catch (e) {
console.error('Failed to show views:', e)
}
} else {
try {
await window.electronAPI.hideViews()
} catch (e) {
console.error('Failed to hide views:', e)
}
}
})
watch(autoDmMode, async (newVal) => {
if (currentView.value !== 'auto_dm') return
if (newVal === 'browser') {
if (isElectron()) await window.electronAPI.showViews()
} else {
if (isElectron()) await window.electronAPI.hideViews()
}
})
</script>
<style scoped>
@import url('https://fonts.googleapis.com/icon?family=Material+Icons+Round');
.yolo-logo {
width: 70%;
}
</style>