# opp_mining_items.dspy - 挖掘类别的类内需求明细(三级下钻第三级) # 入参:cluster_id。可见性经 opp_mining_flow.cluster_detail(含批次项目/org 校验)。 # 每条带来源(source_text 说明文字),整行点击 → opp_item_detail.dspy(爬虫平台详情弹窗, # 快照 src_id 即爬虫平台 tenders.id,直接复用既有详情通道)。 import json as _json uid = await get_user() if not uid: return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}} cluster_id = ((params_kw or {}).get("cluster_id") or "").strip() if not cluster_id: return {"widgettype": "Message", "options": {"title": "缺少参数", "message": "缺少 cluster_id"}} try: limit = int((params_kw or {}).get("limit") or 50) except (TypeError, ValueError): limit = 50 dbname = get_module_dbname('pipeline-opportunity') session_id = (params_kw or {}).get('session_id', '') or '' pipeline_id = (params_kw or {}).get('pipeline_id', '') or 'opportunity_general' _cur_pid = '' _uorg = '' try: from pipeline_service.workspace import get_session_project_id async with DBPools().sqlorContext(dbname) as sor: _cur_pid = await get_session_project_id(sor, uid, session_id, pipeline_id) or '' _urecs = await sor.sqlExe("SELECT orgid FROM users WHERE id=${u}$", {"u": uid}) await sor.sqlExe("COMMIT", {}) if _urecs: _uorg = str(getattr(_urecs[0], "orgid", "") or "") except Exception: pass _ctx = {"project_id": _cur_pid, "user_id": uid, "org_id": _uorg, "pipeline_id": pipeline_id, "session_id": session_id} async with DBPools().sqlorContext(dbname) as sor: from pipeline_opportunity.opp_mining_flow import cluster_detail from pipeline_opportunity.opp_common import source_text _ok, _res = await cluster_detail(sor, _ctx, cluster_id, limit=limit) await sor.sqlExe("COMMIT", {}) detail_url = entire_url("/pipeline-opportunity/api/opp_item_detail.dspy") rows = [] if not _ok: rows.append({"widgettype": "Text", "options": { "text": str(_res)[:200], "color": "#dc2626", "padding": "12px", "halign": "left"}}) cl_name, cl_cnt = "", 0 else: cl = _res.get("cluster") or {} cl_name = str(cl.get("name") or "") cl_cnt = int(cl.get("doc_count") or 0) items = _res.get("samples") or [] if not items: rows.append({"widgettype": "Text", "options": { "text": "该类别暂无明细", "color": "#64748b", "padding": "12px", "halign": "left"}}) for it in items: src_id = str(it.get("src_id") or "") title = str(it.get("title") or "") budget = it.get("budget_wan") or 0 url = str(it.get("url") or "") src_name = source_text(str(it.get("source") or ""), "") meta = "%s | %s | 来源:%s" % ( str(it.get("publish_time") or "")[:10], (("预算 %.2f 万" % float(budget)) if budget else "预算面议"), src_name) row_subs = [ {"widgettype": "Text", "options": {"text": title, "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 src_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(src_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}) elif url: row_opts["cursor"] = "pointer" script = "window.open(" + _json.dumps(url) + ",'_blank');" 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}) return { "widgettype": "PopupWindow", "id": "opp_mine_items_pw", "options": {"title": "类内需求明细:%s(%d 条,显示前 %d)" % (cl_name, cl_cnt, limit), "width": "76%", "height": "80%", "auto_open": True, "resizable": True}, "subwidgets": [{ "widgettype": "VBox", "options": {"css": "filler", "width": "100%", "height": "100%", "padding": "12px 16px", "gap": "0px", "overflow": "auto"}, "subwidgets": [ {"widgettype": "Text", "options": { "text": "点击任一条目弹出需求详情(数据来自数据爬取平台快照,每条标注来源平台)。", "cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}}, ] + rows, }], }