fix(home): 提升首页板块区分度并修复告警样式
新增 HomeSectionHeading 统一分区标题(圆点+加粗),板块间距由 22px 拉到 36px 并加分隔线,板块内间距收紧,解决各板块视觉上粘连难辨的问题; 告警行改为按 tone 上底色与图标,替代原来仅靠 6px 圆点区分严重度; 统一分隔线只由板块起始元素画上边线,避免与相邻元素的边框叠加成两条。
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -221,6 +221,7 @@ export default {
|
||||
blacklisted: '{group} に {count} 件のブラックリスト入り認証情報があります',
|
||||
lowQuota: '{group} の残額度は {remaining}、リセットは',
|
||||
summary: '{count} 件の認証情報に対応が必要です',
|
||||
action: '対応',
|
||||
},
|
||||
spend: {
|
||||
title: '直近 30 日間の概算',
|
||||
|
||||
@@ -212,6 +212,7 @@ export default {
|
||||
blacklisted: '{group} 有 {count} 个凭据已拉黑,需要人工处理',
|
||||
lowQuota: '{group} 账号额度剩 {remaining},重置于',
|
||||
summary: '{count} 个凭据需要你处理',
|
||||
action: '处理',
|
||||
},
|
||||
spend: {
|
||||
title: '近 30 天估算',
|
||||
|
||||
Reference in new issue
Block a user