- fp_calc.calc()返回键实锤核对: total_fp/counts/errors(原total_ufp/by_type兜底猜错,已修) - promote门禁修正: resolve_approval会把status置approved,confirmed只是中间态→门禁接受confirmed/approval_initiated/approved(原只认confirmed会导致审批通过后反而不能立项) - _cluster_ctx补项目隔离(对齐_check_batch_visible口径)+全personal类别如实拒绝可行性样本不足 - 八节模板兜底节=需求规格;四节存量解析口径零变化 - opp_approvals CRUD手改(项目过滤)被xls2ui重生成吃掉→已checkout还原,只保留opp_reports新列部分
142 lines
6.4 KiB
Plaintext
142 lines
6.4 KiB
Plaintext
# 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": {"otext": "当前会话还没有项目,请先选择或新建项目", "text": "当前会话还没有项目,请先选择或新建项目", "i18n": True,
|
||
"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"),
|
||
}
|
||
|
||
TYPE_TXT = {
|
||
"research": ("调研", "#0369a1"),
|
||
"feasibility": ("可行性研究", "#7c3aed"),
|
||
}
|
||
|
||
rows = []
|
||
if not reports:
|
||
rows.append({"widgettype": "Text", "options": {
|
||
"otext": "暂无可见的研发报告(列表按项目 owner 过滤:仅显示你 own 的项目及平台级调研报告)", "text": "暂无可见的研发报告(列表按项目 owner 过滤:仅显示你 own 的项目及平台级调研报告)", "i18n": True,
|
||
"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"))
|
||
rt = str(r.get("report_type") or "research")
|
||
rt_txt, rt_color = TYPE_TXT.get(rt, ("调研", "#0369a1"))
|
||
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": "HBox", "options": {"width": "100%", "gap": "8px",
|
||
"alignItems": "center"},
|
||
"subwidgets": [
|
||
{"widgettype": "Text", "options": {"text": rt_txt,
|
||
"cfontsize": 0.7, "color": "#ffffff", "bgcolor": rt_color,
|
||
"padding": "1px 8px", "borderRadius": "10px"}},
|
||
{"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": {
|
||
"otext": "研发报告(${p0} 份)|按项目 owner 过滤;点击任一条查看研发场景/输入/输出/成果与成果文件下载", "text": "研发报告(${p0} 份)|按项目 owner 过滤;点击任一条查看研发场景/输入/输出/成果与成果文件下载", "i18n": True, "i18n_params": {"p0": str('%d' % (len(reports),))},
|
||
"cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}}
|
||
] + rows
|
||
}
|