2026-04-12 09:56:18 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ArrowLeftOutlined } from "@ant-design/icons-vue";
|
|
|
|
|
import { useQuery } from "@tanstack/vue-query";
|
|
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
import type {
|
|
|
|
|
MonitoringQuestionCitationAnalysisItem,
|
|
|
|
|
MonitoringQuestionDetailCitation,
|
|
|
|
|
MonitoringQuestionDetailPlatform,
|
2026-04-23 09:11:53 +08:00
|
|
|
MonitoringSourceItem,
|
2026-04-12 09:56:18 +08:00
|
|
|
} from "@geo/shared-types";
|
2026-04-23 09:11:53 +08:00
|
|
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from "vue";
|
2026-04-12 09:56:18 +08:00
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
|
|
|
|
|
|
|
import MarkdownPreview from "@/components/MarkdownPreview.vue";
|
|
|
|
|
import { monitoringApi } from "@/lib/api";
|
|
|
|
|
import { formatDateTime } from "@/lib/display";
|
|
|
|
|
import { formatError } from "@/lib/errors";
|
2026-04-23 09:11:53 +08:00
|
|
|
import { isMonitoringPlatformId, normalizeMonitoringPlatformId } from "@/lib/monitoring-platforms";
|
2026-04-12 09:56:18 +08:00
|
|
|
|
|
|
|
|
const { t } = useI18n();
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
const trackingMaxHistoryDays = 30;
|
|
|
|
|
const trackingToday = dayjs().format("YYYY-MM-DD");
|
|
|
|
|
|
|
|
|
|
const brandId = computed(() => parsePositiveNumber(route.params.brandId));
|
|
|
|
|
const questionId = computed(() => parsePositiveNumber(route.params.questionId));
|
|
|
|
|
const questionHash = computed(() => normalizeQueryValue(route.query.question_hash));
|
|
|
|
|
const questionTitleFallback = computed(() => normalizeQueryValue(route.query.question_text));
|
|
|
|
|
const keywordId = computed(() => normalizeQueryValue(route.query.keyword_id));
|
2026-04-23 09:11:53 +08:00
|
|
|
const requestedPlatformId = computed(() => {
|
|
|
|
|
const platformId = normalizeMonitoringPlatformId(normalizeQueryValue(route.query.ai_platform_id));
|
|
|
|
|
return isMonitoringPlatformId(platformId) ? platformId : "";
|
|
|
|
|
});
|
2026-04-12 09:56:18 +08:00
|
|
|
const businessDate = computed(() => normalizeTrackingBusinessDate(route.query.business_date) || trackingToday);
|
|
|
|
|
const dateFrom = computed(() => {
|
|
|
|
|
return normalizeQueryValue(route.query.date_from) || businessDate.value;
|
|
|
|
|
});
|
|
|
|
|
const dateTo = computed(() => {
|
|
|
|
|
return normalizeQueryValue(route.query.date_to) || businessDate.value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const detailQuery = useQuery({
|
|
|
|
|
queryKey: computed(() => [
|
|
|
|
|
"tracking",
|
|
|
|
|
"question-detail-page",
|
|
|
|
|
brandId.value,
|
|
|
|
|
questionId.value,
|
|
|
|
|
questionHash.value,
|
2026-04-13 16:08:12 +08:00
|
|
|
requestedPlatformId.value,
|
2026-04-12 09:56:18 +08:00
|
|
|
dateFrom.value,
|
|
|
|
|
dateTo.value,
|
|
|
|
|
]),
|
|
|
|
|
enabled: computed(() => Boolean(brandId.value && questionId.value)),
|
|
|
|
|
queryFn: () =>
|
|
|
|
|
monitoringApi.questionDetail(brandId.value as number, questionId.value as number, {
|
|
|
|
|
date_from: dateFrom.value,
|
|
|
|
|
date_to: dateTo.value,
|
|
|
|
|
question_hash: questionHash.value || undefined,
|
2026-04-13 16:08:12 +08:00
|
|
|
ai_platform_id: requestedPlatformId.value || undefined,
|
2026-04-12 09:56:18 +08:00
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const activePlatformId = ref("");
|
2026-04-23 09:11:53 +08:00
|
|
|
const detailPlatforms = computed<MonitoringQuestionDetailPlatform[]>(() => detailQuery.data.value?.platforms ?? []);
|
|
|
|
|
const detailCitationAnalysis = computed<MonitoringQuestionCitationAnalysisItem[]>(() => detailQuery.data.value?.citation_analysis ?? []);
|
|
|
|
|
const citationSourcesScrollRef = ref<HTMLElement | null>(null);
|
|
|
|
|
const highlightedCitationIndex = ref<number | null>(null);
|
|
|
|
|
|
|
|
|
|
let citationHighlightTimer: ReturnType<typeof setTimeout> | null = null;
|
2026-04-12 09:56:18 +08:00
|
|
|
|
|
|
|
|
watch(
|
2026-04-23 09:11:53 +08:00
|
|
|
detailPlatforms,
|
2026-04-12 09:56:18 +08:00
|
|
|
(platforms) => {
|
|
|
|
|
if (!platforms?.length) {
|
|
|
|
|
activePlatformId.value = "";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 16:08:12 +08:00
|
|
|
const requestedPlatform = requestedPlatformId.value
|
|
|
|
|
? platforms.find((item) => item.ai_platform_id === requestedPlatformId.value)
|
2026-04-12 09:56:18 +08:00
|
|
|
: null;
|
|
|
|
|
const sampledPlatform = platforms.find((item) => item.sample_status === "sampled");
|
|
|
|
|
const nextPlatform = requestedPlatform ?? sampledPlatform ?? platforms[0];
|
|
|
|
|
|
|
|
|
|
if (!platforms.some((item) => item.ai_platform_id === activePlatformId.value)) {
|
|
|
|
|
activePlatformId.value = nextPlatform.ai_platform_id;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const pageTitle = computed(() => {
|
|
|
|
|
return detailQuery.data.value?.question_text ?? questionTitleFallback.value ?? t("tracking.questionDetailFallback");
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
const activePlatform = computed<MonitoringQuestionDetailPlatform | null>(() => detailPlatforms.value.find((item) => item.ai_platform_id === activePlatformId.value) ?? null);
|
2026-04-12 09:56:18 +08:00
|
|
|
|
|
|
|
|
const activeCitationAnalysis = computed<MonitoringQuestionCitationAnalysisItem[]>(() => {
|
2026-04-23 09:11:53 +08:00
|
|
|
const items = detailCitationAnalysis.value;
|
2026-04-12 09:56:18 +08:00
|
|
|
if (!activePlatformId.value) {
|
|
|
|
|
return items;
|
|
|
|
|
}
|
|
|
|
|
return items.filter((item) => item.ai_platform_id === activePlatformId.value);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const latestSampledAt = computed<string | null>(() => {
|
2026-04-23 09:11:53 +08:00
|
|
|
const platforms = detailPlatforms.value;
|
2026-04-12 09:56:18 +08:00
|
|
|
const sampledAtValues = platforms
|
|
|
|
|
.map((item) => item.sampled_at)
|
|
|
|
|
.filter((value): value is string => Boolean(value));
|
|
|
|
|
|
|
|
|
|
if (!sampledAtValues.length) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sampledAtValues.reduce((latest, current) => {
|
|
|
|
|
if (!latest) {
|
|
|
|
|
return current;
|
|
|
|
|
}
|
|
|
|
|
return dayjs(current).isAfter(dayjs(latest)) ? current : latest;
|
|
|
|
|
}, sampledAtValues[0] ?? null);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const collectedAtDisplay = computed(() => {
|
|
|
|
|
const sampledAt = activePlatform.value?.sampled_at || latestSampledAt.value;
|
|
|
|
|
return sampledAt ? formatDateTime(sampledAt) : "--";
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-22 00:24:21 +08:00
|
|
|
type TrackingAnalyzedContentCitation = {
|
|
|
|
|
key: string;
|
|
|
|
|
contentName: string;
|
|
|
|
|
channelName: string;
|
|
|
|
|
citationCount: number;
|
|
|
|
|
citationRate: number | null;
|
|
|
|
|
citedURL: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
const renderedAnswerContent = computed<string | null>(() => {
|
|
|
|
|
return formatAnswerContent(activePlatform.value);
|
2026-04-22 00:24:21 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const activeInlineContentCitations = computed<TrackingAnalyzedContentCitation[]>(() => {
|
|
|
|
|
return analyzeInlineContentCitations(activePlatform.value);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const shouldShowInlineContentCitations = computed(() => {
|
|
|
|
|
return activeInlineContentCitations.value.length > 0;
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
watch(activePlatformId, () => {
|
|
|
|
|
clearCitationHighlight();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
clearCitationHighlight();
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-12 09:56:18 +08:00
|
|
|
function goBack(): void {
|
|
|
|
|
void router.push({
|
|
|
|
|
name: "tracking",
|
|
|
|
|
query: {
|
|
|
|
|
brand_id: brandId.value ? String(brandId.value) : undefined,
|
|
|
|
|
keyword_id: keywordId.value || undefined,
|
2026-04-13 16:08:12 +08:00
|
|
|
ai_platform_id: requestedPlatformId.value || undefined,
|
2026-04-12 09:56:18 +08:00
|
|
|
business_date: businessDate.value,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatPercent(value: number | null | undefined): string {
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
|
return "--";
|
|
|
|
|
}
|
|
|
|
|
return `${(value * 100).toFixed(2)}%`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function statusTagColor(status: string): string {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case "sampled":
|
|
|
|
|
return "green";
|
|
|
|
|
case "pending":
|
|
|
|
|
return "gold";
|
|
|
|
|
case "not_logged_in":
|
|
|
|
|
return "default";
|
|
|
|
|
case "unavailable":
|
|
|
|
|
return "red";
|
|
|
|
|
default:
|
|
|
|
|
return "default";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function statusLabel(status: string): string {
|
|
|
|
|
return t(`tracking.status.${status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resolveCitationTitle(citation: MonitoringQuestionDetailCitation): string {
|
|
|
|
|
return citation.cited_title || citation.article_title || citation.cited_url;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
function isLinkedInlineCitationPlatform(platformId: string | null | undefined): boolean {
|
|
|
|
|
return platformId === "qwen" || platformId === "yuanbao";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isKimiInlineCitationPlatform(platformId: string | null | undefined): boolean {
|
|
|
|
|
return platformId === "kimi";
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:24:21 +08:00
|
|
|
function createQwenSourceGroupPattern(): RegExp {
|
|
|
|
|
return /\[\[source_group_web_(\d+)\]\]/g;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
function createYuanbaoCitationPattern(): RegExp {
|
|
|
|
|
return /\[citation:\s*(\d+)\]/gi;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createYuanbaoCitationStripPattern(): RegExp {
|
|
|
|
|
return /\[\s*citation\s*:[^\[\]]*?\]/gi;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createQwenSourceGroupStripPattern(): RegExp {
|
|
|
|
|
return /\[\[\s*source_group_web_[^\[\]]*?\]\]/gi;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createMarkdownLinkPattern(): RegExp {
|
|
|
|
|
return /\[([^\]\n]+)\]\((https?:\/\/[^)\s]+)\)/g;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function collectInlineCitationIndexes(answerText: string): number[] {
|
|
|
|
|
const matches = [
|
|
|
|
|
...answerText.matchAll(createQwenSourceGroupPattern()),
|
|
|
|
|
...answerText.matchAll(createYuanbaoCitationPattern()),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return matches
|
|
|
|
|
.map((match) => Number(match[1]))
|
|
|
|
|
.filter((value) => Number.isFinite(value) && value > 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeInlineCitationUrl(value: string | null | undefined): string {
|
|
|
|
|
const normalized = String(value ?? "").trim();
|
|
|
|
|
if (!normalized) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(normalized);
|
|
|
|
|
url.hash = "";
|
|
|
|
|
return url.toString();
|
|
|
|
|
} catch {
|
|
|
|
|
return normalized;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function deriveCitationChannelName(url: string): string {
|
|
|
|
|
try {
|
|
|
|
|
return new URL(url).hostname || "--";
|
|
|
|
|
} catch {
|
|
|
|
|
return "--";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function collectKimiInlineCitationOccurrences(answerText: string): Array<{
|
|
|
|
|
url: string;
|
|
|
|
|
label: string | null;
|
|
|
|
|
citationCount: number;
|
|
|
|
|
}> {
|
|
|
|
|
const counts = new Map<string, { label: string | null; citationCount: number }>();
|
|
|
|
|
const pattern = createMarkdownLinkPattern();
|
|
|
|
|
|
|
|
|
|
for (const match of answerText.matchAll(pattern)) {
|
|
|
|
|
const matchIndex = match.index ?? 0;
|
|
|
|
|
if (matchIndex > 0 && answerText[matchIndex - 1] === "!") {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const label = String(match[1] ?? "").trim() || null;
|
|
|
|
|
const url = normalizeInlineCitationUrl(match[2]);
|
|
|
|
|
if (!url) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = counts.get(url);
|
|
|
|
|
if (existing) {
|
|
|
|
|
counts.set(url, {
|
|
|
|
|
label: existing.label ?? label,
|
|
|
|
|
citationCount: existing.citationCount + 1,
|
|
|
|
|
});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
counts.set(url, {
|
|
|
|
|
label,
|
|
|
|
|
citationCount: 1,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.from(counts.entries()).map(([url, value]) => ({
|
|
|
|
|
url,
|
|
|
|
|
label: value.label,
|
|
|
|
|
citationCount: value.citationCount,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildInlineCitationSourceLookup(platform: MonitoringQuestionDetailPlatform | null): Map<string, MonitoringSourceItem> {
|
|
|
|
|
const lookup = new Map<string, MonitoringSourceItem>();
|
|
|
|
|
for (const item of platform?.inline_citations ?? []) {
|
|
|
|
|
const normalizedURL = normalizeInlineCitationUrl(item.normalized_url ?? item.url);
|
|
|
|
|
if (!normalizedURL || lookup.has(normalizedURL)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
lookup.set(normalizedURL, item);
|
|
|
|
|
}
|
|
|
|
|
return lookup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function countHanCharacters(value: string): number {
|
|
|
|
|
return value.match(/[\u3400-\u9fff]/g)?.length ?? 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function countSuspiciousMojibakeCharacters(value: string): number {
|
|
|
|
|
return value.match(/[\u00c0-\u017f\u2000-\u20ff]/g)?.length ?? 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function looksLikeNonAnswerNoise(value: string): boolean {
|
|
|
|
|
const hanCount = countHanCharacters(value);
|
|
|
|
|
const suspiciousCount = countSuspiciousMojibakeCharacters(value);
|
|
|
|
|
if (hanCount === 0) {
|
|
|
|
|
return suspiciousCount >= 4;
|
|
|
|
|
}
|
|
|
|
|
return suspiciousCount >= 8 && suspiciousCount > hanCount * 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sanitizeAnswerBody(answerText: string | null | undefined): string | null {
|
2026-04-22 00:24:21 +08:00
|
|
|
const normalized = String(answerText ?? "").trim();
|
|
|
|
|
if (!normalized) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
const lines = normalized.split(/\r?\n+/).map((line) => line.trim()).filter(Boolean);
|
|
|
|
|
const keptLines = lines.filter((line) => !looksLikeNonAnswerNoise(line));
|
|
|
|
|
const sanitized = (keptLines.length ? keptLines.join("\n") : normalized)
|
2026-04-22 00:24:21 +08:00
|
|
|
.replace(/[ \t]+([,.;:!?,。!?;:])/g, "$1")
|
|
|
|
|
.replace(/[ \t]{2,}/g, " ")
|
|
|
|
|
.replace(/\n{3,}/g, "\n\n")
|
|
|
|
|
.trim();
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
return sanitized || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildInlineCitationMarker(
|
|
|
|
|
sourceGroupIndex: number,
|
|
|
|
|
platform: MonitoringQuestionDetailPlatform | null,
|
|
|
|
|
): string {
|
|
|
|
|
if (!Number.isFinite(sourceGroupIndex) || sourceGroupIndex < 1) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const hasCitation = Boolean(platform?.citations[sourceGroupIndex - 1]);
|
|
|
|
|
const className = hasCitation
|
|
|
|
|
? "tracking-inline-citation"
|
|
|
|
|
: "tracking-inline-citation tracking-inline-citation--muted";
|
|
|
|
|
|
|
|
|
|
if (!hasCitation) {
|
|
|
|
|
return `<span class="${className}">[${sourceGroupIndex}]</span>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return `<button type="button" class="${className}" data-citation-index="${sourceGroupIndex}">[${sourceGroupIndex}]</button>`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatAnswerContent(platform: MonitoringQuestionDetailPlatform | null): string | null {
|
|
|
|
|
const sanitized = sanitizeAnswerBody(platform?.answer_text);
|
|
|
|
|
if (!sanitized) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const withInlineMarkers = isLinkedInlineCitationPlatform(platform?.ai_platform_id)
|
|
|
|
|
? sanitized
|
|
|
|
|
.replace(createQwenSourceGroupPattern(), (_, sourceGroupIndex) => {
|
|
|
|
|
return buildInlineCitationMarker(Number(sourceGroupIndex), platform);
|
|
|
|
|
})
|
|
|
|
|
.replace(createYuanbaoCitationPattern(), (_, sourceGroupIndex) => {
|
|
|
|
|
return buildInlineCitationMarker(Number(sourceGroupIndex), platform);
|
|
|
|
|
})
|
|
|
|
|
: sanitized;
|
|
|
|
|
|
|
|
|
|
return withInlineMarkers
|
|
|
|
|
.replace(createQwenSourceGroupStripPattern(), "")
|
|
|
|
|
.replace(createYuanbaoCitationStripPattern(), "")
|
|
|
|
|
.replace(/[ \t]{2,}/g, " ")
|
|
|
|
|
.replace(/\n{3,}/g, "\n\n")
|
|
|
|
|
.trim();
|
2026-04-22 00:24:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function analyzeInlineContentCitations(
|
|
|
|
|
platform: MonitoringQuestionDetailPlatform | null,
|
|
|
|
|
): TrackingAnalyzedContentCitation[] {
|
|
|
|
|
const answerText = String(platform?.answer_text ?? "");
|
|
|
|
|
if (!answerText) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
if (isKimiInlineCitationPlatform(platform?.ai_platform_id)) {
|
|
|
|
|
const occurrences = collectKimiInlineCitationOccurrences(answerText);
|
|
|
|
|
if (!occurrences.length) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const totalCitationCount = occurrences.reduce((sum, item) => sum + item.citationCount, 0);
|
|
|
|
|
if (!totalCitationCount) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const inlineCitationLookup = buildInlineCitationSourceLookup(platform);
|
|
|
|
|
return occurrences
|
|
|
|
|
.sort((left, right) => {
|
|
|
|
|
if (right.citationCount !== left.citationCount) {
|
|
|
|
|
return right.citationCount - left.citationCount;
|
|
|
|
|
}
|
|
|
|
|
return left.url.localeCompare(right.url, "zh-CN");
|
|
|
|
|
})
|
|
|
|
|
.map((item) => {
|
|
|
|
|
const source = inlineCitationLookup.get(item.url);
|
|
|
|
|
const contentName = source?.title?.trim() || item.label || item.url;
|
|
|
|
|
const channelName = source?.site_name?.trim() || deriveCitationChannelName(item.url);
|
|
|
|
|
return {
|
|
|
|
|
key: `kimi-${item.url}`,
|
|
|
|
|
contentName,
|
|
|
|
|
channelName,
|
|
|
|
|
citationCount: item.citationCount,
|
|
|
|
|
citationRate: item.citationCount / totalCitationCount,
|
|
|
|
|
citedURL: item.url,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourceGroupIndexes = collectInlineCitationIndexes(answerText);
|
|
|
|
|
if (!sourceGroupIndexes.length) {
|
2026-04-22 00:24:21 +08:00
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const citationCounts = new Map<number, number>();
|
2026-04-23 09:11:53 +08:00
|
|
|
for (const sourceGroupIndex of sourceGroupIndexes) {
|
2026-04-22 00:24:21 +08:00
|
|
|
citationCounts.set(sourceGroupIndex, (citationCounts.get(sourceGroupIndex) ?? 0) + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const totalCitationCount = Array.from(citationCounts.values()).reduce((sum, count) => sum + count, 0);
|
|
|
|
|
if (!totalCitationCount) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Array.from(citationCounts.entries())
|
|
|
|
|
.sort((left, right) => {
|
|
|
|
|
if (right[1] !== left[1]) {
|
|
|
|
|
return right[1] - left[1];
|
|
|
|
|
}
|
|
|
|
|
return left[0] - right[0];
|
|
|
|
|
})
|
2026-04-23 09:11:53 +08:00
|
|
|
.flatMap(([sourceGroupIndex, citationCount]) => {
|
2026-04-22 00:24:21 +08:00
|
|
|
const citation = platform?.citations[sourceGroupIndex - 1] ?? null;
|
2026-04-23 09:11:53 +08:00
|
|
|
const citedURL = citation?.cited_url?.trim() ?? "";
|
|
|
|
|
if (!citedURL) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2026-04-22 00:24:21 +08:00
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
return [{
|
|
|
|
|
key: `${sourceGroupIndex}-${citedURL}`,
|
2026-04-22 00:24:21 +08:00
|
|
|
contentName: citation ? resolveCitationTitle(citation) : `引用内容 #${sourceGroupIndex}`,
|
|
|
|
|
channelName: citation?.site_name ?? "--",
|
|
|
|
|
citationCount,
|
|
|
|
|
citationRate: citationCount / totalCitationCount,
|
2026-04-23 09:11:53 +08:00
|
|
|
citedURL,
|
|
|
|
|
}];
|
2026-04-22 00:24:21 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
function clearCitationHighlight(): void {
|
|
|
|
|
if (citationHighlightTimer !== null) {
|
|
|
|
|
clearTimeout(citationHighlightTimer);
|
|
|
|
|
citationHighlightTimer = null;
|
|
|
|
|
}
|
|
|
|
|
highlightedCitationIndex.value = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function focusCitationSource(sourceGroupIndex: number): Promise<void> {
|
|
|
|
|
if (!Number.isFinite(sourceGroupIndex) || sourceGroupIndex < 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
const citationCard = citationSourcesScrollRef.value?.querySelector<HTMLElement>(
|
|
|
|
|
`[data-citation-index="${sourceGroupIndex}"]`,
|
|
|
|
|
);
|
|
|
|
|
if (!citationCard) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
highlightedCitationIndex.value = sourceGroupIndex;
|
|
|
|
|
citationCard.scrollIntoView({
|
|
|
|
|
behavior: "smooth",
|
|
|
|
|
block: "nearest",
|
|
|
|
|
inline: "nearest",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (citationHighlightTimer !== null) {
|
|
|
|
|
clearTimeout(citationHighlightTimer);
|
|
|
|
|
}
|
|
|
|
|
citationHighlightTimer = setTimeout(() => {
|
|
|
|
|
highlightedCitationIndex.value = null;
|
|
|
|
|
citationHighlightTimer = null;
|
|
|
|
|
}, 2400);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleAnswerContentClick(event: MouseEvent): void {
|
|
|
|
|
const target = event.target;
|
|
|
|
|
if (!(target instanceof HTMLElement)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const trigger = target.closest("[data-citation-index]") as HTMLElement | null;
|
|
|
|
|
if (!trigger) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourceGroupIndex = Number(trigger.dataset.citationIndex ?? "");
|
|
|
|
|
if (!Number.isFinite(sourceGroupIndex) || sourceGroupIndex < 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
void focusCitationSource(sourceGroupIndex);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 09:56:18 +08:00
|
|
|
function normalizeQueryValue(value: unknown): string {
|
|
|
|
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
|
|
|
return String(raw ?? "").trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeTrackingBusinessDate(value: unknown): string | null {
|
|
|
|
|
const normalized = normalizeQueryValue(value);
|
|
|
|
|
if (!normalized) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const parsed = dayjs(normalized);
|
|
|
|
|
if (!parsed.isValid() || parsed.format("YYYY-MM-DD") !== normalized) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const today = dayjs().startOf("day");
|
|
|
|
|
const earliestAllowed = today.subtract(trackingMaxHistoryDays - 1, "day");
|
|
|
|
|
if (parsed.isAfter(today, "day") || parsed.isBefore(earliestAllowed, "day")) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return normalized;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parsePositiveNumber(value: unknown): number | null {
|
|
|
|
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
|
|
|
const parsed = Number(raw);
|
|
|
|
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
2026-04-23 09:11:53 +08:00
|
|
|
|
|
|
|
|
const citationAnalysisColumns = computed(() => [
|
|
|
|
|
{ title: t("tracking.columns.sourcePlatform"), key: "sourcePlatform", dataIndex: "site_name", width: "30%" },
|
|
|
|
|
{ title: t("tracking.columns.domain"), key: "domain", dataIndex: "site_domain", width: "30%", align: "center" as const },
|
|
|
|
|
{ title: t("tracking.columns.citations"), key: "citationCount", dataIndex: "citation_count", align: "center" as const, width: "20%" },
|
|
|
|
|
{ title: t("tracking.columns.share"), key: "citationRate", dataIndex: "citation_rate", align: "center" as const, width: "20%" },
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
const contentCitationColumns = computed(() => [
|
|
|
|
|
{ title: t("tracking.columns.contentName"), key: "contentName", dataIndex: "contentName", width: "40%" },
|
|
|
|
|
{ title: t("tracking.columns.channelName"), key: "channelName", dataIndex: "channelName", width: "20%", align: "center" as const },
|
|
|
|
|
{ title: t("tracking.columns.citations"), key: "citationCount", dataIndex: "citationCount", align: "center" as const, width: "20%" },
|
|
|
|
|
{ title: t("tracking.columns.share"), key: "citationRate", dataIndex: "citationRate", align: "center" as const, width: "20%" },
|
|
|
|
|
]);
|
2026-04-12 09:56:18 +08:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<section class="tracking-question-page">
|
|
|
|
|
<div class="tracking-question-page__hero">
|
|
|
|
|
<div class="tracking-question-page__header-top">
|
|
|
|
|
<button type="button" class="tracking-question-page__back" @click="goBack">
|
|
|
|
|
<ArrowLeftOutlined />
|
|
|
|
|
<span>{{ t("tracking.backToQuestions") }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tracking-question-page__copy">
|
|
|
|
|
<p class="tracking-question-page__eyebrow">{{ t("tracking.questionDetailEyebrow") }}</p>
|
|
|
|
|
<h1>{{ pageTitle }}</h1>
|
|
|
|
|
<p class="tracking-question-page__meta">
|
|
|
|
|
{{ t("tracking.questionDetailCollectedAt", { time: collectedAtDisplay }) }}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<a-alert
|
|
|
|
|
v-if="detailQuery.error.value"
|
|
|
|
|
type="error"
|
|
|
|
|
show-icon
|
|
|
|
|
:message="formatError(detailQuery.error.value)"
|
|
|
|
|
class="tracking-question-page__alert"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<a-spin :spinning="detailQuery.isLoading.value">
|
|
|
|
|
<div class="tracking-question-page__content">
|
|
|
|
|
<div class="tracking-question-page__tabs-wrapper">
|
|
|
|
|
<a-tabs
|
|
|
|
|
v-model:activeKey="activePlatformId"
|
|
|
|
|
size="large"
|
|
|
|
|
class="tracking-question-tabs"
|
|
|
|
|
>
|
|
|
|
|
<a-tab-pane
|
2026-04-23 09:11:53 +08:00
|
|
|
v-for="platform in detailPlatforms"
|
2026-04-12 09:56:18 +08:00
|
|
|
:key="platform.ai_platform_id"
|
|
|
|
|
>
|
|
|
|
|
<template #tab>
|
|
|
|
|
<span :class="platform.sample_status !== 'sampled' ? 'is-muted' : ''">
|
|
|
|
|
{{ platform.platform_name }}
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</a-tab-pane>
|
|
|
|
|
</a-tabs>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div 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">
|
|
|
|
|
<span class="tracking-question-card__accent" />
|
|
|
|
|
<span>{{ t("tracking.answerContent") }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tracking-question-card__body">
|
|
|
|
|
<div class="tracking-question-answer__headline">
|
|
|
|
|
<h2>{{ activePlatform?.platform_name ?? t("common.noData") }}</h2>
|
|
|
|
|
<a-tag :color="statusTagColor(activePlatform?.sample_status ?? 'pending')">
|
|
|
|
|
{{ statusLabel(activePlatform?.sample_status ?? 'pending') }}
|
|
|
|
|
</a-tag>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tracking-question-answer__content custom-scrollbar">
|
|
|
|
|
<MarkdownPreview
|
2026-04-23 09:11:53 +08:00
|
|
|
v-if="renderedAnswerContent"
|
|
|
|
|
:content="renderedAnswerContent"
|
|
|
|
|
@click="handleAnswerContentClick"
|
2026-04-12 09:56:18 +08:00
|
|
|
/>
|
|
|
|
|
<a-empty v-else :description="t('tracking.noAnswer')" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tracking-question-answer__meta">
|
|
|
|
|
<span>{{ activePlatform?.sampled_at ? formatDateTime(activePlatform.sampled_at) : "--" }}</span>
|
|
|
|
|
<span>{{ activePlatform?.provider_model ?? "--" }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<section class="tracking-question-card">
|
|
|
|
|
<div class="tracking-question-card__header">
|
|
|
|
|
<div class="tracking-question-card__section-title">
|
|
|
|
|
<span class="tracking-question-card__accent" />
|
|
|
|
|
<span>{{ t("tracking.citationSourcesTitle") }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
<div ref="citationSourcesScrollRef" class="tracking-question-card__scroll-area custom-scrollbar">
|
2026-04-12 09:56:18 +08:00
|
|
|
<div v-if="activePlatform?.citations?.length" class="tracking-question-source-list">
|
|
|
|
|
<a
|
2026-04-23 09:11:53 +08:00
|
|
|
v-for="(citation, index) in activePlatform.citations"
|
2026-04-12 09:56:18 +08:00
|
|
|
:key="citation.cited_url"
|
|
|
|
|
:href="citation.cited_url"
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
class="tracking-question-source-card"
|
2026-04-23 09:11:53 +08:00
|
|
|
:class="{ 'is-linked': highlightedCitationIndex === index + 1 }"
|
|
|
|
|
:data-citation-index="index + 1"
|
2026-04-12 09:56:18 +08:00
|
|
|
>
|
|
|
|
|
<div class="tracking-question-source-card__headline">
|
2026-04-23 09:11:53 +08:00
|
|
|
<span class="tracking-question-source-card__index">[{{ index + 1 }}]</span>
|
2026-04-12 09:56:18 +08:00
|
|
|
<strong>{{ citation.site_name }}</strong>
|
|
|
|
|
<span class="tracking-question-source-card__divider" v-if="resolveCitationTitle(citation)">·</span>
|
|
|
|
|
<span class="tracking-question-source-card__title" :title="resolveCitationTitle(citation)">{{ resolveCitationTitle(citation) }}</span>
|
|
|
|
|
<a-tag v-if="citation.article_id" color="blue" class="tracking-question-source-card__badge">{{ t("tracking.contentCitationBadge") }}</a-tag>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="tracking-question-source-card__link">
|
|
|
|
|
{{ citation.cited_url }}
|
|
|
|
|
</div>
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
<a-empty v-else :description="t('tracking.noCitations')" />
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
<!-- Citation Analysis -->
|
2026-04-22 00:24:21 +08:00
|
|
|
<section
|
|
|
|
|
:class="[
|
|
|
|
|
'tracking-question-card',
|
|
|
|
|
!shouldShowInlineContentCitations ? 'tracking-question-card--span-2' : '',
|
|
|
|
|
]"
|
|
|
|
|
>
|
2026-04-12 09:56:18 +08:00
|
|
|
<div class="tracking-question-card__header">
|
|
|
|
|
<div class="tracking-question-card__section-title">
|
|
|
|
|
<span class="tracking-question-card__accent" />
|
|
|
|
|
<span>{{ t("tracking.citationAnalysisTitle") }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
<div class="tracking-question-card__scroll-area custom-scrollbar" style="margin: 0; padding: 0;">
|
|
|
|
|
<a-table
|
|
|
|
|
v-if="activeCitationAnalysis.length"
|
|
|
|
|
row-key="site_key"
|
|
|
|
|
class="modern-table"
|
|
|
|
|
:columns="citationAnalysisColumns"
|
|
|
|
|
:data-source="activeCitationAnalysis"
|
|
|
|
|
:pagination="false"
|
|
|
|
|
size="middle"
|
|
|
|
|
>
|
|
|
|
|
<template #bodyCell="{ column, record }">
|
|
|
|
|
<template v-if="column.key === 'sourcePlatform'">
|
|
|
|
|
<div class="cell-primary-stack">
|
|
|
|
|
<strong class="table-text" :title="record.site_name">{{ record.site_name }}</strong>
|
|
|
|
|
<small v-if="record.content_citation_count > 0" class="meta-subtext">
|
|
|
|
|
{{ t("tracking.contentCitationMeta", { count: record.content_citation_count }) }}
|
|
|
|
|
</small>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="column.key === 'domain'">
|
|
|
|
|
<span class="tracking-question-table__secondary">{{ record.site_domain || record.site_key }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="column.key === 'citationCount'">
|
|
|
|
|
<span class="metric-value">{{ record.citation_count }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="column.key === 'citationRate'">
|
|
|
|
|
<strong class="metric-highlight">{{ formatPercent(record.citation_rate) }}</strong>
|
|
|
|
|
</template>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table>
|
|
|
|
|
<a-empty v-else :description="t('tracking.noCitationAnalysis')" style="margin-top: 40px;" />
|
2026-04-12 09:56:18 +08:00
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
<!-- Content Citations -->
|
2026-04-22 00:24:21 +08:00
|
|
|
<section v-if="shouldShowInlineContentCitations" class="tracking-question-card">
|
2026-04-12 09:56:18 +08:00
|
|
|
<div class="tracking-question-card__header">
|
|
|
|
|
<div class="tracking-question-card__section-title">
|
|
|
|
|
<span class="tracking-question-card__accent" />
|
|
|
|
|
<span>{{ t("tracking.contentCitationsTitle") }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
<div class="tracking-question-card__scroll-area custom-scrollbar" style="margin: 0; padding: 0;">
|
|
|
|
|
<a-table
|
|
|
|
|
row-key="key"
|
|
|
|
|
class="modern-table"
|
|
|
|
|
:columns="contentCitationColumns"
|
|
|
|
|
:data-source="activeInlineContentCitations"
|
|
|
|
|
:pagination="false"
|
|
|
|
|
size="middle"
|
|
|
|
|
>
|
|
|
|
|
<template #bodyCell="{ column, record }">
|
|
|
|
|
<template v-if="column.key === 'contentName'">
|
|
|
|
|
<div class="cell-primary-stack">
|
|
|
|
|
<a
|
|
|
|
|
v-if="record.citedURL"
|
|
|
|
|
:href="record.citedURL"
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
:title="record.contentName"
|
|
|
|
|
class="table-link"
|
|
|
|
|
>
|
|
|
|
|
{{ record.contentName }}
|
|
|
|
|
</a>
|
|
|
|
|
<strong v-else class="table-text" :title="record.contentName">{{ record.contentName }}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="column.key === 'channelName'">
|
|
|
|
|
<span class="tracking-question-table__secondary" :title="record.channelName">{{ record.channelName }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="column.key === 'citationCount'">
|
|
|
|
|
<span class="metric-value">{{ record.citationCount }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="column.key === 'citationRate'">
|
|
|
|
|
<strong class="metric-highlight">{{ formatPercent(record.citationRate) }}</strong>
|
|
|
|
|
</template>
|
|
|
|
|
</template>
|
|
|
|
|
</a-table>
|
2026-04-12 09:56:18 +08:00
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a-spin>
|
|
|
|
|
</section>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.tracking-question-page {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__content {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__hero {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
padding: 24px 32px;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
border: 1px solid #e8edf5;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.02);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__header-top {
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__back {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
height: 32px;
|
|
|
|
|
padding: 0 12px 0 8px;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
background: transparent;
|
|
|
|
|
color: #64748b;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
margin-left: -8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__back:hover {
|
|
|
|
|
background: #e2e8f0;
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__copy {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__eyebrow {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
letter-spacing: 0.1em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__copy h1 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
font-size: 32px;
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
line-height: 1.25;
|
|
|
|
|
letter-spacing: -0.02em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__meta {
|
|
|
|
|
margin: 8px 0 0;
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
color: #475569;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
width: fit-content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__alert {
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__tabs-wrapper {
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
padding: 4px 24px 0;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
border: 1px solid #e8edf5;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.02);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-tabs :deep(.ant-tabs-nav) {
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-tabs .is-muted {
|
|
|
|
|
opacity: 0.65;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
|
|
|
|
|
grid-auto-rows: 800px;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
padding: 24px 28px;
|
|
|
|
|
border: 1px solid #e8edf5;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.03);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card--answer {
|
|
|
|
|
/* removed min-height, grid-auto-rows handles this universally */
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 00:24:21 +08:00
|
|
|
.tracking-question-card--span-2 {
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 09:56:18 +08:00
|
|
|
.tracking-question-card__header {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__header--split {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__section-title {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__accent {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
width: 6px;
|
|
|
|
|
height: 28px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: #2563eb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__hint {
|
|
|
|
|
margin: 0;
|
|
|
|
|
max-width: 420px;
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
text-align: right;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__body {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__scroll-area {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
margin-right: -12px;
|
|
|
|
|
padding-right: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-answer__headline {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-answer__content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
margin-right: -12px;
|
|
|
|
|
padding-right: 12px;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar {
|
|
|
|
|
width: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-track {
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
|
|
|
background: #e2e8f0;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
background: #cbd5e1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-answer__headline h2 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-answer__meta {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
gap: 18px;
|
|
|
|
|
margin-top: auto;
|
|
|
|
|
padding-top: 18px;
|
|
|
|
|
color: #9ca3af;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-list,
|
|
|
|
|
.tracking-question-content-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-card,
|
|
|
|
|
.tracking-question-content-card {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
border: 1px solid #edf2f7;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
background: #f8fbff;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-card:hover,
|
|
|
|
|
.tracking-question-content-card:hover {
|
|
|
|
|
border-color: #dbeafe;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.05);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.tracking-question-source-card.is-linked {
|
|
|
|
|
border-color: #93c5fd;
|
|
|
|
|
background: #eef6ff;
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 09:56:18 +08:00
|
|
|
.tracking-question-source-card__headline {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.tracking-question-source-card__index {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
min-width: 34px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: #e8f1ff;
|
|
|
|
|
color: #1d4ed8;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 09:56:18 +08:00
|
|
|
.tracking-question-source-card__headline strong {
|
|
|
|
|
display: block;
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-card__divider {
|
|
|
|
|
color: #cbd5e1;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-card__title {
|
|
|
|
|
color: #475569;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
flex: 1;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-card__badge {
|
|
|
|
|
margin: 0;
|
|
|
|
|
margin-left: auto;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-card__link {
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-content-card__copy a {
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.tracking-question-answer__content :deep(.tracking-inline-citation) {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
min-width: 28px;
|
|
|
|
|
height: 22px;
|
|
|
|
|
margin-left: 6px;
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
border: 1px solid #bfdbfe;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background: #eff6ff;
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
line-height: 1;
|
|
|
|
|
vertical-align: baseline;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.18s ease;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.tracking-question-answer__content :deep(.tracking-inline-citation:hover) {
|
|
|
|
|
border-color: #93c5fd;
|
|
|
|
|
background: #dbeafe;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.tracking-question-answer__content :deep(.tracking-inline-citation--muted) {
|
|
|
|
|
cursor: default;
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
border-color: #e2e8f0;
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Modern Enterprise Table Styles */
|
|
|
|
|
:deep(.modern-table) {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.modern-table .ant-table-thead > tr > th) {
|
|
|
|
|
background-color: transparent !important;
|
|
|
|
|
color: #64748b;
|
|
|
|
|
font-weight: 600;
|
2026-04-12 09:56:18 +08:00
|
|
|
font-size: 13px;
|
2026-04-23 09:11:53 +08:00
|
|
|
border-bottom: 1px solid #e2e8f0;
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.modern-table .ant-table-tbody > tr > td) {
|
|
|
|
|
padding: 16px 24px;
|
|
|
|
|
border-bottom: 1px solid #f1f5f9;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
transition: background-color 0.2s ease;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
:deep(.modern-table .ant-table-tbody > tr:last-child > td) {
|
|
|
|
|
border-bottom: none;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
:deep(.modern-table .ant-table-tbody > tr:hover > td) {
|
|
|
|
|
background-color: #f8fafc !important;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.cell-primary-stack {
|
2026-04-12 09:56:18 +08:00
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2026-04-23 09:11:53 +08:00
|
|
|
gap: 4px;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.table-link {
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
font-size: 14px;
|
2026-04-12 09:56:18 +08:00
|
|
|
font-weight: 600;
|
2026-04-23 09:11:53 +08:00
|
|
|
text-decoration: none;
|
|
|
|
|
transition: color 0.2s ease;
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
line-clamp: 2;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.table-link:hover {
|
|
|
|
|
color: #1d4ed8;
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-text {
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
line-clamp: 2;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-table__secondary {
|
2026-04-23 09:11:53 +08:00
|
|
|
color: #64748b;
|
|
|
|
|
font-size: 13px;
|
2026-04-12 09:56:18 +08:00
|
|
|
word-break: break-all;
|
2026-04-23 09:11:53 +08:00
|
|
|
display: -webkit-box;
|
|
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
line-clamp: 2;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.meta-subtext {
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
font-size: 12px;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.metric-value {
|
|
|
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
|
|
|
color: #475569;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.metric-highlight {
|
|
|
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
|
|
|
color: #0ea5e9;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
background: #f0f9ff;
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
border: 1px solid #e0f2fe;
|
2026-04-12 09:56:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card--debug {
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-card {
|
|
|
|
|
padding: 20px 22px;
|
|
|
|
|
border: 1px solid #edf2f7;
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
background: linear-gradient(180deg, #fbfdff 0%, #f5f9ff 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-card__header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-card__headline {
|
|
|
|
|
display: flex;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-card__headline strong {
|
|
|
|
|
color: #111827;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-card__headline small {
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-card__tags {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
|
|
gap: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-grid__item {
|
|
|
|
|
display: flex;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-grid__item span {
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-grid__item strong {
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
word-break: break-all;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-notes {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
gap: 14px;
|
|
|
|
|
margin-top: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-notes__item {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-notes__item span {
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-notes__item strong {
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
word-break: break-word;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-notes__item.is-error {
|
|
|
|
|
background: #fff7f7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-notes__item.is-error strong {
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1080px) {
|
|
|
|
|
.tracking-question-grid {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-grid,
|
|
|
|
|
.tracking-task-debug-notes {
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 720px) {
|
|
|
|
|
.tracking-question-page__hero {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-page__copy h1 {
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card {
|
|
|
|
|
padding: 22px 18px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__header--split,
|
|
|
|
|
.tracking-task-debug-card__header {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-card__hint,
|
|
|
|
|
.tracking-task-debug-card__tags {
|
|
|
|
|
max-width: none;
|
|
|
|
|
text-align: left;
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-table__head,
|
|
|
|
|
.tracking-question-table__row {
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-question-source-card,
|
|
|
|
|
.tracking-question-content-card {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-23 09:11:53 +08:00
|
|
|
.tracking-question-source-card__meta {
|
2026-04-12 09:56:18 +08:00
|
|
|
align-items: flex-start;
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tracking-task-debug-grid,
|
|
|
|
|
.tracking-task-debug-notes {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|