feat(sidebar+groups): available-channels above channel-status; show rate for subscription groups

- Sidebar user-side order: /available-channels now sits directly above
  /monitor (渠道状态) for regular users, mirroring the admin section where
  it sits above /admin/channels.
- GroupBadge gains an alwaysShowRate prop. Subscription groups default to
  a "订阅"/days-remaining label; the new flag swaps that for the rate
  multiplier while keeping the subscription theme color, so the Available
  Channels page can surface rates on every group type.
This commit is contained in:
erio 2026-04-21 22:10:51 +08:00
parent ff4ef1b574
commit 9dae6c7aee
3 changed files with 24 additions and 5 deletions

View File

@ -83,6 +83,7 @@
:subscription-type="(g.subscription_type || 'standard') as SubscriptionType"
:rate-multiplier="g.rate_multiplier"
:user-rate-multiplier="userGroupRates[g.id] ?? null"
always-show-rate
/>
</div>
<div
@ -104,6 +105,7 @@
:subscription-type="(g.subscription_type || 'standard') as SubscriptionType"
:rate-multiplier="g.rate_multiplier"
:user-rate-multiplier="userGroupRates[g.id] ?? null"
always-show-rate
/>
</div>
<span v-if="section.groups.length === 0" class="text-xs text-gray-400">-</span>

View File

@ -37,13 +37,20 @@ interface Props {
userRateMultiplier?: number | null //
showRate?: boolean
daysRemaining?: number | null // 使
/**
* 订阅分组默认在右侧 label 展示"订阅"或剩余天数
* 开启后订阅分组也改为显示倍率保留订阅主题色 label配合可用渠道这类
* 只关心费率不关心有效期的场景
*/
alwaysShowRate?: boolean
}
const props = withDefaults(defineProps<Props>(), {
subscriptionType: 'standard',
showRate: true,
daysRemaining: null,
userRateMultiplier: null
userRateMultiplier: null,
alwaysShowRate: false
})
const { t } = useI18n()
@ -71,7 +78,8 @@ const showLabel = computed(() => {
// Label text
const labelText = computed(() => {
if (isSubscription.value) {
const rateLabel = props.rateMultiplier !== undefined ? `${props.rateMultiplier}x` : ''
if (isSubscription.value && !props.alwaysShowRate) {
//
if (props.daysRemaining !== null && props.daysRemaining !== undefined) {
if (props.daysRemaining <= 0) {
@ -82,7 +90,7 @@ const labelText = computed(() => {
// ""
return t('groups.subscription')
}
return props.rateMultiplier !== undefined ? `${props.rateMultiplier}x` : ''
return rateLabel
})
// Label style based on type and days remaining

View File

@ -640,7 +640,12 @@ const flagAdminPayment = () => adminSettingsStore.paymentEnabled
// buildSelfNavItems ""
// withDashboard=true false
function buildSelfNavItems(withDashboard: boolean): NavItem[] {
// includeAvailableChannels=false "" admin
// ""
//
// / /
// ""
function buildSelfNavItems(withDashboard: boolean, includeAvailableChannels = true): NavItem[] {
const items: NavItem[] = []
if (withDashboard) {
items.push({ path: '/dashboard', label: t('nav.dashboard'), icon: DashboardIcon })
@ -648,11 +653,15 @@ function buildSelfNavItems(withDashboard: boolean): NavItem[] {
items.push(
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
)
if (includeAvailableChannels) {
items.push({ path: '/available-channels', label: t('nav.availableChannels'), icon: ChannelIcon, hideInSimpleMode: true, featureFlag: flagAvailableChannels })
}
items.push(
{ path: '/monitor', label: t('nav.channelStatus'), icon: SignalIcon, featureFlag: flagChannelMonitor },
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
{ path: '/purchase', label: t('nav.buySubscription'), icon: RechargeSubscriptionIcon, hideInSimpleMode: true, featureFlag: flagPayment },
{ path: '/orders', label: t('nav.myOrders'), icon: OrderListIcon, hideInSimpleMode: true, featureFlag: flagPayment },
{ path: '/available-channels', label: t('nav.availableChannels'), icon: ChannelIcon, hideInSimpleMode: true },
{ path: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
{ path: '/profile', label: t('nav.profile'), icon: UserIcon },
...customMenuItemsForUser.value.map((item): NavItem => ({