Commit 9dae6c7a authored by erio's avatar erio
Browse files

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.
parent ff4ef1b5
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
:subscription-type="(g.subscription_type || 'standard') as SubscriptionType" :subscription-type="(g.subscription_type || 'standard') as SubscriptionType"
:rate-multiplier="g.rate_multiplier" :rate-multiplier="g.rate_multiplier"
:user-rate-multiplier="userGroupRates[g.id] ?? null" :user-rate-multiplier="userGroupRates[g.id] ?? null"
always-show-rate
/> />
</div> </div>
<div <div
...@@ -104,6 +105,7 @@ ...@@ -104,6 +105,7 @@
:subscription-type="(g.subscription_type || 'standard') as SubscriptionType" :subscription-type="(g.subscription_type || 'standard') as SubscriptionType"
:rate-multiplier="g.rate_multiplier" :rate-multiplier="g.rate_multiplier"
:user-rate-multiplier="userGroupRates[g.id] ?? null" :user-rate-multiplier="userGroupRates[g.id] ?? null"
always-show-rate
/> />
</div> </div>
<span v-if="section.groups.length === 0" class="text-xs text-gray-400">-</span> <span v-if="section.groups.length === 0" class="text-xs text-gray-400">-</span>
......
...@@ -37,13 +37,20 @@ interface Props { ...@@ -37,13 +37,20 @@ interface Props {
userRateMultiplier?: number | null // 用户专属倍率 userRateMultiplier?: number | null // 用户专属倍率
showRate?: boolean showRate?: boolean
daysRemaining?: number | null // 剩余天数(订阅类型时使用) daysRemaining?: number | null // 剩余天数(订阅类型时使用)
/**
* 订阅分组默认在右侧 label 展示"订阅"或剩余天数;
* 开启后订阅分组也改为显示倍率(保留订阅主题色 label,配合可用渠道这类
* 只关心费率、不关心有效期的场景)。
*/
alwaysShowRate?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
subscriptionType: 'standard', subscriptionType: 'standard',
showRate: true, showRate: true,
daysRemaining: null, daysRemaining: null,
userRateMultiplier: null userRateMultiplier: null,
alwaysShowRate: false
}) })
const { t } = useI18n() const { t } = useI18n()
...@@ -71,7 +78,8 @@ const showLabel = computed(() => { ...@@ -71,7 +78,8 @@ const showLabel = computed(() => {
// Label text // Label text
const labelText = computed(() => { 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 !== null && props.daysRemaining !== undefined) {
if (props.daysRemaining <= 0) { if (props.daysRemaining <= 0) {
...@@ -82,7 +90,7 @@ const labelText = computed(() => { ...@@ -82,7 +90,7 @@ const labelText = computed(() => {
// 否则显示"订阅" // 否则显示"订阅"
return t('groups.subscription') return t('groups.subscription')
} }
return props.rateMultiplier !== undefined ? `${props.rateMultiplier}x` : '' return rateLabel
}) })
// Label style based on type and days remaining // Label style based on type and days remaining
......
...@@ -640,7 +640,12 @@ const flagAdminPayment = () => adminSettingsStore.paymentEnabled ...@@ -640,7 +640,12 @@ const flagAdminPayment = () => adminSettingsStore.paymentEnabled
// buildSelfNavItems 构造用户自己的导航项(用户端主菜单和管理员的"我的账户"子菜单共享这组声明)。 // buildSelfNavItems 构造用户自己的导航项(用户端主菜单和管理员的"我的账户"子菜单共享这组声明)。
// withDashboard=true 时包含仪表盘(用户端),false 时不含(管理员的个人区已经有独立仪表盘入口)。 // withDashboard=true 时包含仪表盘(用户端),false 时不含(管理员的个人区已经有独立仪表盘入口)。
function buildSelfNavItems(withDashboard: boolean): NavItem[] { // includeAvailableChannels=false 时省略"可用渠道"入口——管理员在 admin 区已经有一个显眼入口,
// 重复显示会让管理员同时看到两处"可用渠道"。
//
// 条目顺序:密钥 → 用量 → 可用渠道 → 渠道状态 → 订阅/支付 → 兑换/资料。
// 可用渠道紧挨渠道状态之上,让用户"先看自己能用什么、再看对应状态"。
function buildSelfNavItems(withDashboard: boolean, includeAvailableChannels = true): NavItem[] {
const items: NavItem[] = [] const items: NavItem[] = []
if (withDashboard) { if (withDashboard) {
items.push({ path: '/dashboard', label: t('nav.dashboard'), icon: DashboardIcon }) items.push({ path: '/dashboard', label: t('nav.dashboard'), icon: DashboardIcon })
...@@ -648,11 +653,15 @@ function buildSelfNavItems(withDashboard: boolean): NavItem[] { ...@@ -648,11 +653,15 @@ function buildSelfNavItems(withDashboard: boolean): NavItem[] {
items.push( items.push(
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon }, { path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true }, { 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: '/monitor', label: t('nav.channelStatus'), icon: SignalIcon, featureFlag: flagChannelMonitor },
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true }, { path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
{ path: '/purchase', label: t('nav.buySubscription'), icon: RechargeSubscriptionIcon, hideInSimpleMode: true, featureFlag: flagPayment }, { 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: '/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: '/redeem', label: t('nav.redeem'), icon: GiftIcon, hideInSimpleMode: true },
{ path: '/profile', label: t('nav.profile'), icon: UserIcon }, { path: '/profile', label: t('nav.profile'), icon: UserIcon },
...customMenuItemsForUser.value.map((item): NavItem => ({ ...customMenuItemsForUser.value.map((item): NavItem => ({
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment