feat(analytics): 在访问统计中增加同比趋势显示
- 在访问概览 API 中新增昨日、上周、上月 PV 字段,用于计算趋势 - 在前端统计卡片中显示今日、本周、本月访问量的同比变化百分比 - 支持 163 邮箱作为邮件通知服务提供商,更新相关文档和设置界面
This commit is contained in:
@@ -88,8 +88,11 @@ export type VisitOverviewResponse = {
|
||||
totalPv: number;
|
||||
totalPages: number;
|
||||
todayPv: number;
|
||||
yesterdayPv?: number;
|
||||
weekPv: number;
|
||||
lastWeekPv?: number;
|
||||
monthPv: number;
|
||||
lastMonthPv?: number;
|
||||
last30Days?: {
|
||||
date: string;
|
||||
total: number;
|
||||
|
||||
@@ -44,6 +44,29 @@
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.trend {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
&.up {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
&.down {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.trend-arrow {
|
||||
margin-right: 2px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.domain-table-wrapper {
|
||||
@@ -191,4 +214,4 @@
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,48 @@
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-label">今日访问量</div>
|
||||
<div class="stats-value">{{ overview.todayPv }}</div>
|
||||
<div class="stats-value">
|
||||
{{ overview.todayPv }}
|
||||
<span
|
||||
v-if="overview.yesterdayPv !== undefined"
|
||||
class="trend"
|
||||
:class="percentageChange >= 0 ? 'up' : 'down'"
|
||||
:title="`对比昨日 ${percentageChange >= 0 ? '增加' : '减少'} ${Math.abs(percentageChange).toFixed(1)}%`"
|
||||
>
|
||||
<span class="trend-arrow">{{ percentageChange >= 0 ? '↑' : '↓' }}</span>
|
||||
{{ Math.abs(percentageChange).toFixed(1) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-label">本周访问量</div>
|
||||
<div class="stats-value">{{ overview.weekPv }}</div>
|
||||
<div class="stats-value">
|
||||
{{ overview.weekPv }}
|
||||
<span
|
||||
v-if="overview.lastWeekPv !== undefined"
|
||||
class="trend"
|
||||
:class="weekPercentageChange >= 0 ? 'up' : 'down'"
|
||||
:title="`对比上周 ${weekPercentageChange >= 0 ? '增加' : '减少'} ${Math.abs(weekPercentageChange).toFixed(1)}%`"
|
||||
>
|
||||
<span class="trend-arrow">{{ weekPercentageChange >= 0 ? '↑' : '↓' }}</span>
|
||||
{{ Math.abs(weekPercentageChange).toFixed(1) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-label">本月访问量</div>
|
||||
<div class="stats-value">{{ overview.monthPv }}</div>
|
||||
<div class="stats-value">
|
||||
{{ overview.monthPv }}
|
||||
<span
|
||||
v-if="overview.lastMonthPv !== undefined"
|
||||
class="trend"
|
||||
:class="monthPercentageChange >= 0 ? 'up' : 'down'"
|
||||
:title="`对比上月 ${monthPercentageChange >= 0 ? '增加' : '减少'} ${Math.abs(monthPercentageChange).toFixed(1)}%`"
|
||||
>
|
||||
<span class="trend-arrow">{{ monthPercentageChange >= 0 ? '↑' : '↓' }}</span>
|
||||
{{ Math.abs(monthPercentageChange).toFixed(1) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-item">
|
||||
<div class="stats-label">有访问记录的页面数</div>
|
||||
@@ -198,11 +231,33 @@ const overview = ref<VisitOverviewResponse>({
|
||||
totalPv: 0,
|
||||
totalPages: 0,
|
||||
todayPv: 0,
|
||||
yesterdayPv: 0,
|
||||
weekPv: 0,
|
||||
lastWeekPv: 0,
|
||||
monthPv: 0,
|
||||
lastMonthPv: 0,
|
||||
last30Days: [],
|
||||
});
|
||||
|
||||
function calculateChange(current: number, previous: number) {
|
||||
if (previous === 0) {
|
||||
return current > 0 ? 100 : 0;
|
||||
}
|
||||
return ((current - previous) / previous) * 100;
|
||||
}
|
||||
|
||||
const percentageChange = computed(() => {
|
||||
return calculateChange(overview.value.todayPv, overview.value.yesterdayPv || 0);
|
||||
});
|
||||
|
||||
const weekPercentageChange = computed(() => {
|
||||
return calculateChange(overview.value.weekPv, overview.value.lastWeekPv || 0);
|
||||
});
|
||||
|
||||
const monthPercentageChange = computed(() => {
|
||||
return calculateChange(overview.value.monthPv, overview.value.lastMonthPv || 0);
|
||||
});
|
||||
|
||||
const rawItems = ref<VisitPageItem[]>([]);
|
||||
const items = computed<VisitPageItem[]>(() => {
|
||||
const list = rawItems.value.slice();
|
||||
@@ -390,8 +445,11 @@ async function loadData() {
|
||||
totalPv: overviewRes.totalPv,
|
||||
totalPages: overviewRes.totalPages,
|
||||
todayPv: overviewRes.todayPv ?? 0,
|
||||
yesterdayPv: overviewRes.yesterdayPv ?? 0,
|
||||
weekPv: overviewRes.weekPv ?? 0,
|
||||
lastWeekPv: overviewRes.lastWeekPv ?? 0,
|
||||
monthPv: overviewRes.monthPv ?? 0,
|
||||
lastMonthPv: overviewRes.lastMonthPv ?? 0,
|
||||
last30Days: Array.isArray(overviewRes.last30Days)
|
||||
? overviewRes.last30Days
|
||||
: [],
|
||||
|
||||
@@ -305,6 +305,7 @@
|
||||
@change="onProviderChange"
|
||||
>
|
||||
<option value="qq">QQ 邮箱</option>
|
||||
<option value="163">163 邮箱</option>
|
||||
<option value="custom">自定义 SMTP</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -361,6 +362,10 @@
|
||||
请登录 QQ 邮箱网页版,在【设置 - 账户】中开启 POP3/SMTP
|
||||
服务并生成授权码。
|
||||
</div>
|
||||
<div v-else-if="smtpProvider === '163'" class="form-hint">
|
||||
注意:163 邮箱必须使用授权码,而非登录密码。<br />
|
||||
请登录 163 邮箱网页版,在【设置 - POP3/SMTP/IMAP】中开启服务并生成授权码。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
@@ -816,6 +821,10 @@ function onProviderChange() {
|
||||
smtpHost.value = "smtp.qq.com";
|
||||
smtpPort.value = 465;
|
||||
smtpSecure.value = true;
|
||||
} else if (smtpProvider.value === "163") {
|
||||
smtpHost.value = "smtp.163.com";
|
||||
smtpPort.value = 465;
|
||||
smtpSecure.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,6 +902,8 @@ async function load() {
|
||||
|
||||
if (emailNotifyRes.smtp.host === "smtp.qq.com") {
|
||||
smtpProvider.value = "qq";
|
||||
} else if (emailNotifyRes.smtp.host === "smtp.163.com") {
|
||||
smtpProvider.value = "163";
|
||||
} else {
|
||||
smtpProvider.value = "custom";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user