初始版本

This commit is contained in:
2026-04-23 16:30:57 +08:00
commit 0d0683a6e6
538 changed files with 113042 additions and 0 deletions

View File

@@ -0,0 +1,979 @@
<template>
<div class="dev-page">
<!-- 顶部横幅 -->
<div class="analytics-hero">
<div class="analytics-hero-content">
<h1 class="analytics-hero-title">📊 数据统计</h1>
<p class="analytics-hero-desc">实时监控应用安装量用户活跃收入趋势等核心指标</p>
</div>
</div>
<div class="analytics-body">
<!-- 时间筛选 -->
<div class="filter-bar">
<a-segmented
v-model:value="dateRange"
:options="dateRangeOptions"
@change="onDateRangeChange"
/>
<a-space class="ml-auto">
<a-button :loading="loading" @click="refresh">刷新</a-button>
<a-button @click="exportReport">导出报告</a-button>
</a-space>
</div>
<!-- 核心指标卡片 -->
<a-row :gutter="[16, 16]" class="mb-6">
<a-col :xs="12" :md="6" v-for="metric in coreMetrics" :key="metric.label">
<div class="metric-card" :class="metric.color">
<div class="metric-header">
<span class="metric-icon">{{ metric.icon }}</span>
<a-tooltip :title="metric.tooltip">
<span class="metric-trend" :class="metric.trendDir">
{{ metric.trendDir === 'up' ? '↑' : metric.trendDir === 'down' ? '↓' : '-' }}
{{ metric.trend }}
</span>
</a-tooltip>
</div>
<div class="metric-value">{{ metric.value }}</div>
<div class="metric-label">{{ metric.label }}</div>
</div>
</a-col>
</a-row>
<a-row :gutter="[16, 16]">
<!-- 安装量趋势 -->
<a-col :xs="24" :lg="16">
<div class="panel">
<div class="panel-header">
<span class="panel-title">📈 安装量趋势</span>
<a-radio-group v-model:value="installChartType" size="small">
<a-radio-button value="line">折线图</a-radio-button>
<a-radio-button value="bar">柱状图</a-radio-button>
</a-radio-group>
</div>
<div class="chart-area">
<div class="chart-placeholder">
<div class="chart-bar-group">
<div
v-for="(bar, i) in installData"
:key="i"
class="chart-bar-col"
>
<div
class="chart-bar"
:style="{ height: bar.percent + '%' }"
:class="{ active: bar.highlight }"
/>
<span class="chart-bar-label">{{ bar.label }}</span>
</div>
</div>
<div class="chart-legend">
<span class="legend-dot green" /> 新安装
<span class="legend-dot blue ml-4" /> 累计安装
</div>
</div>
</div>
</div>
</a-col>
<!-- 收入概览 -->
<a-col :xs="24" :lg="8">
<div class="panel">
<div class="panel-header">
<span class="panel-title">💰 收入概览</span>
<a-tag color="green">本月</a-tag>
</div>
<div class="revenue-content">
<div class="revenue-total">
<div class="revenue-amount">¥{{ revenueData.total }}</div>
<div class="revenue-label">本月总收入</div>
</div>
<a-divider style="margin: 16px 0" />
<div class="revenue-items">
<div v-for="item in revenueData.items" :key="item.label" class="revenue-item">
<div class="revenue-item-left">
<span class="revenue-item-icon">{{ item.icon }}</span>
<span class="revenue-item-label">{{ item.label }}</span>
</div>
<div class="revenue-item-value">
¥{{ item.value }}
<span class="revenue-item-trend" :class="item.trendDir">
{{ item.trend }}
</span>
</div>
</div>
</div>
</div>
</div>
</a-col>
</a-row>
<a-row :gutter="[16, 16]" class="mt-4">
<!-- 应用排行 -->
<a-col :xs="24" :lg="12">
<div class="panel">
<div class="panel-header">
<span class="panel-title">🏆 应用排行</span>
<a-select
v-model:value="rankType"
size="small"
style="min-width: 120px"
@change="refresh"
>
<a-select-option value="installs">按安装量</a-select-option>
<a-select-option value="revenue">按收入</a-select-option>
<a-select-option value="rating">按评分</a-select-option>
</a-select>
</div>
<div class="rank-list">
<div v-for="(app, index) in topApps" :key="app.name" class="rank-item">
<div class="rank-pos" :class="{ top3: index < 3 }">{{ index + 1 }}</div>
<div class="rank-info">
<div class="rank-icon">{{ app.icon }}</div>
<div class="rank-detail">
<div class="rank-name">{{ app.name }}</div>
<div class="rank-desc">{{ app.desc }}</div>
</div>
</div>
<div class="rank-value">{{ app.value }}</div>
</div>
</div>
</div>
</a-col>
<!-- 用户活跃分布 -->
<a-col :xs="24" :lg="12">
<div class="panel">
<div class="panel-header">
<span class="panel-title">👥 用户活跃</span>
</div>
<div class="activity-content">
<!-- 活跃度概要 -->
<div class="activity-summary">
<div class="activity-item">
<div class="activity-value blue">{{ activityData.dau }}</div>
<div class="activity-label">日活跃 (DAU)</div>
</div>
<div class="activity-item">
<div class="activity-value green">{{ activityData.wau }}</div>
<div class="activity-label">周活跃 (WAU)</div>
</div>
<div class="activity-item">
<div class="activity-value purple">{{ activityData.mau }}</div>
<div class="activity-label">月活跃 (MAU)</div>
</div>
</div>
<a-divider style="margin: 12px 0" />
<!-- 活跃度分布 -->
<div class="activity-dist">
<div class="dist-title">活跃度分布</div>
<div v-for="bar in activityDist" :key="bar.label" class="dist-row">
<span class="dist-label">{{ bar.label }}</span>
<div class="dist-bar-wrap">
<div
class="dist-bar"
:style="{ width: bar.percent + '%', background: bar.color }"
/>
</div>
<span class="dist-value">{{ bar.value }}</span>
</div>
</div>
</div>
</div>
</a-col>
</a-row>
<!-- API 调用统计 -->
<div class="panel mt-4">
<div class="panel-header">
<span class="panel-title"> API 调用统计</span>
<a-space>
<a-tag color="blue">近7天</a-tag>
<a-button size="small" type="link">查看详情</a-button>
</a-space>
</div>
<div class="api-stats">
<a-row :gutter="[16, 16]">
<a-col :xs="24" :sm="8" v-for="apiStat in apiStats" :key="apiStat.label">
<div class="api-stat-card">
<div class="api-stat-header">
<span class="api-stat-label">{{ apiStat.label }}</span>
<a-tag :color="apiStat.tagColor" size="small">{{ apiStat.status }}</a-tag>
</div>
<div class="api-stat-value">{{ apiStat.value }}</div>
<div class="api-stat-bar-wrap">
<div class="api-stat-bar" :style="{ width: apiStat.usage + '%', background: apiStat.barColor }" />
</div>
<div class="api-stat-footer">
<span>已用 {{ apiStat.usage }}%</span>
<span>限额 {{ apiStat.limit }}</span>
</div>
</div>
</a-col>
</a-row>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
// TODO: 后端接口就绪后解除注释
// import { getAppAnalytics, exportAnalyticsReport } from '@/api/developer'
import { message } from 'ant-design-vue'
definePageMeta({ layout: 'developer' })
useHead({ title: '数据统计 - 开发者中心' })
const loading = ref(false)
const dateRange = ref('7d')
const installChartType = ref('line')
const rankType = ref('installs')
// ========== Mock 数据(后端接口就绪后替换为真实 API 调用) ==========
const MOCK_ANALYTICS = {
totalInstalls: 128460,
activeUsers: 38920,
monthlyRevenue: 156780.50,
averageRating: 4.7,
installTrend: [
{ date: '03/28', newInstalls: 820, cumulativeInstalls: 124500 },
{ date: '03/29', newInstalls: 960, cumulativeInstalls: 125460 },
{ date: '03/30', newInstalls: 1100, cumulativeInstalls: 126560 },
{ date: '03/31', newInstalls: 750, cumulativeInstalls: 127310 },
{ date: '04/01', newInstalls: 680, cumulativeInstalls: 127990 },
{ date: '04/02', newInstalls: 920, cumulativeInstalls: 128910 },
{ date: '04/03', newInstalls: 550, cumulativeInstalls: 128460 },
] as Array<{ date: string; newInstalls: number; cumulativeInstalls: number }>,
revenueBreakdown: [
{ type: 'subscription', label: '订阅收入', value: 98400, trend: 18.5 },
{ type: 'one-time', label: '一次性购买', value: 32600, trend: 5.2 },
{ type: 'addon', label: '增值服务', value: 15800, trend: 32.1 },
{ type: 'license', label: '授权许可', value: 9980.50, trend: -3.4 },
] as Array<{ type: string; label: string; value: number; trend: number }>,
topApps: [
{ name: '企业官网建站', description: '一站式企业官网生成平台', installs: 45800, revenue: 52000, rating: 4.8 },
{ name: '电商系统', description: '全功能电商解决方案', installs: 32100, revenue: 45000, rating: 4.6 },
{ name: '小程序助手', description: '微信小程序快速开发工具', installs: 28900, revenue: 38000, rating: 4.9 },
{ name: 'AI客服机器人', description: '智能客服对话系统', installs: 15600, revenue: 21500, rating: 4.5 },
{ name: '数据分析平台', description: '业务数据可视化分析', installs: 6060, revenue: 12800, rating: 4.7 },
] as Array<{ name: string; description: string; installs: number; revenue: number; rating: number }>,
userActivity: {
dau: 5830,
wau: 18200,
mau: 38920,
activityDistribution: [
{ level: 'daily', label: '每日活跃', value: 5830, percentage: 15 },
{ level: 'weekly', label: '每周活跃', value: 12370, percentage: 32 },
{ level: 'monthly', label: '每月活跃', value: 20720, percentage: 53 },
] as Array<{ level: string; label: string; value: number; percentage: number }>,
},
apiUsage: [
{ apiName: 'app-list', label: '应用列表 API', calls: 156800, usagePercentage: 52, dailyLimit: 300000, status: '正常' as const },
{ apiName: 'user-auth', label: '用户认证 API', calls: 89200, usagePercentage: 75, dailyLimit: 120000, status: '警告' as const },
{ apiName: 'data-query', label: '数据查询 API', calls: 210500, usagePercentage: 88, dailyLimit: 240000, status: '警告' as const },
] as Array<{ apiName: string; label: string; calls: number; usagePercentage: number; dailyLimit: number; status: '正常' | '警告' | '超限' }>,
}
// API 数据状态
const analyticsData = ref({ ...MOCK_ANALYTICS })
const dateRangeOptions = [
{ label: '近7天', value: '7d' },
{ label: '近30天', value: '30d' },
{ label: '近90天', value: '90d' },
{ label: '全部', value: 'all' },
]
// 核心指标 - 使用API数据
const coreMetrics = computed(() => {
const formatNumber = (num: number) => {
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'
if (num >= 1000) return (num / 1000).toFixed(1) + 'K'
return num.toString()
}
const formatCurrency = (amount: number) => {
return '¥' + amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
return [
{
icon: '📦',
label: '总安装量',
value: formatNumber(analyticsData.value.totalInstalls),
trend: '+12.5%', trendDir: 'up', color: 'blue',
tooltip: '所有应用的累计安装次数'
},
{
icon: '👥',
label: '活跃用户',
value: formatNumber(analyticsData.value.activeUsers),
trend: '+8.3%', trendDir: 'up', color: 'purple',
tooltip: '近30天活跃用户数'
},
{
icon: '💰',
label: '本月收入',
value: formatCurrency(analyticsData.value.monthlyRevenue),
trend: '+23.1%', trendDir: 'up', color: 'green',
tooltip: '本月累计收入'
},
{
icon: '⭐',
label: '平均评分',
value: analyticsData.value.averageRating.toFixed(1),
trend: '+0.2', trendDir: 'up', color: 'orange',
tooltip: '所有应用的平均评分'
},
]
})
// 安装量趋势 - 使用API数据
const installData = computed(() => {
const trendData = analyticsData.value.installTrend
if (!trendData.length) {
return []
}
// 计算最大安装量用于百分比
const maxCumulative = Math.max(...trendData.map(item => item.cumulativeInstalls))
const maxNew = Math.max(...trendData.map(item => item.newInstalls))
const maxValue = Math.max(maxCumulative, maxNew)
return trendData.map((item, index) => {
// 使用新安装量作为图表高度
const percent = maxValue > 0 ? (item.newInstalls / maxValue) * 100 : 0
return {
label: item.date,
percent: Math.min(100, Math.max(5, percent)), // 确保在5-100%范围内
highlight: index === trendData.length - 1,
newInstalls: item.newInstalls,
cumulativeInstalls: item.cumulativeInstalls
}
})
})
// 收入数据 - 使用API数据
const revenueData = computed(() => {
const formatCurrency = (amount: number) => {
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
const getIcon = (type: string) => {
const icons: Record<string, string> = {
'subscription': '🛒',
'one-time': '一次性',
'addon': '🎁',
'license': '📄',
'support': '🆘'
}
return icons[type] || '💰'
}
const breakdown = analyticsData.value.revenueBreakdown
const total = breakdown.reduce((sum, item) => sum + item.value, 0)
return {
total: formatCurrency(total),
items: breakdown.map(item => ({
icon: getIcon(item.type),
label: item.label,
value: formatCurrency(item.value),
trend: item.trend > 0 ? `+${item.trend}%` : `${item.trend}%`,
trendDir: item.trend >= 0 ? 'up' : 'down' as 'up' | 'down'
}))
}
})
// 应用排行 - 使用API数据
const topApps = computed(() => {
const apps = analyticsData.value.topApps
const getDisplayValue = (app: typeof apps[0]) => {
if (rankType.value === 'installs') return `${app.installs}`
if (rankType.value === 'revenue') return `¥${app.revenue.toLocaleString()}`
if (rankType.value === 'rating') return app.rating.toFixed(1)
return `${app.installs}`
}
const getIcon = (name: string) => {
const icons: Record<string, string> = {
'企业官网': '🌐',
'电商': '🛒',
'小程序': '📱',
'AI': '🤖',
'数据': '📊',
'官网': '🌐',
'电商系统': '🛒',
'小程序助手': '📱',
'AI客服': '🤖',
'数据分析': '📊'
}
for (const [key, icon] of Object.entries(icons)) {
if (name.includes(key)) return icon
}
return '📦'
}
return apps.map(app => ({
icon: getIcon(app.name),
name: app.name,
desc: app.description,
value: getDisplayValue(app)
}))
})
// 用户活跃 - 使用API数据
const activityData = computed(() => ({
dau: analyticsData.value.userActivity.dau.toLocaleString(),
wau: analyticsData.value.userActivity.wau.toLocaleString(),
mau: analyticsData.value.userActivity.mau.toLocaleString(),
}))
const activityDist = computed(() => {
return analyticsData.value.userActivity.activityDistribution.map(item => ({
label: item.label,
value: `${item.percentage}%`,
percent: item.percentage,
color: getDistributionColor(item.level)
}))
})
// 根据活跃度级别获取颜色
function getDistributionColor(level: string) {
const colors: Record<string, string> = {
'high': '#4f46e5', // 高活跃
'medium': '#8b5cf6', // 中活跃
'low': '#a78bfa', // 低活跃
'inactive': '#e5e7eb', // 沉默
'daily': '#4f46e5', // 每日活跃
'weekly': '#8b5cf6', // 每周活跃
'monthly': '#a78bfa', // 每月活跃
'silent': '#e5e7eb' // 沉默用户
}
return colors[level] || '#e5e7eb'
}
// API 调用统计 - 使用API数据
const apiStats = computed(() => {
return analyticsData.value.apiUsage.map(item => {
let tagColor = 'green'
let statusText = '正常'
if (item.status === '警告') {
tagColor = 'orange'
statusText = '警告'
} else if (item.status === '超限') {
tagColor = 'red'
statusText = '超限'
}
return {
label: item.label,
value: `${item.calls.toLocaleString()}`,
usage: item.usagePercentage,
limit: `${item.dailyLimit.toLocaleString()}/日`,
status: statusText,
tagColor,
barColor: getApiBarColor(item.usagePercentage)
}
})
})
// 根据使用率获取进度条颜色
function getApiBarColor(usage: number) {
if (usage < 60) return '#22c55e' // 绿色:正常
if (usage < 85) return '#f59e0b' // 黄色:警告
return '#ef4444' // 红色:超限
}
// 加载统计数据TODO: 后端接口就绪后替换 Mock 为真实 API 调用)
// async function loadAnalyticsData() {
// loading.value = true
// try {
// const response = await getAppAnalytics({ dateRange: dateRange.value })
// if (response.data?.success) {
// analyticsData.value = response.data.data
// }
// } catch (error) {
// console.error('加载统计数据失败:', error)
// message.error('加载统计数据失败,请稍后重试')
// } finally {
// loading.value = false
// }
// }
function onDateRangeChange() {
// TODO: 后端接口就绪后调用 loadAnalyticsData()
}
async function refresh() {
loading.value = true
// TODO: 后端接口就绪后替换为 loadAnalyticsData()
setTimeout(() => {
analyticsData.value = { ...MOCK_ANALYTICS }
loading.value = false
message.success('数据已刷新')
}, 300)
}
async function exportReport() {
message.info('导出功能将在后端接口就绪后开放')
}
// 页面无需初始化加载,直接使用 Mock 数据渲染
</script>
<style scoped>
.dev-page {
min-height: 100%;
}
/* Hero 区域 */
.analytics-hero {
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 40%, #1e3a5f 100%);
padding: 28px 28px 24px;
position: relative;
overflow: hidden;
}
.analytics-hero::before {
content: '';
position: absolute;
top: -80px;
right: -80px;
width: 300px;
height: 300px;
border-radius: 50%;
background: rgba(99, 102, 241, 0.2);
filter: blur(60px);
}
.analytics-hero-content {
position: relative;
z-index: 1;
}
.analytics-hero-title {
color: #fff;
font-size: 24px;
font-weight: 700;
margin: 0 0 6px;
}
.analytics-hero-desc {
color: rgba(199, 210, 254, 0.8);
font-size: 14px;
margin: 0;
}
/* 主体内容 */
.analytics-body {
padding: 20px 24px 24px;
}
/* 筛选栏 */
.filter-bar {
display: flex;
align-items: center;
margin-bottom: 20px;
}
/* 指标卡片 */
.metric-card {
padding: 18px;
border-radius: 12px;
border: 1px solid transparent;
transition: all 0.2s;
}
.metric-card:hover { transform: translateY(-1px); }
.metric-card.blue { background: #eff6ff; border-color: #dbeafe; }
.metric-card.purple { background: #f5f3ff; border-color: #e9d5ff; }
.metric-card.green { background: #f0fdf4; border-color: #bbf7d0; }
.metric-card.orange { background: #fff7ed; border-color: #fed7aa; }
.metric-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.metric-icon { font-size: 24px; }
.metric-trend {
font-size: 12px;
font-weight: 500;
padding: 2px 6px;
border-radius: 4px;
}
.metric-trend.up { color: #16a34a; background: #dcfce7; }
.metric-trend.down { color: #dc2626; background: #fef2f2; }
.metric-value {
font-size: 24px;
font-weight: 700;
color: rgba(0, 0, 0, 0.85);
line-height: 1.2;
}
.metric-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
margin-top: 4px;
}
/* 面板通用 */
.panel {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 12px;
overflow: hidden;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
border-bottom: 1px solid #f5f5f5;
}
.panel-title {
font-size: 14px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
/* 图表区域 */
.chart-area {
padding: 20px;
}
.chart-bar-group {
display: flex;
align-items: flex-end;
gap: 12px;
height: 200px;
padding-top: 10px;
}
.chart-bar-col {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
justify-content: flex-end;
}
.chart-bar {
width: 100%;
max-width: 48px;
border-radius: 6px 6px 0 0;
background: linear-gradient(180deg, #6366f1, #4f46e5);
min-height: 8px;
transition: all 0.3s;
cursor: pointer;
}
.chart-bar:hover,
.chart-bar.active {
background: linear-gradient(180deg, #818cf8, #6366f1);
box-shadow: 0 -4px 12px rgba(99, 102, 241, 0.3);
}
.chart-bar-label {
font-size: 11px;
color: rgba(0, 0, 0, 0.4);
margin-top: 8px;
}
.chart-legend {
display: flex;
align-items: center;
gap: 4px;
margin-top: 16px;
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
justify-content: center;
}
.legend-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 2px;
margin-right: 4px;
}
.legend-dot.green { background: #22c55e; }
.legend-dot.blue { background: #4f46e5; }
/* 收入 */
.revenue-content {
padding: 20px;
}
.revenue-total { text-align: center; padding: 4px 0; }
.revenue-amount {
font-size: 32px;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
}
.revenue-label {
font-size: 13px;
color: rgba(0, 0, 0, 0.4);
margin-top: 4px;
}
.revenue-items { padding: 0; }
.revenue-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
}
.revenue-item-left {
display: flex;
align-items: center;
gap: 8px;
}
.revenue-item-icon { font-size: 18px; }
.revenue-item-label {
font-size: 13px;
color: rgba(0, 0, 0, 0.65);
}
.revenue-item-value {
font-size: 14px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
.revenue-item-trend {
font-size: 11px;
margin-left: 6px;
padding: 1px 4px;
border-radius: 3px;
}
.revenue-item-trend.up { color: #16a34a; background: #dcfce7; }
.revenue-item-trend.down { color: #dc2626; background: #fef2f2; }
/* 排行 */
.rank-list { padding: 8px 14px; }
.rank-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid #f9f9f9;
}
.rank-item:last-child { border-bottom: none; }
.rank-pos {
width: 28px;
height: 28px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 600;
color: rgba(0, 0, 0, 0.4);
background: #f5f5f5;
flex-shrink: 0;
}
.rank-pos.top3 {
background: linear-gradient(135deg, #f59e0b, #f97316);
color: #fff;
}
.rank-info {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.rank-icon { font-size: 22px; flex-shrink: 0; }
.rank-detail { min-width: 0; }
.rank-name {
font-size: 14px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rank-desc {
font-size: 12px;
color: rgba(0, 0, 0, 0.4);
margin-top: 2px;
}
.rank-value {
font-size: 14px;
font-weight: 600;
color: #4f46e5;
flex-shrink: 0;
}
/* 活跃度 */
.activity-content { padding: 20px; }
.activity-summary {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
text-align: center;
}
.activity-value {
font-size: 22px;
font-weight: 700;
line-height: 1.2;
}
.activity-value.blue { color: #3b82f6; }
.activity-value.green { color: #22c55e; }
.activity-value.purple { color: #8b5cf6; }
.activity-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.4);
margin-top: 4px;
}
.activity-dist { padding: 0; }
.dist-title {
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.65);
margin-bottom: 12px;
}
.dist-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.dist-label {
font-size: 12px;
color: rgba(0, 0, 0, 0.5);
width: 100px;
flex-shrink: 0;
text-align: right;
}
.dist-bar-wrap {
flex: 1;
height: 8px;
background: #f5f5f5;
border-radius: 4px;
overflow: hidden;
}
.dist-bar {
height: 100%;
border-radius: 4px;
transition: width 0.3s;
}
.dist-value {
font-size: 12px;
font-weight: 500;
color: rgba(0, 0, 0, 0.65);
width: 36px;
flex-shrink: 0;
}
/* API 统计 */
.api-stats { padding: 16px; }
.api-stat-card {
padding: 16px;
border-radius: 10px;
border: 1px solid #f0f0f0;
background: #fafafa;
transition: all 0.15s;
}
.api-stat-card:hover {
border-color: #e0e7ff;
background: #f5f7ff;
}
.api-stat-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.api-stat-label {
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.65);
}
.api-stat-value {
font-size: 20px;
font-weight: 700;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 10px;
}
.api-stat-bar-wrap {
height: 6px;
background: #f0f0f0;
border-radius: 3px;
overflow: hidden;
margin-bottom: 6px;
}
.api-stat-bar {
height: 100%;
border-radius: 3px;
transition: width 0.3s;
}
.api-stat-footer {
display: flex;
justify-content: space-between;
font-size: 11px;
color: rgba(0, 0, 0, 0.4);
}
@media (max-width: 768px) {
.activity-summary {
grid-template-columns: 1fr;
gap: 8px;
}
.dist-label { width: 80px; }
}
</style>