Files
sub2api/frontend/src/components/modelPlaza/PlazaGroupSection.vue
T
feeeei 720c405e35 feat: add model plaza with group-scoped pricing showcase
- public /model-plaza page (standalone + admin-embedded) listing groups
  with discounted effective prices alongside LiteLLM official reference
- faceted platform/group/rate filters: cross-dimension options gray out
  instead of disappearing, platform-tinted chips via accent color-mix
- paid-price columns highlighted with per-platform tint band
- OptionalJWT middleware so anonymous and signed-in users share one route
- admin settings: enable switch, require-auth switch, markdown description
2026-07-28 16:19:41 +08:00

94 lines
3.4 KiB
Vue

<template>
<section
class="rounded-2xl border bg-white shadow-card dark:bg-dark-800/50"
:class="[platformBorderStrongClass(group.platform)]"
>
<!-- 分组头部:名称/平台/倍率徽章/专属/订阅徽章 + 描述 -->
<header class="border-b border-gray-100 px-5 py-4 dark:border-dark-700/60">
<div class="flex flex-wrap items-center gap-2">
<GroupBadge
:name="group.name"
:platform="group.platform as GroupPlatform"
:subscription-type="(group.subscription_type || 'standard') as SubscriptionType"
:rate-multiplier="group.rate_multiplier"
:user-rate-multiplier="group.user_rate_multiplier ?? null"
:peak-rate-enabled="group.peak_rate_enabled"
:peak-start="group.peak_start"
:peak-end="group.peak_end"
:peak-rate-multiplier="group.peak_rate_multiplier"
always-show-rate
/>
<span
v-if="group.is_exclusive"
class="inline-flex items-center gap-1 rounded-md bg-purple-50 px-2 py-0.5 text-xs font-medium text-purple-600 dark:bg-purple-900/20 dark:text-purple-400"
>
<Icon name="shield" size="xs" class="h-3 w-3" />
{{ t('modelPlaza.badges.exclusive') }}
</span>
<span
v-if="group.subscription_type === 'subscription'"
class="inline-flex items-center rounded-md bg-violet-50 px-2 py-0.5 text-xs font-medium text-violet-600 dark:bg-violet-900/20 dark:text-violet-400"
>
{{ t('modelPlaza.badges.subscription') }}
</span>
</div>
<p v-if="group.description" class="mt-2 text-sm text-gray-500 dark:text-dark-400">
{{ group.description }}
</p>
<p
v-if="peakNote"
class="mt-1.5 inline-flex items-center gap-1 text-xs text-amber-600 dark:text-amber-400"
>
<Icon name="clock" size="xs" class="h-3 w-3" />
{{ peakNote }}
</p>
</header>
<!-- 模型价格表 -->
<div class="px-5">
<PlazaModelPricingTable
v-if="group.models.length > 0"
:models="group.models"
:platform="group.platform"
:rate-multiplier="group.rate_multiplier"
:user-rate-multiplier="group.user_rate_multiplier ?? null"
/>
<p v-else class="py-4 text-center text-sm text-gray-400 dark:text-dark-500">
{{ t('modelPlaza.detail.noModels') }}
</p>
</div>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Icon from '@/components/icons/Icon.vue'
import GroupBadge from '@/components/common/GroupBadge.vue'
import PlazaModelPricingTable from './PlazaModelPricingTable.vue'
import type { ModelPlazaGroup } from '@/api/modelPlaza'
import type { GroupPlatform, SubscriptionType } from '@/types'
import { platformBorderStrongClass } from '@/utils/platformColors'
import { hasPeakRate, formatPeakRateWindow, serverTimezoneLabel } from '@/utils/peak-rate'
import { useAppStore } from '@/stores/app'
const props = defineProps<{
group: ModelPlazaGroup
}>()
const { t } = useI18n()
const appStore = useAppStore()
const peakNote = computed(() => {
if (!hasPeakRate(props.group)) return ''
const window = formatPeakRateWindow(
props.group,
serverTimezoneLabel(appStore.cachedPublicSettings?.server_utc_offset)
)
return t('modelPlaza.detail.peakNote', {
window,
multiplier: props.group.peak_rate_multiplier
})
})
</script>