pipeline-opportunity/wwwroot/api/opp_references_items.dspy

132 lines
6.3 KiB
Plaintext
Raw Permalink 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_references_items.dspy - 某软件主题的分项列表references 可查证)
# 入参category=<主题名>sort=time|budget2026-09-15 用户需求:支持按标的金额
# 大到小 / 按时间后到前排序;排序在数据源侧全量执行后再截断,缺省 time
# 交互2026-09-08 用户定夺):
# 每条分项整行点击 → opp_item_detail.dspy 弹出具体内容(爬取平台全文+附件下载);
# 来源写进每条说明文字来源xxx不再渲染来源按钮。
import json as _json
uid = await get_user()
if not uid:
return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}}
category = ((params_kw or {}).get("category") or "").strip()
days = int((params_kw or {}).get("days") or 30)
if not category:
return {"widgettype": "Message", "options": {"title": "缺少参数", "message": "缺少 category"}}
# 排序白名单收敛(非法/缺省一律 time=发布时间后到前,与历史行为一致)
sort = ((params_kw or {}).get("sort") or "time").strip()
if sort not in ("time", "budget"):
sort = "time"
dbname = get_module_dbname('pipeline-opportunity')
detail_url = entire_url("/pipeline-opportunity/api/opp_item_detail.dspy")
self_url = entire_url("/pipeline-opportunity/api/opp_references_items.dspy")
async with DBPools().sqlorContext(dbname) as sor:
from pipeline_opportunity.opp_data_capability import category_references
from pipeline_opportunity.opp_common import source_text
ok, res = await category_references(sor, category, days=days, limit=500, sort=sort)
await sor.sqlExe("COMMIT", {})
# ── 排序切换按钮(点击重建弹窗带 sort 参数;对齐 opp_mining_popup 刷新范式)──
def _sort_btn(label, target_sort, active):
script = ("var old=bricks.getWidgetById('opp_refs_items_pw',bricks.app);"
"if(old&&old.destroy){old.destroy();}"
"var rp=await fetch(" + _json.dumps(self_url) + "+'?category='+encodeURIComponent("
+ _json.dumps(category) + ")+'&days=" + str(days) + "&sort=" + target_sort + "');"
"var d=await rp.json();"
"if(d){bricks.widgetBuild(d,bricks.app);}")
return {"widgettype": "Button",
"options": {"label": label, "css": ("primary" if active else "small")},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": script}]}
sort_bar = {"widgettype": "HBox", "options": {"width": "100%", "gap": "8px",
"alignItems": "center", "margin": "0 0 8px 0"},
"subwidgets": [
_sort_btn("💰 标的金额(大到小)", "budget", sort == "budget"),
_sort_btn("⏱ 发布时间(后到前)", "time", sort == "time"),
{"widgettype": "Filler"},
]}
rows = []
if not ok:
rows.append({"widgettype": "Text", "options": {
"otext": "查询失败:${p0}", "text": "查询失败:${p0}", "i18n": True, "i18n_params": {"p0": str(str(res)[:200])},
"color": "#dc2626", "padding": "12px", "halign": "left"}})
else:
items = res.get("items") or []
if not items:
rows.append({"widgettype": "Text", "options": {
"otext": "该主题近 ${p0} 天无分项", "text": "该主题近 ${p0} 天无分项", "i18n": True, "i18n_params": {"p0": str('%d' % (days,))}, "color": "#64748b", "padding": "12px", "halign": "left"}})
for it in items:
item_id = str(it.get("id") or "")
budget = it.get("budget_wan") or 0
# 来源写进说明文字(不再做来源按钮)
src_name = source_text(str(it.get("source") or ""), str(it.get("source_url") or ""))
meta = "%s %s %s 来源:%s" % (
str(it.get("publish_time") or "")[:10],
str(it.get("region") or "—"),
(("预算 %.0f 万" % budget) if budget else "预算未公开"),
src_name)
att_cnt = int(it.get("attachment_count") or 0)
row_subs = [
{"widgettype": "Text", "options": {
"text": str(it.get("title") or ""), "cfontsize": 0.95,
"color": "#0f172a", "halign": "left", "wrap": True}},
{"widgettype": "HBox", "options": {"width": "100%", "gap": "10px",
"alignItems": "center"},
"subwidgets": [
{"widgettype": "Text", "options": {"text": meta,
"cfontsize": 0.75, "color": "#94a3b8", "halign": "left",
"wrap": True}},
{"widgettype": "Filler"}
] + ([{"widgettype": "Text", "options": {
"text": "📎%d" % att_cnt, "cfontsize": 0.75, "color": "#0ea5e9"}}]
if att_cnt else []) + [
{"widgettype": "Text", "options": {"text": "",
"cfontsize": 1.0, "color": "#94a3b8"}}]
}
]
row_opts = {"width": "100%", "padding": "8px 12px", "gap": "4px",
"border": "1px solid #e2e8f0", "borderRadius": "8px",
"margin": "0 0 6px 0", "bgcolor": "#ffffff"}
if item_id:
row_opts["cursor"] = "pointer"
script = ("var old=bricks.getWidgetById('opp_item_detail_pw',bricks.app);"
"if(old&&old.destroy){old.destroy();}"
"var rp=await fetch(" + _json.dumps(detail_url) + "+'?id='+encodeURIComponent(" + _json.dumps(item_id) + "));"
"var d=await rp.json();"
"if(d){bricks.widgetBuild(d,bricks.app);}")
rows.append({
"widgettype": "VBox", "options": row_opts,
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": script}],
"subwidgets": row_subs})
else:
# 无 id 的记录(异常数据)不可点,如实降级为静态行
rows.append({"widgettype": "VBox", "options": row_opts,
"subwidgets": row_subs})
total = res.get("count", len(rows)) if ok else 0
_sort_label = "标的金额大到小" if sort == "budget" else "时间后到前"
return {
"widgettype": "PopupWindow",
"id": "opp_refs_items_pw",
"options": {"title": "分项明细:%s%d 条 · %s" % (category, total, _sort_label),
"width": "80%", "height": "82%", "auto_open": True, "resizable": True},
"subwidgets": [{
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%",
"padding": "12px 16px", "gap": "0px", "overflow": "auto"},
"subwidgets": [sort_bar,
{"widgettype": "Text", "options": {
"otext": "点击任一条目弹出具体内容(公告全文来自数据爬取平台,有附件的可直接下载);每条说明中已标注数据来源。", "text": "点击任一条目弹出具体内容(公告全文来自数据爬取平台,有附件的可直接下载);每条说明中已标注数据来源。", "i18n": True,
"cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}}
] + rows
}]
}