销售侧汇总统一叫费用合计,并认准空运费的金额。

企微跳转改为账号密码登录,报价卡上的费用按原金额改回;航线按单价数量金额报价时不再记成海运费。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-24 11:15:33 +08:00
co-authored by Cursor
parent 84207bb4e5
commit 4dfd756bcf
24 changed files with 778 additions and 150 deletions
@@ -11,6 +11,7 @@ from __future__ import annotations
import html
from typing import Any
from agent.channel.h5.wecom_close import wecom_auto_close_script
from agent.schema.air_route_options import (
option_fee_lines,
option_total,
@@ -226,10 +227,11 @@ def render_air_route_page(payload: dict[str, Any]) -> str:
def render_air_route_ok() -> str:
"""选定成功。"""
"""选定成功:短暂展示后关企微 H5。"""
return _wrap(
"已选定",
"<div class='msg'><h1>已选定</h1><p>请回企微查看价格明细。</p></div>",
"<div class='msg'><h1>已选定</h1><p>正在返回企微,请查看价格明细。</p></div>",
auto_close=True,
)
@@ -272,12 +274,13 @@ def _option_card(index: int, option: dict[str, Any], quote: dict[str, Any], fact
)
def _wrap(title: str, body: str) -> str:
"""整页壳:标题、viewport、共用样式。"""
def _wrap(title: str, body: str, *, auto_close: bool = False) -> str:
"""整页壳:标题、viewport、共用样式。成功页可挂企微关窗脚本。"""
closer = wecom_auto_close_script() if auto_close else ""
return (
"<!doctype html><html lang='zh-CN'><head><meta charset='utf-8' />"
"<meta name='viewport' content='width=device-width, initial-scale=1' />"
f"<title>{html.escape(title)}</title>"
f"<style>{_PAGE_CSS}</style></head><body>"
f"{body}</body></html>"
f"{body}{closer}</body></html>"
)
@@ -56,10 +56,11 @@ def render_land_route_page(payload: dict[str, Any]) -> str:
def render_land_route_ok() -> str:
"""选定成功。"""
"""选定成功:短暂展示后关企微 H5。"""
return _wrap(
"已选定",
"<div class='msg'><h1>已选定</h1><p>请回企微查看价格明细。</p></div>",
"<div class='msg'><h1>已选定</h1><p>正在返回企微,请查看价格明细。</p></div>",
auto_close=True,
)
@@ -10,6 +10,7 @@ from __future__ import annotations
from html import escape
import json
from agent.channel.h5.wecom_close import wecom_auto_close_script
from agent.policy import inquiry_copy as copy
# 三页共用:浅底、白卡片、企微蓝主按钮。禁止在页面里写 userid。
@@ -188,7 +189,8 @@ def _shell(*, title: str, body: str, tone: str = "ok") -> str:
"""成功 / 拒绝 / 过期共用卡片壳,避免三页三种简陋白底。"""
icons = {"ok": _ICON_OK, "warn": _ICON_WARN, "expired": _ICON_EXPIRED}
icon = icons.get(tone, _ICON_WARN)
extra = '<p class="back">请回企微查看识别结果。</p>' if tone == "ok" else ""
extra = '<p class="back">正在返回企微,请查看识别结果。</p>' if tone == "ok" else ""
closer = wecom_auto_close_script() if tone == "ok" else ""
return f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
@@ -210,6 +212,7 @@ def _shell(*, title: str, body: str, tone: str = "ok") -> str:
{extra}
</div>
</div>
{closer}
</body>
</html>
"""
@@ -0,0 +1,96 @@
"""
企微内嵌 H5 成功后自动关页。
本文件职责:给附件上传成功、选线路成功页一段共用脚本。
手机端可走 WeixinJSBridge;企微 Windows 客户端没有这套桥,必须像复制工单号页一样
先加载 wecom-jssdk、用现成 /inquiry-form/jsapi-signature 做 ww.register,再 closeWindow。
失败、过期页不要挂这段。
副作用:只产出 HTML 片段;关窗签名走浏览器短请求,不占智能体请求线程做慢活。
"""
from __future__ import annotations
from html import escape
from agent.config import get_settings
# 签名完成后再稍停,让销售看见「已成功」。
_CLOSE_DELAY_MS = 400
def wecom_auto_close_script() -> str:
"""
成功页脚:加载企微 JS-SDK,register 后关掉当前 H5 窗口。
线程:浏览器端执行。签名接口与复制页共用,ticket 已缓存在 Redis。
为何这样写:上一版只调 WeixinJSBridge,电脑端企微关不掉,会停在成功页。
"""
settings = get_settings()
corp = escape((settings.wecom_corp_id or "").strip(), quote=True)
agent = escape(str(settings.wecom_agent_id or "1000010").strip(), quote=True)
delay = _CLOSE_DELAY_MS
return f"""<script src="https://wwcdn.weixin.qq.com/node/wework/open/js/wecom-jssdk-2.3.2.js"></script>
<script>
(function () {{
var corpId = "{corp}";
var agentId = Number("{agent}") || 0;
function sign(kind) {{
return function (url) {{
return fetch("/inquiry-form/jsapi-signature?kind=" + encodeURIComponent(kind)
+ "&url=" + encodeURIComponent(url))
.then(function (r) {{
if (!r.ok) throw new Error("sign");
return r.json();
}});
}};
}}
function nativeClose() {{
try {{
if (typeof WeixinJSBridge !== "undefined") {{
WeixinJSBridge.call("closeWindow");
}}
}} catch (e) {{}}
try {{
if (window.ww && typeof ww.closeWindow === "function") {{
ww.closeWindow();
}}
}} catch (e) {{}}
try {{
if (window.wx && typeof wx.closeWindow === "function") {{
wx.closeWindow();
}}
}} catch (e) {{}}
try {{ window.close(); }} catch (e) {{}}
}}
function boot() {{
if (typeof WeixinJSBridge !== "undefined") {{
setTimeout(nativeClose, {delay});
}}
document.addEventListener("WeixinJSBridgeReady", nativeClose, false);
var ready = Promise.resolve();
if (window.ww && ww.register && corpId) {{
ready = ww.register({{
corpId: corpId,
agentId: agentId,
jsApiList: ["closeWindow"],
getConfigSignature: sign("corp"),
getAgentConfigSignature: sign("agent")
}}).catch(function () {{ return null; }});
}}
ready.then(function () {{
setTimeout(nativeClose, {delay});
}}).catch(function () {{
setTimeout(nativeClose, {delay});
}});
}}
if (document.readyState === "loading") {{
document.addEventListener("DOMContentLoaded", boot);
}} else {{
boot();
}}
}})();
</script>"""