Compare commits

3 Commits
main ... custom

Author SHA1 Message Date
1606f9a3e3 融合版 2026-01-23 14:35:09 +08:00
2ce7cd6344 定制版功能出版 2026-01-21 15:32:47 +08:00
1b07facf49 定制版 2026-01-21 14:02:22 +08:00
7 changed files with 339 additions and 433 deletions

View File

@@ -93,5 +93,10 @@ export default {
revenueTime: 'Time', revenueTime: 'Time',
close: 'Close', close: 'Close',
selectPlaceholder: 'Please select', selectPlaceholder: 'Please select',
countryEng: 'Country (EN)',
videoCount: 'Video Count',
heart: 'Likes',
signature: 'Bio Signature',
bioLink: 'Bio Link',
} }
} }

View File

@@ -93,5 +93,10 @@
revenueTime: '时间', revenueTime: '时间',
close: '关闭', close: '关闭',
selectPlaceholder: '请选择', selectPlaceholder: '请选择',
countryEng: '国家(英文)',
videoCount: '视频数量',
heart: '点赞数',
signature: '主页签名',
bioLink: '主页链接',
} }
} }

View File

@@ -38,7 +38,7 @@ const initBridge = () => {
const val = target[prop]; const val = target[prop];
if (typeof val === 'function') return val; if (typeof val === 'function') return val;
// 返回空函数,确保 handleResponse 可调用 // 返回空函数,确保 handleResponse 可调用
return () => {}; return () => { };
}, },
set(target, prop, value) { set(target, prop, value) {
target[prop] = value; target[prop] = value;
@@ -129,6 +129,16 @@ export function usePythonBridge() {
}); });
}; };
// 查询主播列表
const getHosts = (data) => {
return new Promise((resolve) => {
if (!bridge.value) return resolve(null);
callBridge('searchHosts', JSON.stringify(data), (result) => {
resolve(result);
});
});
};
// 在组件挂载时初始化桥接 // 在组件挂载时初始化桥接
onMounted(initBridge); onMounted(initBridge);
@@ -143,5 +153,6 @@ export function usePythonBridge() {
exportToExcel, exportToExcel,
stopScript, stopScript,
getVersion, getVersion,
getHosts,
}; };
} }

View File

@@ -31,7 +31,7 @@
<div class="auth-left"> <div class="auth-left">
<div class="logo "> <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>
<div class="welcome">{{ $t('login.title') }}</div> <div class="welcome">{{ $t('login.title') }}</div>
<div class="from"> <div class="from">

View File

@@ -41,6 +41,13 @@
<span class="material-icons-round text-sm">search</span> <span class="material-icons-round text-sm">search</span>
{{ $t('hostList.query') }} {{ $t('hostList.query') }}
</button> </button>
<button
@click="handleExport"
class="bg-emerald-500 hover:bg-emerald-600 text-white px-6 py-3 rounded-xl font-medium text-sm transition-all shadow-lg shadow-emerald-500/20 flex items-center gap-2"
>
<span class="material-icons-round text-sm">download</span>
{{ $t('hostList.export') }}
</button>
<button <button
@click="filterdialogVisible = true" @click="filterdialogVisible = true"
class="bg-slate-100 hover:bg-slate-200 text-slate-600 p-3 rounded-xl transition-all" class="bg-slate-100 hover:bg-slate-200 text-slate-600 p-3 rounded-xl transition-all"
@@ -61,33 +68,33 @@
<div class="flex-1 overflow-auto"> <div class="flex-1 overflow-auto">
<table class="w-full text-left" style="table-layout: fixed; min-width: 1000px;"> <table class="w-full text-left" style="table-layout: fixed; min-width: 1000px;">
<colgroup> <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: 10%;">
<col style="width: 4%;">
<col style="width: 6%;">
<col style="width: 6%;">
<col style="width: 8%;"> <col style="width: 8%;">
<col style="width: 8%;"> <col style="width: 6%;">
<col style="width: 7%;"> <col style="width: 5%;">
<col style="width: 7%;"> <col style="width: 5%;">
<col style="width: 7%;"> <col style="width: 5%;">
<col style="width: 7%;"> <col style="width: 5%;">
<col style="width: 15%;">
<col style="width: 15%;">
</colgroup> </colgroup>
<thead class="bg-white sticky top-0 z-10"> <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"> <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.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.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.country') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.countryEng') }}</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.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.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.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.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.videoCount') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.anchorType') }}</th> <th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.heart') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.signature') }}</th>
<th class="py-3 px-2 font-semibold bg-white">{{ $t('hostList.bioLink') }}</th>
</tr> </tr>
</thead> </thead>
<tbody class="text-sm text-slate-600"> <tbody class="text-sm text-slate-600">
@@ -112,39 +119,20 @@
{{ row.hostlevel }} {{ row.hostlevel }}
</td> </td>
<!-- Invitation Type -->
<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'"
>
{{ row.invitationType == 1 ? $t('hostList.invitationType1') : $t('hostList.invitationType2') }}
</span>
</td>
<!-- Data Buttons -->
<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)"
class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white rounded-lg text-xs font-medium transition-all"
>
{{ $t('hostList.viewSessions') }}
</button>
<button
@click="getRevenueStats(row.hostId)"
class="px-3 py-1.5 bg-sky-50 text-sky-600 hover:bg-sky-600 hover:text-white rounded-lg text-xs font-medium transition-all"
>
{{ $t('hostList.viewRevenue') }}
</button>
</div>
</td>
<!-- Country --> <!-- Country -->
<td class="py-4 px-2 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>
<!-- Country English -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.countryEng }}
</td>
<!-- Time --> <!-- Time -->
<td class="py-4 px-2 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">
@@ -158,10 +146,7 @@
{{ row.hostsCoins }} {{ row.hostsCoins }}
</td> </td>
<!-- Yesterday Coins -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
{{ row.yesterdayCoins }}
</td>
<!-- Fans --> <!-- Fans -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none"> <td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
@@ -173,16 +158,28 @@
{{ row.fllowernum }} {{ row.fllowernum }}
</td> </td>
<!-- Online Fans --> <!-- Video Count -->
<td class="py-4 px-2 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.onlineFans }} {{ row.videoCount }}
</td> </td>
<!-- Host Kind --> <!-- Heart/Likes -->
<td class="py-4 px-2 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.hostsKind }} {{ row.heart }}
</td> </td>
<!-- Signature -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<span class="truncate block max-w-[150px]" :title="row.signature">{{ row.signature }}</span>
</td>
<!-- Bio Link -->
<td class="py-4 px-2 border-b border-slate-50 group-last:border-none">
<a v-if="row.bioLink" :href="row.bioLink" target="_blank" class="text-primary hover:underline truncate block max-w-[150px]" :title="row.bioLink">
{{ row.bioLink }}
</a>
<span v-else>-</span>
</td>
</tr> </tr>
</tbody> </tbody>
@@ -269,14 +266,14 @@
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<div><label class="text-sm text-slate-500 mb-1 block">{{ $t('hostList.sortType') }}</label></div> <div><label class="text-sm text-slate-500 mb-1 block">{{ $t('hostList.sortType') }}</label></div>
<el-select v-model="sortData.sortType" filterable :placeholder="$t('hostList.selectPlaceholder')" <el-select v-model="sortData.sortBy" filterable :placeholder="$t('hostList.selectPlaceholder')"
class="w-full"> class="w-full">
<el-option v-for="item in sortNameOptions" :key="item.type" :label="item.label" :value="item.type" /> <el-option v-for="item in sortNameOptions" :key="item.type" :label="item.label" :value="item.type" />
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="10"> <el-col :span="10">
<div><label class="text-sm text-slate-500 mb-1 block">{{ $t('hostList.ascending') }}/{{ $t('hostList.descending') }}</label></div> <div><label class="text-sm text-slate-500 mb-1 block">{{ $t('hostList.ascending') }}/{{ $t('hostList.descending') }}</label></div>
<el-select v-model="sortData.sortForm" filterable :placeholder="$t('hostList.selectPlaceholder')" <el-select v-model="sortData.order" filterable :placeholder="$t('hostList.selectPlaceholder')"
class="w-full"> class="w-full">
<el-option <el-option
v-for="item in [{ label: $t('hostList.ascending'), value: 'asc' }, { label: $t('hostList.descending'), value: 'desc' }]" v-for="item in [{ label: $t('hostList.ascending'), value: 'asc' }, { label: $t('hostList.descending'), value: 'desc' }]"
@@ -300,68 +297,23 @@
<!-- Dialogs --> <!-- Dialogs -->
<LiveRecordDialog v-model:modelValue="liveDetailDialogVisible" :rows="liveDetailRecords" @select="handleLiveSelect" /> <LiveRecordDialog v-model:modelValue="liveDetailDialogVisible" :rows="liveDetailRecords" @select="handleLiveSelect" />
<el-dialog v-model="revenueDialogVisible" :title="$t('hostList.liveRevenue')" width="80vw" top="6vh"
:close-on-click-modal="false" destroy-on-close>
<el-table :data="revenueRecords" border height="62vh" style="width: 100%" v-loading="revenueLoading"
table-layout="auto">
<el-table-column prop="displayId" :label="$t('hostList.revenueHost')" />
<el-table-column prop="todayRevenue" :label="$t('hostList.todayRevenueUsd')" />
<el-table-column prop="totalRevenue" :label="$t('hostList.totalRevenueUsd')" />
<el-table-column prop="lastDaysCount" :label="$t('hostList.liveDays')" />
<el-table-column prop="history" :label="$t('hostList.historyRevenueUsd')">
<template #default="{ row }">
<el-tooltip v-if="hasHistory(row.history)" effect="dark" placement="top">
<template #content>{{ buildHistoryTitle(row.history) }}</template>
<div class="history-sparkline-wrap">
<div class="history-sparkline-top">
<span class="history-sparkline-min-top">
{{ $t('hostList.revenueLow') }}: {{ formatRevenueValue(getHistoryMin(row.history)) }}
</span>
<span class="history-sparkline-max">
{{ $t('hostList.revenueHigh') }}: {{ formatRevenueValue(getHistoryMax(row.history)) }}
</span>
</div>
<svg class="history-sparkline" viewBox="0 0 180 48" preserveAspectRatio="none">
<line x1="2" y1="46" x2="178" y2="46" stroke="#e6eef7" stroke-width="1" />
<polyline :points="buildSparklinePoints(row.history)" fill="none" stroke="#45a1ff" stroke-width="2" />
</svg>
<div class="history-sparkline-bottom">
<div class="history-sparkline-dates">
<span class="history-sparkline-date">{{ getHistoryStartDate(row.history) }}</span>
<span class="history-sparkline-date">{{ getHistoryEndDate(row.history) }}</span>
</div>
</div>
</div>
</el-tooltip>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="createdAt" :label="$t('hostList.revenueTime')">
<template #default="scope">
{{ formatTimestamp(scope.row.createdAt) }}
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="revenueDialogVisible = false">{{ $t('hostList.close') }}</el-button>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { tkhostdata, getCountryinfo, liveHostDetail, revenueStats } from '@/api/account'; import { 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, computed } from 'vue'; import { ref, reactive, onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ElMessageBox } from 'element-plus'
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 dateInput = ref(null)
const { givePyAnchorId, exportToExcel } = usePythonBridge(); const { givePyAnchorId, exportToExcel,getHosts } = usePythonBridge();
const userInfo = ref(getUser()) const userInfo = ref(getUser())
@@ -370,34 +322,27 @@ const searchForm = ref({
country: '', country: '',
createTime: '', createTime: '',
hostsId: '', hostsId: '',
fansMin: null, fansMax: null, hostsLevel: '',
onlineFansMin: null, onlineFansMax: null, userId: '',
hostsCoinsMin: null, hostsCoinsMax: null, min_fans: null, max_fans: null
fllowernumMin: null, fllowernumMax: null
}) })
const fields = [ const fields = [
{ label: t('hostList.fansNum'), minModel: 'fansMin', maxModel: 'fansMax' }, { label: t('hostList.fansNum'), minModel: 'min_fans', maxModel: 'max_fans' }
{ label: t('hostList.onlineFans'), minModel: 'onlineFansMin', maxModel: 'onlineFansMax' },
{ label: t('hostList.anchorcoins'), minModel: 'hostsCoinsMin', maxModel: 'hostsCoinsMax' },
{ label: t('hostList.followersNum'), minModel: 'fllowernumMin', maxModel: 'fllowernumMax' },
] ]
let sortData = ref({ sortForm: 'desc', sortType: "createTime" }) let sortData = ref({ order: 'desc', sortBy: "createTime" })
let sortNameOptions = ref([ let sortNameOptions = ref([
{ label: t('hostList.creationTime'), type: 'createTime' }, { label: t('hostList.creationTime'), type: 'createTime' },
{ label: t('hostList.anchorcoins'), type: 'hostsCoins' }, { label: t('hostList.anchorcoins'), type: 'coins' },
{ label: t('hostList.fansNum'), type: 'fans' }, { label: t('hostList.fansNum'), type: 'fans' },
{ label: t('hostList.yesterdayGoldCoins'), type: 'yesterdayCoins' }, { label: t('hostList.hostId'), type: 'hostsId' }
{ label: t('hostList.onlineFans'), type: 'onlineFans' },
{ label: t('hostList.followersNum'), type: 'fllowernum' },
]) ])
let selectHostList = ref([]) let selectHostList = ref([])
let filterdialogVisible = ref(false) let filterdialogVisible = ref(false)
let liveDetailDialogVisible = ref(false) let liveDetailDialogVisible = ref(false)
let liveDetailRecords = ref([]) let liveDetailRecords = ref([])
let revenueDialogVisible = ref(false)
let revenueRecords = ref([]) let revenueRecords = ref([])
let revenueLoading = ref(false) let revenueLoading = ref(false)
@@ -407,8 +352,10 @@ let total = ref(0)
let options = ref([]) let options = ref([])
onMounted(() => { onMounted(() => {
getCountry(); setTimeout(() => {
getlist(); getCountry();
getlist();
}, 1000);
}) })
function serch() { function serch() {
@@ -494,32 +441,38 @@ const paginationPages = computed(() => {
const getlist = () => { const getlist = () => {
loading.value = true loading.value = true
tkhostdata({ console.log('getlist', searchForm.value)
tenantId: Number(userInfo.value.tenantId), getHosts({
sort: sortData.value.sortForm, page: page.value,
sortName: sortData.value.sortType, limit: pageSize.value,
"current": page.value, order: sortData.value.order,
"pageSize": pageSize.value, sortBy: sortData.value.sortBy,
...searchForm.value, country: searchForm.value.country || undefined,
hostsId: searchForm.value.hostsId || undefined,
hostsLevel: searchForm.value.hostsLevel || undefined,
userId: searchForm.value.userId || undefined,
min_fans: searchForm.value.min_fans || undefined,
max_fans: searchForm.value.max_fans || undefined
}).then(res => { }).then(res => {
loading.value = false loading.value = false
if (res) { if (res && res.data) {
console.log('主播列表', res) console.log('主播列表', res)
total.value = Number(res.total) console.error('返回主播列表', res)
tableData.value = res.records.map(item => ({
total.value = Number(res.data.pagination?.total || 0)
tableData.value = (res.data.list || []).map(item => ({
hostId: item.hostsId, hostId: item.hostsId,
hostlevel: item.hostsLevel, hostlevel: item.hostsLevel,
country: item.country, country: item.country,
countryEng: item.countryEng,
createTime: item.createTime, createTime: item.createTime,
fans: item.fans, fans: item.fans,
fllowernum: item.fllowernum, fllowernum: item.fllowernum,
hostsCoins: item.hostsCoins, hostsCoins: item.hostsCoins,
hostsKind: item.hostsKind, videoCount: item.video_count,
onlineFans: item.onlineFans, heart: item.heart,
yesterdayCoins: item.yesterdayCoins, signature: item.signature,
belongBy: item.belongBy, bioLink: item.bio_link,
useable: item.useable,
invitationType: item.invitationType,
})); }));
} }
}).catch(() => { }).catch(() => {
@@ -532,46 +485,63 @@ function handelClick() {
getlist() getlist()
} }
// 导出Excel
function handleExport() {
ElMessageBox.confirm(
'请选择导出方式',
'导出数据',
{
confirmButtonText: '导出并删除',
cancelButtonText: '仅导出',
distinguishCancelAndClose: true,
type: 'info',
}
)
.then(() => {
// 点击"导出并删除"
doExport(true)
})
.catch((action) => {
if (action === 'cancel') {
// 点击"仅导出"
doExport(false)
}
// 点击关闭按钮则不做任何操作
})
}
function doExport(isDelete) {
exportToExcel({
page: page.value,
limit: pageSize.value,
order: sortData.value.order,
sortBy: sortData.value.sortBy,
country: searchForm.value.country || undefined,
hostsId: searchForm.value.hostsId || undefined,
hostsLevel: searchForm.value.hostsLevel || undefined,
userId: searchForm.value.userId || undefined,
min_fans: searchForm.value.min_fans || undefined,
max_fans: searchForm.value.max_fans || undefined,
isDelete: isDelete
})
}
function reset() { function reset() {
searchForm.value.fansMin = null searchForm.value.min_fans = null
searchForm.value.fansMax = null searchForm.value.max_fans = null
searchForm.value.onlineFansMin = null
searchForm.value.onlineFansMax = null
searchForm.value.hostsCoinsMin = null
searchForm.value.hostsCoinsMax = null
searchForm.value.fllowernumMin = null
searchForm.value.fllowernumMax = null
} }
function handleClose(done) { function handleClose(done) {
done() done()
} }
function getliveHost(hostId) {
liveHostDetail({
"hostsId": hostId,
"tenantId": userInfo.value.tenantId
}).then(res => {
const detailList = Array.isArray(res) ? res : (res?.records || [])
liveDetailRecords.value = detailList
liveDetailDialogVisible.value = true
})
}
function handleLiveSelect(row) { function handleLiveSelect(row) {
liveDetailDialogVisible.value = false liveDetailDialogVisible.value = false
} }
function getRevenueStats(hostId) {
revenueLoading.value = true
revenueStats(hostId).then(res => {
const detailList = Array.isArray(res) ? res : (res?.records || [])
revenueRecords.value = detailList
revenueDialogVisible.value = true
}).finally(() => {
revenueLoading.value = false
})
}
function openHTML(id) { function openHTML(id) {
givePyAnchorId(id) givePyAnchorId(id)

View File

@@ -1,272 +1,188 @@
<template> <template>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-4"> <!-- 主容器 - 全屏高度布局 -->
<!-- Stat Cards --> <div class="flex flex-col h-full gap-6">
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-slate-500">{{ $t('workbenches.totalnumber') }}</span>
<span class="material-icons-round text-primary/40">analytics</span>
</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white">{{ hostData.totalCount }}</div>
</div>
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-slate-500">{{ $t('workbenches.createHost') }}</span>
<span class="material-icons-round text-secondary/40">person_add</span>
</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white">{{ hostData.validAnchorsCount }}</div>
</div>
<div class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium text-slate-500">{{ $t('workbenches.query') }} / {{ $t('workbenches.invite')
}}</span>
<span class="material-icons-round text-slate-400">compare_arrows</span>
</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white">{{ hostData.checkedDataCount }} <span
class="text-slate-300 text-lg mx-2">/</span> {{ hostData.canInvitationCount }}</div>
</div>
<div
class="bg-white dark:bg-slate-900 p-6 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800 flex flex-col justify-center">
<div class="flex items-center justify-between">
<div>
<span class="text-xs font-semibold text-slate-400 uppercase tracking-wider block mb-1">{{
$t('workbenches.runTime') }}</span>
<div class="text-2xl font-mono font-bold text-primary">{{ formattedTime }}</div>
</div>
<button @click="openTK"
class="bg-primary hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-semibold transition-all shadow-lg shadow-primary/25">
{{ $t('workbenches.openTK') }}
</button>
</div>
</div>
</div>
<!-- Guild Accounts --> <!-- 顶部统计区域 - 大数字展示 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div v-for="(item, index) in 2" :key="index" class="bg-white border border-slate-100 p-5 rounded-xl shadow-sm"> <!-- 总数量 -->
<div class="flex justify-between items-center mb-6"> <div class="bg-gradient-to-br from-blue-500 to-blue-600 p-6 rounded-2xl shadow-lg shadow-blue-500/20 text-white relative overflow-hidden">
<div class="flex items-center gap-2"> <div class="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/2"></div>
<span class="w-2 h-2 rounded-full" :class="tkData[index].code == 1 ? 'bg-emerald-500' : 'bg-red-500'"></span> <div class="relative z-10">
<h3 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenches.guildAccount') }} {{ index === 0 ? 'A' <div class="flex items-center gap-2 mb-2">
: 'B' }}</h3> <span class="material-icons-round text-white/80">analytics</span>
</div> <span class="text-sm font-medium text-white/80">{{ $t('workbenches.totalnumber') }}</span>
<span class="text-xs text-slate-500">{{ $t('workbenches.queriedNum') }}: {{ tkData[index].num }}</span>
</div>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="text-xs font-semibold text-slate-500 mb-1 block">{{ $t('workbenches.guildAccount') }}</label>
<input
class="w-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-primary/20 outline-none transition-all disabled:opacity-50"
type="text" v-model="tkData[index].account" :placeholder="$t('workbenches.guildAccountPlace')"
:disabled="!(tkData[index].code == 0 && !isLogin[index])" />
</div>
<div>
<label class="text-xs font-semibold text-slate-500 mb-1 block">{{ $t('workbenches.guildPass') }}</label>
<div class="relative">
<input
class="w-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg px-4 py-2 text-sm focus:ring-2 focus:ring-primary/20 outline-none transition-all disabled:opacity-50"
type="password" v-model="tkData[index].password" :placeholder="$t('workbenches.guildPassPlace')"
:disabled="!(tkData[index].code == 0 && !isLogin[index])" />
</div>
</div> </div>
<div class="text-4xl font-bold tracking-tight">{{ hostData.totalCount.toLocaleString() }}</div>
<div class="mt-2 text-xs text-white/60">累计获取主播</div>
</div> </div>
<button @click="loginTK(index)" :disabled="!(tkData[index].code == 0 && !isLogin[index])"
class="w-full bg-slate-900 dark:bg-slate-700 hover:bg-black text-white py-2.5 rounded-lg font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed">
{{ $t('workbenches.loginBackend') }}
</button>
</div> </div>
</div>
</div>
<!-- Configuration Panel --> <!-- 新建主播 -->
<div <div class="bg-gradient-to-br from-emerald-500 to-emerald-600 p-6 rounded-2xl shadow-lg shadow-emerald-500/20 text-white relative overflow-hidden">
class="bg-white dark:bg-slate-900 rounded-2xl shadow-sm border border-slate-100 dark:border-slate-800 overflow-hidden"> <div class="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/2"></div>
<div class="p-6 border-b border-slate-100 dark:border-slate-800 flex justify-between items-center"> <div class="relative z-10">
<div class="flex items-center gap-3"> <div class="flex items-center gap-2 mb-2">
<div class="w-8 h-8 bg-slate-100 dark:bg-slate-800 rounded-lg flex items-center justify-center"> <span class="material-icons-round text-white/80">person_add</span>
<span class="material-icons-round text-slate-600 dark:text-slate-400 text-lg">settings</span> <span class="text-sm font-medium text-white/80">{{ $t('workbenches.createHost') }}</span>
</div>
<div class="text-4xl font-bold tracking-tight">{{ hostData.validAnchorsCount.toLocaleString() }}</div>
<div class="mt-2 text-xs text-white/60">有效主播数量</div>
</div> </div>
<h2 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenchesSetup.workbenches') }}</h2>
</div> </div>
<div class="flex items-center gap-4 text-sm">
<div class="text-slate-500">{{ $t('workbenchesSetup.network') }}: <span class="text-primary font-bold">{{ locale <!-- 网络状态 -->
== 'zh' ? countryData : countryDataEN }}</span></div> <div class="bg-gradient-to-br from-violet-500 to-violet-600 p-6 rounded-2xl shadow-lg shadow-violet-500/20 text-white relative overflow-hidden">
<div class="flex items-center gap-2"> <div class="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/2"></div>
<span class="text-slate-500">指定国家:</span> <div class="relative z-10">
<select v-model="country_info" <div class="flex items-center gap-2 mb-2">
class="bg-slate-50 dark:bg-slate-800 border-none rounded-lg text-xs font-medium focus:ring-0"> <span class="material-icons-round text-white/80">public</span>
<option value="全部">全部</option> <span class="text-sm font-medium text-white/80">{{ $t('workbenchesSetup.network') }}</span>
<option v-for="(item, index) in country_Lst" :key="index" :value="item">{{ item }}</option> </div>
</select> <div class="text-2xl font-bold tracking-tight">{{ locale == 'zh' ? countryData : countryDataEN }}</div>
<div class="mt-2 flex items-center gap-2">
<span class="w-2 h-2 rounded-full" :class="isWifi ? 'bg-red-400 animate-pulse' : 'bg-green-400'"></span>
<span class="text-xs text-white/60">{{ isWifi ? 'VPN 断开' : 'VPN 已连接' }}</span>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6 mb-6"> <!-- 中央核心操作区 -->
<!-- Coins --> <div class="flex-1 bg-white dark:bg-slate-900 rounded-3xl shadow-xl border border-slate-100 dark:border-slate-800 overflow-hidden">
<div> <div class="h-full flex flex-col">
<h4 class="text-sm font-bold text-slate-800 dark:text-white mb-4 flex items-center gap-2">
<span class="w-1 h-4 bg-primary rounded-full"></span> <!-- 运行时间 - 中央突出展示 -->
{{ $t('workbenchesSetup.setCoinsNum') }} <div class="bg-gradient-to-r from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-900 p-8 text-center border-b border-slate-200 dark:border-slate-700">
</h4> <div class="inline-flex items-center gap-3 mb-4">
<div class="space-y-3"> <span class="w-3 h-3 rounded-full animate-pulse" :class="!pyData.isStart ? 'bg-emerald-500' : 'bg-slate-300'"></span>
<div class="flex shadow-sm rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700"> <span class="text-sm font-semibold text-slate-500 uppercase tracking-widest">{{ $t('workbenches.runTime') }}</span>
<span </div>
class="bg-slate-50 dark:bg-slate-800 px-3 py-2 text-xs font-medium text-slate-500 w-28 flex items-center border-r border-slate-200 dark:border-slate-700">{{ <div class="text-6xl font-mono font-bold text-slate-900 dark:text-white tracking-wider mb-6">
$t('workbenchesSetup.minCoinsNum') }}</span> {{ formattedTime }}
<input </div>
class="flex-1 px-4 py-2 text-sm bg-white dark:bg-slate-900 border-none outline-none focus:ring-0 disabled:bg-slate-100"
type="number" v-model="pyData.gold.min" :disabled="!pyData.isStart" /> <!-- 操作按钮组 -->
<div class="flex items-center justify-center gap-4">
<button @click="openTK"
class="bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 px-6 py-3 rounded-xl text-sm font-semibold transition-all flex items-center gap-2">
<span class="material-icons-round text-lg">play_arrow</span>
{{ $t('workbenches.openTK') }}
</button>
<button v-if="pyData.isStart" @click="submit"
class="bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white px-10 py-4 rounded-xl font-bold text-lg shadow-xl shadow-blue-500/30 transition-all flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98]">
<span class="material-icons-round">bolt</span>
{{ $t('workbenchesSetup.start') }}
</button>
<button v-else @click="unsubmit"
class="bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white px-10 py-4 rounded-xl font-bold text-lg shadow-xl shadow-red-500/30 transition-all flex items-center gap-3 hover:scale-[1.02] active:scale-[0.98] animate-pulse">
<span class="material-icons-round">stop</span>
{{ $t('workbenchesSetup.stop') }}
</button>
</div>
<p class="mt-4 text-xs font-medium text-slate-400">
到期时间: <span class="text-emerald-600 dark:text-emerald-400">{{ timestampToTime(expiredTime) }}</span>
</p>
</div>
<!-- 配置区域 - 左右两侧卡片 -->
<div class="flex-1 p-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 h-full">
<!-- 左侧金币设置 -->
<div class="bg-slate-50 dark:bg-slate-800/50 rounded-2xl p-5 border border-slate-200 dark:border-slate-700">
<div class="flex items-center gap-3 mb-5">
<div class="w-10 h-10 bg-gradient-to-br from-amber-400 to-orange-500 rounded-xl flex items-center justify-center shadow-lg shadow-amber-500/20">
<span class="material-icons-round text-white">monetization_on</span>
</div>
<div>
<h4 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenchesSetup.setCoinsNum') }}</h4>
<p class="text-xs text-slate-400">设置金币筛选范围</p>
</div>
</div>
<div class="space-y-3">
<div>
<label class="text-xs text-slate-500 mb-1.5 block">{{ $t('workbenchesSetup.minCoinsNum') }}</label>
<input
class="w-full px-4 py-3 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-xl outline-none focus:border-amber-500 focus:ring-2 focus:ring-amber-500/20 transition-all disabled:bg-slate-100 disabled:cursor-not-allowed"
type="number" v-model="pyData.gold.min" :disabled="!pyData.isStart" placeholder="0" />
</div>
<div>
<label class="text-xs text-slate-500 mb-1.5 block">{{ $t('workbenchesSetup.maxCoinsNum') }}</label>
<input
class="w-full px-4 py-3 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-xl outline-none focus:border-amber-500 focus:ring-2 focus:ring-amber-500/20 transition-all disabled:bg-slate-100 disabled:cursor-not-allowed"
type="number" v-model="pyData.gold.max" :disabled="!pyData.isStart" placeholder="999999" />
</div>
</div>
</div> </div>
<div class="flex shadow-sm rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700">
<span <!-- 中间国家设置 -->
class="bg-slate-50 dark:bg-slate-800 px-3 py-2 text-xs font-medium text-slate-500 w-28 flex items-center border-r border-slate-200 dark:border-slate-700">{{ <div class="bg-slate-50 dark:bg-slate-800/50 rounded-2xl p-5 border border-slate-200 dark:border-slate-700">
$t('workbenchesSetup.maxCoinsNum') }}</span> <div class="flex items-center gap-3 mb-5">
<input <div class="w-10 h-10 bg-gradient-to-br from-violet-400 to-purple-500 rounded-xl flex items-center justify-center shadow-lg shadow-violet-500/20">
class="flex-1 px-4 py-2 text-sm bg-white dark:bg-slate-900 border-none outline-none focus:ring-0 disabled:bg-slate-100" <span class="material-icons-round text-white">flag</span>
type="number" v-model="pyData.gold.max" :disabled="!pyData.isStart" /> </div>
<div>
<h4 class="font-bold text-slate-800 dark:text-white">指定国家</h4>
<p class="text-xs text-slate-400">选择爬取的目标国家</p>
</div>
</div>
<div class="space-y-3">
<div>
<label class="text-xs text-slate-500 mb-1.5 block">目标国家</label>
<select v-model="country_info"
class="w-full px-4 py-3 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-xl outline-none focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 transition-all disabled:bg-slate-100 appearance-none cursor-pointer"
:disabled="!pyData.isStart">
<option value="全部">全部国家</option>
<option v-for="(item, index) in country_Lst" :key="index" :value="item">{{ item }}</option>
</select>
</div>
<div class="pt-2">
<div class="flex items-center justify-between text-xs text-slate-500 mb-2">
<span>当前IP所在地</span>
<span class="font-semibold text-slate-700 dark:text-slate-300">{{ locale == 'zh' ? countryData : countryDataEN }}</span>
</div>
<div class="h-1 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-violet-500 to-purple-500 rounded-full" style="width: 100%"></div>
</div>
</div>
</div>
</div> </div>
<!-- 右侧粉丝设置 -->
<div class="bg-slate-50 dark:bg-slate-800/50 rounded-2xl p-5 border border-slate-200 dark:border-slate-700">
<div class="flex items-center gap-3 mb-5">
<div class="w-10 h-10 bg-gradient-to-br from-pink-400 to-rose-500 rounded-xl flex items-center justify-center shadow-lg shadow-pink-500/20">
<span class="material-icons-round text-white">favorite</span>
</div>
<div>
<h4 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenchesSetup.setFansNum') }}</h4>
<p class="text-xs text-slate-400">设置粉丝筛选范围</p>
</div>
</div>
<div class="space-y-3">
<div>
<label class="text-xs text-slate-500 mb-1.5 block">{{ $t('workbenchesSetup.minFansNum') }}</label>
<input
class="w-full px-4 py-3 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-xl outline-none focus:border-pink-500 focus:ring-2 focus:ring-pink-500/20 transition-all disabled:bg-slate-100 disabled:cursor-not-allowed"
type="number" v-model="pyData.fans.min" :disabled="!pyData.isStart" placeholder="0" />
</div>
<div>
<label class="text-xs text-slate-500 mb-1.5 block">{{ $t('workbenchesSetup.maxFansNum') }}</label>
<input
class="w-full px-4 py-3 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-xl outline-none focus:border-pink-500 focus:ring-2 focus:ring-pink-500/20 transition-all disabled:bg-slate-100 disabled:cursor-not-allowed"
type="number" v-model="pyData.fans.max" :disabled="!pyData.isStart" placeholder="999999" />
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- Fans -->
<div>
<h4 class="text-sm font-bold text-slate-800 dark:text-white mb-4 flex items-center gap-2">
<span class="w-1 h-4 bg-secondary rounded-full"></span>
{{ $t('workbenchesSetup.setFansNum') }}
</h4>
<div class="space-y-3">
<div class="flex shadow-sm rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700">
<span
class="bg-slate-50 dark:bg-slate-800 px-3 py-2 text-xs font-medium text-slate-500 w-28 flex items-center border-r border-slate-200 dark:border-slate-700">{{
$t('workbenchesSetup.minFansNum') }}</span>
<input
class="flex-1 px-4 py-2 text-sm bg-white dark:bg-slate-900 border-none outline-none focus:ring-0 disabled:bg-slate-100"
type="number" v-model="pyData.fans.min" :disabled="!pyData.isStart" />
</div>
<div class="flex shadow-sm rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700">
<span
class="bg-slate-50 dark:bg-slate-800 px-3 py-2 text-xs font-medium text-slate-500 w-28 flex items-center border-r border-slate-200 dark:border-slate-700">{{
$t('workbenchesSetup.maxFansNum') }}</span>
<input
class="flex-1 px-4 py-2 text-sm bg-white dark:bg-slate-900 border-none outline-none focus:ring-0 disabled:bg-slate-100"
type="number" v-model="pyData.fans.max" :disabled="!pyData.isStart" />
</div>
</div>
</div>
<!-- Frequency -->
<div>
<h4 class="text-sm font-bold text-slate-800 dark:text-white mb-4 flex items-center gap-2">
<span class="w-1 h-4 bg-emerald-500 rounded-full"></span>
{{ $t('workbenchesSetup.setQuery') }}
</h4>
<div class="space-y-3">
<div class="flex shadow-sm rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700">
<input
class="flex-1 px-4 py-2 text-sm bg-white dark:bg-slate-900 border-none outline-none focus:ring-0 disabled:bg-slate-100"
type="number" v-model="pyData.frequency.hour" :disabled="!pyData.isStart" />
<span
class="bg-slate-100 dark:bg-slate-800 px-3 py-2 text-xs font-medium text-slate-500 w-24 flex items-center justify-center border-l border-slate-200 dark:border-slate-700">{{
$t('workbenchesSetup.hour') }}</span>
</div>
<div class="flex shadow-sm rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700">
<input
class="flex-1 px-4 py-2 text-sm bg-white dark:bg-slate-900 border-none outline-none focus:ring-0 disabled:bg-slate-100"
type="number" v-model="pyData.frequency.day" :disabled="!pyData.isStart" />
<span
class="bg-slate-100 dark:bg-slate-800 px-3 py-2 text-xs font-medium text-slate-500 w-24 flex items-center justify-center border-l border-slate-200 dark:border-slate-700">{{
$t('workbenchesSetup.hour24') }}</span>
</div>
</div>
</div>
<!-- Quantity Limit -->
<div>
<h4 class="text-sm font-bold text-slate-800 dark:text-white mb-4 flex items-center gap-2">
<span class="w-1 h-4 bg-orange-400 rounded-full"></span>
{{ $t('workbenchesSetup.setNum') }}
<span class="text-[10px] text-slate-400 font-normal ml-1">({{ $t('workbenchesSetup.prompt') }})</span>
</h4>
<div class="space-y-3">
<div class="flex gap-2">
<button @click="isLimit = true" :disabled="!pyData.isStart"
class="flex-1 px-3 py-2 text-xs font-semibold rounded-md border transition-colors"
:class="isLimit ? 'bg-primary text-white border-primary' : 'bg-white text-slate-600 border-slate-200 hover:border-primary/50'">
{{ $t('workbenchesSetup.setHostNum') }}
</button>
<button @click="isLimit = false" :disabled="!pyData.isStart"
class="flex-1 px-3 py-2 text-xs font-semibold rounded-md border transition-colors"
:class="!isLimit ? 'bg-slate-500 text-white border-slate-500' : 'bg-white text-slate-600 border-slate-200 hover:border-slate-400'">
{{ $t('workbenchesSetup.unlimitedQuantity') }}
</button>
</div>
<div v-if="isLimit"
class="flex shadow-sm rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700">
<input
class="flex-1 px-4 py-2 text-sm bg-white dark:bg-slate-900 border-none outline-none focus:ring-0 disabled:bg-slate-100"
type="number" v-model="hostNum" :disabled="!pyData.isStart" />
<span
class="bg-slate-100 dark:bg-slate-800 px-3 py-2 text-xs font-medium text-slate-500 w-16 flex items-center justify-center border-l border-slate-200 dark:border-slate-700">{{
$t('workbenchesSetup.num') }}</span>
</div>
</div>
</div>
</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">
<div class="flex items-center gap-6">
<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>
</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>
</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>
</div>
</div>
</div>
<div class="mt-6 text-center">
<button v-if="pyData.isStart" @click="submit"
class="bg-slate-900 dark:bg-primary hover:scale-[1.02] active:scale-[0.98] text-white px-10 py-3 rounded-xl font-bold text-lg shadow-xl shadow-slate-900/10 dark:shadow-primary/20 transition-all flex items-center gap-2 mx-auto">
<span class="material-icons-round">bolt</span>
{{ $t('workbenchesSetup.start') }}
</button>
<button v-else @click="unsubmit"
class="bg-red-500 hover:bg-red-600 hover:scale-[1.02] active:scale-[0.98] text-white px-12 py-4 rounded-xl font-bold text-lg shadow-xl shadow-red-500/20 transition-all flex items-center gap-3 mx-auto">
<span class="material-icons-round">stop</span>
{{ $t('workbenchesSetup.stop') }}
</button>
<p class="mt-4 text-xs font-medium text-emerald-600 dark:text-emerald-400">
到期时间: {{ timestampToTime(expiredTime) }}
</p>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@@ -297,8 +213,7 @@ let getNumTimer = ref(null);
let hostData = ref({ let hostData = ref({
totalCount: 0, totalCount: 0,
validAnchorsCount: 0, validAnchorsCount: 0,
canInvitationCount: 0,
checkedDataCount: 0,
}); });
//是否开启tk //是否开启tk
// let isTk = ref(true); // let isTk = ref(true);
@@ -461,17 +376,7 @@ const submit = () => {
ElMessage.error('请输入正确的区间值'); ElMessage.error('请输入正确的区间值');
return; return;
} }
if (Number(pyData.value.frequency.hour) <= 0 || Number(pyData.value.frequency.day) <= 0 || pyData.value.frequency.hour == '' || pyData.value.frequency.day == '') {
ElMessage.error('请输入正确的频率区间值');
return;
}
//是否限制爬取数量
if (isLimit.value) {
if (hostNum.value <= 0) {
ElMessage.error('请输入正确的可邀请数量');
return;
}
}
ElMessageBox.confirm( ElMessageBox.confirm(
@@ -509,12 +414,7 @@ const submit = () => {
getHostTimer.value = setInterval(() => { getHostTimer.value = setInterval(() => {
fetchDataCount().then((res) => { fetchDataCount().then((res) => {
hostData.value = JSON.parse(res); hostData.value = JSON.parse(res);
if (isLimit.value) {
if (hostData.value.canInvitationCount >= hostNum.value) {
unsubmit();
alert('爬取完毕')
}
}
}) })
}, 1000); }, 1000);
@@ -580,6 +480,8 @@ const reset = () => {
const toggleFilter = (filterName) => { const toggleFilter = (filterName) => {
if (!pyData.value.isStart) return; // 如果已启动则不允许修改 if (!pyData.value.isStart) return; // 如果已启动则不允许修改
pyData.value[filterName] = !pyData.value[filterName]; pyData.value[filterName] = !pyData.value[filterName];
console.log(pyData.value)
}; };
@@ -804,4 +706,8 @@ const checkVPN = async () => {
Most styles are replaced by Tailwind utility classes. Most styles are replaced by Tailwind utility classes.
We can keep specific overrides or custom animations here if needed. We can keep specific overrides or custom animations here if needed.
*/ */
.text-size {
text-size: 50px !important;
}
</style> </style>

View File

@@ -80,9 +80,18 @@ body {
align-items: center; align-items: center;
min-height: 100vh; min-height: 100vh;
overflow: hidden; overflow: hidden;
-webkit-user-select: none; /* Chrome / Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE / Edge */
user-select: none; /* 标准 */
} }
.nav-container { .nav-container {
transform-origin: center center; transform-origin: center center;
} }
</style> </style>