fix(home): 提升首页板块区分度并修复告警样式

新增 HomeSectionHeading 统一分区标题(圆点+加粗),板块间距由 22px
拉到 36px 并加分隔线,板块内间距收紧,解决各板块视觉上粘连难辨的问题;
告警行改为按 tone 上底色与图标,替代原来仅靠 6px 圆点区分严重度;
统一分隔线只由板块起始元素画上边线,避免与相邻元素的边框叠加成两条。
This commit is contained in:
tbphp committed 2026-08-18 18:42:34 +08:00
1 parent a266628b14
commit 237c3c0a89
9 files changed
+137 -95

No files matched your search

@@ -95,7 +95,8 @@ const models = computed(() =>
.current-access-key {
display: grid;
gap: 14px;
border-block: 1px solid var(--color-border-subtle);
/* 同上:只留上边线,避免和下一个板块的上边线撞成两条。 */
border-top: 1px solid var(--color-border-subtle);
background: color-mix(in srgb, var(--color-action-soft) 28%, transparent);
padding: 18px 0;
}
+12 -26
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Layers3, Zap } from '@lucide/vue'
import { Zap } from '@lucide/vue'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
@@ -18,6 +18,7 @@ import InlineFeedback from '@/components/ui/InlineFeedback.vue'
import ChannelIcon from '@/components/brand/ChannelIcon.vue'
import ClientPicker from './ClientPicker.vue'
import HomeSectionHeading from './HomeSectionHeading.vue'
import {
ccSwitchTargets,
@@ -475,10 +476,7 @@ onBeforeUnmount(() => {
<template>
<section class="gateway-connection" aria-labelledby="gateway-connection-title">
<div class="gateway-connection__heading">
<Layers3 :size="15" aria-hidden="true" />
<h2 id="gateway-connection-title">{{ t('home.ledger.connection.title') }}</h2>
</div>
<HomeSectionHeading id="gateway-connection-title" :title="t('home.ledger.connection.title')" />
<div v-if="!selectedKey" class="gateway-connection__empty">
<p>{{ t('home.ledger.connection.noAccessKey') }}</p>
@@ -723,26 +721,14 @@ onBeforeUnmount(() => {
</template>
<style scoped>
/*
* 板块边界:原来板块之间 22px、板块内部 14–18px,只差 4px,眼睛靠邻近关系
* 分不出组。这里把板块间距拉到 36px、板块内收到 12px,再补一条细线。
*/
.gateway-connection {
padding-top: 22px;
}
.gateway-connection__heading {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 4px;
}
.gateway-connection__heading h2 {
margin: 0;
font-family: var(--font-serif);
font-size: var(--title-section);
font-weight: 500;
}
.gateway-connection__heading svg {
color: var(--color-text-faint);
margin-top: 36px;
border-top: 1px solid var(--color-border-subtle);
padding-top: 20px;
}
.gateway-connection__toolbar {
@@ -750,7 +736,7 @@ onBeforeUnmount(() => {
align-items: flex-end;
gap: 16px;
flex-wrap: wrap;
margin-top: 18px;
margin-top: 12px;
}
.gateway-connection__key {
@@ -790,7 +776,7 @@ onBeforeUnmount(() => {
.gateway-connection__panel {
display: grid;
gap: var(--space-3);
margin-top: 14px;
margin-top: 12px;
border: 1px solid var(--color-border-subtle);
border-radius: var(--radius-sheet);
background: var(--color-surface);
+43 -28
View File
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { CircleAlert, TriangleAlert } from '@lucide/vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
@@ -49,12 +50,14 @@ function quotaPercent(remaining: number): string {
<RouterLink
v-if="overflowing"
class="home-attention__row"
class="home-attention__row home-attention__row--danger"
:to="monitorLocation({ tab: 'health' })"
>
<span class="home-attention__dot" data-tone="danger" aria-hidden="true"></span>
<CircleAlert :size="14" aria-hidden="true" />
<span>{{ t('home.ledger.attention.summary', { count: total }) }}</span>
<span class="home-attention__go" aria-hidden="true">→</span>
<span class="home-attention__go" aria-hidden="true">
{{ t('home.ledger.attention.action') }} →
</span>
</RouterLink>
<template v-else>
@@ -62,13 +65,11 @@ function quotaPercent(remaining: number): string {
v-for="item in visibleItems"
:key="`${item.kind}-${item.groupID}`"
class="home-attention__row"
:class="`home-attention__row--${item.kind === 'blacklisted' ? 'danger' : 'warning'}`"
:to="itemLocation(item.groupID, item.kind)"
>
<span
class="home-attention__dot"
:data-tone="item.kind === 'blacklisted' ? 'danger' : 'warning'"
aria-hidden="true"
></span>
<CircleAlert v-if="item.kind === 'blacklisted'" :size="14" aria-hidden="true" />
<TriangleAlert v-else :size="14" aria-hidden="true" />
<span v-if="item.kind === 'blacklisted'">
{{ t('home.ledger.attention.blacklisted', { group: item.groupName, count: item.value }) }}
</span>
@@ -86,7 +87,9 @@ function quotaPercent(remaining: number): string {
:empty-label="''"
/>
</span>
<span class="home-attention__go" aria-hidden="true">→</span>
<span class="home-attention__go" aria-hidden="true">
{{ t('home.ledger.attention.action') }} →
</span>
</RouterLink>
</template>
</section>
@@ -96,36 +99,49 @@ function quotaPercent(remaining: number): string {
.home-attention {
display: flex;
flex-direction: column;
gap: 2px;
gap: 6px;
padding-top: 14px;
}
/*
* 告警要一眼认出来:按 tone 上底色与描边,配警示图标,回到仓库
* 「图标 + 文字 + 颜色」的三重编码。原来只有一个 6px 圆点承载严重度。
*/
.home-attention__row {
display: flex;
align-items: center;
gap: 9px;
margin-inline: -10px;
border: 1px solid transparent;
border-radius: var(--radius-control);
padding: 8px 10px;
color: var(--color-text);
padding: 8px 11px;
font-size: var(--text-meta);
transition: background-color var(--duration-fast) var(--easing-standard);
transition:
border-color var(--duration-fast) var(--easing-standard),
background-color var(--duration-fast) var(--easing-standard);
}
.home-attention__row:hover {
background: var(--color-interactive-hover);
}
.home-attention__dot {
.home-attention__row svg {
flex: none;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-danger);
}
.home-attention__dot[data-tone='warning'] {
background: var(--color-warning);
.home-attention__row--warning {
border-color: color-mix(in srgb, var(--color-warning) 30%, var(--color-border-subtle));
background: var(--color-warning-bg);
color: var(--color-warning);
}
.home-attention__row--danger {
border-color: color-mix(in srgb, var(--color-danger) 30%, var(--color-border-subtle));
background: var(--color-danger-bg);
color: var(--color-danger);
}
.home-attention__row--warning:hover {
border-color: var(--color-warning);
}
.home-attention__row--danger:hover {
border-color: var(--color-danger);
}
.home-attention__quota {
@@ -138,8 +154,7 @@ function quotaPercent(remaining: number): string {
.home-attention__go {
margin-left: auto;
color: var(--color-action);
font-family: var(--font-mono);
font-weight: 600;
font-weight: 650;
white-space: nowrap;
}
</style>
@@ -0,0 +1,59 @@
<script setup lang="ts">
defineProps<{
title: string
id?: string
}>()
</script>
<template>
<header class="home-section-heading">
<h2 :id="id">{{ title }}</h2>
<div v-if="$slots.actions" class="home-section-heading__actions">
<slot name="actions" />
</div>
</header>
</template>
<style scoped>
/*
* 与监控页分区标题同款:圆点 + 加粗。原来的「淡色图标 + serif 500」比板块
* 内容还轻,扫视时抓不住锚点,这里把标题的视觉重量补回来。
*/
.home-section-heading {
display: flex;
min-width: 0;
min-height: 26px;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
flex-wrap: wrap;
}
.home-section-heading h2 {
display: flex;
min-width: 0;
align-items: center;
gap: var(--space-2-5);
margin: 0;
font-size: var(--title-section);
font-weight: 650;
letter-spacing: -0.01em;
line-height: var(--line-compact);
}
.home-section-heading h2::before {
flex: 0 0 auto;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-action);
content: '';
}
.home-section-heading__actions {
display: flex;
flex: 0 0 auto;
align-items: center;
gap: var(--space-2);
}
</style>
+13 -38
View File
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { CircleDollarSign } from '@lucide/vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterLink } from 'vue-router'
@@ -8,6 +7,8 @@ import type { HomeStatisticsDto } from '@/app/resources/home'
import { monitorLocation } from '@/app/route-locations'
import DataTable from '@/components/ui/DataTable.vue'
import SkeletonBlock from '@/components/ui/SkeletonBlock.vue'
import HomeSectionHeading from './HomeSectionHeading.vue'
import { formatEstimatedCost, formatInteger, formatTokens } from '@/lib/format'
const props = withDefaults(
@@ -49,15 +50,13 @@ function tokenCellAttributes(totalTokens: number): { title: string; 'aria-label'
<template>
<section class="home-spend" aria-labelledby="home-spend-title">
<div class="home-spend__header">
<div class="home-spend__heading">
<CircleDollarSign :size="15" aria-hidden="true" />
<h2 id="home-spend-title">{{ t('home.ledger.spend.title') }}</h2>
</div>
<RouterLink class="home-spend__link" :to="monitorLocation({ tab: 'usage', range: '30d' })">
{{ t('home.ledger.spend.viewDetail') }}
</RouterLink>
</div>
<HomeSectionHeading id="home-spend-title" :title="t('home.ledger.spend.title')">
<template #actions>
<RouterLink class="home-spend__link" :to="monitorLocation({ tab: 'usage', range: '30d' })">
{{ t('home.ledger.spend.viewDetail') }}
</RouterLink>
</template>
</HomeSectionHeading>
<SkeletonBlock
v-if="loading"
@@ -134,33 +133,9 @@ function tokenCellAttributes(totalTokens: number): { title: string; 'aria-label'
<style scoped>
.home-spend {
padding: 22px 0 4px;
}
.home-spend__header {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-bottom: 4px;
}
.home-spend__heading {
display: flex;
align-items: center;
gap: 10px;
}
.home-spend__heading h2 {
margin: 0;
font-family: var(--font-serif);
font-size: var(--title-section);
font-weight: 500;
}
.home-spend__heading svg {
color: var(--color-text-faint);
margin-top: 36px;
border-top: 1px solid var(--color-border-subtle);
padding: 20px 0 4px;
}
.home-spend__link {
@@ -175,7 +150,7 @@ function tokenCellAttributes(totalTokens: number): { title: string; 'aria-label'
}
.home-spend__value {
margin: 0 0 18px;
margin: 10px 0 16px;
font-family: var(--font-mono);
font-size: var(--stat-value);
font-weight: 550;
+5 -2
View File
@@ -69,8 +69,11 @@ const updatedTitle = computed(() =>
justify-content: space-between;
gap: 22px;
flex-wrap: wrap;
border-bottom: 1px solid var(--color-border-control);
padding-bottom: 20px;
/*
* 不画下边线:分隔线一律由「开启新板块」的元素自己的 border-top 承担。
* 否则没有告警时,这条线会和下一个板块的上边线并排出现两条。
* 下方间距同样交给板块的 margin-top,这里不再重复留白。
*/
}
.home-summary__facts {
+1
View File
@@ -223,6 +223,7 @@ export default {
blacklisted: '{group} has {count} blacklisted credentials that need manual action',
lowQuota: '{group} has {remaining} quota left, resets',
summary: '{count} credentials need your attention',
action: 'Resolve',
},
spend: {
title: 'Estimated over 30 days',
+1
View File
@@ -221,6 +221,7 @@ export default {
blacklisted: '{group} に {count} 件のブラックリスト入り認証情報があります',
lowQuota: '{group} の残額度は {remaining}、リセットは',
summary: '{count} 件の認証情報に対応が必要です',
action: '対応',
},
spend: {
title: '直近 30 日間の概算',
+1
View File
@@ -212,6 +212,7 @@ export default {
blacklisted: '{group} 有 {count} 个凭据已拉黑,需要人工处理',
lowQuota: '{group} 账号额度剩 {remaining},重置于',
summary: '{count} 个凭据需要你处理',
action: '处理',
},
spend: {
title: '近 30 天估算',