# opp_reports_list.dspy - 研发报告列表(「研发报告」菜单入口,2026-09-09 用户要求改造) # 替换原 CRUD 裸表页:按项目 owner 过滤(list_visible_reports 单一事实源), # 整行点击 → opp_report_detail.dspy 弹出详情(四节+成果文件下载)。 # 交互范式对齐 opp_references_items.dspy(整行下钻,fetch+widgetBuild)。 import json as _json uid = await get_user() if not uid: return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}} dbname = get_module_dbname('pipeline-opportunity') 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", {}) 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 }