refactor(admin-web/ai-points): rework usage page into card layout

把页面改造成两张白底卡片:顶部卡片承载页头 + 概览四宫格,下面卡片承载用量
台账。指标格子改成圆形图标 + 文字两栏布局,排版更接近现有控制台其它视图。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 18:35:58 +08:00
parent fa905519f6
commit 9683a53403
+158 -122
View File
@@ -114,49 +114,67 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
</script>
<template>
<div class="ai-points-page">
<header class="ai-points-header">
<div>
<h1>{{ t("route.aiPoints.title") }}</h1>
<p>{{ t("route.aiPoints.description") }}</p>
</div>
<a-button :loading="refreshing" @click="refresh">
<template #icon><ReloadOutlined /></template>
{{ t("common.refresh") }}
</a-button>
</header>
<section class="ai-points-summary">
<div class="ai-points-balance">
<span class="summary-label">{{ t("aiPoints.balance") }}</span>
<strong>{{ quotaSummary?.ai_points_balance ?? "--" }}</strong>
<a-progress :percent="aiPointUsedPercent" :show-info="false" stroke-color="#1677ff" />
<div class="ai-points-view">
<section class="ai-points-view__top-card">
<div class="ai-points-view__header">
<div class="ai-points-view__header-title">
<h2>{{ t("route.aiPoints.title") }}</h2>
<p>{{ t("route.aiPoints.description") }}</p>
</div>
<div class="ai-points-view__header-actions">
<a-button :loading="refreshing" @click="refresh">
<template #icon><ReloadOutlined /></template>
{{ t("common.refresh") }}
</a-button>
</div>
</div>
<div class="summary-metric">
<ThunderboltOutlined />
<span>{{ t("common.used") }}</span>
<strong>{{ quotaSummary?.ai_points_used ?? "--" }} / {{ quotaSummary?.ai_points_total ?? "--" }}</strong>
</div>
<a-divider style="margin: 0; border-color: #f0f0f0;" />
<div class="summary-metric">
<CalculatorOutlined />
<span>{{ t("aiPoints.baseChars") }}</span>
<strong>{{ quotaSummary?.ai_point_base_chars ?? "--" }}</strong>
</div>
<div class="ai-points-view__summary">
<div class="ai-points-balance">
<span class="summary-label">{{ t("aiPoints.balance") }}</span>
<strong>{{ quotaSummary?.ai_points_balance ?? "--" }}</strong>
<a-progress :percent="aiPointUsedPercent" :show-info="false" stroke-color="#1677ff" />
</div>
<div class="summary-metric">
<FieldTimeOutlined />
<span>{{ t("shell.resetAt") }}</span>
<strong>{{ formatDateTime(quotaSummary?.ai_points_reset_at) }}</strong>
<div class="summary-metric">
<div class="summary-metric-icon">
<ThunderboltOutlined />
</div>
<div class="summary-metric-content">
<span>{{ t("common.used") }}</span>
<strong>{{ quotaSummary?.ai_points_used ?? "--" }} / {{ quotaSummary?.ai_points_total ?? "--" }}</strong>
</div>
</div>
<div class="summary-metric">
<div class="summary-metric-icon">
<CalculatorOutlined />
</div>
<div class="summary-metric-content">
<span>{{ t("aiPoints.baseChars") }}</span>
<strong>{{ quotaSummary?.ai_point_base_chars ?? "--" }}</strong>
</div>
</div>
<div class="summary-metric">
<div class="summary-metric-icon">
<FieldTimeOutlined />
</div>
<div class="summary-metric-content">
<span>{{ t("shell.resetAt") }}</span>
<strong>{{ formatDateTime(quotaSummary?.ai_points_reset_at) }}</strong>
</div>
</div>
</div>
</section>
<section class="ai-points-ledger">
<div class="ledger-head">
<section class="ai-points-view__table-card">
<div class="ai-points-view__table-head">
<div>
<h2>{{ t("aiPoints.ledgerTitle") }}</h2>
<span>{{ t("aiPoints.ledgerTotal", { total: usageTotal }) }}</span>
<p class="eyebrow">{{ t("aiPoints.ledgerTitle") }}</p>
<h3>{{ t("aiPoints.ledgerTotal", { total: usageTotal }) }}</h3>
</div>
</div>
@@ -215,124 +233,136 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
</template>
<style scoped>
.ai-points-page {
max-width: 1180px;
margin: 0 auto;
}
.ai-points-header {
.ai-points-view {
display: flex;
flex-direction: column;
gap: 22px;
}
.ai-points-view__top-card {
background: #fff;
border: 1px solid #e6edf5;
border-radius: 12px;
overflow: hidden;
}
.ai-points-view__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
margin-bottom: 20px;
align-items: flex-start;
padding: 24px;
}
.ai-points-header h1,
.ledger-head h2 {
.ai-points-view__header-title h2 {
margin: 0;
color: #141414;
font-weight: 750;
letter-spacing: 0;
font-size: 20px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.4;
}
.ai-points-header h1 {
font-size: 24px;
line-height: 32px;
.ai-points-view__header-title p {
margin: 6px 0 0 0;
font-size: 13px;
color: #8c8c8c;
}
.ai-points-header p {
margin: 6px 0 0;
color: var(--muted);
font-size: 14px;
line-height: 22px;
.ai-points-view__header-actions {
display: flex;
gap: 12px;
}
.ai-points-summary {
.ai-points-view__summary {
display: grid;
grid-template-columns: minmax(260px, 1.5fr) repeat(3, minmax(160px, 1fr));
gap: 12px;
margin-bottom: 20px;
gap: 16px;
padding: 24px;
}
.ai-points-balance,
.summary-metric,
.ai-points-ledger {
border: 1px solid #edf0f5;
border-radius: 8px;
background: #fff;
.summary-metric {
border: 1px solid #e6edf5;
border-radius: 10px;
background: #fafcff;
}
.ai-points-balance {
padding: 18px;
padding: 20px;
}
.summary-label,
.summary-metric span,
.ledger-head span,
.usage-type-cell span,
.usage-rule {
color: var(--muted);
color: #8c8c8c;
}
.ai-points-balance strong {
display: block;
margin: 6px 0 12px;
color: #141414;
font-size: 36px;
font-weight: 750;
line-height: 42px;
margin: 8px 0 16px;
color: #1a1a1a;
font-size: 32px;
font-weight: 600;
line-height: 1.2;
}
.summary-metric {
display: grid;
align-content: center;
gap: 6px;
min-height: 112px;
padding: 16px;
}
.summary-metric :deep(.anticon) {
color: #1677ff;
font-size: 18px;
}
.summary-metric span,
.summary-label {
font-size: 12px;
line-height: 18px;
}
.summary-metric strong {
color: #141414;
font-size: 16px;
font-weight: 650;
line-height: 22px;
}
.ai-points-ledger {
overflow: hidden;
}
.ledger-head {
display: flex;
align-items: center;
gap: 16px;
padding: 20px;
}
.summary-metric-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 50%;
background: #e6f4ff;
color: #1677ff;
font-size: 20px;
}
.summary-metric-content {
display: flex;
flex-direction: column;
gap: 4px;
}
.summary-metric-content span {
font-size: 13px;
line-height: 1.4;
}
.summary-metric-content strong {
color: #1a1a1a;
font-size: 18px;
font-weight: 600;
line-height: 1.2;
}
.ai-points-view__table-card {
padding: 24px;
background: #fff;
border: 1px solid #e6edf5;
border-radius: 12px;
}
.ai-points-view__table-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
padding: 18px 20px;
border-bottom: 1px solid #edf0f5;
margin-bottom: 18px;
}
.ledger-head h2 {
font-size: 16px;
line-height: 24px;
}
.ledger-head span {
display: block;
margin-top: 3px;
font-size: 12px;
line-height: 18px;
.ai-points-view__table-head h3 {
margin: 6px 0 0;
font-size: 24px;
font-weight: 600;
color: #1a1a1a;
}
.usage-type-cell {
@@ -341,8 +371,8 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
}
.usage-type-cell strong {
color: #141414;
font-weight: 650;
color: #1a1a1a;
font-weight: 600;
}
.usage-type-cell span,
@@ -357,7 +387,7 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
.points-delta {
color: #cf1322;
font-weight: 750;
font-weight: 600;
}
.points-delta--refund {
@@ -377,18 +407,24 @@ function formatAIPointDelta(item: AIPointUsageLog): string {
}
@media (max-width: 1100px) {
.ai-points-summary {
.ai-points-view__summary {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 760px) {
.ai-points-header {
display: grid;
.ai-points-view__header {
flex-direction: column;
gap: 16px;
}
.ai-points-summary {
.ai-points-view__summary {
grid-template-columns: 1fr;
}
.ai-points-view__table-head {
flex-direction: column;
align-items: stretch;
}
}
</style>