优化达人工作台

This commit is contained in:
2026-04-28 17:33:39 +08:00
parent ff25bb3922
commit acb93c09f9
6 changed files with 511 additions and 33 deletions

View File

@@ -4,7 +4,7 @@
<div class="mx-4 flex max-h-[84vh] w-full max-w-6xl flex-col overflow-hidden rounded-2xl bg-white shadow-2xl">
<div class="flex items-center justify-between border-b border-gray-200 px-5 py-4">
<div>
<h3 class="text-lg font-semibold text-gray-900">大哥池</h3>
<h3 class="text-lg font-semibold text-gray-900">{{ featureLabels.poolName }}</h3>
</div>
<div class="flex items-center gap-2">
<button @click="handleDeleteAll" :disabled="loading || total === 0"
@@ -63,31 +63,31 @@
<table class="min-w-full border-collapse text-left text-sm">
<thead class="sticky top-0 bg-slate-900 text-slate-100">
<tr>
<th class="px-4 py-3 font-medium">ID</th>
<th class="px-4 py-3 font-medium">displayId</th>
<th class="px-4 py-3 font-medium">昵称</th>
<th class="px-4 py-3 font-medium">地区</th>
<th class="px-4 py-3 font-medium">等级</th>
<th class="px-4 py-3 font-medium">打赏金币</th>
<th class="px-4 py-3 font-medium">粉丝数</th>
<th class="px-4 py-3 font-medium">主播ID</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">ID</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">昵称</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">等级</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">打赏金币</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">主播ID</th>
<th class="px-4 py-3 font-medium">创建时间</th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows" :key="row.id" class="border-b border-gray-100 hover:bg-blue-50/50">
<td class="px-4 py-3 text-gray-700">{{ row.id ?? '-' }}</td>
<td class="px-4 py-3 font-medium text-gray-900">{{ row.displayId || '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ row.nickname || '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ row.region || '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ row.level ?? '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ formatNumber(row.hostcoins) }}</td>
<td class="px-4 py-3 text-gray-700">{{ formatNumber(row.followerCount) }}</td>
<td class="px-4 py-3 text-gray-700">{{ row.hostDisplayId || '-' }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.id ?? '-' }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.nickname || '-' }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.level ?? '-' }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ formatNumber(row.hostcoins) }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.hostDisplayId || '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ formatTime(row.createTime) }}</td>
</tr>
<tr v-if="!loading && rows.length === 0">
<td colspan="9" class="px-4 py-12 text-center text-gray-400">暂无大哥池数据</td>
<td :colspan="featureLabels.expertTenant ? 4 : 9" class="px-4 py-12 text-center text-gray-400">暂无{{ featureLabels.poolName }}数据</td>
</tr>
</tbody>
</table>
@@ -131,6 +131,7 @@
<script setup>
import { reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { getCurrentBrotherFeatureLabels } from '@/utils/tenantFeature'
const props = defineProps({
visible: {
@@ -140,6 +141,7 @@ const props = defineProps({
})
const emit = defineEmits(['close'])
const featureLabels = getCurrentBrotherFeatureLabels()
const loading = ref(false)
const rows = ref([])
@@ -182,7 +184,7 @@ async function loadData() {
rows.value = []
total.value = 0
totalPages.value = 0
ElMessage.error('当前客户端未加载大哥池查询桥接,请重启客户端后再试')
ElMessage.error(`当前客户端未加载${featureLabels.poolName}查询桥接,请重启客户端后再试`)
return
}
@@ -191,7 +193,7 @@ async function loadData() {
const raw = await tkBridge.queryBrotherInfo(JSON.stringify(buildPayload()))
const result = typeof raw === 'string' ? JSON.parse(raw) : raw
if (result?.status !== 'success') {
throw new Error(result?.message || '查询大哥池失败')
throw new Error(result?.message || `查询${featureLabels.poolName}失败`)
}
rows.value = Array.isArray(result?.list) ? result.list : []
total.value = Number(result?.total || 0)
@@ -210,7 +212,7 @@ async function loadData() {
async function handleDeleteAll() {
const tkBridge = getTkBridge()
if (!tkBridge?.getAllBrotherInfo || !tkBridge?.deleteBrotherInfo) {
ElMessage.error('当前客户端未加载大哥池删除桥接,请重启客户端后再试')
ElMessage.error(`当前客户端未加载${featureLabels.poolName}删除桥接,请重启客户端后再试`)
return
}
@@ -221,7 +223,7 @@ async function confirmDeleteAll() {
const tkBridge = getTkBridge()
if (!tkBridge?.getAllBrotherInfo || !tkBridge?.deleteBrotherInfo) {
showDeleteConfirm.value = false
ElMessage.error('当前客户端未加载大哥池删除桥接,请重启客户端后再试')
ElMessage.error(`当前客户端未加载${featureLabels.poolName}删除桥接,请重启客户端后再试`)
return
}
@@ -230,7 +232,7 @@ async function confirmDeleteAll() {
const rawList = await tkBridge.getAllBrotherInfo()
const listResult = typeof rawList === 'string' ? JSON.parse(rawList) : rawList
if (listResult?.status !== 'success') {
throw new Error(listResult?.message || '读取大哥池失败')
throw new Error(listResult?.message || `读取${featureLabels.poolName}失败`)
}
const ids = (Array.isArray(listResult?.list) ? listResult.list : [])
@@ -239,7 +241,7 @@ async function confirmDeleteAll() {
if (ids.length === 0) {
showDeleteConfirm.value = false
ElMessage.success('大哥池已经是空的')
ElMessage.success(`${featureLabels.poolName}已经是空的`)
rows.value = []
total.value = 0
totalPages.value = 1
@@ -250,10 +252,10 @@ async function confirmDeleteAll() {
const rawDelete = await tkBridge.deleteBrotherInfo(JSON.stringify({ ids }))
const deleteResult = typeof rawDelete === 'string' ? JSON.parse(rawDelete) : rawDelete
if (deleteResult?.status !== 'success') {
throw new Error(deleteResult?.message || '删除大哥池失败')
throw new Error(deleteResult?.message || `删除${featureLabels.poolName}失败`)
}
ElMessage.success(`已删除 ${ids.length}大哥池数据`)
ElMessage.success(`已删除 ${ids.length}${featureLabels.poolName}数据`)
showDeleteConfirm.value = false
page.value = 1
await loadData()

View File

@@ -0,0 +1,440 @@
<template>
<Teleport to="body">
<div v-if="visible" class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/50">
<div class="mx-4 flex max-h-[84vh] w-full max-w-6xl flex-col overflow-hidden rounded-2xl bg-white shadow-2xl">
<div class="flex items-center justify-between border-b border-gray-200 px-5 py-4">
<div>
<h3 class="text-lg font-semibold text-gray-900">{{ featureLabels.poolName }}</h3>
</div>
<div class="flex items-center gap-2">
<button
@click="handleDeleteAll"
:disabled="loading || total === 0"
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-600 transition-colors hover:bg-red-100 disabled:cursor-not-allowed disabled:opacity-50"
>
全部删除
</button>
<button
@click="handleClose"
class="rounded-lg px-3 py-2 text-sm text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700"
>
关闭
</button>
</div>
</div>
<div class="border-b border-gray-100 bg-gray-50 px-5 py-4">
<div class="grid grid-cols-1 gap-3 md:grid-cols-4">
<input
v-model.trim="filters.keyword"
type="text"
placeholder="关键词displayId / 昵称 / userIdStr"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
@keyup.enter="handleSearch"
/>
<input
v-model.trim="filters.region"
type="text"
placeholder="地区"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
@keyup.enter="handleSearch"
/>
<input
v-model.trim="filters.hostDisplayId"
type="text"
placeholder="主播 displayId"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
@keyup.enter="handleSearch"
/>
<input
v-model.trim="filters.displayId"
type="text"
:placeholder="`${featureLabels.roleName} displayId`"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
@keyup.enter="handleSearch"
/>
</div>
<div class="mt-3 grid grid-cols-2 gap-3 md:grid-cols-6">
<input
v-model.number="filters.minLevel"
type="number"
min="0"
placeholder="最低等级"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
/>
<input
v-model.number="filters.maxLevel"
type="number"
min="0"
placeholder="最高等级"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
/>
<input
v-model.number="filters.minCoins"
type="number"
min="0"
placeholder="最低金币"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
/>
<input
v-model.number="filters.maxCoins"
type="number"
min="0"
placeholder="最高金币"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
/>
<select
v-model.number="pageSize"
class="h-10 rounded-lg border border-gray-300 bg-white px-3 text-sm outline-none focus:border-blue-500"
>
<option :value="10">10 / </option>
<option :value="20">20 / </option>
<option :value="50">50 / </option>
<option :value="100">100 / </option>
</select>
<div class="flex gap-2">
<button
@click="handleSearch"
:disabled="loading"
class="flex-1 rounded-lg bg-blue-600 px-3 py-2 text-sm text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60"
>
查询
</button>
<button
@click="handleReset"
:disabled="loading"
class="flex-1 rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-60"
>
重置
</button>
</div>
</div>
</div>
<div class="flex items-center justify-between border-b border-gray-100 px-5 py-3 text-sm text-gray-500">
<div> {{ total }} 本页 {{ rows.length }} </div>
<div v-if="loading" class="text-blue-600">加载中...</div>
</div>
<div class="min-h-0 flex-1 overflow-auto">
<table class="min-w-full border-collapse text-left text-sm">
<thead class="sticky top-0 bg-slate-900 text-slate-100">
<tr>
<th class="px-4 py-3 font-medium">displayId</th>
<th class="px-4 py-3 font-medium">地区</th>
<th class="px-4 py-3 font-medium">粉丝数</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">ID</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">昵称</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">等级</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">打赏金币</th>
<th v-if="!featureLabels.expertTenant" class="px-4 py-3 font-medium">主播ID</th>
<th class="px-4 py-3 font-medium">创建时间</th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows" :key="row.id" class="border-b border-gray-100 hover:bg-blue-50/50">
<td class="px-4 py-3 font-medium text-gray-900">{{ row.displayId || '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ row.region || '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ formatNumber(row.followerCount) }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.id ?? '-' }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.nickname || '-' }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.level ?? '-' }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ formatNumber(row.hostcoins) }}</td>
<td v-if="!featureLabels.expertTenant" class="px-4 py-3 text-gray-700">{{ row.hostDisplayId || '-' }}</td>
<td class="px-4 py-3 text-gray-700">{{ formatTime(row.createTime) }}</td>
</tr>
<tr v-if="!loading && rows.length === 0">
<td :colspan="featureLabels.expertTenant ? 4 : 9" class="px-4 py-12 text-center text-gray-400">暂无{{ featureLabels.poolName }}数据</td>
</tr>
</tbody>
</table>
</div>
<div class="flex items-center justify-between border-t border-gray-200 px-5 py-4">
<div class="text-sm text-gray-500"> {{ page }} / {{ totalPages || 1 }} </div>
<div class="flex items-center gap-2">
<button
@click="goPrev"
:disabled="loading || page <= 1"
class="rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50"
>
上一页
</button>
<button
@click="goNext"
:disabled="loading || page >= totalPages"
class="rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50"
>
下一页
</button>
</div>
</div>
</div>
<div v-if="showDeleteConfirm" class="absolute inset-0 z-[10001] flex items-center justify-center bg-black/45 px-4">
<div class="w-full max-w-md rounded-2xl bg-white p-6 shadow-2xl">
<div class="text-lg font-semibold text-gray-900">确认全部删除</div>
<p class="mt-3 text-sm leading-6 text-gray-600">
将删除本地 sqlite
<code class="rounded bg-gray-100 px-1.5 py-0.5 text-xs text-gray-700">brother_info</code>
的全部数据删除后不可恢复
</p>
<div class="mt-6 flex justify-end gap-3">
<button
@click="showDeleteConfirm = false"
:disabled="loading"
class="rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-700 transition-colors hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50"
>
取消
</button>
<button
@click="confirmDeleteAll"
:disabled="loading"
class="rounded-lg bg-red-600 px-4 py-2 text-sm text-white transition-colors hover:bg-red-700 disabled:cursor-not-allowed disabled:opacity-50"
>
{{ loading ? '删除中...' : '确认删除' }}
</button>
</div>
</div>
</div>
</div>
</Teleport>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { getCurrentBrotherFeatureLabels } from '@/utils/tenantFeature'
const props = defineProps({
visible: {
type: Boolean,
required: true,
},
})
const emit = defineEmits(['close'])
const featureLabels = getCurrentBrotherFeatureLabels()
const loading = ref(false)
const rows = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const totalPages = ref(0)
const showDeleteConfirm = ref(false)
const filters = reactive({
keyword: '',
region: '',
hostDisplayId: '',
displayId: '',
minLevel: undefined,
maxLevel: undefined,
minCoins: undefined,
maxCoins: undefined,
})
function getTkBridge() {
return window?.electronAPI?.tk || null
}
function buildPayload() {
const nextFilters = {}
for (const [key, value] of Object.entries(filters)) {
if (value === '' || value === null || value === undefined) {
continue
}
nextFilters[key] = value
}
return {
page: page.value,
pageSize: pageSize.value,
filters: nextFilters,
}
}
async function loadData() {
const tkBridge = getTkBridge()
if (!tkBridge?.queryBrotherInfo) {
rows.value = []
total.value = 0
totalPages.value = 0
ElMessage.error(`当前客户端未加载${featureLabels.poolName}查询桥接,请重启客户端后再试`)
return
}
loading.value = true
try {
const raw = await tkBridge.queryBrotherInfo(JSON.stringify(buildPayload()))
const result = typeof raw === 'string' ? JSON.parse(raw) : raw
if (result?.status !== 'success') {
throw new Error(result?.message || `查询${featureLabels.poolName}失败`)
}
rows.value = Array.isArray(result?.list) ? result.list : []
total.value = Number(result?.total || 0)
totalPages.value = Math.max(1, Number(result?.totalPages || 0))
page.value = Number(result?.page || page.value || 1)
} catch (error) {
rows.value = []
total.value = 0
totalPages.value = 0
ElMessage.error(error instanceof Error ? error.message : String(error))
} finally {
loading.value = false
}
}
function handleDeleteAll() {
const tkBridge = getTkBridge()
if (!tkBridge?.getAllBrotherInfo || !tkBridge?.deleteBrotherInfo) {
ElMessage.error(`当前客户端未加载${featureLabels.poolName}删除桥接,请重启客户端后再试`)
return
}
showDeleteConfirm.value = true
}
async function confirmDeleteAll() {
const tkBridge = getTkBridge()
if (!tkBridge?.getAllBrotherInfo || !tkBridge?.deleteBrotherInfo) {
showDeleteConfirm.value = false
ElMessage.error(`当前客户端未加载${featureLabels.poolName}删除桥接,请重启客户端后再试`)
return
}
loading.value = true
try {
const rawList = await tkBridge.getAllBrotherInfo()
const listResult = typeof rawList === 'string' ? JSON.parse(rawList) : rawList
if (listResult?.status !== 'success') {
throw new Error(listResult?.message || `读取${featureLabels.poolName}失败`)
}
const ids = (Array.isArray(listResult?.list) ? listResult.list : [])
.map(item => Number(item?.id))
.filter(id => Number.isFinite(id))
if (ids.length === 0) {
showDeleteConfirm.value = false
ElMessage.success(`${featureLabels.poolName}已经是空的`)
rows.value = []
total.value = 0
totalPages.value = 1
page.value = 1
return
}
const rawDelete = await tkBridge.deleteBrotherInfo(JSON.stringify({ ids }))
const deleteResult = typeof rawDelete === 'string' ? JSON.parse(rawDelete) : rawDelete
if (deleteResult?.status !== 'success') {
throw new Error(deleteResult?.message || `删除${featureLabels.poolName}失败`)
}
ElMessage.success(`已删除 ${ids.length}${featureLabels.poolName}数据`)
showDeleteConfirm.value = false
page.value = 1
await loadData()
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : String(error))
} finally {
loading.value = false
}
}
function handleSearch() {
page.value = 1
void loadData()
}
function handleReset() {
filters.keyword = ''
filters.region = ''
filters.hostDisplayId = ''
filters.displayId = ''
filters.minLevel = undefined
filters.maxLevel = undefined
filters.minCoins = undefined
filters.maxCoins = undefined
page.value = 1
pageSize.value = 20
void loadData()
}
function handleClose() {
emit('close')
}
function goPrev() {
if (page.value <= 1) {
return
}
page.value -= 1
void loadData()
}
function goNext() {
if (page.value >= totalPages.value) {
return
}
page.value += 1
void loadData()
}
function formatNumber(value) {
if (value === null || value === undefined || value === '') {
return '-'
}
const numericValue = Number(value)
return Number.isFinite(numericValue) ? numericValue.toLocaleString() : String(value)
}
function formatTime(value) {
const timestamp = Number(value)
if (!Number.isFinite(timestamp) || timestamp <= 0) {
return '-'
}
const date = new Date(timestamp)
if (Number.isNaN(date.getTime())) {
return '-'
}
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
}
watch(
() => props.visible,
(visible) => {
if (!visible) {
return
}
void loadData()
}
)
watch(pageSize, () => {
if (!props.visible) {
return
}
page.value = 1
void loadData()
})
</script>

View File

@@ -44,17 +44,17 @@
<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)]'"
title="大哥工作台">
:title="brotherFeatureLabels.workbenchName">
<img :src="currentView === 'FanWorkbench' ? nav44 : nav4" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="menu-label">大哥工作台</span>
</button>
<button v-if="tenantId === '12384'" @click="currentView = 'expert_workbench'"
<button v-if="showExpertWorkbench" @click="currentView = 'expert_workbench'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'expert_workbench' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'"
title="达人工作台">
:title="brotherFeatureLabels.workbenchName">
<img :src="currentView === 'expert_workbench' ? nav44 : nav4" class="w-9 h-9 object-contain flex-shrink-0" />
<span class="menu-label">达人工作台</span>
<span class="menu-label">{{ brotherFeatureLabels.workbenchName }}</span>
</button>
<button @click="currentView = 'pk_mini'"
@@ -134,8 +134,9 @@
</div>
<div v-show="currentView === 'expert_workbench'" 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="`${brotherFeatureLabels.workbenchName}未开通`"
:description="`您当前没有使用${brotherFeatureLabels.workbenchName}功能的权限`" :placeholder-image="placeholderHosts"
:contacts="serviceContacts">
<ExpertWorkbench />
</PermissionMask>
</div>
@@ -193,6 +194,7 @@ import placeholderWebAi from '@/assets/placeholder-webai.png'
import placeholderBigBrother from '@/assets/placeholder-bigbrother.png'
import { getUser } from '@/utils/storage';
import { getBrotherFeatureLabels, isExpertPoolTenant } from '@/utils/tenantFeature'
const tenantId = ref('');
const emit = defineEmits(['logout', 'go-back', 'stop-all'])
@@ -205,6 +207,8 @@ const shopUrl = ENV.SHOP_URL
const sidebarRef = useTemplateRef('sidebarRef')
const navSidebarWidth = ref(200)
const tkWorkbenchKey = ref(0)
const showExpertWorkbench = ref(false)
const brotherFeatureLabels = ref(getBrotherFeatureLabels(''))
const reloadTkWorkbench = () => {
tkWorkbenchKey.value++
@@ -243,6 +247,8 @@ onMounted(() => {
loadServiceContacts()
tenantId.value = String(getUser()?.tenantId || '');
showExpertWorkbench.value = isExpertPoolTenant(tenantId.value)
brotherFeatureLabels.value = getBrotherFeatureLabels(tenantId.value)
if (!isElectron()) return
resizeObserver = new ResizeObserver((entries) => {

View File

@@ -0,0 +1,27 @@
import { getUser } from '@/utils/storage'
const EXPERT_POOL_TENANT_IDS = new Set(['12384'])
export function getCurrentTenantId() {
return String(getUser()?.tenantId || '')
}
export function isExpertPoolTenant(tenantId) {
return EXPERT_POOL_TENANT_IDS.has(String(tenantId || ''))
}
export function getBrotherFeatureLabels(tenantId) {
const expertTenant = isExpertPoolTenant(tenantId)
return {
expertTenant,
poolName: expertTenant ? '达人池' : '大哥池',
workbenchName: expertTenant ? '达人工作台' : '大哥工作台',
roleName: expertTenant ? '达人' : '大哥',
roleIdLabel: expertTenant ? '达人ID' : '大哥ID',
}
}
export function getCurrentBrotherFeatureLabels() {
return getBrotherFeatureLabels(getCurrentTenantId())
}

View File

@@ -140,7 +140,7 @@
:class="configForm.dataPoolSource === 'brother_info' ? 'border-purple-500 bg-purple-50 text-purple-700' : 'border-gray-200 bg-gray-50 text-gray-700'">
<input v-model="configForm.dataPoolSource" :disabled="isConfigControlDisabled" type="radio"
value="brother_info" class="h-4 w-4 border-gray-300 text-purple-600 focus:ring-purple-500" />
<span>大哥池</span>
<span>{{ brotherFeatureLabels.poolName }}</span>
</label>
</div>
<!-- <div v-if="isBrotherInfoMode" class="mt-4">
@@ -218,14 +218,14 @@
<section class="rounded-xl border border-gray-200 bg-white p-5 shadow-sm">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-gray-900">大哥池</div>
<div class="text-sm font-medium text-gray-900">{{ brotherFeatureLabels.poolName }}</div>
</div>
<span
:class="['rounded-full px-3 py-1 text-xs font-medium', isBrotherInfoMode ? 'bg-fuchsia-100 text-fuchsia-700' : 'bg-gray-100 text-gray-600']">{{
isBrotherInfoMode ? '当前使用中' : '可切换' }}</span>
</div>
<button @click="showBrotherInfoDialog = true"
class="mt-4 w-full rounded-lg border border-fuchsia-200 bg-fuchsia-50 px-4 py-3 text-sm font-medium text-fuchsia-600 transition-colors hover:bg-fuchsia-100">打开大哥池</button>
class="mt-4 w-full rounded-lg border border-fuchsia-200 bg-fuchsia-50 px-4 py-3 text-sm font-medium text-fuchsia-600 transition-colors hover:bg-fuchsia-100">打开{{ brotherFeatureLabels.poolName }}</button>
</section>
<!-- <section class="rounded-xl bg-slate-950 p-5 shadow-sm">
@@ -317,7 +317,7 @@
<AIConfigDialog :visible="showAIDialog" :config="aiConfig" @close="showAIDialog = false" @save="handleSaveAIConfig"
@change="(key, value) => aiConfig[key] = value" />
<HostListDialog :visible="showHostDialog" @close="showHostDialog = false" @save="() => { }" />
<BrotherInfoDialog :visible="showBrotherInfoDialog" @close="showBrotherInfoDialog = false" />
<TenantBrotherInfoDialog :visible="showBrotherInfoDialog" @close="showBrotherInfoDialog = false" />
<GreetingDialog :visible="showGreetingDialog" @close="showGreetingDialog = false"
@confirm="handleGreetingConfirm" />
</div>
@@ -327,11 +327,12 @@
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { isElectron } from '@/utils/electronBridge'
import { getCurrentBrotherFeatureLabels } from '@/utils/tenantFeature'
import ViewPlaceholder from '@/components/ViewPlaceholder.vue'
import HostListDialog from '@/components/HostListDialog.vue'
import AIConfigDialog from '@/components/AIConfigDialog.vue'
import GreetingDialog from '@/components/GreetingDialog.vue'
import BrotherInfoDialog from '@/components/BrotherInfoDialog.vue'
import TenantBrotherInfoDialog from '@/components/TenantBrotherInfoDialog.vue'
const props = defineProps({ navSidebarWidth: { type: Number, default: 144 } })
const TIKTOK_VIEW_IDS = Array.from({ length: 9 }, (_, index) => index + 10)
@@ -366,7 +367,8 @@ const replyMessageCount = computed(() => replyMessages.value.length)
const languageCount = computed(() => Object.keys(configForm.prologueList || {}).length)
const isBrotherInfoMode = computed(() => configForm.dataPoolSource === 'brother_info')
const effectiveReplyUnreadMessages = computed(() => !isBrotherInfoMode.value && Boolean(configForm.replyUnreadMessages))
const dataPoolLabel = computed(() => (isBrotherInfoMode.value ? '大哥池' : '主播池'))
const brotherFeatureLabels = getCurrentBrotherFeatureLabels()
const dataPoolLabel = computed(() => (isBrotherInfoMode.value ? brotherFeatureLabels.poolName : '主播池'))
const normalizedGroupSwitchMinutes = computed(() => clampMinutes(configForm.groupSwitchMinutes))
const normalizedGroupViewCounts = computed(() => normalizeGroupViewCounts(configForm.groupViewCounts))
const effectiveInitialFullGroupNumber = computed(() => resolveInitialFullGroupNumber(configForm.initialFullGroupNumber, normalizedGroupViewCounts.value))

View File

@@ -239,6 +239,7 @@ import { getCountryName } from "@/utils/countryUtil";
import { ElMessage, ElMessageBox, ElLoading } from "element-plus";
import { useI18n } from 'vue-i18n';
import { useCountryInfo } from '@/composables/useCountryInfo';
import { isExpertPoolTenant } from '@/utils/tenantFeature';
// Mock API calls if not present
// Ideally we should import these from api file, but for simplicity I will mock them or use empty callbacks
@@ -488,7 +489,7 @@ function stopTimerfun() {
// Specify Room Logic
// 动态计算最大行数限制tenantId=12741 为 5000 条,其他为 50 条
const maxSpecifyLines = computed(() => {
return userInfo.value.tenantId == 12384 || userInfo.value.tenantId == 12741 ? 5000 : 50;
return isExpertPoolTenant(userInfo.value.tenantId) || userInfo.value.tenantId == 12741 ? 5000 : 50;
});
// 当前行数