tk版私信出版

This commit is contained in:
2026-04-16 17:31:45 +08:00
parent 1f8b830d27
commit c0125a5a9f
4 changed files with 808 additions and 117 deletions

View File

@@ -1,139 +1,187 @@
<template>
<div class="flex h-screen w-screen overflow-hidden bg-white">
<!-- Left Navigation Sidebar -->
<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
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%;">
<!-- Logo or Brand -->
<div class="">
<div>
<img :src="yoloIcon" class="yolo-logo" />
</div>
</div>
<div class="flex-1 flex flex-col w-full px-2" style="gap: 2vh;">
<!-- TK Workbench Tab -->
<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)]'">
<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>
<!-- Hosts List Tab -->
<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)]'">
<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>
<!-- Auto DM Workbench Tab -->
<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)]'">
<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>
<!-- Fan Workbench Tab -->
<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)]'">
<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>
<!-- PK 工作台 Tab -->
<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)]'">
<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>
<!-- yolo商店 Tab -->
<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)]'">
<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>
<!-- 尽请期待 Tab -->
<button class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200"
style="height: 6vh; :disabled"
:class="currentView === 'test' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'">
<span class="text-base font-medium truncate">请期待...</span>
<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">
<!-- Logout -->
<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">
<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>
<!-- Main Content Area -->
<div class="flex-1 h-full relative">
<!-- Tab 1: Auto DM Workbench (Config + Browser) - webAi 权限 -->
<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">
<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" />
<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" />
<YoloBrowser v-bind="$attrs" :nav-sidebar-width="navSidebarWidth" @go-back="handleBackToConfig" @stop-all="handleStopAll" />
</div>
</PermissionMask>
</div>
<!-- Tab 2: TK Workbench - crawl 权限 -->
<div v-show="currentView === 'auto_dm_tk'" class="absolute inset-0 z-20 h-full w-full">
<PermissionMask
permission-key="webAi"
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">
<PermissionMask
permission-key="crawl"
title="TK工作台未开通"
description="您当前没有使用TK工作台功能的权限"
:placeholder-image="placeholderTk"
:contacts="serviceContacts"
>
<TkWorkbenches :key="tkWorkbenchKey" />
</PermissionMask>
</div>
<!-- Tab 3: Hosts List - crawl 权限 -->
<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">
<PermissionMask
permission-key="crawl"
title="主播列表未开通"
description="您当前没有使用主播列表功能的权限"
:placeholder-image="placeholderHosts"
:contacts="serviceContacts"
>
<HostsList />
</PermissionMask>
</div>
<!-- Tab 4: Fan Workbench - bigBrother 权限 -->
<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">
<PermissionMask
permission-key="bigBrother"
title="大哥工作台未开通"
description="您当前没有使用大哥工作台功能的权限"
:placeholder-image="placeholderBigBrother"
:contacts="serviceContacts"
>
<FanWorkbench />
</PermissionMask>
</div>
<!-- Tab 5: PK Mini 工作台 - 无需权限控制 -->
<div v-show="currentView === 'pk_mini'" class="absolute inset-0 z-20 h-full overflow-hidden">
<PkMiniWorkbench />
</div>
<!-- Tab 6: yolo商店 - Electron BrowserViewWeb iframe 兜底 -->
<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 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"
<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"></iframe>
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-downloads"
/>
</div>
</div>
</div>
@@ -148,11 +196,11 @@ 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'
@@ -168,7 +216,6 @@ 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'
@@ -176,30 +223,26 @@ import placeholderBigBrother from '@/assets/placeholder-bigbrother.png'
const emit = defineEmits(['logout', 'go-back', 'stop-all'])
const currentView = ref('tk') // Default Tab
const autoDmMode = ref('config') // Default Sub-state: 'config' or 'browser'
const adminLoaded = ref(false) // Web iframe 懒加载(仅非 Electron
const shopOpened = ref(false) // Electron 只首开加载一次
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) // 左侧导航菜单的实际宽度px传给 YoloBrowser/Sidebar 使用
const tkWorkbenchKey = ref(0) // 用于触发 TK 工作台重新加载
const navSidebarWidth = ref(200)
const tkWorkbenchKey = ref(0)
// 重新加载 TK 工作台
const reloadTkWorkbench = () => {
tkWorkbenchKey.value++
console.log('TK 工作台已重新加载')
}
// 将重新加载 TK 工作台的方法挂载到 window 对象
window.reloadTkWorkbench = reloadTkWorkbench
// 客服名片
const serviceContacts = ref([])
const loadServiceContacts = async () => {
try {
const res = await getCustomServiceInfo()
console.log("获取名片", res)
if (res) {
serviceContacts.value = res.map(item => ({
avatar: item.avater,
@@ -214,14 +257,14 @@ const loadServiceContacts = async () => {
}
}
// 监听菜单栏实际宽度,通知后端更新 BrowserView 定位
let resizeObserver = null
const notifySidebarWidth = (width) => {
navSidebarWidth.value = Math.round(width)
if (isElectron()) {
window.electronAPI.setSidebarWidth(Math.round(width)).catch(() => { })
window.electronAPI.setSidebarWidth(Math.round(width)).catch(() => {})
}
}
onMounted(() => {
loadServiceContacts()
if (!isElectron()) return
@@ -231,10 +274,10 @@ onMounted(() => {
})
if (sidebarRef.value) {
resizeObserver.observe(sidebarRef.value)
// 立即上报初始宽度
notifySidebarWidth(sidebarRef.value.getBoundingClientRect().width)
}
})
onUnmounted(() => {
resizeObserver?.disconnect()
})
@@ -257,15 +300,11 @@ const handleStopAll = () => {
emit('stop-all')
}
// 处理配置更新事件
const handleConfigUpdated = () => {
// 触发自定义事件通知 YoloBrowser 重新加载配置
window.dispatchEvent(new CustomEvent('config-updated'))
}
// Watch for view changes to manage native Electron BrowserViews
watch(currentView, async (newVal, oldVal) => {
// 懒加载 Web 端 iframe仅非 Electron
if (newVal === 'shop' && !adminLoaded.value && !isElectron()) {
adminLoaded.value = true
}
@@ -273,19 +312,11 @@ watch(currentView, async (newVal, oldVal) => {
if (!isElectron()) return
if (newVal === 'shop') {
if (!shopOpened.value) {
try {
shopOpened.value = true
try {
await window.electronAPI.openShop(shopUrl)
} catch (e) {
console.error('打开商店失败:', e)
}
} else {
try {
await window.electronAPI.openShop(shopUrl)
} catch (e) {
console.error('打开商店失败:', e)
}
await window.electronAPI.openShop(shopUrl)
} catch (e) {
console.error('打开商店失败:', e)
}
} else if (oldVal === 'shop') {
try {
@@ -295,24 +326,24 @@ watch(currentView, async (newVal, oldVal) => {
}
}
if (newVal === 'auto_dm' && autoDmMode.value === 'browser') {
// Switching TO Auto DM tab AND we are in browser mode: Show views
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 {
// Switching AWAY from Auto DM tab OR we are in config mode: Hide views
try {
await window.electronAPI.hideViews()
} catch (e) {
// console.error('Failed to hide views:', e)
console.error('Failed to hide views:', e)
}
}
})
// Watch sub-mode changes
watch(autoDmMode, async (newVal) => {
if (currentView.value !== 'auto_dm') return
@@ -325,7 +356,6 @@ watch(autoDmMode, async (newVal) => {
</script>
<style scoped>
/* Material Icons support - simplistic import, ideal to put in index.html or main.js */
@import url('https://fonts.googleapis.com/icon?family=Material+Icons+Round');
.yolo-logo {