diff --git a/cwd-admin/src/api/admin.ts b/cwd-admin/src/api/admin.ts index 72ec767..ab15ce0 100644 --- a/cwd-admin/src/api/admin.ts +++ b/cwd-admin/src/api/admin.ts @@ -111,6 +111,17 @@ export type DomainListResponse = { domains: string[]; }; +export type LikeStatsItem = { + pageSlug: string; + pageTitle: string | null; + pageUrl: string | null; + likes: number; +}; + +export type LikeStatsResponse = { + items: LikeStatsItem[]; +}; + export async function loginAdmin(name: string, password: string): Promise { const res = await post('/admin/login', { name, password }); const key = res.data.key; @@ -273,3 +284,7 @@ export function fetchVisitPages(domain?: string, order?: 'pv' | 'latest'): Promi export function fetchDomainList(): Promise { return get('/admin/stats/domains'); } + +export function fetchLikeStats(): Promise { + return get('/admin/likes/stats'); +} diff --git a/cwd-admin/src/views/AnalyticsVisitView.vue b/cwd-admin/src/views/AnalyticsVisitView.vue index 20e8d86..af60b5c 100644 --- a/cwd-admin/src/views/AnalyticsVisitView.vue +++ b/cwd-admin/src/views/AnalyticsVisitView.vue @@ -95,12 +95,55 @@ + + + + + +
+

点赞页面排行榜

+
加载中...
+
{{ error }}
+
暂无点赞数据
+
+
+
+
排名
+
页面标题
+ +
页面地址
+
+
+
+ {{ index + 1 }} +
+
+ {{ item.pageTitle || item.pageSlug }} +
+ + @@ -120,6 +163,8 @@ import { fetchVisitPages, type VisitOverviewResponse, type VisitPageItem, + fetchLikeStats, + type LikeStatsItem, } from "../api/admin"; const loading = ref(false); @@ -133,6 +178,7 @@ const overview = ref({ monthPv: 0, last30Days: [], }); + const items = ref([]); const visitTab = ref<"pv" | "latest">("pv"); const visitTabStorageKey = "cwd-analytics-visit-tab"; @@ -140,6 +186,7 @@ const visitTabStorageKey = "cwd-analytics-visit-tab"; const injectedDomainFilter = inject | null>("domainFilter", null); const domainFilter = injectedDomainFilter ?? ref(""); const last30Days = ref<{ date: string; total: number }[]>([]); +const likeStatsItems = ref([]); const toastMessage = ref(""); const toastType = ref<"success" | "error">("success"); @@ -213,6 +260,20 @@ function getVisitOrderParam(): "pv" | "latest" | undefined { return undefined; } +function filterLikeStatsByDomain(list: LikeStatsItem[], domain: string | undefined): LikeStatsItem[] { + if (!domain) { + return list; + } + return list.filter((item) => { + const source = item.pageUrl || item.pageSlug; + const d = extractDomain(source); + if (!d) { + return false; + } + return d === domain; + }); +} + function loadVisitTabFromStorage() { if (typeof window === "undefined") { return; @@ -243,9 +304,10 @@ async function loadData() { try { const domain = domainFilter.value || undefined; const order = getVisitOrderParam(); - const [overviewRes, pagesRes] = await Promise.all([ + const [overviewRes, pagesRes, likeStatsRes] = await Promise.all([ fetchVisitOverview(domain), fetchVisitPages(domain, order), + fetchLikeStats(), ]); overview.value = { totalPv: overviewRes.totalPv, @@ -257,7 +319,10 @@ async function loadData() { ? overviewRes.last30Days : [], }; - items.value = pagesRes.items || []; + const likeItemsRaw = Array.isArray(likeStatsRes.items) ? likeStatsRes.items : []; + likeStatsItems.value = filterLikeStatsByDomain(likeItemsRaw, domain); + const pageItems = Array.isArray(pagesRes.items) ? pagesRes.items : []; + items.value = pageItems; last30Days.value = Array.isArray(overviewRes.last30Days) ? overviewRes.last30Days : []; @@ -282,7 +347,8 @@ async function loadVisitPagesOnly() { const domain = domainFilter.value || undefined; const order = getVisitOrderParam(); const pagesRes = await fetchVisitPages(domain, order); - items.value = pagesRes.items || []; + const pageItems = Array.isArray(pagesRes.items) ? pagesRes.items : []; + items.value = pageItems; } catch (e: any) { const msg = e.message || "加载访问统计数据失败"; error.value = msg; @@ -514,6 +580,21 @@ watch(domainFilter, () => { text-align: center; } +.domain-cell-rank { + flex: 0 0 60px; + text-align: center; +} + +.domain-cell-like { + flex: 0 0 80px; + text-align: center; +} + +.domain-cell-like-rate { + flex: 0 0 90px; + text-align: center; +} + .domain-cell-time { flex: 0 0 170px; } @@ -539,6 +620,18 @@ watch(domainFilter, () => { width: 100px; } + .domain-cell-rank { + width: 60px; + } + + .domain-cell-like { + width: 80px; + } + + .domain-cell-like-rate { + width: 90px; + } + .domain-cell-time { width: 150px; } diff --git a/docs/widget/src/core/CWDComments.js b/docs/widget/src/core/CWDComments.js index 2b05637..2946549 100644 --- a/docs/widget/src/core/CWDComments.js +++ b/docs/widget/src/core/CWDComments.js @@ -23,7 +23,7 @@ export class CWDComments { * 以下字段由组件自动推导或从后端读取,无需通过 config 传入: * - postSlug:window.location.origin + window.location.pathname * - postTitle:document.title 或 postSlug - * - postUrl:window.location.href + * - postUrl:window.location.origin + window.location.pathname * - avatarPrefix/adminEmail/adminBadge:通过 /api/config/comments 接口获取 */ constructor(config) { @@ -35,7 +35,7 @@ export class CWDComments { this.config.postTitle = document.title || this.config.postSlug; } if (typeof window !== 'undefined') { - this.config.postUrl = window.location.href; + this.config.postUrl = window.location.origin + window.location.pathname; } this.hostElement = this._resolveElement(config.el); this.shadowRoot = null; @@ -522,7 +522,7 @@ export class CWDComments { this.config.postTitle = document.title || this.config.postSlug; } if (typeof window !== 'undefined') { - this.config.postUrl = window.location.href; + this.config.postUrl = window.location.origin + window.location.pathname; } // 更新主题