feat(storefront): 多价目定价改表格展示——表头=定价要素+价格,行=要素组合对应价

病根:qwen-image-3.0-pro 按分辨率×类型四档价,一行文本拼出「按次 ¥0.02 元/张 |
按次 ¥0.02 元/张 | 按次 ¥0.25 元/张 | 按次 ¥0.5 元/张」,客户看不出各价适用条件。

- core.py 新增 _build_price_table:len(prices)>1 时返回 price_table
  {headers,rows,html};要素列=计费方式(label有区分度时)+定价维度(filter_labels,
  英文维度名中文润饰)+计价因子(factor各行不同时,token三因子);全表同因子(flat)
  省略因子列;让利时价格附划线原价;html 全量 escape 防注入
- get_customer_price_display 返回值增 price_table 键;有表格时 original_text 置空
  (原价已进表格单元格)
- storefront/index.ui 价格区:有 price_table 渲染 Html 表格,否则原文本路径不变
  (单价目无歧义)
- 实测:图像四档/token三因子/视频SR分档/产线包月包年四场景表头行值正确,XSS转义通过
This commit is contained in:
yumoqing 2026-09-09 11:59:14 +08:00
parent e1963b30c5
commit 100dbe28cd
3 changed files with 128 additions and 3 deletions

View File

@ -247,6 +247,7 @@ product_use(product_id, user_id, user_org_id, used_amount, used_unit)
- **展示价唯一入口** `get_customer_price_display(product_id, user_org_id)`:显示价 = 资源真实定价 × 客户折扣率(专属折扣 → `'*'` 兜底 → 1.0;未登录按 `'*'` 报价,访客可见)。
- **原价仅让利时显示**2026-09-08折扣 <1 才输出 `original_text` 原价划线折扣 1无折扣/加价如存储 1.5产线 1.0不显示原价——加价时原价低于现价还划线是误导展示页core.py与购买页purchase_confirm.dspy 服务端初始价 + 前端 JS 刷新脚本三处同规则
- **多价目必带表头**2026-09-08产品有多个价格因子时如模型按量的 非缓存输入Token/输出tokens/缓存Token 三档),`pricing_text` 每段前拼 `factor_label`(来自定价 YAML fields 的 label禁止输出裸的「¥2.1 元/百万 | ¥8.4 元/百万」让客户猜含义。单价目(如存储 元/GB月不加表头产线用 包月/包年 表头。
- **多价目表格化**2026-09-09`len(prices)>1``get_customer_price_display` 额外返回 `price_table = {'headers','rows','html'}`——一行文本拼「按次 ¥0.02 | 按次 ¥0.02 | ...」qwen-image-3.0-pro 按分辨率×类型四档价)客户看不出适用条件。要素列生成规则(`_build_price_table`计费方式列label 有区分度时,产线包月/包年)→ 定价维度列YAML role=filter 的 filter_labels如 分辨率/类型/SR英文维度名经 `_DIM_LABEL_POLISH` 润饰为中文)→ 计价因子列factor 各行不同时token 三因子)。全表同因子(如 flat=按次)省略因子列——价格单位(元/张)已表达口径。让利(折扣<1时价格单元格附划线原价storefront/index.ui 卡片价格区 `price_table` 渲染 Html 表格控件否则走文本+划线原价单价目无歧义不变)。html 全部经 `html.escape` 转义维度值来自定价 YAML防注入)。
- **卡片按钮按 product_type 分流**
| product_type | 卡片点击 | 右下角 |
|---|---|---|

View File

