diff --git a/apps/admin-web/src/views/TrackingView.vue b/apps/admin-web/src/views/TrackingView.vue index ac1f069..20afc02 100644 --- a/apps/admin-web/src/views/TrackingView.vue +++ b/apps/admin-web/src/views/TrackingView.vue @@ -13,7 +13,7 @@ import type { MonitoringPlatformAuthorizationStatus, } from "@geo/shared-types"; import { aiPlatformCatalog } from "@geo/shared-types"; -import { computed, watch } from "vue"; +import { computed, ref, watch } from "vue"; import { useI18n } from "vue-i18n"; import { useRoute, useRouter } from "vue-router"; @@ -504,25 +504,72 @@ const visibleCitedArticles = computed(() => { return citationSummaryQuery.data.value?.cited_articles ?? []; }); -const citationChartStyle = computed(() => { +const citationPieSlices = computed(() => { const segments = citationRankingRows.value.filter((item) => (item.saas_source_count ?? item.cited_answer_count ?? 0) > 0); if (!segments.length) { - return { - background: "linear-gradient(180deg, #eef4ff 0%, #e5ecff 100%)", - }; + return []; } - let offset = 0; - const gradientStops = segments.map((item) => { - const next = offset + item.share * 360; - const stop = `${item.color} ${offset.toFixed(2)}deg ${next.toFixed(2)}deg`; - offset = next; - return stop; - }); + const cx = 100; + const cy = 100; + const r = 100; + let offset = 0; + + return segments.map((item) => { + const share = item.share; + + if (share === 1) { + return { + ...item, + isFull: true, + path: "", + }; + } + + const startAngle = offset * 2 * Math.PI - Math.PI / 2; + const endAngle = (offset + share) * 2 * Math.PI - Math.PI / 2; + + const x1 = cx + r * Math.cos(startAngle); + const y1 = cy + r * Math.sin(startAngle); + const x2 = cx + r * Math.cos(endAngle); + const y2 = cy + r * Math.sin(endAngle); + + const largeArcFlag = share > 0.5 ? 1 : 0; + const path = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${largeArcFlag} 1 ${x2} ${y2} Z`; + + offset += share; + + return { + ...item, + isFull: false, + path, + }; + }); +}); + +const hoveredSlice = ref(null); +const tooltipPos = ref({ x: 0, y: 0 }); + +function onPieMouseMove(e: MouseEvent) { + const target = e.currentTarget as HTMLElement; + if (!target) return; + const rect = target.getBoundingClientRect(); + tooltipPos.value = { + x: e.clientX - rect.left + 15, + y: e.clientY - rect.top + 15 + }; +} + +const hoveredSliceInfo = computed(() => { + if (!hoveredSlice.value) return null; + return citationPieSlices.value.find(s => s.ai_platform_id === hoveredSlice.value) ?? null; +}); + +const citationChartStyle = computed(() => { return { - background: `conic-gradient(${gradientStops.join(", ")})`, + background: "linear-gradient(180deg, #eef4ff 0%, #e5ecff 100%)", }; }); @@ -927,8 +974,38 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean {
-
-
+
+ + + +
+ +
+
+ {{ hoveredSliceInfo.platform_name }} + {{ formatPercent(hoveredSliceInfo.share) }} +
@@ -1399,12 +1476,74 @@ function disableTrackingDate(current: dayjs.Dayjs): boolean { } .citation-ranking-visual__canvas { + position: relative; display: flex; align-items: center; justify-content: center; width: 100%; } +.citation-pie-svg { + width: min(32vw, 420px); + height: min(32vw, 420px); + min-width: 280px; + min-height: 280px; + border-radius: 999px; + overflow: visible; + filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.05)); +} + +.citation-pie-slice { + transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s; + transform-origin: 100px 100px; + cursor: pointer; + stroke: #ffffff; + stroke-width: 1.5px; + stroke-linejoin: round; +} + +.citation-pie-slice.is-hovered { + transform: scale(1.05); + z-index: 2; +} + +.citation-pie-slice.is-dimmed { + opacity: 0.4; +} + +.citation-pie-tooltip { + position: absolute; + pointer-events: none; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(4px); + border: 1px solid #e5e7eb; + padding: 8px 12px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + gap: 8px; + z-index: 10; + font-size: 13px; + white-space: nowrap; + transition: opacity 0.2s; +} + +.citation-pie-tooltip__marker { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.citation-pie-tooltip__label { + color: #4b5563; +} + +.citation-pie-tooltip__value { + color: #111827; + font-weight: 600; +} + .citation-ranking-visual__circle { width: min(32vw, 420px); height: min(32vw, 420px);