pipeline-opportunity/wwwroot/api/opp_demand_items.dspy

128 lines
6.0 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_demand_items.dspy - 某软件主题的众包需求分项列表(国内猪八戒/一品威客)
# 入参category=<主题名>sort=time|budget2026-09-15 用户需求:支持按赏金
# 大到小 / 按时间后到前排序;排序在数据源侧全量执行后再截断,缺省 time
# 交互2026-09-08 用户定夺):
# 每条分项整行点击 → opp_item_detail.dspy 弹出具体内容(爬取平台数据+附件下载);
# 来源写进每条说明文字(来源:猪八戒/一品威客),不再渲染来源按钮。
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_demand_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,
record_type="demand", sort=sort)
await sor.sqlExe("COMMIT", {})
# ── 排序切换按钮(点击重建弹窗带 sort 参数;对齐 opp_mining_popup 刷新范式)──
def _sort_btn(label, target_sort, active):
script = ("var old=bricks.getWidgetById('opp_demand_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" % (
str(it.get("publish_time") or "")[:10],
(("赏金 %.2f 万" % budget) if budget else "赏金面议"),
src_name)
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": "",
"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:
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_demand_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
}]
}