pipeline-sdlc/wwwroot/api/my_todos_popup.dspy
ymq 3465825d38 feat(todos): 待办弹窗按source=ticket分流——工单badge(青)+详情指向/ticket/api/ticket_detail_popup.dspy?from_todo=1
配合pipeline-service的register_todo_provider(工单模块注入): 平台待办通道新增工单来源,
客户回复待确认/运维待认领/受理人待处理 统一在头部角标+待办弹窗异步通知
2026-09-10 16:14:39 +08:00

171 lines
6.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# my_todos_popup.dspy - 我的待办列表(返回 bricks widget 描述,由 todo_badge.js 构建)
# 列表只做导航:每条待办点「查看内容并处理」→ todo_detail.dspy 渲染正文后再确认。
import json as _json
user_id = await get_user()
if not user_id:
return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录后查看待办"}}
todos = await list_my_human_todos(user_id)
dbname = get_module_dbname('pipeline-sdlc')
TYPE_LABEL = {
'requirement_confirmation': '需求确认',
'design_confirmation': '设计确认',
'bug_acceptance': 'Bug 验收',
}
TYPE_COLOR = {
'requirement_confirmation': '#2563eb',
'design_confirmation': '#2563eb',
'bug_acceptance': '#f0a040',
}
detail_url = entire_url("/pipeline-sdlc/api/todo_detail.dspy")
ticket_detail_url = entire_url("/ticket/api/ticket_detail_popup.dspy")
def _s(v):
if v is None:
return ''
return str(v)
def _clip(s, n):
s = ' '.join(_s(s).split())
if len(s) > n:
return s[:n] + '…'
return s
# 项目名(一次查全,避免逐条查库)
proj_ids = []
for t in (todos or []):
pid = _s(t.get('project_id'))
if pid and pid not in proj_ids:
proj_ids.append(pid)
proj_names = {}
if proj_ids:
id_list = ','.join(["'" + p.replace("'", "") + "'" for p in proj_ids])
async with DBPools().sqlorContext(dbname) as sor:
precs = await sor.sqlExe("SELECT id, name FROM sd_projects WHERE id IN (" + id_list + ")", {})
await sor.sqlExe("COMMIT", {})
for r in (precs or []):
proj_names[_s(getattr(r, 'id', ''))] = _s(getattr(r, 'name', ''))
cards = []
for t in (todos or []):
src = _s(t.get('source'))
tid = _s(t.get('id'))
kind = 'question' if src == 'question' else 'human_task'
task_type = _s(t.get('task_type'))
if src == 'ticket':
# 工单待办(2026-09-10 接入平台待办):badge 用 provider 给的分类
badge = _s(t.get('badge')) or '工单'
badge_color = '#0ea5e9'
title = _clip(t.get('title'), 80) or '工单'
summary = _clip(t.get('description'), 110)
elif src == 'question':
badge = '待答问题'
badge_color = '#8b5cf6'
title = _clip(t.get('question'), 80) or '冒泡问题'
summary = '提问角色:' + (_s(t.get('from_role')) or '-') + ' | 类型:' + (_s(t.get('problem_type')) or '-')
else:
badge = TYPE_LABEL.get(task_type, '人类任务')
badge_color = TYPE_COLOR.get(task_type, '#2563eb')
title = _clip(t.get('title'), 80) or '待办任务'
summary = _clip(t.get('description'), 110)
pid = _s(t.get('project_id'))
meta_bits = []
pname = proj_names.get(pid) or _s(t.get('project_name')) or pid
if pname:
meta_bits.append('项目:' + pname)
if t.get('created_at'):
meta_bits.append(_s(t.get('created_at')))
if src == 'ticket':
# 工单待办详情走 ticket 模块自持端点(from_todo=1 → 弹窗 id 用待办专用,
# 关闭时连带关待办列表弹窗)
open_script = ("var du=" + _json.dumps(ticket_detail_url) + "+'?from_todo=1&id='+encodeURIComponent("
+ _json.dumps(tid) + ");"
"var rp=await fetch(du);var dd=await rp.json();"
"await bricks.widgetBuild(dd,bricks.app);")
else:
open_script = ("var du=" + _json.dumps(detail_url) + "+'?kind=" + kind + "&id='+encodeURIComponent("
+ _json.dumps(tid) + ");"
"var rp=await fetch(du);var dd=await rp.json();"
"await bricks.widgetBuild(dd,bricks.app);")
sub = [
{
"widgettype": "HBox",
"options": {"width": "100%", "gap": "8px", "alignItems": "center"},
"subwidgets": [
{"widgettype": "Text",
"options": {"text": badge, "cfontsize": 0.7, "color": "#ffffff", "padding": "1px 8px",
"bgcolor": badge_color, "borderRadius": "10px", "whiteSpace": "nowrap"}},
{"widgettype": "Text",
"options": {"text": title, "cfontsize": 0.95, "color": "#0f172a"}}
]
}
]
if meta_bits:
sub.append({"widgettype": "Text",
"options": {"text": ' | '.join(meta_bits), "cfontsize": 0.7, "color": "#94a3b8"}})
if summary:
sub.append({"widgettype": "Text",
"options": {"text": summary, "cfontsize": 0.78, "color": "#475569", "width": "100%"}})
sub.append({
"widgettype": "HBox",
"options": {"width": "100%", "gap": "6px", "halign": "right"},
"subwidgets": [{
"widgettype": "Button",
"options": {"label": "查看内容并处理", "css": "primary small"},
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
"target": "self", "script": open_script}]
}]
})
cards.append({
"widgettype": "VBox",
"options": {"width": "100%", "bgcolor": "#ffffff", "border": "1px solid #e2e8f0",
"borderRadius": "8px", "padding": "10px 14px", "gap": "6px",
"marginBottom": "6px"},
"subwidgets": sub
})
if not cards:
cards = [{"widgettype": "Text",
"options": {"text": "暂无待办", "cfontsize": 0.9, "color": "#94a3b8", "padding": "16px"}}]
# embed=1(手机端「待办」Tab 整页渲染):返回纯 VBox,不套 PopupWindow 弹窗壳
_embed = ((params_kw or {}).get('embed') or '').strip()
if _embed == '1':
return {
"widgettype": "VBox",
"options": {"css": "filler", "width": "100%", "height": "100%",
"padding": "0", "gap": "0px"},
"subwidgets": [{
"widgettype": "VScrollPanel",
"options": {"css": "filler", "width": "100%", "height": "100%",
"padding": "12px", "gap": "8px"},
"subwidgets": cards
}]
}
return {
"widgettype": "PopupWindow",
"id": "my_todos_pw",
"options": {"title": "我的待办(" + str(len(todos or [])) + ")", "width": "76%", "height": "80%",
"auto_open": True, "resizable": True},
"subwidgets": [{
"widgettype": "VScrollPanel",
"options": {"css": "filler", "width": "100%", "height": "100%",
"padding": "12px", "gap": "8px"},
"subwidgets": cards
}]
}