pipeline-opportunity/wwwroot/api/opp_item_detail.dspy

202 lines
8.6 KiB
Plaintext
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.

# opp_item_detail.dspy - 单条数据项的具体内容弹窗(数据来自数据爬取平台)
# 入参:id=<爬虫平台条目id>。展示公告/需求全量字段 + 公告全文 + 来源说明 + 附件列表。
# 附件点击下载:走 opp_attachment_dl.dspy 代理(平台副本优先,缺失回退原始地址)。
import json as _json
uid = await get_user()
if not uid:
return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}}
item_id = str((params_kw or {}).get("id") or "").strip()
if not item_id:
return {"widgettype": "Message", "options": {"title": "缺少参数", "message": "缺少 id"}}
dbname = get_module_dbname('pipeline-opportunity')
async with DBPools().sqlorContext(dbname) as sor:
from pipeline_opportunity.opp_data_capability import tender_detail, tender_attachments
from pipeline_opportunity.opp_common import source_text
ok, det = await tender_detail(sor, item_id)
await sor.sqlExe("COMMIT", {})
att_ok, atts = await tender_attachments(sor, item_id)
await sor.sqlExe("COMMIT", {})
dl_base = entire_url("/pipeline-opportunity/api/opp_attachment_dl.dspy")
body = []
if not ok or not isinstance(det, dict):
body.append({"widgettype": "Text", "options": {
"text": "获取详情失败:" + str(det)[:200],
"color": "#dc2626", "padding": "12px", "halign": "left"}})
else:
# ── 标题 ──
body.append({"widgettype": "Text", "options": {
"text": str(det.get("title") or ""), "cfontsize": 1.15,
"color": "#0f172a", "halign": "left", "wrap": True,
"margin": "0 0 8px 0"}})
# ── 字段区 ──
rt = str(det.get("record_type") or "tender")
budget = det.get("budget_wan") or 0
fields = []
fields.append(("发布时间", str(det.get("publish_time") or "—")))
if rt == "tender":
fields.append(("公告类型", str(det.get("notice_type") or "—")))
fields.append(("地区", str(det.get("region") or "—")))
fields.append(("采购人", str(det.get("buyer") or "—")))
fields.append(("预算", ("%.2f 万元" % budget) if budget else "未公开"))
if det.get("item_category"):
fields.append(("品目", str(det.get("item_category"))))
if det.get("project_code"):
fields.append(("项目编号", str(det.get("project_code"))))
if det.get("agency"):
fields.append(("代理机构", str(det.get("agency"))))
if det.get("contact") or det.get("contact_phone"):
fields.append(("联系人", ("%s %s" % (str(det.get("contact") or ""),
str(det.get("contact_phone") or ""))).strip()))
else:
fields.append(("需求类型", str(det.get("notice_type") or "—")))
if det.get("item_category"):
fields.append(("站内类目", str(det.get("item_category"))))
fields.append(("赏金/预算", ("%.2f 万元" % budget) if budget else "未公开"))
if det.get("ai_score"):
fields.append(("AI相关度", str(det.get("ai_score"))))
field_rows = []
for k, v in fields:
field_rows.append({
"widgettype": "HBox",
"options": {"width": "100%", "gap": "8px", "padding": "2px 0"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": k, "cfontsize": 0.85,
"color": "#64748b", "halign": "left", "width": "90px"}},
{"widgettype": "Text", "options": {"text": v, "cfontsize": 0.9,
"color": "#0f172a", "halign": "left", "wrap": True}}
]
})
body.append({"widgettype": "VBox", "options": {
"width": "100%", "padding": "8px 12px", "gap": "2px",
"border": "1px solid #e2e8f0", "borderRadius": "8px",
"margin": "0 0 10px 0", "bgcolor": "#f8fafc"},
"subwidgets": field_rows})
# ── 来源说明(来源写进说明,不做来源按钮;2026-09-08 用户定夺)──
src_name = source_text(str(det.get("source") or ""), str(det.get("source_url") or ""))
src_lines = ["来源:" + src_name + "(数据爬取平台采集)"]
url = str(det.get("url") or "")
src_url = str(det.get("source_url") or "")
src_note = "|".join(src_lines)
link_btns = []
if url:
link_btns.append({"widgettype": "Button", "options": {"label": "打开原文页", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "window.open(" + _json.dumps(url) + ",'_blank','noopener');"}]})
if src_url:
link_btns.append({"widgettype": "Button", "options": {"label": "打开采集来源页", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "window.open(" + _json.dumps(src_url) + ",'_blank','noopener');"}]})
body.append({
"widgettype": "HBox",
"options": {"width": "100%", "gap": "10px", "alignItems": "center",
"margin": "0 0 10px 0"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": src_note,
"cfontsize": 0.8, "color": "#64748b", "halign": "left", "wrap": True}},
{"widgettype": "Filler"}
] + link_btns
})
# ── 公告/需求全文(markdown 渲染,链接可点击;2026-09-09 用户定夺改 MdWidget)──
detail_text = str(det.get("detail_text") or "").strip()
body.append({"widgettype": "Text", "options": {
"text": "全文内容", "cfontsize": 0.95, "color": "#0f172a",
"halign": "left", "margin": "0 0 4px 0"}})
if detail_text:
shown = detail_text[:20000]
if len(detail_text) > 20000:
shown += "\n\n……(全文 %d 字,已截断显示,完整内容见原文页)" % len(detail_text)
body.append({"widgettype": "MdWidget", "options": {
"mdtext": shown, "width": "100%",
"padding": "8px 12px", "border": "1px solid #e2e8f0",
"borderRadius": "8px", "margin": "0 0 10px 0", "bgcolor": "#ffffff"}})
else:
body.append({"widgettype": "Text", "options": {
"text": "爬取平台未保存该条全文,可点「打开原文页」查看。",
"cfontsize": 0.82, "color": "#94a3b8", "halign": "left",
"margin": "0 0 10px 0"}})
# ── 附件区(有附件则逐个可点击下载)──
body.append({"widgettype": "Text", "options": {
"text": "附件(%d)" % (len(atts) if att_ok else 0),
"cfontsize": 0.95, "color": "#0f172a", "halign": "left",
"margin": "0 0 4px 0"}})
if not att_ok:
# 附件端点未就绪(爬虫平台附件子系统部署中)——如实提示,不静默
body.append({"widgettype": "Text", "options": {
"text": "附件服务暂不可用:" + str(atts)[:120],
"cfontsize": 0.8, "color": "#94a3b8", "halign": "left",
"margin": "0 0 10px 0"}})
elif not atts:
body.append({"widgettype": "Text", "options": {
"text": "该条暂无附件", "cfontsize": 0.8, "color": "#94a3b8",
"halign": "left", "margin": "0 0 10px 0"}})
else:
for a in atts:
a_name = str(a.get("name") or "附件")
a_url = str(a.get("url") or "")
a_id = str(a.get("id") or "")
a_size = int(a.get("size") or 0)
size_txt = ""
if a_size >= 1048576:
size_txt = "%.1f MB" % (a_size / 1048576.0)
elif a_size > 0:
size_txt = "%.0f KB" % (a_size / 1024.0)
act_btns = []
if a.get("downloaded"):
# 平台已存副本 → 走代理下载
dl_query = "?tender_id=" + item_id
if a_id:
dl_query += "&att_id=" + a_id
elif a_url:
dl_query += "&att_url=" + a_url
dl_href = dl_base + dl_query
act_btns.append({"widgettype": "Button", "options": {"label": "下载", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "window.location.href=" + _json.dumps(dl_href) + ";"}]})
elif a_url:
# 平台未存副本(如需登录才能取)→ 如实给原始页入口,不伪装成可下载
act_btns.append({"widgettype": "Button", "options": {"label": "打开原始页", "css": "small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self",
"script": "window.open(" + _json.dumps(a_url) + ",'_blank','noopener');"}]})
body.append({
"widgettype": "HBox",
"options": {"width": "100%", "gap": "10px", "alignItems": "center",
"padding": "6px 12px", "border": "1px solid #e2e8f0",
"borderRadius": "8px", "margin": "0 0 6px 0", "bgcolor": "#ffffff"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": "📎 " + a_name,
"cfontsize": 0.88, "color": "#0f172a", "halign": "left",
"wrap": True}},
{"widgettype": "Text", "options": {"text": size_txt,
"cfontsize": 0.75, "color": "#94a3b8"}},
{"widgettype": "Filler"}
] + act_btns
})
return {
"widgettype": "PopupWindow",
"id": "opp_item_detail_pw",
"options": {"title": "数据项详情", "width": "70%", "height": "85%",
"auto_open": True, "resizable": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%",
"padding": "12px 16px", "gap": "0px", "overflow": "auto"},
"subwidgets": body
}]
}