@ -2,6 +2,7 @@
import json
import time
import datetime
from html import escape as _html_escape
from appPublic.uniqueID import getID
import json, time
@ -1293,6 +1294,100 @@ class ProductManager:
return {'success': False, 'message': err}
return await fn(ref_id)
# 多价目表格化:常见英文维度名 → 中文表头润饰YAML fields 无中文 label 时兜底,
# 数据源头 label 由提取层生成,此处只做展示润饰不做语义裁决)
_DIM_LABEL_POLISH = {
'resolution': '分辨率', 'type': '类型', 'size': '尺寸',
'quality': '质量', 'duration': '时长', 'style': '风格',
}
# ppid_pairs 的通用挂载标签——对表格无区分度,不作为要素列
_GENERIC_PRICE_LABELS = ('', '按量', '容量')
def _build_price_table(self, prices, discount):
"""多价目定价表格2026-09-09 用户要求)。
病根同一模型多条定价qwen-image-3.0-pro 分辨率×类型 四档价拼成
一行文本时全是重复前缀按次 ¥0.02 / | ...客户看不出各价的适用
条件改为表格表头 = 定价要素1..N + 价格 = 要素取值组合 价格
要素列生成规则回答什么条件用哪个价
- 计费方式列prices[].label 有区分度 ''/按量/容量时出现
产线 包月/包年 两行
- 定价维度列定价 YAML role=filter 维度filter_labels按首次出现序
图像模型 分辨率×类型视频模型 SR
- 计价因子列factor 各行不同时出现token 模型 输入/输出/缓存三因子
全表同因子 flat=按次时省略价格列单位/已表达口径
Returns:
{'headers': [...], 'rows': [[要素值..., 价格文本]], 'html': '<table>…'}
html .ui 直接嵌入 Html 控件让利discount<1时价格单元格附划线原价
"""
cols = [] # [(key, header)]
seen = set()
def _add_col(key, header):
if key not in seen:
seen.add(key)
cols.append((key, header))
if any((p.get('label') or '') not in self._GENERIC_PRICE_LABELS
for p in prices):
_add_col('__label__', '计费方式')
for p in prices:
for k in (p.get('filter_labels') or {}):
header = self._DIM_LABEL_POLISH.get(str(k), '') or str(k)
_add_col('f:' + str(k), header)
factors = set((p.get('factor') or '') for p in prices)
if len(factors) > 1 or not cols:
_add_col('__factor__', '计价因子')
# 表头兜底:无 label 的列按用户口径命名「定价要素N」
headers = []
for i, (k, h) in enumerate(cols):
headers.append(h if str(h).strip() else '定价要素%d' % (i + 1))
headers.append('价格')
rows = []
html = ['<table style="border-collapse:collapse;width:100%;'
'font-size:11px;line-height:1.5;">']
th_style = ('padding:3px 6px;border:1px solid #e2e8f0;background:#f8fafc;'
'color:#475569;font-weight:600;text-align:left;'
'white-space:nowrap;')
td_style = ('padding:3px 6px;border:1px solid #e2e8f0;color:#334155;'
'white-space:nowrap;')
html.append('<tr>' + ''.join(
'<th style="%s">%s</th>' % (th_style, _html_escape(str(h)))
for h in headers) + '</tr>')
for p in prices:
vals = []
for key, _h in cols:
if key == '__label__':
v = p.get('label') or ''
v = '' if v in self._GENERIC_PRICE_LABELS else v
elif key == '__factor__':
v = p.get('factor_label') or p.get('factor') or ''
else:
v = (p.get('filter_labels') or {}).get(key[2:], '')
vals.append(str(v if v is not None else ''))
price_text = '¥%.4g %s' % (p['amount'], p.get('unit_label') or '')
if discount < 1.0 - 1e-9:
price_text += '(原价 ¥%.4g' % p['original_price']
rows.append(vals + [price_text])
price_html = ('<span style="color:#e74c3c;font-weight:600;">%s</span>'
% _html_escape('¥%.4g %s' % (
p['amount'], p.get('unit_label') or '')))
if discount < 1.0 - 1e-9:
price_html += (' <span style="color:#94a3b8;text-decoration:'
'line-through;"%.4g</span>' % p['original_price'])
html.append('<tr>' + ''.join(
'<td style="%s">%s</td>' % (td_style, _html_escape(v))
for v in vals)
+ '<td style="%s">%s</td></tr>' % (td_style, price_html))
html.append('</table>')
return {'headers': headers, 'rows': rows, 'html': ''.join(html)}
async def get_customer_price_display(self, product_id=None, product_code=None,
user_org_id=None):
"""客户视角展示价 = 资源真实定价 × 客户折扣率2026-09-06 新增)。
@ -1309,8 +1404,10 @@ class ProductManager:
未登录user_org_id 时用 '*' 通用折扣报价展示页对访客可见
Returns:
{'success', 'prices': [{'label','unit_price','unit_label','amount','unit'}],
'pricing_text', 'discount', 'original_text'}
{'success', 'prices': [{'label','unit_price','unit_label','amount','unit',
'filter_labels','original_price'}],
'pricing_text', 'discount', 'original_text',
'price_table': {'headers','rows','html'} None多价目时给出}
"""
iface, product, err = await self._get_product_interface(
product_id, product_code)
@ -1405,6 +1502,7 @@ class ProductManager:
'factor_label': pf.get('label', ''),
'unit': pf.get('unit', ''),
'unit_label': unit_suffix or pf.get('unit_label', ''),
'filter_labels': item.get('filter_labels') or {},
'original_price': round(total, 6),
'amount': round(total * discount, 6),
})
@ -1414,6 +1512,15 @@ class ProductManager:
'prices': [], 'pricing_text': '未配置定价',
'discount': discount, 'original_text': ''}
# 3b. 多价目表格化2026-09-09 用户要求):同一模型多条定价(如
# qwen-image-3.0-pro 按 分辨率×类型 四档价)拼成一行文本时客户
# 看不出各价适用条件(「按次 ¥0.02 | 按次 ¥0.02 | ...」),改为
# 表格展示:表头 = 定价要素1..N + 价格,每行 = 一组要素取值对应的价格。
# 单价目产品仍走文本(无歧义)。
price_table = None
if len(prices) > 1:
price_table = self._build_price_table(prices, discount)
# 4. 拼展示文本(仅折扣<1 让利时给原价划线,让客户看见优惠幅度;
# 折扣≥1无折扣/加价不显示原价——2026-09-08 用户定夺:加价时
# 「原价」低于现价还划线展示是误导,产线展示页/购买页同规则)
@ -1437,9 +1544,13 @@ class ProductManager:
if orig_parts:
original_text = '原价 %s %s' % (
' / '.join(orig_parts), prices[0]['unit_label'] or '')
if price_table:
# 有表格时文本降为摘要(一行文本装不下要素条件,仅作降级展示)
original_text = ''
return {'success': True, 'prices': prices, 'pricing_text': pricing_text,
'original_text': original_text, 'discount': discount}
'original_text': original_text, 'discount': discount,
'price_table': price_table}
async def check_product_availability(self, product_id=None, product_code=None,
user_org_id=None):

