用户需求(2026-09-09):研发报告按钮点击按项目owner过滤列表;每项点击看
研发场景/输入/输出/成果;成果文件多个列出、逐个点击下载。
1. 列表页 api/opp_reports_list.dspy(替换CRUD裸表页作菜单入口):
- list_visible_reports 单一事实源:挂项目的报告仅项目owner可见
(sd_projects.created_by==uid;agent建的项目降级同机构,对齐
check_project_owner既有语义);未挂项目的平台级调研登录可见;
孤儿报告(项目已删)不可见(LIKE 'agent.%' 写法有 rag_client.py 生产先例)
- 整行点击下钻详情,交互范式照抄 opp_references_items.dspy
2. 详情弹窗 api/opp_report_detail.dspy:
- parse_report_sections 四节解析(## 研发场景/输入/输出/成果),
MdWidget逐节渲染;兼容序号/冒号/加粗标题变体;三级标题不误判;
旧报告无节时全文归输出节;空节如实标注不编造
- check_report_visible 与列表同一可见性规则(语义不漂移)
3. 文件下载 api/opp_report_file_dl.dspy + list_report_files:
- 范围=项目deliverables/全部文件+报告PPT(realpath去重)
- 安全模型对齐 opp_attachment_dl:前端只传report_id+name,
服务端回查清单命中才给文件,文件名不参与路径拼接(防穿越)
4. agent侧四节模板硬要求:OPP_PROMPT新增节+create/update_report工具描述
5. RBAC load_path 补3端点;i18n zh/en 补9词条;README 补改造说明
离线实测11项断言全过:标准四节/标题变体/旧报告归输出/缺节/三级标题不误判/
重复节拼接/空正文/deliverables扫描+PPT去重/项目外PPT/无项目不崩/防穿越
83 lines
3.5 KiB
Plaintext
83 lines
3.5 KiB
Plaintext
# 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
|
||
}
|