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

View File

@@ -15,11 +15,13 @@
</div> </div>
<div class="relative flex-1 min-w-[200px]"> <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 <input
ref="dateInput"
v-model="searchForm.createTime" v-model="searchForm.createTime"
type="date" 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> </div>
@@ -50,45 +52,53 @@
</header> </header>
<!-- Table --> <!-- Table -->
<div class="flex-1 overflow-auto p-8 relative"> <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-10 flex items-center justify-center"> <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> <span class="material-icons-round animate-spin text-primary text-4xl">sync</span>
</div> </div>
<table class="w-full text-left border-separate border-spacing-0">
<thead> <!-- Table with Fixed Layout -->
<tr class="text-slate-400 text-xs font-semibold uppercase tracking-wider sticky top-0 bg-white z-0"> <div class="flex-1 overflow-auto">
<!-- Selection Header --> <table class="w-full text-left" style="table-layout: fixed; min-width: 1000px;">
<th class="pb-4 pl-4 font-normal bg-white"> <colgroup>
<!-- Select All Logic not full implemented yet, just visual for design match --> <col style="width: 12%;">
<input type="checkbox" class="rounded border-slate-300 text-primary focus:ring-primary/20" /> <col style="width: 5%;">
</th> <col style="width: 8%;">
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.hostId') }}</th> <col style="width: 15%;">
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.grade') }}</th> <col style="width: 6%;">
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.invitationType') }}</th> <col style="width: 10%;">
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.liveSessions') }} / {{ $t('hostList.liveRevenue') }}</th> <col style="width: 8%;">
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.country') }}</th> <col style="width: 8%;">
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.creationTime') }}</th> <col style="width: 7%;">
<th class="pb-4 font-semibold bg-white">{{ $t('hostList.anchorcoins') }}</th> <col style="width: 7%;">
<th class="pb-4 font-semibold text-right pr-4 bg-white">操作</th> <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> </tr>
</thead> </thead>
<tbody class="text-sm text-slate-600"> <tbody class="text-sm text-slate-600">
<tr v-if="tableData.length === 0" class="text-center"> <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>
<tr v-for="row in tableData" :key="row.hostId" class="group hover:bg-slate-50/80 transition-colors"> <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 --> <!-- 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 <span
@click="openHTML(row.hostId)" @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" 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> </td>
<!-- Level --> <!-- 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 }} {{ row.hostlevel }}
</td> </td>
<!-- Invitation Type --> <!-- 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 <span
class="px-3 py-1 text-[10px] font-bold uppercase rounded-full" 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'" :class="row.invitationType == 1 ? 'bg-green-50 text-green-600' : 'bg-amber-50 text-amber-600'"
@@ -113,7 +123,7 @@
</td> </td>
<!-- Data Buttons --> <!-- 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"> <div class="flex gap-2">
<button <button
@click="getliveHost(row.hostId)" @click="getliveHost(row.hostId)"
@@ -131,12 +141,12 @@
</td> </td>
<!-- Country --> <!-- 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 }} {{ row.country }}
</td> </td>
<!-- Time --> <!-- 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"> <div class="flex flex-col">
<span>{{ formatTimeOnlyDate(row.createTime) }}</span> <span>{{ formatTimeOnlyDate(row.createTime) }}</span>
<span class="text-[10px] text-slate-400">{{ formatTimeOnlyTime(row.createTime) }}</span> <span class="text-[10px] text-slate-400">{{ formatTimeOnlyTime(row.createTime) }}</span>
@@ -144,20 +154,41 @@
</td> </td>
<!-- Coins --> <!-- 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 }} {{ row.hostsCoins }}
</td> </td>
<!-- Actions --> <!-- Yesterday Coins -->
<td class="py-5 border-b border-slate-50 group-last:border-none text-right pr-4"> <td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<button class="p-1 hover:bg-slate-200 rounded transition-colors text-slate-400"> {{ row.yesterdayCoins }}
<span class="material-icons-round text-lg">more_horiz</span>
</button>
</td> </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> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<!-- Footer / Pagination --> <!-- 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"> <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> <span class="material-icons-round text-lg">chevron_left</span>
</button> </button>
<!-- Simplified Pagination Logic --> <!-- Page Numbers -->
<button class="w-8 h-8 rounded-lg bg-slate-900 text-white text-xs font-bold transition-all shadow-md">{{ page }}</button> <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 <button
@click="changePage(page + 1)" @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" 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> <span class="material-icons-round text-lg">chevron_right</span>
@@ -313,12 +354,13 @@
import { tkhostdata, getCountryinfo, liveHostDetail, revenueStats } from '@/api/account'; import { tkhostdata, getCountryinfo, liveHostDetail, revenueStats } from '@/api/account';
import { usePythonBridge } from '@/utils/pythonBridge' import { usePythonBridge } from '@/utils/pythonBridge'
import { getUser } from '@/utils/storage' import { getUser } from '@/utils/storage'
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive, onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import LiveRecordDialog from '@/components/LiveRecordDialog.vue' import LiveRecordDialog from '@/components/LiveRecordDialog.vue'
const { t } = useI18n() const { t } = useI18n()
const loading = ref(false) const loading = ref(false)
const dateInput = ref(null)
const { givePyAnchorId, exportToExcel } = usePythonBridge(); const { givePyAnchorId, exportToExcel } = usePythonBridge();
const userInfo = ref(getUser()) const userInfo = ref(getUser())
@@ -374,6 +416,19 @@ function serch() {
getlist(); 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() { function handleSizeChange() {
page.value = 1 page.value = 1
getlist(); getlist();
@@ -385,6 +440,57 @@ function changePage(newPage) {
getlist() 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 = () => { const getlist = () => {
loading.value = true loading.value = true

View File

@@ -218,24 +218,36 @@
<div <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"> 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"> <div class="flex items-center gap-6">
<label class="flex items-center gap-2 cursor-pointer group"> <div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterGame')">
<input class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary/20" type="checkbox" <span
v-model="pyData.filterGame" :disabled="!pyData.isStart" /> 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 <span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤游戏主播</span> class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤游戏主播</span>
</label> </div>
<label class="flex items-center gap-2 cursor-pointer group"> <div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('filterSelling')">
<input class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary/20" type="checkbox" <span
v-model="pyData.filterSelling" :disabled="!pyData.isStart" /> 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 <span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤带货主播</span> class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤带货主播</span>
</label> </div>
<label class="flex items-center gap-2 cursor-pointer group"> <div class="flex items-center gap-2 cursor-pointer group" @click="toggleFilter('rankingList')">
<input class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary/20" type="checkbox" <span
v-model="pyData.rankingList" :disabled="!pyData.isStart" /> 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 <span
class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤排行榜单</span> class="text-sm text-slate-600 dark:text-slate-400 group-hover:text-primary transition-colors">过滤排行榜单</span>
</label> </div>
</div> </div>
</div> </div>
@@ -564,6 +576,12 @@ const reset = () => {
pyData.value.frequency = { hour: 0, day: 0 }; 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) => { const loginTK = (index) => {
setTkUser(tkData.value) setTkUser(tkData.value)