Phase 1: OKR模板 — 两步弹窗(O+KR结构) + 14个CMA模板 + okr_templates表 + API

MapCanvasDialogs.vue:
- 节点编辑弹窗改为两步交互(模板选择→O+KR编辑)
- 第一步:按维度展示对应模板(财务4/客户3/流程4/学习3)
- 第二步:编辑O名称+描述+3个KR(名称/目标值/权重)
- 14个CMA标准模板硬编码在前端
- 支持添加/删除KR、自定义目标跳过模板

MapCanvas.vue:
- 传递 editingLayerKey 到弹窗
- onSaveDialog 适配新 O+KR 数据结构
- onNodeSave 存储 krs 和 template_name 字段

Backend:
- OKRTemplate 模型 (okr_templates 表)
- okr_templates API: GET(按维度筛选) + POST(用户自定义) + increment use_count
- seed_okr_templates.py 迁移脚本(建表+14条种子数据)
- 在 main.py 注册新路由
This commit is contained in:
Hermes CI Fix
2026-07-21 17:22:43 +08:00
parent a9cc7c3f1d
commit 9c6c1614fb
8 changed files with 1251 additions and 41 deletions
+55 -1
View File
@@ -13,7 +13,7 @@ from app.models import (
User, StrategicMap, KPIDefinition, KPITemplate, KPIValue,
DataSourceConfig, KPIAlert, OperationLog, NotificationChannel,
NotificationLog, RolePermission, ActionPlan, OrgNode,
StrategicMapVersion, MapObjective,
StrategicMapVersion, MapObjective, Objective,
)
from app.models.budget_plan import BudgetPlan
from app.models.cost_model import StandardCost, ActualCost, AbcActivity, AbcAllocation
@@ -451,6 +451,21 @@ def bot_query(
for c in sc
]
if q in ("okr", "all"):
objs = db.query(Objective).filter(Objective.status == "active").all()
result["okr"] = []
for o in objs:
krs = db.query(ActionPlan).filter(ActionPlan.objective_id == o.id).all()
result["okr"].append({
"id": o.id, "title": o.title, "quarter": o.quarter,
"dimension": o.dimension, "progress": o.progress,
"confidence": o.confidence,
"key_results": [
{"title": kr.title, "status": kr.status, "progress": kr.progress}
for kr in krs
]
})
if q in ("actions", "all"):
acts = db.query(ActionPlan).limit(30).all()
result["actions"] = [
@@ -526,6 +541,44 @@ def bot_import_excel(
# ── 自然语言查询 ──
@router.post("/okr/create")
def bot_okr_create(
title: str = Query(...),
quarter: str = Query(...),
dimension: Optional[str] = Query(None),
bot: dict = Depends(verify_bot_key),
db: Session = Depends(get_db),
):
"""Bot创建OKR目标"""
from app.models import Objective
obj = Objective(title=title, quarter=quarter, dimension=dimension, owner=bot["name"])
db.add(obj)
db.commit()
db.refresh(obj)
return {"ok": True, "id": obj.id, "title": obj.title, "confidence": obj.confidence}
@router.get("/okr/list")
def bot_okr_list(
quarter: Optional[str] = Query(None),
bot: dict = Depends(verify_bot_key),
db: Session = Depends(get_db),
):
"""Bot列出OKR(含KR进度)"""
from app.models import Objective
q = db.query(Objective)
if quarter:
q = q.filter(Objective.quarter == quarter)
objs = q.order_by(Objective.quarter.desc()).all()
return {"total": len(objs), "items": [
{"id": o.id, "title": o.title, "quarter": o.quarter,
"dimension": o.dimension, "progress": o.progress,
"confidence": o.confidence, "status": o.status,
"kr_count": db.query(func.count(ActionPlan.id)).filter(ActionPlan.objective_id == o.id).scalar() or 0}
for o in objs
]}
@router.get("/nlp")
def bot_nlp(
intent: str = Query("overview"),
@@ -544,6 +597,7 @@ def bot_nlp(
"成本": "cost", "成本分析": "cost",
"战略": "maps", "战略地图": "maps",
"行动": "actions", "改善": "actions",
"okr": "okr", "目标": "okr", "季度目标": "okr",
}
resolved = m.get(intent, intent)
return bot_query(q=resolved, bot=bot, db=db)