fix(storefront): 多价目产品显示价带因子表头(非缓存输入Token/输出tokens/缓存Token)+模型产品去立即购买按钮改「按量付费」字样

- core.py get_customer_price_display 第4步: len(prices)>1 且 label 空/按量/容量时,
  段前拼 factor_label(定价YAML fields 的 label),根治裸「¥2.1 元/百万 | ¥8.4 元/百万」
  客户看不懂各是什么价;单价目(存储)/产线(包月包年)行为不变
- index.ui: is_model(pipeline_llm_model/llm_model) 卡片无 binds(cursor=default,
  不再弹购买框),右下角出「按量付费」文字替代立即购买按钮——purchase_realtime
  只支持 account/storage/pipeline,模型消费走推理入口按量落账,购买按钮本身是坏流程
- README 补「商店展示价与卡片行为」节;验证脚本 test_storefront_model_card.py 30断言全过
This commit is contained in:
yumoqing 2026-09-08 17:56:38 +08:00
parent b0b74a2b9f
commit c74b9cb885
3 changed files with 33 additions and 2 deletions

View File

@ -242,6 +242,19 @@ product_use(product_id, user_id, user_org_id, used_amount, used_unit)
└─ 写入 product_usage_log
```
### 商店展示价与卡片行为storefront
- **展示价唯一入口** `get_customer_price_display(product_id, user_org_id)`:显示价 = 资源真实定价 × 客户折扣率(专属折扣 → `'*'` 兜底 → 1.0;未登录按 `'*'` 报价,访客可见)。
- **多价目必带表头**2026-09-08产品有多个价格因子时如模型按量的 非缓存输入Token/输出tokens/缓存Token 三档),`pricing_text` 每段前拼 `factor_label`(来自定价 YAML fields 的 label禁止输出裸的「¥2.1 元/百万 | ¥8.4 元/百万」让客户猜含义。单价目(如存储 元/GB月不加表头产线用 包月/包年 表头。
- **卡片按钮按 product_type 分流**
| product_type | 卡片点击 | 右下角 |
|---|---|---|
| pipeline | 进产线 open_tab规划中→提示 | 订阅制说明 + 立即购买/进入产线 |
| workspace_storage / account | 弹 purchase_confirm 购买框 | 立即购买(未登录→登录后购买) |
| pipeline_llm_model / llm_model | **无点击行为**cursor=default | **「按量付费」文字,无按钮** |
模型产品不出购买入口的根因:`purchase_realtime` 只支持 account/storage/pipeline 三类(订阅/买断性质),模型消费走推理入口(会话/llm_v1按用量落账无需也无法「购买」。
## 目录结构
```

View File

@ -1415,10 +1415,15 @@ class ProductManager:
'discount': discount, 'original_text': ''}
# 4. 拼展示文本折扣≠1 时同时给原价,让客户看见让利/加价幅度)
# 多价目产品(如模型按量:输入/输出/缓存三档 tokens 价)必须带因子表头,
# 否则客户看到裸的「¥2.1 元/百万 | ¥8.4 元/百万」不知道各是什么价
# 2026-09-08 用户反馈。factor_label 来自定价 YAML fields 的 label。
parts = []
orig_parts = []
for p in prices:
head = p['label'] if p['label'] and p['label'] not in ('按量', '容量') else ''
if not head and len(prices) > 1:
head = p.get('factor_label') or p.get('factor') or ''
seg = ('%s ' % head if head else '') + '¥%.4g %s' % (
p['amount'], p['unit_label'] or '')
parts.append(seg.strip())

View File

@ -56,6 +56,9 @@
{% set pricing_text = (cust or {}).get('pricing_text', '定价暂不可用') if (cust or {}).get('success') else ((cust or {}).get('message') or '定价暂不可用') %}
{% set original_text = (cust or {}).get('original_text', '') %}
{% set is_pipeline = (p.get('product_type') == 'pipeline') %}
{# 模型按量产品不支持也无需「购买」purchase_realtime 只支持 account/storage/pipeline
卡片不弹购买框、不出立即购买按钮改为「按量付费」字样2026-09-08 用户要求) #}
{% set is_model = (p.get('product_type') in ('pipeline_llm_model', 'llm_model')) %}
{% set extra = {} %}
{% if is_pipeline %}
{# 产线订阅状态:已订阅→「进入产线」直达;未订阅→「立即购买」弹购买确认 #}
@ -73,7 +76,7 @@
"cheight": 15,
"padding": "20px",
"bgcolor": "#fff",
"cursor": "pointer",
"cursor": {{json.dumps('default' if is_model else 'pointer')}},
"gap": "8px"
},
{% if is_pipeline %}
@ -96,7 +99,7 @@
}
{% endif %}
],
{% else %}
{% elif not is_model %}
"binds": [
{
"wid": "self",
@ -221,6 +224,16 @@
}
]
}
{% elif is_model %}
{
"widgettype": "Text",
"options": {
"text": "按量付费",
"fontSize": "13px",
"color": "#e74c3c",
"fontWeight": "600"
}
}
{% elif userid %}
{
"widgettype": "Button",