From 9045323adb34e9958772d2e59258751f9fffbdae Mon Sep 17 00:00:00 2001 From: ymq Date: Wed, 2 Sep 2026 11:56:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BE=85=E5=8A=9E=E4=BA=A4=E4=BB=98?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4=E8=A1=A5=E6=A0=87=E4=B9=A6=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E9=93=BE=E6=8E=A5(bid=5Fdelivery=5Fconfirm);project=5Ffile?= =?UTF-8?q?=E6=94=BE=E5=AE=BD=E5=88=B0=E9=A1=B9=E7=9B=AE=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E5=B1=82=E6=A0=A1=E9=AA=8C(=E5=85=BC=E5=AE=B9=E6=97=A7?= =?UTF-8?q?=E5=B9=B3=E9=93=BA=E7=BB=93=E6=9E=84)+=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=90=AB=E9=A1=B9=E7=9B=AE=E6=A0=87=E8=AF=86=E9=98=B2=E8=B6=8A?= =?UTF-8?q?=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wwwroot/api/project_file.dspy | 21 +++++++++++++++++++-- wwwroot/api/todo_detail.dspy | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/wwwroot/api/project_file.dspy b/wwwroot/api/project_file.dspy index 1996247..913a9c1 100644 --- a/wwwroot/api/project_file.dspy +++ b/wwwroot/api/project_file.dspy @@ -32,6 +32,16 @@ async with DBPools().sqlorContext(dbname) as sor: proj_org = getattr(precs[0], 'org_id', '0') or '0' project_dir, workspace_base = await get_project_dir_by_id(sor, project_id) +# 项目空间基准:产线空间层 {space}/——项目输出文件的合法根(新旧结构都在其下: +# 新结构 {space}/projects/{项目名}/,旧平铺 {space}/{项目名}_{id}/) +space_dir = '' +if project_dir: + # 新结构 project_dir = {space}/projects/{项目名} → 空间层 = 上两级 + if os.path.basename(os.path.dirname(project_dir)) == 'projects': + space_dir = os.path.dirname(os.path.dirname(project_dir)) + else: + space_dir = os.path.dirname(project_dir) + if not project_dir or not os.path.isdir(project_dir): return {"widgettype": "Message", "options": {"title": "错误", "message": "项目目录不可用"}} @@ -46,10 +56,17 @@ if str(uorg) != '0' and str(proj_org) != '0' and str(uorg) != str(proj_org): # 路径解析:绝对路径直接用;相对路径拼项目目录 cand = fpath if fpath.startswith('/') else os.path.join(project_dir, fpath) -# 路径穿越校验:真实路径必须落在项目目录内 +# 防跨项目越权:真实路径必须落在本项目的产线空间内,且路径中含项目标识 +# (项目目录名/项目id/项目名任一)——新结构落在项目目录内,旧平铺落在 {项目名}_{id}/ 内,均满足。 real_proj = os.path.realpath(project_dir) +real_space = os.path.realpath(space_dir) if space_dir else real_proj real_full = os.path.realpath(cand) -if not real_full.startswith(real_proj + os.sep): +_proj_rec_name = getattr(precs[0], 'name', '') or '' +_proj_idents = [x for x in (os.path.basename(project_dir), project_id, _proj_rec_name) if x] +_path_ok = (real_space and real_full.startswith(real_space + os.sep)) or \ + real_full.startswith(real_proj + os.sep) +_ident_ok = any(ix in cand for ix in _proj_idents) +if not _path_ok or not _ident_ok: return {"widgettype": "Message", "options": {"title": "错误", "message": "非法路径(不在项目空间内)"}} if not os.path.isfile(real_full): return {"widgettype": "Message", "options": {"title": "文件不存在", diff --git a/wwwroot/api/todo_detail.dspy b/wwwroot/api/todo_detail.dspy index c7ca790..506ac5f 100644 --- a/wwwroot/api/todo_detail.dspy +++ b/wwwroot/api/todo_detail.dspy @@ -493,6 +493,38 @@ async with DBPools().sqlorContext(dbname) as sor: ] }) + elif task_type == 'bid_delivery_confirm': + # 投标产线交付确认:展示最新标书文件(打开/下载链接)+ 确认按钮 + bdoc = [] + if project_id: + brecs = await sor.sqlExe( + "SELECT doc_name, file_path, version, total_score, max_score, status, updated_at " + "FROM bid_documents WHERE project_id=${p}$ AND file_path<>'' " + "ORDER BY version DESC LIMIT 1", {"p": project_id}) + await sor.sqlExe("COMMIT", {}) + if brecs: + bdoc = brecs + if bdoc: + b0 = bdoc[0] + md.append('## 待确认标书') + md.append('') + md.append('- 名称:' + (_s(getattr(b0, 'doc_name', '')) or '-') + + ' | 版本:v' + _s(getattr(b0, 'version', '')) + + ' | 评分:' + _s(getattr(b0, 'total_score', '')) + '/' + + _s(getattr(b0, 'max_score', '')) + + ' | 状态:' + (_s(getattr(b0, 'status', '')) or '-')) + md.append('') + md.append('请下载标书后核对用印/密封/份数/递交要求,确认无误后点击确认交付。') + md.append('') + _bf = _s(getattr(b0, 'file_path', '')) + if _bf: + _file_rows.append(_file_link_row(os.path.basename(_bf), project_id, _bf)) + else: + md.append('## 待确认标书') + md.append('') + md.append('未找到该项目的标书文件记录。') + md.append('') + else: if not desc: md.append('## 待办说明')