diff --git a/wwwroot/api/todo_detail.dspy b/wwwroot/api/todo_detail.dspy index b94d893..948ebc7 100644 --- a/wwwroot/api/todo_detail.dspy +++ b/wwwroot/api/todo_detail.dspy @@ -95,10 +95,32 @@ def _btn(label, css, script, conform=None): def _file_link_row(fname, project_id, fpath): - """待办中的输出文件行:文件名 + 打开/下载按钮(链接到项目空间内的真实文件)。""" + """待办中的输出文件行:文件名 + 打开/下载按钮(链接到项目空间内的真实文件)。 + + 打开(2026-09-16 修复):md/txt 点「打开」曾直接 window.open(project_file.dspy) + ——浏览器不能原生渲染 md,落成以 URL 名(project_file.dspy)命名的下载。 + 改为弹出内嵌 MdWidget(md_url 拉取渲染)的在线查看窗口(对齐 2026-09-03 + 交付件渲染规范:md/txt → MdWidget 渲染 + 下载链接)。 + office/pdf 等浏览器不可内联渲染的类型「打开」等同下载(如实标注按钮文案)。 + """ open_url = (entire_url("/pipeline-sdlc/api/project_file.dspy") + "?project_id=" + _quote(project_id) + "&path=" + _quote(fpath)) dl_url = open_url + "&download=1" + ext = os.path.splitext(fname or '')[1].lower() + if ext in ('.md', '.txt', '.markdown'): + # 在线查看:PopupWindow + widgetBuild(MdWidget)(对齐 cockpit_cards.dspy 实证模式: + # JS 构造器不认 subwidgets,必须 content_w.add_widget;md_url 拉取渲染, + # 超长 md 也不撑爆 widget JSON) + open_script = ( + "var pw=new bricks.PopupWindow({title:" + _json.dumps("查看 " + fname) + + ",cwidth:70,cheight:36,auto_open:true});" + "bricks.widgetBuild({widgettype:'MdWidget',options:{md_url:" + + _json.dumps(open_url) + ",width:'100%'}},pw.content_w)" + ".then(function(w){if(w)pw.content_w.add_widget(w);});") + open_btn = _btn("在线查看", "small", open_script) + else: + open_btn = _btn("下载查看", "small", + "window.open(" + _json.dumps(dl_url) + ",'_blank');") return {"widgettype": "HBox", "options": {"width": "100%", "gap": "8px", "padding": "6px 14px", "alignItems": "center", "bgcolor": "#f8fafc", @@ -107,7 +129,7 @@ def _file_link_row(fname, project_id, fpath): {"widgettype": "Text", "options": {"text": "📎 " + fname, "cfontsize": 0.85, "color": "#334155", "flex": "1 1 auto", "overflow": "hidden", "textOverflow": "ellipsis", "whiteSpace": "nowrap"}}, - _btn("打开", "small", "window.open(" + _json.dumps(open_url) + ",'_blank');"), + open_btn, _btn("下载", "small", "window.open(" + _json.dumps(dl_url) + ",'_blank');") ]} @@ -525,11 +547,34 @@ async with DBPools().sqlorContext(dbname) as sor: _bf = _s(getattr(b0, 'file_path', '')) if _bf: _file_rows.append(_file_link_row(os.path.basename(_bf), project_id, _bf)) + # 2026-09-16:md 有 docx 同名产物时一并给出(标书最终交付 docx) + _bf_docx = os.path.splitext(_bf)[0] + '.docx' + if _bf_docx != _bf: + _file_rows.append(_file_link_row(os.path.basename(_bf_docx), project_id, _bf_docx)) else: md.append('## 待确认标书') md.append('') md.append('未找到该项目的标书文件记录。') md.append('') + # 确认交付按钮(2026-09-16 修复:此分支曾只渲染文件行,忘了 action 按钮, + # 通用兜底分支又被 elif 挡住 → 待办成死卡片无法确认)。 + # 通道 = human_task_complete(同机构+assignee 校验);置 done 后对账器 + # G 段自动收尾:标书 delivered + 项目 completed。 + action_widgets.append(_input_box( + 'bid_confirm_note', '交付备注(可选:用印/密封/份数/递交方式核对说明)', '可选备注')) + _ok_script = (_read_input_js('bid_confirm_note') + + "var rd=JSON.stringify({content:(cv||'确认交付')});" + + _post_js(complete_url, [('human_task_id', _json.dumps(oid)), + ('result_data', 'rd')]) + _tail) + action_widgets.append({ + "widgettype": "HBox", + "options": {"width": "100%", "gap": "8px", "padding": "12px 14px", "halign": "right"}, + "subwidgets": [ + _btn('确认交付(项目完成)', 'primary', _ok_script, + conform={"title": "确认交付", "message": "确认标书已复核(用印/密封/份数/递交)?确认后标书置为已交付、项目完成。", + "conform": {"label": "确认交付"}, "discard": {"label": "再看看"}}) + ] + }) elif task_type == 'flow_plan_confirm': # 产线流程裁剪确认:正文(description)已含裁剪方案对照表(propose 时渲染),