yumoqing 1f399d6b00 feat(storefront): 显示价=定价×客户折扣率(三类统一)+产线订阅购买链路+修既有控件bug
1) get_customer_price_display 新增(core.py + __init__.py + env 注册)
   统一三类产品对外报价口径:显示价 = 资源真实定价 × 客户折扣率
   - 产线 pipeline_pricing_map / 存储 storres_pricing_map / 模型 llm_model.ppid
     各自解析 ppid → get_pricing_display 取单位价 → × 折扣
   - 折扣率解析走 discount 模块 get_min_product_discount(专属优先/'*'兜底/1.0)
     >1 加价(存储1.5)、<1 让利(百炼0.9);折扣≠1 时返回 original_text 原价
   - 未登录(user_org_id 空)传 '' 只命中 '*' 通用折扣 → 访客可见报价

2) purchase_realtime 加产线分支(product_type=='pipeline')
   订阅制:无 usage 表,直接算价→余额预检→product_accounting_generic 实时记账→开订阅权益
   charge_mode month/year,entitlement.duration_days 30/365

3) 展示页 index.ui:接 get_customer_price_display;产线卡显示月/年价+原价划线+订阅态文案;
   按钮按订阅状态分流(已订阅→进入产线 / 未订阅→立即购买);product_code 兼容
   opportunity_general/bidding_general/sdlc_general 与旧 PIPE-* 两套映射

4) 修两个既有 bug(购买页原本就坏):
   - InputNumber 控件 bricks 未注册 → widgetBuild 返回 null 静默不渲染,改 UiInt
   - 支付脚本 getWidgetById 缺第二参数(查找起点) → from_widget undefined 抛 TypeError
     点击支付无反应,补 bricks.app.root;UiType 取值 .value → resultValue()
   - UiType 系控件同时传 name(表单/getValue)+id(set_id 覆盖 DOM id 供 getWidgetById)

本地 mock 渲染测试 6 项全过:三类价格/订阅态分流/规划中占位/未登录访客可见价无购买按钮
2026-09-06 16:55:49 +08:00

