mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(frontend): refine dashboard account metadata
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
+53
-35
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user