达人工作台完善

This commit is contained in:
2026-04-28 16:39:59 +08:00
parent 54402b0dd4
commit d5d6495cba
4 changed files with 88 additions and 9 deletions

View File

@@ -49,7 +49,7 @@
<span class="menu-label">大哥工作台</span> <span class="menu-label">大哥工作台</span>
</button> </button>
<button @click="currentView = 'expert_workbench'" <button v-if="tenantId === '12384'" @click="currentView = 'expert_workbench'"
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;" class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200" style="height: 6vh;"
:class="currentView === 'expert_workbench' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'" :class="currentView === 'expert_workbench' ? 'bg-white text-blue-600 shadow shadow-blue-900/20' : 'text-slate-400 hover:bg-[rgba(21,96,250,0.06)]'"
title="达人工作台"> title="达人工作台">
@@ -75,8 +75,7 @@
<button <button
class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200 text-slate-400 hover:bg-[rgba(21,96,250,0.06)]" class="w-full rounded-xl flex items-center gap-2 px-3 py-2.5 transition-all duration-200 text-slate-400 hover:bg-[rgba(21,96,250,0.06)]"
style="height: 6vh;" style="height: 6vh;" title="敬请期待">
title="敬请期待">
<span class="menu-label">敬请期待...</span> <span class="menu-label">敬请期待...</span>
</button> </button>
</div> </div>
@@ -193,6 +192,9 @@ import placeholderHosts from '@/assets/placeholder-hosts.png'
import placeholderWebAi from '@/assets/placeholder-webai.png' import placeholderWebAi from '@/assets/placeholder-webai.png'
import placeholderBigBrother from '@/assets/placeholder-bigbrother.png' import placeholderBigBrother from '@/assets/placeholder-bigbrother.png'
import { getUser } from '@/utils/storage';
const tenantId = ref('');
const emit = defineEmits(['logout', 'go-back', 'stop-all']) const emit = defineEmits(['logout', 'go-back', 'stop-all'])
const currentView = ref('tk') const currentView = ref('tk')
@@ -239,6 +241,9 @@ const notifySidebarWidth = (width) => {
onMounted(() => { onMounted(() => {
loadServiceContacts() loadServiceContacts()
tenantId.value = String(getUser()?.tenantId || '');
if (!isElectron()) return if (!isElectron()) return
resizeObserver = new ResizeObserver((entries) => { resizeObserver = new ResizeObserver((entries) => {
const width = entries[0]?.contentRect.width const width = entries[0]?.contentRect.width

View File

@@ -209,6 +209,7 @@ export interface ElectronAPI {
visitAnchor: (id: string) => Promise<void> visitAnchor: (id: string) => Promise<void>
exportData: (data: string) => Promise<void> exportData: (data: string) => Promise<void>
controlTask: (data: string) => Promise<{ success: boolean; message?: string; error?: string }> controlTask: (data: string) => Promise<{ success: boolean; message?: string; error?: string }>
controlExpertCapture: (data: string) => Promise<{ success: boolean; message?: string; error?: string }>
controlCheckTask: (payload: { isRunning: boolean; model: boolean }) => Promise<{ success: boolean; message?: string; error?: string }> controlCheckTask: (payload: { isRunning: boolean; model: boolean }) => Promise<{ success: boolean; message?: string; error?: string }>
getBrotherInfo: () => Promise<string> getBrotherInfo: () => Promise<string>
queryBrotherInfo: (data: string) => Promise<string> queryBrotherInfo: (data: string) => Promise<string>

View File

@@ -106,6 +106,11 @@ export function usePythonBridge() {
await window.electronAPI.tk.controlTask(data); await window.electronAPI.tk.controlTask(data);
}; };
const controlExpertCapture = async (data) => {
if (!inElectron) return { success: false, error: 'Not in Electron' };
return await window.electronAPI.tk.controlExpertCapture(data);
};
const controlCheckTask = async (isRunning, model) => { const controlCheckTask = async (isRunning, model) => {
if (!inElectron) return { success: false, error: 'Not in Electron' }; if (!inElectron) return { success: false, error: 'Not in Electron' };
return await window.electronAPI.tk.controlCheckTask({ return await window.electronAPI.tk.controlCheckTask({
@@ -250,6 +255,7 @@ export function usePythonBridge() {
getTkLoginStatus, getTkLoginStatus,
// New Fan Workbench exports // New Fan Workbench exports
controlTask, controlTask,
controlExpertCapture,
controlCheckTask, controlCheckTask,
getBrotherInfo, getBrotherInfo,
Specifystreaming, Specifystreaming,

View File

@@ -21,6 +21,22 @@
</div> </div>
<div class="flex flex-wrap items-end gap-4"> <div class="flex flex-wrap items-end gap-4">
<div class="flex flex-col gap-1">
<label class="text-xs font-bold text-slate-400 uppercase tracking-wider">国家</label>
<div
class="flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-700">
<span>{{ countryData || '未识别' }}</span>
<span class="rounded bg-slate-100 px-2 py-0.5 text-xs text-slate-500">{{ regionCode || '--' }}</span>
<button @click="editCountry" :disabled="isRunning || isRefreshingCountry"
class="rounded-md p-1 text-slate-500 transition-colors hover:bg-slate-100 disabled:opacity-50">
<span class="material-icons-round text-base">edit</span>
</button>
<button @click="refreshCountryFn" :disabled="isRunning || isRefreshingCountry"
class="rounded-md p-1 text-slate-500 transition-colors hover:bg-slate-100 disabled:opacity-50">
<span class="material-icons-round text-base" :class="{ 'animate-spin': isRefreshingCountry }">refresh</span>
</button>
</div>
</div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<label class="text-xs font-bold text-slate-400 uppercase tracking-wider">最小粉丝数</label> <label class="text-xs font-bold text-slate-400 uppercase tracking-wider">最小粉丝数</label>
<el-input v-model="crawlForm.fansMin" placeholder="最小值" style="width: 150px" type="number" <el-input v-model="crawlForm.fansMin" placeholder="最小值" style="width: 150px" type="number"
@@ -33,9 +49,6 @@
</div> </div>
</div> </div>
<div class="mt-4 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700">
当前仓库里还没有达人爬虫的后端启动接口这里先保留前端配置和任务态等后端接口接入后再把参数真正传下去
</div>
</section> </section>
<section class="rounded-2xl border border-slate-100 bg-white p-5"> <section class="rounded-2xl border border-slate-100 bg-white p-5">
@@ -119,6 +132,9 @@ import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { getUser } from '@/utils/storage'; import { getUser } from '@/utils/storage';
import { expertInfoPage } from '@/api/account'; import { expertInfoPage } from '@/api/account';
import { useCountryInfo } from '@/composables/useCountryInfo';
import { CountryCode } from '@/utils/countryUtil';
import { usePythonBridge } from '@/utils/pythonBridge';
const userInfo = ref({}); const userInfo = ref({});
const tenantId = ref(''); const tenantId = ref('');
@@ -135,11 +151,14 @@ const tableData = ref([]);
const isRunning = ref(false); const isRunning = ref(false);
const elapsedSeconds = ref(0); const elapsedSeconds = ref(0);
const elapsedTimerId = ref(null); const elapsedTimerId = ref(null);
const { countryData, isLoading: isRefreshingCountry, initCountryInfo, refreshCountry, showEditCountryDialog } = useCountryInfo();
const { controlExpertCapture } = usePythonBridge();
const stats = computed(() => ({ const stats = computed(() => ({
total: total.value, total: total.value,
pageCount: tableData.value.length, pageCount: tableData.value.length,
})); }));
const regionCode = computed(() => resolveRegionCode(countryData.value));
const formattedElapsed = computed(() => { const formattedElapsed = computed(() => {
const hours = Math.floor(elapsedSeconds.value / 3600); const hours = Math.floor(elapsedSeconds.value / 3600);
@@ -152,6 +171,7 @@ onMounted(() => {
userInfo.value = getUser() || {}; userInfo.value = getUser() || {};
tenantId.value = String(userInfo.value?.tenantId || ''); tenantId.value = String(userInfo.value?.tenantId || '');
queryDate.value = buildTodayText(); queryDate.value = buildTodayText();
void initCountryInfo((key) => key);
void loadList(); void loadList();
}); });
@@ -176,6 +196,15 @@ function normalizeNumber(value) {
return Number.isFinite(parsed) ? parsed : undefined; return Number.isFinite(parsed) ? parsed : undefined;
} }
function resolveRegionCode(countryName) {
if (!countryName) {
return '';
}
const matched = Object.entries(CountryCode).find(([code, name]) => code.length === 2 && name === countryName);
return matched?.[0] || '';
}
function formatDateTime(value) { function formatDateTime(value) {
if (!value) { if (!value) {
return ''; return '';
@@ -229,9 +258,23 @@ function validateCrawlRange() {
return false; return false;
} }
if (!regionCode.value) {
ElMessage.error('当前国家无法转换为地区代码,请先修改国家');
return false;
}
return true; return true;
} }
function buildCapturePayload(isStart) {
return {
region: regionCode.value,
MinFollowerCount: normalizeNumber(crawlForm.value.fansMin) ?? 0,
MaxFollowerCount: normalizeNumber(crawlForm.value.fansMax) ?? 0,
isStart,
};
}
async function loadList() { async function loadList() {
if (!tenantId.value) { if (!tenantId.value) {
tableData.value = []; tableData.value = [];
@@ -270,7 +313,7 @@ function stopElapsedTimer() {
} }
} }
function handleStart() { async function handleStart() {
if (!tenantId.value) { if (!tenantId.value) {
ElMessage.error('缺少 tenantId无法启动达人工作台'); ElMessage.error('缺少 tenantId无法启动达人工作台');
return; return;
@@ -280,17 +323,41 @@ function handleStart() {
return; return;
} }
isRunning.value = true; const result = await controlExpertCapture(JSON.stringify(buildCapturePayload(true)));
startElapsedTimer(); if (!result?.success) {
ElMessage.success('达人工作台已进入运行态'); ElMessage.error(result?.error || '启动达人爬虫失败');
return;
}
isRunning.value = true;
startElapsedTimer();
ElMessage.success('达人工作台已开始运行');
}
async function handleStop() {
const result = await controlExpertCapture(JSON.stringify(buildCapturePayload(false)));
if (!result?.success) {
ElMessage.error(result?.error || '停止达人爬虫失败');
return;
} }
function handleStop() {
isRunning.value = false; isRunning.value = false;
stopElapsedTimer(); stopElapsedTimer();
ElMessage.success('达人工作台已停止'); ElMessage.success('达人工作台已停止');
} }
async function refreshCountryFn() {
await refreshCountry((key) => key);
}
async function editCountry() {
try {
await showEditCountryDialog((key) => key);
} catch {
// 用户取消
}
}
async function handleSearch() { async function handleSearch() {
page.value = 1; page.value = 1;
await loadList(); await loadList();