284 lines
13 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% set cats_r = get_category_tree(org_id='0') %}
{% set cats = (cats_r or {}).get('tree', []) if (cats_r or {}).get('success') else [] %}
{% set userid = get_user() %}
{% set userorgid = get_userorgid() %}
{
"widgettype": "VBox",
"options": {
"width": "100%",
"height": "100%",
"padding": "0",
"bgcolor": "#f1f5f9"
},
"subwidgets": [
{
"widgettype": "TabPanel",
"options": {
"css": "filler",
"width": "100%",
"height": "100%",
"tab_pos": "top",
"items": [
{% for cat in cats %}
{% set pr = get_products_by_category(cat.id, org_id='0') %}
{% set prods = (pr or {}).get('products', []) %}
{
"name": "cat_{{cat.id}}",
"label": {{json.dumps(cat.name or '', ensure_ascii=False)}},
"content": {
"widgettype": "VScrollPanel",
"options": {
"width": "100%",
"height": "100%"
},
"subwidgets": [
{
"widgettype": "VBox",
"options": {
"width": "100%",
"padding": "20px 24px",
"gap": "16px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"width": "100%",
"flexWrap": "wrap",
"gap": "16px"
},
"subwidgets": [
{% set ns = namespace(first=true) %}
{% for p in prods %}
{% if p.get('is_active', true) %}
{# 显示价 = 资源真实定价 × 客户折扣率2026-09-06未登录按 '*' 通用折扣报价 #}
{% 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', '') %}
{% set is_pipeline = (p.get('product_type') == 'pipeline') %}
{% set extra = {} %}
{% if is_pipeline %}
{# 产线订阅状态:已订阅→「进入产线」直达;未订阅→「立即购买」弹购买确认 #}
{% set sub = check_subscription_valid(p.get('id'), userorgid or '') if userorgid else {'valid': False} %}
{% set _urls = {'PIPE-OPP': '/pipeline-opportunity/agent', 'PIPE-BID': '/pipeline-bidding/agent', 'PIPE-DEV': '/pipeline_core/agent', 'opportunity_general': '/pipeline-opportunity/agent', 'bidding_general': '/pipeline-bidding/agent', 'sdlc_general': '/pipeline_core/agent'} %}
{% set extra = {'tab_name': 'pipeline_card_' + str(p.get('product_code','')), 'tab_label': p.get('product_name') or '', 'url': _urls.get(p.get('product_code') or '', ''), 'planned': not _urls.get(p.get('product_code') or '', ''), 'message': (p.get('product_name') or '') + '规划中,尚未开发', 'subscribed': (sub or {}).get('valid', False), 'sub_end': (sub or {}).get('end_date', '')} %}
{% endif %}
{% if not ns.first %},{% endif %}
{% set ns.first = false %}
{
"widgettype": "VBox",
"options": {
"css": "card",
"cwidth": 24,
"cheight": 15,
"padding": "20px",
"bgcolor": "#fff",
"cursor": "pointer",
"gap": "8px"
},
{% if is_pipeline %}
"binds": [
{% if extra.planned %}
{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": {{json.dumps("bricks.show_message({title:" + json.dumps(extra.tab_label, ensure_ascii=False) + ",message:" + json.dumps(extra.message, ensure_ascii=False) + "});", ensure_ascii=False)}}
}
{% else %}
{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": {{json.dumps("var _tp=bricks.getWidgetById('main_content',bricks.app);if(_tp&&_tp.open_tab){_tp.open_tab({name:" + json.dumps(extra.tab_name, ensure_ascii=False) + ",label:" + json.dumps(extra.tab_label, ensure_ascii=False) + ",url:" + json.dumps(extra.url, ensure_ascii=False) + ",removable:true});}", ensure_ascii=False)}}
}
{% endif %}
],
{% else %}
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "PopupWindow",
"popup_options": {
"title": {{json.dumps('购买 - ' + (p.get('product_name') or ''), ensure_ascii=False)}},
"cwidth": 40,
"cheight": 26
},
"options": {
"method": "POST",
"url": "{{entire_url('/product_management/storefront/api/purchase_confirm.dspy')}}",
"params": {
"product_id": {{json.dumps(p.get('id') or '', ensure_ascii=False)}}
}
}
}
],
{% endif %}
"subwidgets": [
{
"widgettype": "Title4",
"options": {
"text": {{json.dumps(p.get('product_name') or '', ensure_ascii=False)}},
"color": "#1e293b"
}
},
{
"widgettype": "VScrollPanel",
"options": {
"flex": "1",
"width": "100%"
},
"subwidgets": [
{
"widgettype": "Text",
"options": {
"text": {{json.dumps(p.get('brief_intro') or '暂无介绍', ensure_ascii=False)}},
"fontSize": "13px",
"color": "#64748b",
"wrap": true
}
}
]
},
{% if is_pipeline and extra.planned %}
{
"widgettype": "Text",
"options": {
"text": "规划中",
"fontSize": "14px",
"color": "#94a3b8",
"fontWeight": "600"
}
},
{% else %}
{
"widgettype": "Text",
"options": {
"text": {{json.dumps(pricing_text, ensure_ascii=False)}},
"fontSize": "14px",
"color": "#e74c3c"
}
},
{% if original_text %}
{
"widgettype": "Text",
"options": {
"text": {{json.dumps(original_text, ensure_ascii=False)}},
"fontSize": "11px",
"color": "#94a3b8",
"textDecoration": "line-through"
}
},
{% endif %}
{% if is_pipeline %}
{
"widgettype": "Text",
"options": {
"text": {{json.dumps(('订阅制 · 已订阅至 ' + extra.sub_end) if extra.subscribed else '订阅制 · 模型/存储消耗另计', ensure_ascii=False)}},
"fontSize": "11px",
"color": {{json.dumps('#27ae60' if extra.subscribed else '#94a3b8')}}
}
},
{% endif %}
{% endif %}
{
"widgettype": "HBox",
"options": {
"width": "100%",
"justifyContent": "flex-end"
},
"subwidgets": [
{% if is_pipeline and extra.planned %}
{
"widgettype": "Text",
"options": {
"text": "敬请期待",
"fontSize": "13px",
"color": "#94a3b8"
}
}
{% elif is_pipeline and extra.subscribed %}
{
"widgettype": "Button",
"options": {
"label": "进入产线",
"bgcolor": "#2563eb",
"color": "#fff",
"padding": "8px 24px",
"borderRadius": "6px"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "script",
"target": "self",
"script": {{json.dumps("var _tp=bricks.getWidgetById('main_content',bricks.app);if(_tp&&_tp.open_tab){_tp.open_tab({name:" + json.dumps(extra.tab_name, ensure_ascii=False) + ",label:" + json.dumps(extra.tab_label, ensure_ascii=False) + ",url:" + json.dumps(extra.url, ensure_ascii=False) + ",removable:true});}", ensure_ascii=False)}}
}
]
}
{% elif userid %}
{
"widgettype": "Button",
"options": {
"label": "立即购买",
"bgcolor": "#e74c3c",
"color": "#fff",
"padding": "8px 24px",
"borderRadius": "6px"
},
"binds": [
{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "PopupWindow",
"popup_options": {
"title": {{json.dumps('购买 - ' + (p.get('product_name') or ''), ensure_ascii=False)}},
"cwidth": 40,
"cheight": 26
},
"options": {
"method": "POST",
"url": "{{entire_url('/product_management/storefront/api/purchase_confirm.dspy')}}",
"params": {
"product_id": {{json.dumps(p.get('id') or '', ensure_ascii=False)}}
}
}
}
]
}
{% else %}
{
"widgettype": "Text",
"options": {
"text": "登录后购买",
"fontSize": "13px",
"color": "#94a3b8"
}
}
{% endif %}
]
}
]
}
{% endif %}
{% endfor %}
]
}
]
}
]
}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}
}
]
}