# opp_reports_list.dspy - 研发报告列表(「研发报告」菜单入口,2026-09-09 用户要求改造) # 替换原 CRUD 裸表页:按项目 owner 过滤(list_visible_reports 单一事实源), # 整行点击 → opp_report_detail.dspy 弹出详情(四节+成果文件下载)。 # 交互范式对齐 opp_references_items.dspy(整行下钻,fetch+widgetBuild)。 # 2026-09-11 会话当前项目过滤:只显示当前项目的报告 + 平台级调研(未挂项目, # 不属于任何项目、不构成跨项目泄漏);没当前项目 → 提示选择或新建项目。 import json as _json uid = await get_user() if not uid: return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}} 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' # 会话当前项目(统一解析:session→全局兜底+产线隔离+悬空自愈) _cur_pid = '' 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 '' except Exception: _cur_pid = '' if not _cur_pid: _proj_base = entire_url("/pipeline_core/api/agent_project_popup.dspy") _proj_q = [] if session_id: _proj_q.append("session_id=" + session_id) if pipeline_id: _proj_q.append("pipeline_id=" + pipeline_id) def _pbtn(label, css, op): _js = ("var r=await fetch(" + _json.dumps(_proj_base + "?op=" + op + (("&" + "&".join(_proj_q)) if _proj_q else "")) + ");" "var d=await r.json();if(d){bricks.widgetBuild(d,bricks.app);}") return {"widgettype": "Button", "options": {"label": label, "css": css}, "binds": [{"wid": "self", "event": "click", "actiontype": "script", "target": "self", "script": _js}]} return {"widgettype": "VBox", "options": {"width": "100%", "gap": "12px", "padding": "24px"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": "当前会话还没有项目,请先选择或新建项目", "cfontsize": 1, "color": "#64748b"}}, {"widgettype": "HBox", "options": {"width": "100%", "gap": "10px"}, "subwidgets": [_pbtn("✨ 新建项目", "primary", "new"), _pbtn("🔀 切换项目", "small", "switch")]} ]} async with DBPools().sqlorContext(dbname) as sor: from pipeline_opportunity.opp_report_capability import list_visible_reports reports = await list_visible_reports(sor, uid) await sor.sqlExe("COMMIT", {}) # 当前项目过滤:本项目报告 + 平台级调研(project_id 空,未挂任何项目) reports = [r for r in reports if (not str(r.get("project_id") or "")) or str(r.get("project_id")) == _cur_pid] detail_url = entire_url("/pipeline-opportunity/api/opp_report_detail.dspy") STATUS_TXT = { "draft": ("草稿", "#94a3b8"), "confirmed": ("已确认", "#2563eb"), "approval_initiated": ("审批中", "#d97706"), "approved": ("审批通过", "#16a34a"), "rejected": ("已驳回", "#dc2626"), } rows = [] if not reports: rows.append({"widgettype": "Text", "options": { "text": "暂无可见的研发报告(列表按项目 owner 过滤:仅显示你 own 的项目及平台级调研报告)", "cfontsize": 0.85, "color": "#64748b", "padding": "12px", "halign": "left"}}) for r in reports: rid = str(r.get("id") or "") st = str(r.get("status") or "") st_txt, st_color = STATUS_TXT.get(st, (st or "—", "#64748b")) proj = str(r.get("project_name") or "") meta = "%s | %s | %s" % ( str(r.get("created_at") or "")[:16], ("项目:" + proj) if proj else "平台级调研(未挂项目)", st_txt) row_subs = [ {"widgettype": "Text", "options": { "text": str(r.get("title") or r.get("software") 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": st_color, "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 rid: row_opts["cursor"] = "pointer" script = ("var old=bricks.getWidgetById('opp_report_detail_pw',bricks.app);" "if(old&&old.destroy){old.destroy();}" "var rp=await fetch(" + _json.dumps(detail_url) + "+'?report_id='+encodeURIComponent(" + _json.dumps(rid) + "));" "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}) return { "widgettype": "VBox", "options": {"css": "filler", "width": "100%", "height": "100%", "padding": "12px 16px", "gap": "0px", "overflow": "auto"}, "subwidgets": [ {"widgettype": "Text", "options": { "text": "研发报告(%d 份)|按项目 owner 过滤;点击任一条查看研发场景/输入/输出/成果与成果文件下载" % len(reports), "cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}} ] + rows }