会员目录响应下发推荐档位与不设并发上限标志
ProfileMembershipPlanResponse 新增 recommended 与 unlimitedConcurrency 两个派生布尔 module-runtime 新增 PROFILE_MEMBERSHIP_RECOMMENDED_PLAN 常量并导出,作为推荐档位唯一真相源 api-server 目录响应按该常量与 is_unlimited_concurrency 派生标志 前端 ProfileMembershipPlanRecord 契约补上两个字段 membershipCopy 删除硬编码的 'plus' 与 128,并发文案改读 unlimitedConcurrency 充值弹窗推荐标签改读 plan.recommended 更新充值弹窗测试夹具
This commit is contained in:
@@ -96,6 +96,8 @@ const STARTER_PLAN = {
|
||||
periodPoints: 400,
|
||||
modelAccess: 'basic' as const,
|
||||
concurrentJobLimit: 1,
|
||||
recommended: false,
|
||||
unlimitedConcurrency: false,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
@@ -109,6 +111,7 @@ const PLUS_PLAN = {
|
||||
periodPoints: 1_150,
|
||||
modelAccess: 'full' as const,
|
||||
concurrentJobLimit: 3,
|
||||
recommended: true,
|
||||
};
|
||||
|
||||
const MAX_PLAN = {
|
||||
@@ -121,6 +124,7 @@ const MAX_PLAN = {
|
||||
periodPoints: 8_650,
|
||||
modelAccess: 'full' as const,
|
||||
concurrentJobLimit: 128,
|
||||
unlimitedConcurrency: true,
|
||||
};
|
||||
|
||||
vi.mock('qrcode', () => ({
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
resolveProfileMudPointDisplayTotal,
|
||||
} from '../../utils/mudPoints';
|
||||
import {
|
||||
RECOMMENDED_MEMBERSHIP_PLAN,
|
||||
resolveMembershipConcurrencyLabel,
|
||||
resolveMembershipConcurrencyShortLabel,
|
||||
resolveMembershipModelAccessLabel,
|
||||
@@ -673,7 +672,7 @@ function PlatformMembershipPlanCard({
|
||||
</div>
|
||||
<ul className="grid gap-0.5 text-[10px] leading-5 text-[var(--platform-text-soft)]">
|
||||
<li>{resolveMembershipModelAccessLabel(plan.modelAccess)}</li>
|
||||
<li>{resolveMembershipConcurrencyLabel(plan.concurrentJobLimit)}</li>
|
||||
<li>{resolveMembershipConcurrencyLabel(plan)}</li>
|
||||
</ul>
|
||||
<PlatformRechargeActionButton
|
||||
ariaLabel={`${plan.title} ${cycleKind === 'yearly' ? '年付' : '月付'} ${actionLabel}`}
|
||||
@@ -716,9 +715,7 @@ function PlatformMembershipComparisonTable({
|
||||
},
|
||||
{
|
||||
label: '独立任务并发',
|
||||
values: plans.map((plan) =>
|
||||
resolveMembershipConcurrencyShortLabel(plan.concurrentJobLimit),
|
||||
),
|
||||
values: plans.map((plan) => resolveMembershipConcurrencyShortLabel(plan)),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1380,7 +1377,7 @@ export function PlatformProfileRechargeModal({
|
||||
plan={plan}
|
||||
cycleKind={cycleKind}
|
||||
isCurrentPlan={plan.plan === membership?.plan}
|
||||
recommended={plan.plan === RECOMMENDED_MEMBERSHIP_PLAN}
|
||||
recommended={plan.recommended}
|
||||
actionLabel={action.label}
|
||||
disabled={action.disabled}
|
||||
submitting={planSubmitting}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import type {
|
||||
ProfileMembershipModelAccess,
|
||||
ProfileMembershipPlan,
|
||||
ProfileMembershipPlanRecord,
|
||||
} from '../../contracts/runtime';
|
||||
|
||||
/**
|
||||
* 会员侧的纯展示文案。
|
||||
*
|
||||
* 价格、每期泥点、模型权限、并发上限、标题的唯一真相源是后端 `profile_membership_plan` 目录,
|
||||
* 这里只放后端没有、也不该由后端承担的市场表达(数量估算、推荐标签、省费口径)。
|
||||
* 价格、每期泥点、模型权限、并发上限、标题、推荐档位与「是否不设并发上限」的唯一真相源
|
||||
* 都是后端 `profile_membership_plan` 目录响应;这里只放后端没有、也不该由后端承担的
|
||||
* 市场表达(数量估算、省费口径)。
|
||||
*/
|
||||
export const RECOMMENDED_MEMBERSHIP_PLAN: ProfileMembershipPlan = 'plus';
|
||||
|
||||
/** 并发上限哨兵值:与后端 `MEMBERSHIP_UNLIMITED_CONCURRENCY` 对齐,`>= 128` 视为不设上限。 */
|
||||
export const UNLIMITED_CONCURRENT_JOB_LIMIT = 128;
|
||||
|
||||
type MembershipPlanCopy = {
|
||||
/** 每月预计可完成的数量估算。 */
|
||||
@@ -66,24 +64,26 @@ export function resolveMembershipModelAccessLabel(
|
||||
return modelAccess === 'full' ? '基础及高性能模型' : '基础模型';
|
||||
}
|
||||
|
||||
/** 套餐卡里的并发文案。 */
|
||||
/** 套餐卡里的并发文案;是否不设上限由后端 `unlimitedConcurrency` 标志决定。 */
|
||||
export function resolveMembershipConcurrencyLabel(
|
||||
concurrentJobLimit: number,
|
||||
plan: Pick<
|
||||
ProfileMembershipPlanRecord,
|
||||
'concurrentJobLimit' | 'unlimitedConcurrency'
|
||||
>,
|
||||
): string {
|
||||
return concurrentJobLimit >= UNLIMITED_CONCURRENT_JOB_LIMIT
|
||||
return plan.unlimitedConcurrency
|
||||
? '独立任务并发不设套餐上限'
|
||||
: `同时运行 ${concurrentJobLimit} 个独立任务`;
|
||||
: `同时运行 ${plan.concurrentJobLimit} 个独立任务`;
|
||||
}
|
||||
|
||||
/** 对比表里的并发短文案。 */
|
||||
export function resolveMembershipConcurrencyShortLabel(
|
||||
concurrentJobLimit: number,
|
||||
plan: Pick<
|
||||
ProfileMembershipPlanRecord,
|
||||
'concurrentJobLimit' | 'unlimitedConcurrency'
|
||||
>,
|
||||
): string {
|
||||
return isUnlimitedConcurrency(concurrentJobLimit)
|
||||
return plan.unlimitedConcurrency
|
||||
? '不设套餐上限'
|
||||
: `${concurrentJobLimit} 个`;
|
||||
}
|
||||
|
||||
export function isUnlimitedConcurrency(concurrentJobLimit: number): boolean {
|
||||
return concurrentJobLimit >= UNLIMITED_CONCURRENT_JOB_LIMIT;
|
||||
: `${plan.concurrentJobLimit} 个`;
|
||||
}
|
||||
|
||||
@@ -163,6 +163,10 @@ export type ProfileMembershipPlanRecord = {
|
||||
modelAccess: ProfileMembershipModelAccess;
|
||||
/** 并发上限;`128` 表示不设上限。 */
|
||||
concurrentJobLimit: number;
|
||||
/** 是否为运营推荐档位;由后端目录给出,前端不再硬编码档位 token。 */
|
||||
recommended: boolean;
|
||||
/** 是否不设并发上限;由后端按哨兵值派生,前端不再硬编码 `128`。 */
|
||||
unlimitedConcurrency: boolean;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ use axum::{
|
||||
};
|
||||
use hmac::{Hmac, Mac};
|
||||
use module_runtime::{
|
||||
AnalyticsGranularity, PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_H5, PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_JSAPI,
|
||||
AnalyticsGranularity, PROFILE_MEMBERSHIP_RECOMMENDED_PLAN,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_MOCK, PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_H5,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_JSAPI,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM_VIRTUAL,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_NATIVE, RuntimeProfileCodeOperationRecord,
|
||||
@@ -24,7 +25,7 @@ use module_runtime::{
|
||||
RuntimeProfileRewardCodeRedeemRecord, RuntimeProfileTaskCenterRecord,
|
||||
RuntimeProfileTaskClaimRecord, RuntimeProfileTaskConfigRecord, RuntimeProfileTaskCycle,
|
||||
RuntimeProfileTaskItemRecord, RuntimeProfileTaskStatus, RuntimeProfileWalletLedgerSourceType,
|
||||
RuntimeReferralInviteCenterRecord, RuntimeTrackingScopeKind,
|
||||
RuntimeReferralInviteCenterRecord, RuntimeTrackingScopeKind, is_unlimited_concurrency,
|
||||
};
|
||||
use platform_wechat::pay::{WechatPayError, WechatPayNotifyOrder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -2061,7 +2062,8 @@ fn build_profile_recharge_product_response(
|
||||
}
|
||||
}
|
||||
|
||||
/// 会员档位目录 → 对外响应;`concurrent_job_limit` 的 `128` 哨兵直接透出,由前端展示为“不设上限”。
|
||||
/// 会员档位目录 → 对外响应。`recommended` 与 `unlimited_concurrency` 由后端派生,
|
||||
/// 前端只消费标志,不再各自硬编码推荐档位或并发哨兵 `128`。
|
||||
fn build_profile_membership_plan_response(
|
||||
record: RuntimeProfileMembershipPlanRecord,
|
||||
) -> ProfileMembershipPlanResponse {
|
||||
@@ -2074,6 +2076,8 @@ fn build_profile_membership_plan_response(
|
||||
period_points: record.period_points,
|
||||
model_access: record.model_access.as_str().to_string(),
|
||||
concurrent_job_limit: record.concurrent_job_limit,
|
||||
recommended: record.plan == PROFILE_MEMBERSHIP_RECOMMENDED_PLAN,
|
||||
unlimited_concurrency: is_unlimited_concurrency(record.concurrent_job_limit),
|
||||
enabled: record.enabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ pub use llm_billing::*;
|
||||
// 通配会把公历基元和目录内部件都变成公开 API,模块继续增长后容易命名冲突;
|
||||
// 这里只重导出真正对外的会员契约符号,其余留在 `membership::` / `civil_calendar::` 命名空间下。
|
||||
pub use membership::{
|
||||
MEMBERSHIP_UNLIMITED_CONCURRENCY, RuntimeProfileMembershipPlanRecord,
|
||||
RuntimeProfileMembershipPlanSnapshot, RuntimeProfileMembershipUpgradeQuoteInput,
|
||||
beijing_local_micros, build_runtime_profile_membership_plan_snapshot,
|
||||
check_expired_membership_upgrade_settlement, is_unlimited_concurrency, membership_cycle_window,
|
||||
membership_expires_at, parse_runtime_profile_membership_product_id,
|
||||
quote_runtime_profile_membership_upgrade, resolve_runtime_profile_membership_plan_row,
|
||||
runtime_profile_membership_plan_catalog, runtime_profile_membership_plan_rank,
|
||||
runtime_profile_membership_product_id,
|
||||
MEMBERSHIP_UNLIMITED_CONCURRENCY, PROFILE_MEMBERSHIP_RECOMMENDED_PLAN,
|
||||
RuntimeProfileMembershipPlanRecord, RuntimeProfileMembershipPlanSnapshot,
|
||||
RuntimeProfileMembershipUpgradeQuoteInput, beijing_local_micros,
|
||||
build_runtime_profile_membership_plan_snapshot, check_expired_membership_upgrade_settlement,
|
||||
is_unlimited_concurrency, membership_cycle_window, membership_expires_at,
|
||||
parse_runtime_profile_membership_product_id, quote_runtime_profile_membership_upgrade,
|
||||
resolve_runtime_profile_membership_plan_row, runtime_profile_membership_plan_catalog,
|
||||
runtime_profile_membership_plan_rank, runtime_profile_membership_product_id,
|
||||
validate_runtime_profile_membership_plan_row_against_catalog,
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,11 @@ use crate::{
|
||||
RuntimeProfileMembershipModelAccess, RuntimeProfileMembershipPlan,
|
||||
};
|
||||
|
||||
/// 运营推荐档位。推荐是后端单一真相源:目录响应按此给出 `recommended` 标志,
|
||||
/// 前端只消费标志,不再各自硬编码 `'plus'`。
|
||||
pub const PROFILE_MEMBERSHIP_RECOMMENDED_PLAN: RuntimeProfileMembershipPlan =
|
||||
RuntimeProfileMembershipPlan::Plus;
|
||||
|
||||
/// 目录一行:一个档位的可配置价格与权益。
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -274,6 +274,10 @@ pub struct ProfileMembershipPlanResponse {
|
||||
pub model_access: String,
|
||||
/// 并发上限;`128` 表示不设上限。
|
||||
pub concurrent_job_limit: u32,
|
||||
/// 是否为运营推荐档位;由后端目录决定,前端不再硬编码档位 token。
|
||||
pub recommended: bool,
|
||||
/// 是否不设并发上限;由后端按哨兵值派生,前端不再硬编码 `128`。
|
||||
pub unlimited_concurrency: bool,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
@@ -1699,6 +1703,8 @@ mod tests {
|
||||
period_points: 1_150,
|
||||
model_access: "full".to_string(),
|
||||
concurrent_job_limit: 3,
|
||||
recommended: true,
|
||||
unlimited_concurrency: false,
|
||||
enabled: true,
|
||||
}],
|
||||
latest_order: None,
|
||||
|
||||
Reference in New Issue
Block a user