Files
web-fusion/src/views/tk/Workbenches.vue
2026-02-09 21:04:09 +08:00

813 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="h-full w-full overflow-y-auto bg-gray-50 p-6">
<div class="container mx-auto">
<div class="grid grid-cols-1 lg:grid-cols-12 gap-4 mb-4">
<!-- Stat Cards -->
<!-- 总数量 (较小) -->
<div
class="lg:col-span-2 bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">{{ $t('workbenches.totalnumber') }}</span>
<span class="material-icons-round text-primary/40 text-lg">analytics</span>
</div>
<div class="text-xl font-bold text-slate-900 dark:text-white">{{ hostData.totalCount }}</div>
</div>
<!-- 新建主播 (较小) -->
<div
class="lg:col-span-2 bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">{{ $t('workbenches.createHost') }}</span>
<span class="material-icons-round text-secondary/40 text-lg">person_add</span>
</div>
<div class="text-xl font-bold text-slate-900 dark:text-white">{{ hostData.validAnchorsCount }}</div>
</div>
<!-- 查询 (较小) -->
<div
class="lg:col-span-2 bg-white dark:bg-slate-900 p-4 rounded-xl shadow-sm border border-slate-100 dark:border-slate-800">
<div class="flex items-center justify-between mb-1">
<span class="text-xs font-medium text-slate-500">{{ $t('workbenches.query') }}</span>
<span class="material-icons-round text-amber-400/60 text-lg">search</span>
</div>
<div class="text-xl font-bold text-slate-900 dark:text-white">{{ hostData.checkedDataCount }}</div>
</div>
<!-- 邀请 (较大突出显示) -->
<div
class="lg:col-span-3 bg-gradient-to-br from-blue-600 to-blue-500 p-5 rounded-xl shadow-lg shadow-blue-500/20 text-white relative overflow-hidden">
<div class="absolute top-0 right-0 w-24 h-24 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/2">
</div>
<div class="flex items-center justify-between mb-2 relative z-10">
<span class="text-sm font-medium text-white/80">{{ $t('workbenches.invite') }}</span>
<span class="material-icons-round text-white/60">mail_outline</span>
</div>
<div class="text-3xl font-bold text-white relative z-10">{{ hostData.canInvitationCount }}</div>
<div class="text-xs text-white/60 mt-1">可邀请主播</div>
</div>
<!-- 运行时间 (较大) -->
<div
class="lg:col-span-3 bg-white dark:bg-slate-900 p-5 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-blue-600">{{ formattedTime }}</div>
</div>
<div class="flex items-center gap-2">
<span class="w-4 h-4 rounded-full" :class="isTkLoggedIn ? 'bg-emerald-500' : 'bg-red-500'"></span>
<button @click="openTK"
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-semibold shadow-lg shadow-blue-600/25">
{{ $t('workbenches.openTK') }}
</button>
</div>
</div>
</div>
</div>
<!-- Guild Accounts -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-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="flex items-center gap-2">
<span class="w-4 h-4 rounded-full"
:class="tkData[index].code == 1 ? 'bg-emerald-500' : 'bg-red-500'"></span>
<h3 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenches.guildAccount') }} {{ index === 0
? 'A'
: 'B' }}</h3>
</div>
<span class=" text-slate-500" style="font-size: 17px;">{{ $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>
<el-input v-model="tkData[index].account" :placeholder="$t('workbenches.guildAccountPlace')"
:disabled="!(tkData[index].code == 0 && !isLogin[index])" class="el-input-custom" />
</div>
<div>
<label class="text-xs font-semibold text-slate-500 mb-1 block">{{ $t('workbenches.guildPass') }}</label>
<el-input v-model="tkData[index].password" type="password" show-password
:placeholder="$t('workbenches.guildPassPlace')"
:disabled="!(tkData[index].code == 0 && !isLogin[index])" class="el-input-custom" />
</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>
<!-- Configuration Panel -->
<div
class="bg-white dark:bg-slate-900 rounded-2xl shadow-sm border border-slate-100 dark:border-slate-800 overflow-hidden">
<div class="p-6 border-b border-slate-100 dark:border-slate-800 flex justify-between items-center">
<div class="flex items-center gap-3">
<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-slate-600 dark:text-slate-400 text-lg">settings</span>
</div>
<h2 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenchesSetup.workbenches') }}</h2>
</div>
<div class="flex items-center gap-4 text-sm">
<div class="flex items-center gap-2 text-slate-500">
<span>{{ $t('workbenchesSetup.network') }}:</span>
<span class="text-blue-600 font-bold">{{ locale == 'zh' ? countryData : countryDataEN }}</span>
<button @click="refreshCountry" :disabled="isRefreshingCountry"
class="ml-1 p-1 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
:title="$t('workbenchesSetup.refreshCountry') || '刷新国家'">
<span class="material-icons-round text-slate-500 text-base" :class="{ 'animate-spin': isRefreshingCountry }">refresh</span>
</button>
</div>
<div class="flex items-center gap-2">
<span class="text-slate-500">指定国家:</span>
<select v-model="country_info"
class="bg-slate-50 dark:bg-slate-800 border-none rounded-lg text-xs font-medium focus:ring-0">
<option value="全部">全部</option>
<option v-for="(item, index) in country_Lst" :key="index" :value="item">{{ item }}</option>
</select>
</div>
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6 mb-6">
<!-- Coins -->
<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-blue-600 rounded-full"></span>
{{ $t('workbenchesSetup.setCoinsNum') }}
</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.minCoinsNum') }}</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.gold.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.maxCoinsNum') }}</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.gold.max" :disabled="!pyData.isStart" />
</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-blue-600 text-white border-blue-600' : 'bg-white text-slate-600 border-slate-200 hover:border-blue-600/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-blue-600 border-blue-600' : '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-blue-600 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-blue-600 border-blue-600' : '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-blue-600 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-blue-600 border-blue-600' : '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-blue-600 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-blue-600 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-blue-600/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>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import { usePythonBridge, } from '@/utils/pythonBridge'
import { setNumData, getNumData, getUser, setTkUser, getTkUser } from '@/utils/storage'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getCountryName } from '@/utils/countryUtil'
import { tkaccountuseinfo, getExpiredTime } from '@/api/account'
import { useI18n } from 'vue-i18n'
import { useCountryStore } from '@/stores/countryStore'
const { t, locale } = useI18n()
const countryStore = useCountryStore()
//导入python交互方法
const { fetchDataConfig, fetchDataCount, loginBackStage, loginTikTok, backStageloginStatus, backStageloginStatusCopy, getTkLoginStatus } = usePythonBridge();
//ip国家 - 使用共享 store
const countryData = computed(() => countryStore.countryData);
const countryDataEN = computed(() => countryStore.countryDataEN);
const isRefreshingCountry = computed(() => countryStore.isLoading);
let country_info = ref('全部');
let country_Lst = ref();
//获取主播数量的定时器
let getHostTimer = ref(null);
//获取查询次数定时器
let getNumTimer = ref(null);
//获取的主播信息
let hostData = ref({
totalCount: 0,
validAnchorsCount: 0,
canInvitationCount: 0,
checkedDataCount: 0,
});
//是否开启tk
// let isTk = ref(true);
//账号是否登陆中
let isLogin = ref([false, false]);
//TK登录状态
let isTkLoggedIn = ref(false);
//TK状态轮询定时器
let tkStatusTimer = ref(null);
//设置状态轮询定时器
let statusTimer = ref(null);
let statusTimerCopy = ref(null);
//设置次数最大值
let maxCount = ref([
{
hourMax: 50,
dayMax: 300,
},
{
hourMax: 100,
dayMax: 600,
},
]);
//tk账号信息
let tkData = ref([
{
account: '',
password: '',
index: 1,
code: 0,
num: 0
},
{
account: '',
password: '',
index: 2,
code: 0,
num: 0
},
]);
//python需要的数据
let pyData = ref({
gold: { min: 0, max: 0 },
fans: { min: 0, max: 0 },
frequency: { hour: 0, day: 0 },
isStart: true,
country: countryData.value,
filterSelling: false,
filterGame: false,
rankingList: false,
tenantId: getUser()?.tenantId || '',
userId: getUser()?.userId || '',
});
//是否限制查询数量
let isLimit = ref(false);
//需要查询的主播数
let hostNum = ref(0);
//按钮提交状态
let submitting = ref(true);
let expiredTime = ref(null);
onMounted(async () => {
//从缓存获取数据
if (getNumData()) {
pyData.value = getNumData();
}
if (getTkUser()) {
tkData.value = getTkUser();
tkData.value[0].code = 0;
tkData.value[1].code = 0;
}
tkaccountuse(tkData.value[0].account, 0)
tkaccountuse(tkData.value[1].account, 1)
// 使用共享 store 初始化国家信息
countryStore.initCountryInfo(t)
setTimeout(() => {
// Check if user exists before calling getExpiredTime
if (getUser()?.tenantId) {
getExpiredTime(getUser().tenantId).then((res) => {
console.log('time:', res);
expiredTime.value = res.expiredTime
})
}
}, 1000);
checkVPN()
setInterval(async () => {
await checkVPN()
}, 1000 * 20)
})
// 刷新国家信息 - 使用共享 store
const refreshCountry = async () => {
await countryStore.refreshCountry(t);
// 刷新成功后获取国家列表
if (countryStore.countryData) {
fetchCountryList(countryStore.countryData);
}
};
// 获取国家列表
const fetchCountryList = async (countryName) => {
try {
const url = `https://datasave.api.yolozs.com/api/save_data/country_info?countryName=${countryName}`;
const res = await fetch(url);
const countryres = await res.json();
if (countryres.data) {
country_Lst.value = countryres.data;
}
} catch (error) {
console.error('获取国家列表失败:', error);
}
};
//提交数据到py
const submit = () => {
pyData.value.country = countryData.value;
console.log('提交的区间值:', pyData.value);
if (((Number(pyData.value.gold.min) > Number(pyData.value.gold.max)) || (Number(pyData.value.fans.min) > Number(pyData.value.fans.max)))) {
ElMessage.error('请输入正确的区间值');
return;
}
if ((Number(pyData.value.gold.max) <= 0 || Number(pyData.value.fans.max <= 0)) || pyData.value.gold.max == '' || pyData.value.fans.max == '') {
ElMessage.error('请输入正确的区间值');
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(
'确认开始爬取数据?',
'开始',
{
confirmButtonText: '开始',
cancelButtonText: '取消',
type: 'success',
}
)
.then(() => {
// console.log('提交的区间值:', pyData.value.gold, pyData.value.fans, pyData.value.frequency);
//开始按钮的状态 改为禁用
submitting.value = true;
setNumData(pyData.value);
console.error('提交的区间值:', JSON.stringify(pyData.value));
fetchDataConfig(JSON.stringify({
gold: pyData.value.gold,
fans: pyData.value.fans,
frequency: pyData.value.frequency,
isStart: true,
filterSelling: pyData.value.filterSelling,
filterGame: pyData.value.filterGame,
rankingList: !pyData.value.rankingList,
country: countryData.value,
tenantId: getUser().tenantId,
userId: getUser().id,
crawl_single_nation: country_info.value == '全部' ? '' : country_info.value
})).then((res) => {
//开始计时器
startTimer();
//开启查询次数
getHostTimer.value = setInterval(() => {
fetchDataCount().then((res) => {
hostData.value = JSON.parse(res);
if (isLimit.value) {
if (hostData.value.canInvitationCount >= hostNum.value) {
unsubmit();
alert('爬取完毕')
}
}
})
}, 1000);
getNumTimer.value = setInterval(() => {
tkaccountuse(tkData.value[0].account, 0)
tkaccountuse(tkData.value[1].account, 1)
}, 5000);
}).finally(() => {
setTimeout(() => {
pyData.value.isStart = false;
submitting.value = false;
}, 2000)
})
})
.catch(() => {
})
};
//停止
const unsubmit = () => {
fetchDataConfig(JSON.stringify({
gold: pyData.value.gold,
fans: pyData.value.fans,
frequency: pyData.value.frequency,
isStart: false,
filterSelling: pyData.value.filterSelling,
filterGame: pyData.value.filterGame,
rankingList: !pyData.value.rankingList,
country: countryData.value,
tenantId: getUser().tenantId,
userId: getUser().id,
crawl_single_nation: country_info.value == '全部' ? '' : country_info.value
})).then((res) => {
pauseTimer();
pyData.value.isStart = true;
clearInterval(getHostTimer.value);
getHostTimer.value = null;
clearInterval(getNumTimer.value);
getNumTimer.value = null;
// ElMessage.sussec('已停止')
}).catch((err) => {
// ElMessage.error('停止失败')
})
};
// 重置 (Unused but kept for port completeness)
// const reset = () => {
// pyData.value.gold = { min: 0, max: 0 };
// pyData.value.fans = { min: 0, max: 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) => {
setTkUser(tkData.value)
loginBackStage({
account: tkData.value[index].account,
password: tkData.value[index].password,
index: index
})
if (index == 0) {
isLogin.value[1] = true;
statusTimer = setInterval(() => {
getloginStatus(tkData.value[index].account);
}, 2000)
} else if (index == 1) {
isLogin.value[0] = true;
statusTimerCopy = setInterval(() => {
getloginStatus(tkData.value[index].account);
}, 2000)
}
}
const openTK = () => {
loginTikTok();
// 开始轮询TK登录状态
if (tkStatusTimer.value) {
clearInterval(tkStatusTimer.value);
}
tkStatusTimer.value = setInterval(() => {
checkTkLoginStatus();
}, 3000);
}
// 检查TK登录状态
const checkTkLoginStatus = () => {
getTkLoginStatus().then((res) => {
isTkLoggedIn.value = res === true || res === 'true';
}).catch(() => {
isTkLoggedIn.value = false;
});
}
function getloginStatus(account) {
backStageloginStatus(account).then((res) => {
console.log("登录状态", res)
if (!res) return;
const data = res;
// 根据 account 找到对应的 index
const index = tkData.value.findIndex(item => item.account === account);
if (index === -1) return;
// 兼容新旧接口返回格式
// 新格式: { status, account, loginStatus, message }
// 旧格式: { code, index, message }
const isSuccess = data.message === "登录成功"
tkData.value[index].code = isSuccess ? 1 : 0;
if (isSuccess) {
clearInterval(statusTimer);
clearInterval(statusTimerCopy);
statusTimer = null;
statusTimerCopy = null;
submitting.value = false
isLogin.value[0] = false;
isLogin.value[1] = false;
}
})
}
// function getloginStatusCopy() {
// backStageloginStatusCopy().then((res) => {
// const data = JSON.parse(res);
// tkData.value[data.index].code = data.code
// if (data.code == 1) {
// clearInterval(statusTimer);
// statusTimer = null;
// submitting.value = false
// isLogin.value[0] = false;
// }
// })
// }
function tkaccountuse(id, index) {
let num = 0;
console.log(id, index, "查询次数")
if (!id || id == '') {
return
}
tkaccountuseinfo(id).then((res) => {
console.log("查询返回", res)
num = res
tkData.value[index].num = num
setTkUser(tkData.value)
console.log('账号使用次数', tkData.value[index].num)
}).catch((err) => {
console.log('账号使用次数', err)
})
}
const isRunning = ref(false);
const totalSeconds = ref(0);
//定时器
let timerCrawl = null;
const startTimedata = ref(null);
//清空时间 并开始运行
const startTimer = () => {
resetTimer();
if (isRunning.value) return;
isRunning.value = true;
startTimedata.value = Date.now();
timerCrawl = setInterval(() => {
totalSeconds.value = Math.floor((Date.now() - startTimedata.value) / 1000);
}, 1000);
};
//结束运行 暂停
const pauseTimer = () => {
isRunning.value = false;
clearInterval(timerCrawl);
};
//清空时间
const resetTimer = () => {
isRunning.value = false;
clearInterval(timerCrawl);
totalSeconds.value = 0;
};
// 格式化时间为 HH:MM:SS
const formattedTime = computed(() => {
const hours = Math.floor(totalSeconds.value / 3600);
const minutes = Math.floor((totalSeconds.value % 3600) / 60);
const seconds = totalSeconds.value % 60;
return [
hours.toString().padStart(2, '0'),
minutes.toString().padStart(2, '0'),
seconds.toString().padStart(2, '0')
].join(':');
});
function timestampToTime(timestamp_ms) {
const date = new Date(timestamp_ms);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
// const milliseconds = date.getMilliseconds().toString().padStart(3, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
let isWifi = ref(false);
const checkVPN = async () => {
try {
// 设置超时 5 秒钟
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), 10000) // 10秒超时
);
// 使用 Promise.race 来进行超时控制
const response = await Promise.race([
fetch('https://www.google.com', { method: 'HEAD', mode: 'no-cors' }),
timeout
]);
// 判断 fetch 请求是否成功
if (response && response.type === 'opaque') {
// ElMessage.success('VPN连接正常');
isWifi.value = false;
} else {
ElMessage.error('VPN连接失败无法访问网络。');
isWifi.value = true;
}
} catch (error) {
// 捕获超时错误或其他错误
ElMessage.error('VPN连接失败无法访问网络。');
isWifi.value = true;
}
};
</script>
<style scoped>
/* Element Plus 输入框统一样式 */
.el-input-custom :deep(.el-input__wrapper) {
background-color: white;
border: 1px solid rgb(226, 232, 240);
border-radius: 0.5rem;
padding: 0.5rem 1rem;
box-shadow: none;
transition: all 0.15s ease;
}
.el-input-custom :deep(.el-input__wrapper:hover) {
border-color: rgb(203, 213, 225);
}
.el-input-custom :deep(.el-input__wrapper.is-focus) {
border-color: var(--el-color-primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.el-input-custom :deep(.el-input__inner) {
font-size: 0.875rem;
}
.el-input-custom :deep(.el-input__wrapper.is-disabled) {
opacity: 0.5;
}
/* 暗色模式支持 */
.dark .el-input-custom :deep(.el-input__wrapper) {
background-color: rgb(30, 41, 59);
border-color: rgb(51, 65, 85);
}
.dark .el-input-custom :deep(.el-input__wrapper:hover) {
border-color: rgb(71, 85, 105);
}
</style>