From 7d0e9a0a204d64eb746158631e3b5c1fc9719b7e Mon Sep 17 00:00:00 2001 From: Zyy Date: Mon, 31 Aug 2026 19:23:10 +0800 Subject: [PATCH] fix(frontend): refine dashboard account metadata Co-authored-by: Codex --- frontend/eslint.config.ts | 88 +++++++++++-------- frontend/src/components/ProviderIconGroup.vue | 20 ++++- .../components/AccountIdentityCell.vue | 14 ++- .../accounts/components/AccountPlanBadge.vue | 12 +-- .../components/AccountOverviewCard.vue | 3 +- 5 files changed, 88 insertions(+), 49 deletions(-) diff --git a/frontend/eslint.config.ts b/frontend/eslint.config.ts index 4fb7e291b..6074a0fc0 100644 --- a/frontend/eslint.config.ts +++ b/frontend/eslint.config.ts @@ -1,47 +1,65 @@ +import type { ESLint, Rule } from 'eslint' import antfu from '@antfu/eslint-config' -const templateStylePlugin = { - rules: { - 'single-line-simple-interpolation': { - meta: { - type: 'layout', - fixable: 'whitespace', - schema: [], - messages: { - expectedSingleLine: 'Expected a simple mustache interpolation to stay on one line.', - }, - }, - create(context) { - const { parserServices } = context.sourceCode - if (!parserServices.defineTemplateBodyVisitor) - return {} +type LocatedRuleNode = Rule.Node & { + loc: NonNullable +} - return parserServices.defineTemplateBodyVisitor({ - 'VExpressionContainer[expression!=null]': function (node) { - if (node.parent.type !== 'VElement') - return +interface TemplateExpressionContainer { + type: 'VExpressionContainer' + parent: { type: string } + expression: LocatedRuleNode + loc: NonNullable + range: NonNullable +} - const expression = node.expression - if ( - expression.loc.start.line !== expression.loc.end.line - || node.loc.start.line === node.loc.end.line - ) { - return - } +const singleLineSimpleInterpolationRule = { + meta: { + type: 'layout', + fixable: 'whitespace', + schema: [], + messages: { + expectedSingleLine: 'Expected a simple mustache interpolation to stay on one line.', + }, + }, + create(context) { + const { parserServices } = context.sourceCode + if (!parserServices.defineTemplateBodyVisitor) + return {} - context.report({ - node, - messageId: 'expectedSingleLine', - fix(fixer) { - return fixer.replaceText(node, `{{ ${context.sourceCode.getText(expression)} }}`) - }, - }) + return parserServices.defineTemplateBodyVisitor({ + 'VExpressionContainer[expression!=null]': function (node: TemplateExpressionContainer) { + if (node.parent.type !== 'VElement') + return + + const expression = node.expression + if ( + expression.loc.start.line !== expression.loc.end.line + || node.loc.start.line === node.loc.end.line + ) { + return + } + + context.report({ + node, + messageId: 'expectedSingleLine', + fix(fixer) { + return fixer.replaceTextRange( + node.range, + `{{ ${context.sourceCode.getText(expression)} }}`, + ) }, }) }, - }, + }) }, -} +} satisfies Rule.RuleModule + +const templateStylePlugin = { + rules: { + 'single-line-simple-interpolation': singleLineSimpleInterpolationRule, + }, +} satisfies ESLint.Plugin export default antfu( { diff --git a/frontend/src/components/ProviderIconGroup.vue b/frontend/src/components/ProviderIconGroup.vue index 21d2df8da..b34d2940c 100644 --- a/frontend/src/components/ProviderIconGroup.vue +++ b/frontend/src/components/ProviderIconGroup.vue @@ -7,7 +7,7 @@ const props = withDefaults( defineProps<{ provider?: string | null authenticationKind?: string | null - size?: 'sm' | 'md' + size?: 'xs' | 'sm' | 'md' }>(), { size: 'md', @@ -17,9 +17,21 @@ const props = withDefaults( const normalizedProvider = computed(() => (props.provider ?? '').trim().toLowerCase()) const normalizedAuthenticationKind = computed(() => (props.authenticationKind ?? '').trim().toLowerCase()) const showAuthenticationKind = computed(() => props.authenticationKind !== undefined) -const groupGapClass = computed(() => (props.size === 'sm' ? 'gap-1' : 'gap-2')) -const iconContainerClass = computed(() => (props.size === 'sm' ? 'size-5 rounded-md' : 'size-7 rounded-lg')) -const iconClass = computed(() => (props.size === 'sm' ? 'size-3' : 'size-4')) +const groupGapClass = computed(() => { + if (props.size === 'xs') + return 'gap-0.75' + return props.size === 'sm' ? 'gap-1' : 'gap-2' +}) +const iconContainerClass = computed(() => { + if (props.size === 'xs') + return 'size-4.5 rounded-md' + return props.size === 'sm' ? 'size-5 rounded-md' : 'size-7 rounded-lg' +}) +const iconClass = computed(() => { + if (props.size === 'xs') + return 'size-2.5' + return props.size === 'sm' ? 'size-3' : 'size-4' +}) const providerLabel = computed(() => formatProviderLabel(props.provider, '未知平台')) diff --git a/frontend/src/views/accounts/components/AccountIdentityCell.vue b/frontend/src/views/accounts/components/AccountIdentityCell.vue index 72a1b8695..6f23669f6 100644 --- a/frontend/src/views/accounts/components/AccountIdentityCell.vue +++ b/frontend/src/views/accounts/components/AccountIdentityCell.vue @@ -16,12 +16,14 @@ const props = withDefaults( showPlan?: boolean titleMode?: 'local-part' | 'email' metaPosition?: 'title' | 'secondary' + metaSize?: 'xs' | 'sm' }>(), { size: 'md', showPlan: false, titleMode: 'local-part', metaPosition: 'title', + metaSize: 'sm', }, ) @@ -54,6 +56,8 @@ const secondaryClass = computed(() => : 'mt-0.5 font-mono text-cp-xs text-cp-text-quaternary', ) +const metaGapClass = computed(() => props.metaSize === 'xs' ? 'gap-1' : 'gap-1.5') + const avatarToneClass = computed(() => { const identity = props.account.id || props.account.email || displayTitle.value return stablePresetVisualToneClass(identity) @@ -75,18 +79,20 @@ const avatarToneClass = computed(() => { - +
- +
{{ secondaryText }} diff --git a/frontend/src/views/accounts/components/AccountPlanBadge.vue b/frontend/src/views/accounts/components/AccountPlanBadge.vue index 261849ca3..cda354d26 100644 --- a/frontend/src/views/accounts/components/AccountPlanBadge.vue +++ b/frontend/src/views/accounts/components/AccountPlanBadge.vue @@ -6,7 +6,7 @@ import { stableVisualIndex } from '../utils/visualTone' const props = withDefaults( defineProps<{ planType?: string | null - size?: 'sm' | 'md' + size?: 'xs' | 'sm' | 'md' }>(), { planType: null, @@ -29,11 +29,13 @@ const fallbackPalettes = [ const label = computed(() => props.planType?.trim() || 'Free') -const sizeClass = computed(() => - props.size === 'sm' +const sizeClass = computed(() => { + if (props.size === 'xs') + return 'h-4.5 rounded-full px-1.5 text-[10px] font-bold' + return props.size === 'sm' ? 'h-5 rounded-full px-1.75 text-cp-xs font-bold' - : 'h-5.5 rounded-full px-2 text-cp-xs font-heavy', -) + : 'h-5.5 rounded-full px-2 text-cp-xs font-heavy' +}) const paletteClass = computed(() => { const key = label.value.toLowerCase() diff --git a/frontend/src/views/dashboard/components/AccountOverviewCard.vue b/frontend/src/views/dashboard/components/AccountOverviewCard.vue index f84b560c5..c948d65eb 100644 --- a/frontend/src/views/dashboard/components/AccountOverviewCard.vue +++ b/frontend/src/views/dashboard/components/AccountOverviewCard.vue @@ -240,13 +240,14 @@ const statusBars = computed(() => { show-plan title-mode="email" meta-position="secondary" + meta-size="xs" class="min-w-0" >