feat(analytics): 扩展页面访问统计接口并新增站点隔离文档

- 在 `/admin/analytics/pages` 接口响应中添加 `itemsByPv` 和 `itemsByLatest` 字段,分别返回按访问量和最新访问排序的前20条数据,优化前端数据展示逻辑
- 前端管理后台适配新的接口响应结构,提升页面访问统计页面的性能和用户体验
- 新增站点隔离配置文档 (`/docs/config/site-isolation.md`),说明如何通过 `siteId` 配置实现数据隔离
- 在前端配置指南中补充 `siteId` 配置示例
- 新增常见问题文档 (`/docs/common-problems.md`),解答设置 `siteId` 后评论数据不显示的问题及数据迁移方案
- 在导航栏和侧边栏添加“常见问题”和“配置”相关入口,完善文档结构
- 修复前端组件 API 调用中 `postSlug` 参数可能为 `postUrl` 的兼容性问题
This commit is contained in:
anghunk
2026-02-09 14:47:28 +08:00
parent 2453929455
commit 2085a4733c
11 changed files with 149 additions and 51 deletions

View File

@@ -111,6 +111,8 @@ export type VisitPageItem = {
export type VisitPagesResponse = {
items: VisitPageItem[];
itemsByPv?: VisitPageItem[];
itemsByLatest?: VisitPageItem[];
};
export type DomainListResponse = {

View File

@@ -150,12 +150,12 @@
</div>
<div class="domain-cell domain-cell-url">
<a
v-if="item.postSlug"
:href="item.postSlug"
v-if="item.postUrl"
:href="item.postUrl"
target="_blank"
rel="noreferrer"
>
{{ item.postSlug }}
{{ item.postUrl }}
</a>
<span v-else>-</span>
</div>
@@ -259,24 +259,13 @@ const monthPercentageChange = computed(() => {
return calculateChange(overview.value.monthPv, overview.value.lastMonthPv || 0);
});
const rawItems = ref<VisitPageItem[]>([]);
const itemsByPv = ref<VisitPageItem[]>([]);
const itemsByLatest = ref<VisitPageItem[]>([]);
const items = computed<VisitPageItem[]>(() => {
const list = rawItems.value.slice();
list.sort((a, b) => {
const aLast = getLastVisitAtTs(a.lastVisitAt);
const bLast = getLastVisitAtTs(b.lastVisitAt);
if (visitTab.value === "latest") {
if (bLast !== aLast) {
return bLast - aLast;
}
return b.pv - a.pv;
}
if (b.pv !== a.pv) {
return b.pv - a.pv;
}
return bLast - aLast;
});
return list;
if (visitTab.value === "latest") {
return itemsByLatest.value;
}
return itemsByPv.value;
});
const visitTab = ref<"pv" | "latest">("pv");
const visitTabStorageKey = "cwd-analytics-visit-tab";
@@ -353,11 +342,11 @@ function extractDomain(source: string | null | undefined): string | null {
}
}
function getVisitOrderParam(): "pv" | "latest" | undefined {
function getVisitOrderParam(): "pv" | "latest" {
if (visitTab.value === "latest") {
return "latest";
}
return undefined;
return "pv";
}
function filterLikeStatsByDomain(list: LikeStatsItem[], domain: string | undefined): LikeStatsItem[] {
@@ -457,8 +446,36 @@ async function loadData() {
};
const likeItemsRaw = Array.isArray(likeStatsRes.items) ? likeStatsRes.items : [];
likeStatsItems.value = filterLikeStatsByDomain(likeItemsRaw, domain);
const pageItems = Array.isArray(pagesRes.items) ? pagesRes.items : [];
rawItems.value = pageItems;
const pageItemsByPv = Array.isArray(pagesRes.itemsByPv)
? pagesRes.itemsByPv
: [];
const pageItemsByLatest = Array.isArray(pagesRes.itemsByLatest)
? pagesRes.itemsByLatest
: [];
if (pageItemsByPv.length === 0 && pageItemsByLatest.length === 0) {
const baseItems = Array.isArray(pagesRes.items) ? pagesRes.items : [];
const sortedByPv = baseItems.slice().sort((a, b) => {
if (b.pv !== a.pv) {
return b.pv - a.pv;
}
const aLast = getLastVisitAtTs(a.lastVisitAt);
const bLast = getLastVisitAtTs(b.lastVisitAt);
return bLast - aLast;
});
const sortedByLatest = baseItems.slice().sort((a, b) => {
const aLast = getLastVisitAtTs(a.lastVisitAt);
const bLast = getLastVisitAtTs(b.lastVisitAt);
if (bLast !== aLast) {
return bLast - aLast;
}
return b.pv - a.pv;
});
itemsByPv.value = sortedByPv;
itemsByLatest.value = sortedByLatest;
} else {
itemsByPv.value = pageItemsByPv;
itemsByLatest.value = pageItemsByLatest;
}
last30Days.value = Array.isArray(overviewRes.last30Days)
? overviewRes.last30Days
: [];