|
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
|
|
MonitoringTimeBucket,
|
|
|
|
|
} from "@geo/shared-types";
|
|
|
|
|
import { aiPlatformCatalog } from "@geo/shared-types";
|
|
|
|
|
import { computed, ref, watch } from "vue";
|
|
|
|
|
import { computed, watch } from "vue";
|
|
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
|
|
|
|
|
|
@@ -21,8 +21,6 @@ import PageHero from "@/components/PageHero.vue";
|
|
|
|
|
import { brandsApi, monitoringApi, tenantAccountsApi } from "@/lib/api";
|
|
|
|
|
import { formatDateTime } from "@/lib/display";
|
|
|
|
|
import { formatError } from "@/lib/errors";
|
|
|
|
|
import { kickoffMonitoringCycle } from "@/lib/monitoring-plugin";
|
|
|
|
|
import { loadPublisherRuntimeState } from "@/lib/publisher-runtime";
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n();
|
|
|
|
|
const route = useRoute();
|
|
|
|
@@ -102,7 +100,6 @@ const selectedBrandId = useStorage<number | null>("tracking_selected_brand", nul
|
|
|
|
|
const selectedKeywordId = useStorage<number | null>("tracking_selected_keyword", null);
|
|
|
|
|
const selectedPlatformId = useStorage<string>("tracking_selected_ai_platform_id", "all");
|
|
|
|
|
const selectedBusinessDate = useStorage<string>("tracking_selected_business_date", trackingToday);
|
|
|
|
|
const pluginPreparing = ref(false);
|
|
|
|
|
|
|
|
|
|
selectedPlatformId.value = normalizeTrackingPlatformId(selectedPlatformId.value);
|
|
|
|
|
selectedBusinessDate.value = normalizeTrackingBusinessDate(selectedBusinessDate.value) ?? trackingToday;
|
|
|
|
@@ -228,25 +225,6 @@ const trackingWindow = computed(() => {
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
async function ensureMonitoringPluginReady(options?: { silent?: boolean }): Promise<void> {
|
|
|
|
|
pluginPreparing.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const runtime = await loadPublisherRuntimeState();
|
|
|
|
|
if (!runtime.ping.installed) {
|
|
|
|
|
throw new Error("monitoring_plugin_required");
|
|
|
|
|
}
|
|
|
|
|
if (!runtime.pluginInstallationId) {
|
|
|
|
|
throw new Error("monitoring_plugin_register_required");
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (!options?.silent) {
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
pluginPreparing.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dashboardQuery = useQuery({
|
|
|
|
|
queryKey: computed(() => [
|
|
|
|
|
"tracking",
|
|
|
|
@@ -274,52 +252,28 @@ const aiAccountsQuery = useQuery({
|
|
|
|
|
|
|
|
|
|
const collectNowMutation = useMutation({
|
|
|
|
|
mutationFn: async () => {
|
|
|
|
|
if (!selectedBrandId.value) {
|
|
|
|
|
throw new Error("tracking_brand_required");
|
|
|
|
|
}
|
|
|
|
|
if (!selectedKeywordId.value) {
|
|
|
|
|
throw new Error("tracking_keyword_required");
|
|
|
|
|
}
|
|
|
|
|
await ensureMonitoringPluginReady();
|
|
|
|
|
const collectNowResult = await monitoringApi.collectNow(selectedBrandId.value as number, {
|
|
|
|
|
|
|
|
|
|
return monitoringApi.collectNow(selectedBrandId.value, {
|
|
|
|
|
keyword_id: selectedKeywordId.value,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const kickoffResult = await kickoffMonitoringCycle();
|
|
|
|
|
return {
|
|
|
|
|
collectNowResult,
|
|
|
|
|
kickoffResult,
|
|
|
|
|
kickoffError: null,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
collectNowResult,
|
|
|
|
|
kickoffResult: null,
|
|
|
|
|
kickoffError: formatError(error),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onSuccess: ({ collectNowResult, kickoffResult, kickoffError }) => {
|
|
|
|
|
if (kickoffError) {
|
|
|
|
|
message.warning(`${collectNowResult.message},但后台采集启动失败:${kickoffError}`);
|
|
|
|
|
void dashboardQuery.refetch();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (kickoffResult?.status === "already_running") {
|
|
|
|
|
message.success(`${collectNowResult.message},插件后台已继续执行`);
|
|
|
|
|
void dashboardQuery.refetch();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onSuccess: (collectNowResult) => {
|
|
|
|
|
message.success(collectNowResult.message);
|
|
|
|
|
void dashboardQuery.refetch();
|
|
|
|
|
},
|
|
|
|
|
onError: (error) => {
|
|
|
|
|
if (error instanceof Error && error.message === "tracking_keyword_required") {
|
|
|
|
|
message.warning(t("tracking.collectKeywordRequired"));
|
|
|
|
|
if (error instanceof Error && error.message === "tracking_brand_required") {
|
|
|
|
|
message.warning(t("tracking.collectBrandRequired"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (error instanceof Error && (error.message === "monitoring_plugin_required" || error.message === "monitoring_plugin_register_required")) {
|
|
|
|
|
message.warning(formatError(error));
|
|
|
|
|
if (error instanceof Error && error.message === "tracking_keyword_required") {
|
|
|
|
|
message.warning(t("tracking.collectKeywordRequired"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
message.error(formatError(error));
|
|
|
|
@@ -330,6 +284,26 @@ const selectedBrand = computed<Brand | null>(() => {
|
|
|
|
|
return brandsQuery.data.value?.find((item) => item.id === selectedBrandId.value) ?? null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const currentUserClientOnline = computed(() => {
|
|
|
|
|
return dashboardQuery.data.value?.runtime.current_user_client_online === true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const collectNowDisabledReason = computed(() => {
|
|
|
|
|
if (!selectedBrandId.value) {
|
|
|
|
|
return t("tracking.collectBrandRequired");
|
|
|
|
|
}
|
|
|
|
|
if (!selectedKeywordId.value) {
|
|
|
|
|
return t("tracking.collectKeywordRequired");
|
|
|
|
|
}
|
|
|
|
|
if (dashboardQuery.isLoading.value && !dashboardQuery.data.value) {
|
|
|
|
|
return t("tracking.collectClientChecking");
|
|
|
|
|
}
|
|
|
|
|
if (!currentUserClientOnline.value) {
|
|
|
|
|
return t("tracking.collectClientOnlineRequired");
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const selectedDateHasData = computed(() => {
|
|
|
|
|
return (dashboardQuery.data.value?.overview.actual_sample_count ?? 0) > 0;
|
|
|
|
|
});
|
|
|
|
@@ -514,8 +488,6 @@ const citationChartStyle = computed(() => {
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
void ensureMonitoringPluginReady({ silent: true });
|
|
|
|
|
|
|
|
|
|
const heroDescription = computed(() => {
|
|
|
|
|
const overview = dashboardQuery.data.value?.overview;
|
|
|
|
|
if (!overview?.last_sampled_at) {
|
|
|
|
@@ -767,15 +739,19 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
|
|
|
|
|
:disabled-date="disableTrackingDate"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<a-button
|
|
|
|
|
type="primary"
|
|
|
|
|
:disabled="!selectedBrandId || !selectedKeywordId"
|
|
|
|
|
:loading="collectNowMutation.isPending.value || pluginPreparing"
|
|
|
|
|
@click="collectNowMutation.mutate()"
|
|
|
|
|
>
|
|
|
|
|
<template #icon><ReloadOutlined /></template>
|
|
|
|
|
{{ t("tracking.collectNow") }}
|
|
|
|
|
</a-button>
|
|
|
|
|
<a-tooltip :title="collectNowDisabledReason ?? undefined">
|
|
|
|
|
<span>
|
|
|
|
|
<a-button
|
|
|
|
|
type="primary"
|
|
|
|
|
:disabled="Boolean(collectNowDisabledReason)"
|
|
|
|
|
:loading="collectNowMutation.isPending.value"
|
|
|
|
|
@click="collectNowMutation.mutate()"
|
|
|
|
|
>
|
|
|
|
|
<template #icon><ReloadOutlined /></template>
|
|
|
|
|
{{ t("tracking.collectNow") }}
|
|
|
|
|
</a-button>
|
|
|
|
|
</span>
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
</a-space>
|
|
|
|
|
</template>
|
|
|
|
|
</PageHero>
|
|
|
|
|