feat(opp): 当前项目过滤——opp_reports_list只显示当前项目报告+平台级调研(未挂项目),无项目提示'选择或新建'带直达按钮;opp_approvals get注入会话级强制过滤;三菜单加require_project门禁

This commit is contained in:
yumoqing 2026-09-11 18:29:52 +08:00
parent 09d77f7483
commit 5c37341f4c
3 changed files with 70 additions and 4 deletions

View File

@ -464,11 +464,11 @@ def register_opp_ability():
roles=OPP_ROLES,
menus=[
{"label": "📊 研发报告", "icon": "", "url": "/pipeline-opportunity/api/opp_reports_list.dspy",
"type": "popup", "width": "88%", "height": "82%"},
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "✅ 研发审批", "icon": "", "url": "/pipeline-opportunity/opp_approvals/index.ui",
"type": "popup", "width": "88%", "height": "82%"},
"type": "popup", "width": "88%", "height": "82%", "require_project": True},
{"label": "📋 数据参考", "icon": "", "url": "/pipeline-opportunity/api/opp_references_popup.dspy",
"type": "popup", "width": "70%", "height": "76%"},
"type": "popup", "width": "70%", "height": "76%", "require_project": True},
],
)
register_ability(ability)

View File

@ -2,6 +2,8 @@
# 替换原 CRUD 裸表页:按项目 owner 过滤(list_visible_reports 单一事实源),
# 整行点击 → opp_report_detail.dspy 弹出详情(四节+成果文件下载)。
# 交互范式对齐 opp_references_items.dspy(整行下钻,fetch+widgetBuild)。
# 2026-09-11 会话当前项目过滤:只显示当前项目的报告 + 平台级调研(未挂项目,
# 不属于任何项目、不构成跨项目泄漏);没当前项目 → 提示选择或新建项目。
import json as _json
@ -11,11 +13,55 @@ if not uid:
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 = {

View File

@ -1,6 +1,26 @@
ns = params_kw.copy()
# 会话当前项目强制过滤(2026-09-11 用户要求):会话级解析(session→全局兜底+产线隔离+悬空自愈),
# 无项目报错提示;强制覆盖前端 project_id 传值(含搜索框),列表只显示当前项目数据。
_uid = await get_user()
if not _uid:
_uid = 'user-01'
_sid = (params_kw or {}).get('session_id', '') or ''
_pl = (params_kw or {}).get('pipeline_id', '') or 'opportunity_general'
_cur_pid = ''
try:
from pipeline_service.workspace import get_session_project_id
async with DBPools().sqlorContext(get_module_dbname('pipeline-opportunity')) as _sor:
_cur_pid = await get_session_project_id(_sor, _uid, _sid, _pl) or ''
except Exception:
_cur_pid = ''
if not _cur_pid:
return {"widgettype":"Error","options":{"title":"错误","timeout":3,"cwidth":22,"cheight":9,"message":"当前会话没有项目,请先选择或新建项目"}}
ns['_cur_pid'] = _cur_pid
ns['project_id'] = _cur_pid
debug(f'get_opp_approvals.dspy:{ns=}')
if not ns.get('page'):
@ -12,7 +32,7 @@ if not ns.get('sort'):
sql = '''select * from opp_approvals where 1=1 [[filterstr]]'''
sql = '''select * from opp_approvals where project_id = ${_cur_pid}$ [[filterstr]]'''
filterjson = params_kw.get('data_filter')
if filterjson and isinstance(filterjson, str):