View File

@ -55,6 +55,9 @@
{% set cust = get_customer_price_display(p.get('id'), user_org_id=userorgid or '') %}
{% 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', '') %}
{# 多价目产品(如模型按分辨率×类型分档)用表格展示定价要素+价格2026-09-09 用户要求),
一行文本拼「A ¥x | A ¥y」客户看不出适用条件单价目仍走文本 #}
{% set price_table = (cust or {}).get('price_table') or none %}
{% set is_pipeline = (p.get('product_type') == 'pipeline') %}
{# 模型按量产品不支持也无需「购买」purchase_realtime 只支持 account/storage/pipeline
卡片不弹购买框、不出立即购买按钮改为「按量付费」字样2026-09-08 用户要求) #}
@ -157,6 +160,15 @@
"fontWeight": "600"
}
},
{% else %}
{% if price_table %}
{# 多价目:表格展示(要素列+价格列),产线包月/包年、模型分辨率×类型分档都走这里 #}
{
"widgettype": "Html",
"options": {
"html": {{json.dumps(price_table.html, ensure_ascii=False)}}
}
},
{% else %}
{
"widgettype": "Text",
@ -177,6 +189,7 @@
}
},
{% endif %}
{% endif %}
{% if is_pipeline %}
{
"widgettype": "Text",