新版ui
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<!-- 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,19 +154,40 @@
|
||||
</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 -->
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user