fix(frontend): refine dashboard account metadata

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-08-31 19:23:10 +08:00
co-authored by Codex
parent 09982d7221
commit 7d0e9a0a20
5 changed files with 88 additions and 49 deletions
+53 -35
View File
@@ -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<Rule.Node['loc']>
}
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<Rule.Node['loc']>
range: NonNullable<Rule.Node['range']>
}
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(
{
+16 -4
View File
@@ -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, '未知平台'))
@@ -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(() => {
</span>
<span
v-if="metaPosition === 'title' && (showPlan || $slots.meta)"
class="inline-flex shrink-0 items-center justify-end gap-1.5"
class="inline-flex shrink-0 items-center justify-end"
:class="metaGapClass"
>
<slot name="meta" />
<AccountPlanBadge v-if="showPlan" :plan-type="account.planType" size="sm" />
<AccountPlanBadge v-if="showPlan" :plan-type="account.planType" :size="metaSize" />
</span>
</div>
<div
v-if="metaPosition === 'secondary' && (showPlan || $slots.meta)"
class="mt-0.5 inline-flex min-w-0 items-center gap-1.5"
class="mt-0.5 inline-flex min-w-0 items-center"
:class="metaGapClass"
>
<slot name="meta" />
<AccountPlanBadge v-if="showPlan" :plan-type="account.planType" size="sm" />
<AccountPlanBadge v-if="showPlan" :plan-type="account.planType" :size="metaSize" />
</div>
<div v-else-if="secondaryText" class="truncate font-emphasis" :class="secondaryClass">
{{ secondaryText }}
@@ -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()
@@ -240,13 +240,14 @@ const statusBars = computed(() => {
show-plan
title-mode="email"
meta-position="secondary"
meta-size="xs"
class="min-w-0"
>
<template #meta>
<ProviderIconGroup
:provider="account.provider"
:authentication-kind="account.authenticationKind"
size="sm"
size="xs"
/>
</template>
</AccountIdentityCell>