feat(admin-web): surface monitoring platform authorization status
Dashboard and question-detail views now read platform_authorization_status from the API. Tracking disables collect-now when the account has no desktop client, no authorized platforms, or the selected platform is unauthorized, and keeps the six-platform matrix stable; question-detail replaces tabs and cards with an explicit unavailable notice when the account is not authorized for monitoring. Adds the shared type and en/zh strings covering each status. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -351,6 +351,9 @@ const enUS = {
|
||||
collectTodayOnly: "Collect Now only supports today's data. Switch back to today to continue.",
|
||||
collectClientChecking: "Checking whether the current account's desktop client is online.",
|
||||
collectClientOnlineRequired: "The current account's desktop client must be online before collecting.",
|
||||
noDesktopClientCollectReason: "This account has no registered desktop client, so collection cannot start.",
|
||||
noAuthorizedPlatformsCollectReason: "This account has no authorized AI platforms, so collection cannot start.",
|
||||
collectPlatformUnauthorized: "The selected platform is not authorized for this account.",
|
||||
pluginMode: "Plugin sampling",
|
||||
pluginHint: "Plugin-mode data depends on device uptime. Low coverage should be treated as directional only.",
|
||||
awaitingSnapshot: "Waiting for the next valid snapshot",
|
||||
@@ -393,6 +396,25 @@ const enUS = {
|
||||
noCitationAnalysis: "No citation analysis for this platform",
|
||||
noContentCitations: "No content citations for the active platform",
|
||||
noTaskDebug: "No async task debug data for the current filters",
|
||||
noPlatformBreakdown: "No platform data is available for the current filters",
|
||||
selectedPlatformUnauthorizedTitle: "Selected Platform Not Authorized",
|
||||
selectedPlatformUnauthorizedDescription: "This AI platform has no active authorization for the current account. Switch to an authorized platform or authorize it in the desktop client.",
|
||||
noDetailPlatformsTitle: "No Platforms Available",
|
||||
noDetailPlatformsDescription: "No AI platform data is available for the current filters.",
|
||||
platformAuthorization: {
|
||||
authorized: {
|
||||
title: "AI Platform Authorization Active",
|
||||
description: "This account has AI platform authorization available for monitoring collection.",
|
||||
},
|
||||
no_desktop_client: {
|
||||
title: "No Desktop Client Detected",
|
||||
description: "This account has not registered a desktop client. Daily tasks will wait for an eligible client before dispatch, and the dashboard will not show collection as available.",
|
||||
},
|
||||
no_authorized_platforms: {
|
||||
title: "No Authorized AI Platforms",
|
||||
description: "This account has no bound or authorized AI platform accounts. The system will not generate or dispatch collection tasks for unauthorized platforms.",
|
||||
},
|
||||
},
|
||||
samples: "samples",
|
||||
citations: "citations",
|
||||
citedArticleMeta: "{count} cited articles",
|
||||
|
||||
@@ -351,6 +351,9 @@ const zhCN = {
|
||||
collectTodayOnly: "立即采集只支持今天的数据,请切回今天后再操作",
|
||||
collectClientChecking: "正在检查当前账号的桌面客户端状态",
|
||||
collectClientOnlineRequired: "当前登录账号的桌面客户端未在线,暂时不能立即采集",
|
||||
noDesktopClientCollectReason: "当前账号尚未注册桌面客户端,不能发起采集",
|
||||
noAuthorizedPlatformsCollectReason: "当前账号暂无已授权 AI 平台,不能发起采集",
|
||||
collectPlatformUnauthorized: "当前筛选平台未授权,不能发起采集",
|
||||
pluginMode: "插件采样",
|
||||
pluginHint: "插件模式下结果受设备在线率影响,低覆盖样本仅供趋势参考。",
|
||||
awaitingSnapshot: "等待下一次有效快照",
|
||||
@@ -393,6 +396,25 @@ const zhCN = {
|
||||
noCitationAnalysis: "该平台暂无引用分析",
|
||||
noContentCitations: "当前平台暂无内容引用",
|
||||
noTaskDebug: "当前条件下暂无异步任务调试数据",
|
||||
noPlatformBreakdown: "当前筛选条件下暂无可展示的平台数据",
|
||||
selectedPlatformUnauthorizedTitle: "当前平台未授权",
|
||||
selectedPlatformUnauthorizedDescription: "该 AI 平台没有当前账号的有效授权记录,请切换到已授权平台或在桌面端完成授权。",
|
||||
noDetailPlatformsTitle: "暂无可展示平台",
|
||||
noDetailPlatformsDescription: "当前筛选条件下没有可展示的 AI 平台数据。",
|
||||
platformAuthorization: {
|
||||
authorized: {
|
||||
title: "AI 平台授权正常",
|
||||
description: "当前账号已有可用于监控采集的 AI 平台授权。",
|
||||
},
|
||||
no_desktop_client: {
|
||||
title: "未检测到桌面客户端",
|
||||
description: "当前账号尚未注册桌面客户端。每日任务会等待有效客户端上线后再下发,仪表盘不会展示为可采集状态。",
|
||||
},
|
||||
no_authorized_platforms: {
|
||||
title: "暂无已授权 AI 平台",
|
||||
description: "当前账号没有已绑定或已授权的 AI 平台。系统不会为未授权平台生成或下发采集任务。",
|
||||
},
|
||||
},
|
||||
samples: "次采样",
|
||||
citations: "次引用",
|
||||
citedArticleMeta: "{count} 篇内容被引用",
|
||||
|
||||
@@ -65,6 +65,21 @@ const detailQuery = useQuery({
|
||||
|
||||
const activePlatformId = ref("");
|
||||
const detailPlatforms = computed<MonitoringQuestionDetailPlatform[]>(() => detailQuery.data.value?.platforms ?? []);
|
||||
const hasNoDetailPlatforms = computed(() => {
|
||||
return Boolean(detailQuery.data.value) && detailPlatforms.value.length === 0;
|
||||
});
|
||||
const shouldShowDetailUnavailableNotice = computed(() => {
|
||||
return hasNoDetailPlatforms.value;
|
||||
});
|
||||
const detailUnavailableNoticeType = computed<"warning" | "info">(() => {
|
||||
return "info";
|
||||
});
|
||||
const detailUnavailableNoticeTitle = computed(() => {
|
||||
return requestedPlatformId.value ? t("tracking.selectedPlatformUnauthorizedTitle") : t("tracking.noDetailPlatformsTitle");
|
||||
});
|
||||
const detailUnavailableNoticeDescription = computed(() => {
|
||||
return requestedPlatformId.value ? t("tracking.selectedPlatformUnauthorizedDescription") : t("tracking.noDetailPlatformsDescription");
|
||||
});
|
||||
const detailCitationAnalysis = computed<MonitoringQuestionCitationAnalysisItem[]>(() => detailQuery.data.value?.citation_analysis ?? []);
|
||||
const citationSourcesScrollRef = ref<HTMLElement | null>(null);
|
||||
const highlightedCitationIndex = ref<number | null>(null);
|
||||
@@ -919,7 +934,19 @@ const contentCitationColumns = computed(() => [
|
||||
|
||||
<a-spin :spinning="detailQuery.isLoading.value">
|
||||
<div class="tracking-question-page__content">
|
||||
<div class="tracking-question-page__tabs-wrapper">
|
||||
<a-alert
|
||||
v-if="shouldShowDetailUnavailableNotice"
|
||||
:type="detailUnavailableNoticeType"
|
||||
show-icon
|
||||
:message="detailUnavailableNoticeTitle"
|
||||
:description="detailUnavailableNoticeDescription"
|
||||
class="tracking-question-page__alert"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="!shouldShowDetailUnavailableNotice"
|
||||
class="tracking-question-page__tabs-wrapper"
|
||||
>
|
||||
<a-tabs
|
||||
v-model:activeKey="activePlatformId"
|
||||
size="large"
|
||||
@@ -938,7 +965,10 @@ const contentCitationColumns = computed(() => [
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
||||
<div class="tracking-question-grid">
|
||||
<div
|
||||
v-if="!shouldShowDetailUnavailableNotice"
|
||||
class="tracking-question-grid"
|
||||
>
|
||||
<section class="tracking-question-card tracking-question-card--answer">
|
||||
<div class="tracking-question-card__header">
|
||||
<div class="tracking-question-card__section-title">
|
||||
@@ -1035,7 +1065,7 @@ const contentCitationColumns = computed(() => [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tracking-question-card__scroll-area custom-scrollbar" style="margin: 0; padding: 0;">
|
||||
<div class="tracking-question-card__scroll-area custom-scrollbar tracking-question-table-wrap">
|
||||
<a-table
|
||||
v-if="activeCitationAnalysis.length"
|
||||
row-key="site_key"
|
||||
@@ -1065,7 +1095,7 @@ const contentCitationColumns = computed(() => [
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-empty v-else :description="t('tracking.noCitationAnalysis')" style="margin-top: 40px;" />
|
||||
<a-empty v-else :description="t('tracking.noCitationAnalysis')" class="tracking-question-empty" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1078,7 +1108,7 @@ const contentCitationColumns = computed(() => [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tracking-question-card__scroll-area custom-scrollbar" style="margin: 0; padding: 0;">
|
||||
<div class="tracking-question-card__scroll-area custom-scrollbar tracking-question-table-wrap">
|
||||
<a-table
|
||||
row-key="key"
|
||||
class="modern-table"
|
||||
@@ -1311,6 +1341,15 @@ const contentCitationColumns = computed(() => [
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.tracking-question-table-wrap {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tracking-question-empty {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.tracking-question-answer__headline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
Brand,
|
||||
DesktopAccountInfo,
|
||||
MonitoringHotQuestion,
|
||||
MonitoringPlatformAuthorizationStatus,
|
||||
} from "@geo/shared-types";
|
||||
import { aiPlatformCatalog } from "@geo/shared-types";
|
||||
import { computed, watch } from "vue";
|
||||
@@ -283,6 +284,22 @@ const currentUserClientOnline = computed(() => {
|
||||
return dashboardQuery.data.value?.runtime.current_user_client_online === true;
|
||||
});
|
||||
|
||||
const platformBreakdownRows = computed(() => {
|
||||
return dashboardQuery.data.value?.platform_breakdown ?? [];
|
||||
});
|
||||
|
||||
const platformAuthorizationStatus = computed<MonitoringPlatformAuthorizationStatus>(() => {
|
||||
return dashboardQuery.data.value?.runtime.platform_authorization_status ?? "authorized";
|
||||
});
|
||||
|
||||
const selectedPlatformAuthorized = computed(() => {
|
||||
if (selectedPlatformId.value === "all") {
|
||||
return true;
|
||||
}
|
||||
const authorizedPlatformIDs = dashboardQuery.data.value?.runtime.authorized_monitoring_platform_ids ?? [];
|
||||
return authorizedPlatformIDs.some((item) => normalizeMonitoringPlatformId(item) === selectedPlatformId.value);
|
||||
});
|
||||
|
||||
const collectNowDisabledReason = computed(() => {
|
||||
if (!selectedBrandId.value) {
|
||||
return t("tracking.collectBrandRequired");
|
||||
@@ -296,6 +313,15 @@ const collectNowDisabledReason = computed(() => {
|
||||
if (dashboardQuery.isLoading.value && !dashboardQuery.data.value) {
|
||||
return t("tracking.collectClientChecking");
|
||||
}
|
||||
if (platformAuthorizationStatus.value === "no_desktop_client") {
|
||||
return t("tracking.noDesktopClientCollectReason");
|
||||
}
|
||||
if (platformAuthorizationStatus.value === "no_authorized_platforms") {
|
||||
return t("tracking.noAuthorizedPlatformsCollectReason");
|
||||
}
|
||||
if (selectedPlatformId.value !== "all" && dashboardQuery.data.value && !selectedPlatformAuthorized.value) {
|
||||
return t("tracking.collectPlatformUnauthorized");
|
||||
}
|
||||
if (!currentUserClientOnline.value) {
|
||||
return t("tracking.collectClientOnlineRequired");
|
||||
}
|
||||
@@ -625,7 +651,7 @@ function normalizeTrackingBusinessDate(value: unknown): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const today = dayjs().startOf("day");
|
||||
const today = dayjs(trackingToday).startOf("day");
|
||||
const earliestAllowed = today.subtract(trackingMaxHistoryDays - 1, "day");
|
||||
if (parsed.isAfter(today, "day") || parsed.isBefore(earliestAllowed, "day")) {
|
||||
return null;
|
||||
@@ -634,8 +660,8 @@ function normalizeTrackingBusinessDate(value: unknown): string | null {
|
||||
}
|
||||
|
||||
function disableTrackingDate(current: dayjs.Dayjs): boolean {
|
||||
const today = dayjs().endOf("day");
|
||||
const earliestAllowed = dayjs().subtract(trackingMaxHistoryDays - 1, "day").startOf("day");
|
||||
const today = dayjs(trackingToday).endOf("day");
|
||||
const earliestAllowed = dayjs(trackingToday).subtract(trackingMaxHistoryDays - 1, "day").startOf("day");
|
||||
return current.isAfter(today) || current.isBefore(earliestAllowed);
|
||||
}
|
||||
</script>
|
||||
@@ -739,7 +765,7 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
|
||||
<h3 class="panel-title">{{ t("tracking.platformMatrixTitle") }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="platform-table">
|
||||
<div v-if="platformBreakdownRows.length" class="platform-table">
|
||||
<div class="platform-table__head">
|
||||
<span>{{ t("tracking.columns.platform") }}</span>
|
||||
<span>{{ t("tracking.metrics.mentionRate") }}</span>
|
||||
@@ -748,7 +774,7 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="item in dashboardQuery.data.value?.platform_breakdown ?? []"
|
||||
v-for="item in platformBreakdownRows"
|
||||
:key="item.ai_platform_id"
|
||||
class="platform-table__row"
|
||||
>
|
||||
@@ -763,6 +789,11 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty
|
||||
v-else
|
||||
:description="t('tracking.noPlatformBreakdown')"
|
||||
class="tracking-panel__empty"
|
||||
/>
|
||||
</a-card>
|
||||
</div>
|
||||
|
||||
@@ -1188,6 +1219,10 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tracking-panel__empty {
|
||||
padding: 28px 0 16px;
|
||||
}
|
||||
|
||||
.tracking-metric-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
@@ -1110,8 +1110,16 @@ export interface MonitoringCitedArticle {
|
||||
citation_rate: number | null;
|
||||
}
|
||||
|
||||
export type MonitoringPlatformAuthorizationStatus =
|
||||
| "authorized"
|
||||
| "no_desktop_client"
|
||||
| "no_authorized_platforms";
|
||||
|
||||
export interface MonitoringDashboardRuntime {
|
||||
current_user_client_online: boolean;
|
||||
platform_authorization_status: MonitoringPlatformAuthorizationStatus;
|
||||
authorized_monitoring_platform_ids: string[];
|
||||
authorized_monitoring_platform_count: number;
|
||||
}
|
||||
|
||||
export interface MonitoringDashboardCompositeResponse {
|
||||
@@ -1174,6 +1182,7 @@ export interface MonitoringQuestionDetailResponse {
|
||||
question_id: number;
|
||||
question_hash: string;
|
||||
question_text: string;
|
||||
platform_authorization_status: MonitoringPlatformAuthorizationStatus;
|
||||
time_window: {
|
||||
date_from: string;
|
||||
date_to: string;
|
||||
|
||||
Reference in New Issue
Block a user