pipeline-sdlc/wwwroot/api/my_todos_popup.dspy

192 lines
7.6 KiB
Plaintext
Raw Permalink 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 验收',
'flow_plan_confirm': '流程裁剪确认',
'tech_template_confirm': '技术方案骨架确认',
'bid_delivery_confirm': '交付确认',
'material_supply': '实体材料缺口',
}
TYPE_COLOR = {
'requirement_confirmation': '#2563eb',
'design_confirmation': '#2563eb',
'bug_acceptance': '#f0a040',
'flow_plan_confirm': '#7c3aed',
'tech_template_confirm': '#0891b2',
'bid_delivery_confirm': '#059669',
'material_supply': '#d97706',
}
detail_url = entire_url("/pipeline-sdlc/api/todo_detail.dspy")
ticket_detail_url = entire_url("/ticket/api/ticket_detail_popup.dspy")
wallet_detail_url = entire_url("/pipeline-llm/api/llm_wallet_todo_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 == 'llm_wallet':
# 模型账号钱包余额不足(2026-09-11 接入平台待办):状态派生,充值到账自动消失
badge = _s(t.get('badge')) or '钱包充值'
badge_color = '#dc2626'
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 ('(项目已删除)' if pid else '')
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);")
elif src == 'llm_wallet':
# 钱包待办详情走 pipeline-llm 自持端点(弹窗内直接充值)
open_script = ("var du=" + _json.dumps(wallet_detail_url) + "+'?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": "var(--ink-0)"}}
]
}
]
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": "var(--ink-3)", "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": {"otext": "暂无待办", "text": "暂无待办", "i18n": True, "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
}]
}