This commit is contained in:
2026-01-16 20:12:21 +08:00
parent 32f9c48c91
commit afaacbd2fa
3 changed files with 187 additions and 64 deletions

View File

@@ -29,7 +29,7 @@
</div>
<div class="auth-card">
<div class="auth-left">
<div class="logo center-justify">
<div class="logo ">
<img class="logo-image" src="@/assets/logo2.png" alt="">
<span class="logo-title">TK主播数据助手</span>
</div>
@@ -283,7 +283,6 @@ const onSubmit = () => {
// margin-bottom: 24px;
display: flex;
align-items: center;
gap: 16px;
}
.logo-image {
@@ -292,13 +291,13 @@ const onSubmit = () => {
}
.logo-title {
font-size: 22px;
font-size: 20px;
font-weight: 600;
color: #2b3347;
letter-spacing: 2px;
position: relative;
padding-left: 14px;
margin-top: 30px;
padding: 10px 0px 10px 14px;
}
.logo-title::before {

View File

@@ -15,11 +15,13 @@
</div>
<div class="relative flex-1 min-w-[200px]">
<span class="material-icons-round absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm">calendar_today</span>
<span class="material-icons-round absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 text-sm pointer-events-none">calendar_today</span>
<input
ref="dateInput"
v-model="searchForm.createTime"
type="date"
class="w-full bg-slate-50 border-none rounded-xl py-3 pl-10 pr-4 text-sm text-slate-600 focus:ring-2 focus:ring-primary/20 shadow-soft-inner outline-none"
@click="openDatePicker"
class="w-full bg-slate-50 border-none rounded-xl py-3 pl-10 pr-4 text-sm text-slate-600 focus:ring-2 focus:ring-primary/20 shadow-soft-inner outline-none cursor-pointer"
/>
</div>
@@ -50,45 +52,53 @@
</header>
<!-- Table -->
<div class="flex-1 overflow-auto p-8 relative">
<div v-if="loading" class="absolute inset-0 bg-white/50 z-10 flex items-center justify-center">
<div class="flex-1 flex flex-col overflow-hidden relative" style="padding-left: 20px;">
<div v-if="loading" class="absolute inset-0 bg-white/50 z-30 flex items-center justify-center">
<span class="material-icons-round animate-spin text-primary text-4xl">sync</span>
</div>
<table class="w-full text-left border-separate border-spacing-0">
<thead>
<tr class="text-slate-400 text-xs font-semibold uppercase tracking-wider sticky top-0 bg-white z-0">
<!-- Selection Header -->
<th class="pb-4 pl-4 font-normal bg-white">
<!-- Select All Logic not full implemented yet, just visual for design match -->
<input type="checkbox" class="rounded border-slate-300 text-primary focus:ring-primary/20" />
</th>
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.hostId') }}</th>
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.grade') }}</th>
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.invitationType') }}</th>
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.liveSessions') }} / {{ $t('hostList.liveRevenue') }}</th>
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.country') }}</th>
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.creationTime') }}</th>
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.anchorcoins') }}</th>
<th class="pb-4 font-semibold text-right pr-4 bg-white">操作</th>
<!-- Table with Fixed Layout -->
<div class="flex-1 overflow-auto">
<table class="w-full text-left" style="table-layout: fixed; min-width: 1000px;">
<colgroup>
<col style="width: 12%;">
<col style="width: 5%;">
<col style="width: 8%;">
<col style="width: 15%;">
<col style="width: 6%;">
<col style="width: 10%;">
<col style="width: 8%;">
<col style="width: 8%;">
<col style="width: 7%;">
<col style="width: 7%;">
<col style="width: 7%;">
<col style="width: 7%;">
</colgroup>
<thead class="bg-white sticky top-0 z-10">
<tr class="text-slate-400 text-xs font-semibold uppercase tracking-wider border-b border-slate-100">
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.hostId') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.grade') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.invitationType') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.liveSessions') }}/{{ $t('hostList.liveRevenue') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.country') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.creationTime') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.anchorcoins') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.yesterdayGoldCoins') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.fansNum') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.followersNum') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.onlineFans') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.anchorType') }}</th>
</tr>
</thead>
<tbody class="text-sm text-slate-600">
<tr v-if="tableData.length === 0" class="text-center">
<td colspan="9" class="py-10 text-slate-400">暂无数据</td>
<td colspan="14" class="py-10 text-slate-400">暂无数据</td>
</tr>
<tr v-for="row in tableData" :key="row.hostId" class="group hover:bg-slate-50/80 transition-colors">
<!-- Checkbox -->
<td class="py-5 pl-4 border-b border-slate-50 group-last:border-none">
<input
type="checkbox"
class="rounded border-slate-300 text-primary focus:ring-primary/20"
:value="row.hostId"
v-model="selectHostList"
/>
</td>
<!-- Host ID -->
<td class="py-5 border-b border-slate-50 group-last:border-none">
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<span
@click="openHTML(row.hostId)"
class="font-medium text-slate-900 border-b border-transparent group-hover:border-primary/30 transition-all cursor-pointer hover:text-primary"
@@ -98,12 +108,12 @@
</td>
<!-- Level -->
<td class="py-5 border-b border-slate-50 group-last:border-none">
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.hostlevel }}
</td>
<!-- Invitation Type -->
<td class="py-5 border-b border-slate-50 group-last:border-none">
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<span
class="px-3 py-1 text-[10px] font-bold uppercase rounded-full"
:class="row.invitationType == 1 ? 'bg-green-50 text-green-600' : 'bg-amber-50 text-amber-600'"
@@ -113,7 +123,7 @@
</td>
<!-- Data Buttons -->
<td class="py-5 border-b border-slate-50 group-last:border-none">
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<div class="flex gap-2">
<button
@click="getliveHost(row.hostId)"
@@ -131,12 +141,12 @@
</td>
<!-- Country -->
<td class="py-5 border-b border-slate-50 group-last:border-none">
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.country }}
</td>
<!-- Time -->
<td class="py-5 border-b border-slate-50 group-last:border-none">
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<div class="flex flex-col">
<span>{{ formatTimeOnlyDate(row.createTime) }}</span>
<span class="text-[10px] text-slate-400">{{ formatTimeOnlyTime(row.createTime) }}</span>
@@ -144,20 +154,41 @@
</td>
<!-- Coins -->
<td class="py-5 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none font-semibold text-slate-900">
{{ row.hostsCoins }}
</td>
<!-- Actions -->
<td class="py-5 border-b border-slate-50 group-last:border-none text-right pr-4">
<button class="p-1 hover:bg-slate-200 rounded transition-colors text-slate-400">
<span class="material-icons-round text-lg">more_horiz</span>
</button>
<!-- Yesterday Coins -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.yesterdayCoins }}
</td>
<!-- Fans -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.fans }}
</td>
<!-- Followers -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.fllowernum }}
</td>
<!-- Online Fans -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.onlineFans }}
</td>
<!-- Host Kind -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.hostsKind }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Footer / Pagination -->
<footer class="px-8 py-6 border-t border-slate-100 dark:border-slate-200/60 bg-white/50 backdrop-blur-md flex flex-wrap items-center justify-between gap-4">
@@ -187,12 +218,22 @@
<span class="material-icons-round text-lg">chevron_left</span>
</button>
<!-- Simplified Pagination Logic -->
<button class="w-8 h-8 rounded-lg bg-slate-900 text-white text-xs font-bold transition-all shadow-md">{{ page }}</button>
<!-- Page Numbers -->
<template v-for="(p, index) in paginationPages" :key="index">
<span v-if="p === '...'" class="w-8 h-8 flex items-center justify-center text-slate-400 text-sm">...</span>
<button
v-else
@click="changePage(p)"
class="w-8 h-8 rounded-lg text-xs font-bold transition-all"
:class="p === page ? 'bg-slate-900 text-white shadow-md' : 'text-slate-600 hover:bg-slate-100'"
>
{{ p }}
</button>
</template>
<button
@click="changePage(page + 1)"
:disabled="page * pageSize >= total"
:disabled="page >= totalPages"
class="p-2 text-slate-400 hover:bg-slate-100 rounded-lg transition-colors disabled:opacity-50"
>
<span class="material-icons-round text-lg">chevron_right</span>
@@ -313,12 +354,13 @@
import { tkhostdata, getCountryinfo, liveHostDetail, revenueStats } from '@/api/account';
import { usePythonBridge } from '@/utils/pythonBridge'
import { getUser } from '@/utils/storage'
import { ref, reactive, onMounted } from 'vue';
import { ref, reactive, onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n'
import LiveRecordDialog from '@/components/LiveRecordDialog.vue'
const { t } = useI18n()
const loading = ref(false)
const dateInput = ref(null)
const { givePyAnchorId, exportToExcel } = usePythonBridge();
const userInfo = ref(getUser())
@@ -374,6 +416,19 @@ function serch() {
getlist();
}
// 手动打开日期选择器 (解决Electron中点击问题)
function openDatePicker(event) {
const input = event.target;
if (input && typeof input.showPicker === 'function') {
try {
input.showPicker();
} catch (e) {
// showPicker may fail in some environments, fallback to focus
input.focus();
}
}
}
function handleSizeChange() {
page.value = 1
getlist();
@@ -385,6 +440,57 @@ function changePage(newPage) {
getlist()
}
// 计算总页数
const totalPages = computed(() => Math.ceil(total.value / pageSize.value))
// 生成分页页码数组 (类似Element UI风格)
const paginationPages = computed(() => {
const current = page.value
const totalP = totalPages.value
const pages = []
if (totalP <= 7) {
// 总页数小于等于7显示全部
for (let i = 1; i <= totalP; i++) {
pages.push(i)
}
} else {
// 总是显示第一页
pages.push(1)
if (current > 4) {
pages.push('...')
}
// 计算中间页码范围
let start = Math.max(2, current - 2)
let end = Math.min(totalP - 1, current + 2)
// 调整范围确保显示足够的页码
if (current <= 4) {
end = Math.min(5, totalP - 1)
}
if (current >= totalP - 3) {
start = Math.max(totalP - 4, 2)
}
for (let i = start; i <= end; i++) {
pages.push(i)
}
if (current < totalP - 3) {
pages.push('...')
}
// 总是显示最后一页
if (totalP > 1) {
pages.push(totalP)
}
}
return pages
})
const getlist = () => {
loading.value = true

View File

@@ -218,24 +218,36 @@
<div
class="flex flex-col lg:flex-row items-center justify-between gap-6 pt-4 border-t border-slate-100 dark:border-slate-800">
<div class="flex items-center gap-6">
<label class="flex items-center gap-2 cursor-pointer group">
<input class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary/20" type="checkbox"
v-model="pyData.filterGame" :disabled="!pyData.isStart" />
<div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterGame')">
<span
class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all"
:class="pyData.filterGame ? 'bg-primary border-primary' : 'bg-white border-slate-300'"
>
<span v-if="pyData.filterGame" class="material-icons-round text-white text-xs">check</span>
</span>
<span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤游戏主播</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<input class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary/20" type="checkbox"
v-model="pyData.filterSelling" :disabled="!pyData.isStart" />
</div>
<div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterSelling')">
<span
class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all"
:class="pyData.filterSelling ? 'bg-primary border-primary' : 'bg-white border-slate-300'"
>
<span v-if="pyData.filterSelling" class="material-icons-round text-white text-xs">check</span>
</span>
<span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤带货主播</span>
</label>
<label class="flex items-center gap-2 cursor-pointer group">
<input class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary/20" type="checkbox"
v-model="pyData.rankingList" :disabled="!pyData.isStart" />
</div>
<div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('rankingList')">
<span
class="w-4 h-4 rounded border-2 flex items-center justify-center transition-all"
:class="pyData.rankingList ? 'bg-primary border-primary' : 'bg-white border-slate-300'"
>
<span v-if="pyData.rankingList" class="material-icons-round text-white text-xs">check</span>
</span>
<span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤排行榜单</span>
</label>
</div>
</div>
</div>
@@ -564,6 +576,12 @@ const reset = () => {
pyData.value.frequency = { hour: 0, day: 0 };
};
// 切换过滤选项 (用于Electron环境下的即时响应)
const toggleFilter = (filterName) => {
if (!pyData.value.isStart) return; // 如果已启动则不允许修改
pyData.value[filterName] = !pyData.value[filterName];
};
const loginTK = (index) => {
setTkUser(tkData.value)