From ccc0755a82d4d25c3ab7411b22592123fea56a74 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sun, 2 Aug 2026 16:07:48 +0800 Subject: [PATCH] feat: 4 dynamic cards with popups on cockpit dashboard --- wwwroot/api/cockpit_cards.dspy | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 wwwroot/api/cockpit_cards.dspy diff --git a/wwwroot/api/cockpit_cards.dspy b/wwwroot/api/cockpit_cards.dspy new file mode 100644 index 0000000..e4f2d76 --- /dev/null +++ b/wwwroot/api/cockpit_cards.dspy @@ -0,0 +1,60 @@ +# cockpit_cards.dspy — Returns 4 dynamic dashboard cards with popups +uid = await get_user() +org_id = await get_userorgid() or '0' + +async with DBPools().sqlorContext(get_module_dbname('pipeline-sdlc')) as sor: + # Counts + projs = await sor.sqlExe("SELECT COUNT(*) as cnt FROM sd_projects WHERE status='active' AND org_id=${oid}$", {"oid": org_id}) + iters = await sor.sqlExe("SELECT COUNT(*) as cnt FROM sd_iterations WHERE status='active' AND org_id=${oid}$", {"oid": org_id}) + bugs = await sor.sqlExe("SELECT COUNT(*) as cnt FROM sd_bugs WHERE status IN ('open','confirmed')", {}) + approvs = await sor.sqlExe("SELECT COUNT(*) as cnt FROM pipeline_task_steps WHERE state='waiting' AND step_type IN ('human_task','approval_gate')", {}) + + pc = projs[0].cnt if projs else 0 + ic = iters[0].cnt if iters else 0 + bc = bugs[0].cnt if bugs else 0 + ac = approvs[0].cnt if approvs else 0 + +cards = [ + {"title": "活跃项目", "count": pc, "color": "#3b82f6", "icon": "📁", + "popup_url": "/pipeline-sdlc/sd_projects/index.ui", "popup_title": "项目管理"}, + {"title": "进行中迭代", "count": ic, "color": "#10b981", "icon": "🔄", + "popup_url": "/pipeline-sdlc/sd_iterations/index.ui", "popup_title": "迭代管理"}, + {"title": "待处理Bug", "count": bc, "color": "#ef4444", "icon": "🐛", + "popup_url": "/pipeline-sdlc/sd_bugs/index.ui", "popup_title": "Bug管理"}, + {"title": "待审批", "count": ac, "color": "#f59e0b", "icon": "✅", + "popup_url": "/pipeline-sdlc/sd_iterations/index.ui", "popup_title": "待审批任务"}, +] + +card_widgets = [] +for c in cards: + url = c["popup_url"] + title = c["popup_title"] + card_widgets.append({ + "widgettype": "VBox", + "options": { + "css": "card", + "width": "25%", + "bgcolor": c["color"] + "08", + "border": f"1px solid {c['color']}33", + "borderRadius": "10px", + "padding": "14px 16px", + "alignItems": "center", + "gap": "6px", + "hover_border": f"1px solid {c['color']}99", + }, + "binds": [{ + "wid": "self", "event": "click", "actiontype": "script", "target": "self", + "script": f"var pw=new bricks.PopupWindow({{title:'{title}',cwidth:44,cheight:32,auto_open:true}});bricks.widgetBuild({{widgettype:'urlwidget',options:{{url:'{url}',method:'GET'}}}},pw.content_w).then(function(w){{if(w)pw.content_w.add_widget(w);}});" + }], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": c["icon"], "cfontsize": 1.8}}, + {"widgettype": "Text", "options": {"text": str(c["count"]), "cfontsize": 1.6, "color": c["color"], "fontWeight": "bold"}}, + {"widgettype": "Text", "options": {"text": c["title"], "cfontsize": 0.8, "color": "#475569"}}, + ] + }) + +return { + "widgettype": "HBox", + "options": {"width": "100%", "gap": "12px"}, + "subwidgets": card_widgets +}