Files
tiantian-system/app/pages/developer/analytics.vue
2026-04-08 17:10:58 +08:00

980 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>