diff --git a/backend/app/api/kpis.py b/backend/app/api/kpis.py
index 8d2c020d..88639818 100644
--- a/backend/app/api/kpis.py
+++ b/backend/app/api/kpis.py
@@ -35,13 +35,13 @@ def list_kpis(
if dims:
query = query.filter(KPIDefinition.dimension.in_(dims))
if dimension:
- query = query.filter(KPIDefinition.dimension == dimension)
+ dims_list = [d.strip() for d in dimension.split(',')] if ',' in dimension else [dimension]
+ query = query.filter(KPIDefinition.dimension.in_(dims_list))
if keyword:
query = query.filter(KPIDefinition.kpi_name.contains(keyword))
- if epic:
- query = query.filter(KPIDefinition.epic == epic)
if category:
- query = query.filter(KPIDefinition.category == category)
+ cats_list = [c.strip() for c in category.split(',')] if ',' in category else [category]
+ query = query.filter(KPIDefinition.category.in_(cats_list))
total = query.count()
kpis = query.order_by(KPIDefinition.kpi_code).offset((page-1)*page_size).limit(page_size).all()
return {"total": total, "page": page, "page_size": page_size, "data": [kpi_to_dict(k) for k in kpis]}
@@ -134,7 +134,37 @@ def delete_kpi(kpi_id: int, db: Session = Depends(get_db), user=WRITE_ROLES):
def kpi_to_dict(k):
- return {c.name: getattr(k, c.name) for c in k.__table__.columns}
+ d = {c.name: getattr(k, c.name) for c in k.__table__.columns}
+ # 附加战略地图信息
+ if k.map_id:
+ from app.database import get_session_local
+ try:
+ sess = get_session_local()()
+ m = sess.query(StrategicMap).filter(StrategicMap.id == k.map_id).first()
+ d["map_title"] = m.title if m else None
+ sess.close()
+ except:
+ d["map_title"] = None
+ else:
+ d["map_title"] = None
+ return d
+
+
+@router.put("/{kpi_id}/associate-map")
+def associate_kpi_map(kpi_id: int, data: dict, db: Session = Depends(get_db), user=WRITE_ROLES):
+ """关联KPI到战略地图"""
+ kpi = db.query(KPIDefinition).filter(KPIDefinition.id == kpi_id).first()
+ if not kpi:
+ raise HTTPException(404, "KPI不存在")
+ map_id = data.get("map_id")
+ if map_id is not None:
+ m = db.query(StrategicMap).filter(StrategicMap.id == map_id).first()
+ if not m:
+ raise HTTPException(404, "战略地图不存在")
+ kpi.map_id = map_id
+ db.commit()
+ _log(db, 1, "update", "kpi", kpi_id, {"action": "associate-map", "map_id": map_id})
+ return kpi_to_dict(kpi)
def _log(db, user_id, action, target_type, target_id, detail):
diff --git a/frontend/src/views/KPIList.vue b/frontend/src/views/KPIList.vue
index 9f1e9e66..51e9b2a6 100644
--- a/frontend/src/views/KPIList.vue
+++ b/frontend/src/views/KPIList.vue
@@ -94,6 +94,12 @@
{{ sourceLabel(row.data_source_type) }}
+
+
+ {{ row.map_title }}
+ -
+
+
@@ -427,21 +433,20 @@ async function loadCategories() {
}
}
-// ── 树节点勾选 ──
+// ── 树节点勾选(支持多选)──
function onTreeCheck(data: any, { checkedKeys }: any) {
- // 简单模式:只支持单选过滤
- if (data.key === '_all') {
+ // 从checkedKeys中分离出维度和分类
+ const dimKeys = ['finance', 'customer', 'process', 'learning']
+ const checkedDims = checkedKeys.filter((k: string) => dimKeys.includes(k))
+ const checkedCats = checkedKeys.filter((k: string) => !dimKeys.includes(k) && k !== '_all')
+
+ if (data.key === '_all' || checkedKeys.includes('_all')) {
// 勾选全部=清空过滤
treeFilterDims.value = []
treeFilterCats.value = []
- } else if (data.children) {
- // 维度节点
- treeFilterDims.value = checkedKeys.includes(data.key) ? [data.key] : []
- treeFilterCats.value = []
} else {
- // 类别节点
- treeFilterCats.value = checkedKeys.includes(data.key) ? [data.key] : []
- treeFilterDims.value = []
+ treeFilterDims.value = checkedDims
+ treeFilterCats.value = checkedCats
}
loadKpis()
}
@@ -454,8 +459,8 @@ async function loadKpis() {
if (searchKeyword.value) params.keyword = searchKeyword.value
if (searchDimension.value) params.dimension = searchDimension.value
if (searchCategory.value) params.category = searchCategory.value
- if (treeFilterDims.value.length > 0 && !params.dimension) params.dimension = treeFilterDims.value[0]
- if (treeFilterCats.value.length > 0 && !params.category) params.category = treeFilterCats.value[0]
+ if (treeFilterDims.value.length > 0 && !params.dimension) params.dimension = treeFilterDims.value.join(',')
+ if (treeFilterCats.value.length > 0 && !params.category) params.category = treeFilterCats.value.join(',')
const r: any = await kpiApi.list(params)
kpis.value = r.data || []