feat(strategy-map): A3 三维切片(时间/门店/产品)——切片重算 + 显式空态 + entity 隔离
ci/woodpecker/push/main Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
- 新增 /api/cma/maps/{id}/dimension-options 与 /{id}/slice(只读)
- 时间:kpi_values.period 月/季/年;flow 求和、ratio 均值、balance/status 取最新
- 产品:product_sales + 逐月源一致性闸门(偏差>1% 不切片)
- 门店:库内无门店级事实 ⇒ available=false + reason,绝不回退总额
- 前端 MapDimensionFilter 挂到 MapCanvas,切换即 emit 切片,清除恢复 /review
This commit is contained in:
committed by
Hermes CI Fix
parent
c92dbab96a
commit
d85dd2a155
@@ -25,6 +25,9 @@
|
||||
@toggle-chain-mode="toggleChainMode"
|
||||
/>
|
||||
|
||||
<!-- A3 三维切片(时间/门店/产品):切换即重算;空态显式,不回退总额 -->
|
||||
<MapDimensionFilter :map-id="selectedMap" @slice="onDimensionSlice" />
|
||||
|
||||
<!-- 因果链查看模式提示 -->
|
||||
<div v-if="chainMode" class="chain-mode-bar">
|
||||
<span>🔍 因果链查看模式 — 点击节点聚焦其相关因果链(再次点击取消),其余节点自动变淡</span>
|
||||
@@ -202,6 +205,7 @@ import { sanitizeKrName } from "../utils/krName"
|
||||
import StrategyLayer from "../components/strategy-map/StrategyLayer.vue"
|
||||
import ConnectionLines from "../components/strategy/ConnectionLines.vue"
|
||||
import MapCanvasToolbar from "../components/map-canvas/MapCanvasToolbar.vue"
|
||||
import MapDimensionFilter from "../components/map-canvas/MapDimensionFilter.vue"
|
||||
import MapCanvasDialogs from "../components/map-canvas/MapCanvasDialogs.vue"
|
||||
import ConnectionManagerDialog from "../components/map-canvas/ConnectionManagerDialog.vue"
|
||||
import MyDialog from "../components/MyDialog.vue"
|
||||
@@ -329,6 +333,44 @@ interface KpiProgressItem {
|
||||
}
|
||||
const kpiProgressMap = reactive<Record<string, KpiProgressItem>>({})
|
||||
|
||||
// ── A3 维度切片(时间/门店/产品)──────────────────────────────
|
||||
// 切片激活时用它覆盖 objectiveLevels / kpiProgressMap 的展示值:
|
||||
// - available=false 的 KPI 一律 null,不用总额或其它期间冒充
|
||||
const dimSlice = ref<any | null>(null)
|
||||
|
||||
/** 切片结果 → 节点键(dimKey-index);按 目标名 对齐 map_objectives 与本地 dimensions */
|
||||
const sliceByKey = computed<Record<string, any>>(() => {
|
||||
const out: Record<string, any> = {}
|
||||
const payload = dimSlice.value
|
||||
if (!payload || !Array.isArray(payload.objectives)) return out
|
||||
for (const o of payload.objectives) {
|
||||
const dim = dimensions.find((d: any) => d.key === o.dimension_key)
|
||||
if (!dim) continue
|
||||
const idx = (dim.objectives || []).findIndex((x: any) => x.name === o.name)
|
||||
if (idx < 0) continue
|
||||
const kpis = (o.kpis || []).map((k: any) => ({
|
||||
code: k.kpi_code, name: k.kpi_name, actual: k.actual_value, target: k.target_value,
|
||||
unit: k.unit, available: k.available, reason: k.reason,
|
||||
}))
|
||||
const ratios = (o.kpis || [])
|
||||
.filter((k: any) => k.available && k.achievement_pct != null)
|
||||
.map((k: any) => Number(k.achievement_pct))
|
||||
out[`${o.dimension_key}-${idx}`] = {
|
||||
ratio: ratios.length ? Math.round(ratios.reduce((a: number, b: number) => a + b, 0) / ratios.length) : 0,
|
||||
level: o.has_data ? (o.level || 'gray') : 'gray',
|
||||
kpiList: kpis,
|
||||
slice: true,
|
||||
}
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
function onDimensionSlice(payload: any | null) {
|
||||
dimSlice.value = payload
|
||||
// 切回全景(payload=null)时恢复 /review 的原始状态
|
||||
if (!payload) loadObjectiveLevels()
|
||||
}
|
||||
|
||||
// 弹窗
|
||||
const showNodeDialog = ref(false)
|
||||
const editingNodeData = ref<any>(null)
|
||||
@@ -429,9 +471,20 @@ function setLayerRef(key: string, el: any) {
|
||||
}
|
||||
|
||||
function getNodeLevel(key: string): string {
|
||||
if (dimSlice.value) return sliceByKey.value[key]?.level || 'gray'
|
||||
return objectiveLevels[key] || 'gray'
|
||||
}
|
||||
function getNodeProgress(key: string): any {
|
||||
if (dimSlice.value) {
|
||||
const s = sliceByKey.value[key]
|
||||
return {
|
||||
ratio: s?.ratio ?? 0,
|
||||
level: s?.level || 'gray',
|
||||
kpiList: s?.kpiList || [],
|
||||
planSummary: kpiProgressMap[key]?.planSummary ?? null,
|
||||
slice: true,
|
||||
}
|
||||
}
|
||||
return kpiProgressMap[key] || null
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user