merge: origin/main (A3 三维切片) 合入战略地图改造分支

This commit is contained in:
Hermes CI Fix
2026-10-19 14:32:33 +08:00
12 changed files with 986 additions and 1 deletions
+3
View File
@@ -360,6 +360,8 @@
<el-button type="primary" @click="savePlan" :loading="savingPlan">保存</el-button>
</template>
</MyDialog>
<!-- KBFF-v1.1: 按 related_page 关联知识接线 · /alerts -->
<KnowledgePanel related-page="/alerts" />
</div>
</template>
@@ -372,6 +374,7 @@ import BotSourceTag from '../components/BotSourceTag.vue'
import RiskCell from '../components/charts/RiskCell.vue'
import { sessionEntityName } from '../utils/entity'
import MyDialog from '../components/MyDialog.vue'
import KnowledgePanel from '../components/KnowledgePanel.vue'
const route = useRoute()
const activeTab = ref((route.meta?.tab as string) || 'alerts')
+3
View File
@@ -769,6 +769,8 @@
</el-form>
<template #footer><el-button @click="showDerivationRuleDialog = false">取消</el-button><el-button type="primary" @click="saveDerivationRule">保存</el-button></template>
</MyDialog>
<!-- KBFF-v1.1: 按 related_page 关联知识接线 · /budget -->
<KnowledgePanel related-page="/budget" />
</div>
</div>
</template>
@@ -778,6 +780,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { budgetApi, kpiApi, mapApi, actionPlanApi } from '../api/index'
import MyDialog from '../components/MyDialog.vue'
import DriverFactorBudget from '../components/DriverFactorBudget.vue'
import KnowledgePanel from '../components/KnowledgePanel.vue'
const activeTab = ref('input')
const currentYear = new Date().getFullYear()
@@ -184,6 +184,8 @@
<!-- 无数据提示 -->
<el-empty v-if="!loading && hierarchyData.length === 0 && reportData.length === 0" description="暂无差异分析数据,请先在预算管理中录入预算" style="padding:40px 0;" />
<!-- KBFF-v1.1: 按 related_page 关联知识接线 · /deviations -->
<KnowledgePanel related-page="/deviations" />
</div>
</template>
@@ -191,6 +193,7 @@
import { ref, computed, onMounted, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { budgetApi, kpiApi } from '../api/index'
import KnowledgePanel from '../components/KnowledgePanel.vue'
const currentYear = new Date().getFullYear()
const yearOptions = computed(() => {
+3
View File
@@ -398,6 +398,8 @@
<el-button type="primary" :disabled="!selectedTemplate" @click="doInstantiate">从模板创建</el-button>
</template>
</MyDialog>
<!-- KBFF-v1.1: 按 related_page 关联知识接线 · /kpis -->
<KnowledgePanel related-page="/kpis" />
</div>
</template>
@@ -407,6 +409,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { kpiApi, templateApi, dashboardApi, userApi, orgApi } from '../api/index'
import MyDialog from '../components/MyDialog.vue'
import { useFormGuard, trackDialogForm } from '../composables/useFormGuard'
import KnowledgePanel from '../components/KnowledgePanel.vue'
// ── 数据 ──
const kpis = ref<any[]>([])
+53
View File
@@ -54,6 +54,9 @@
@completeness="showCompleteness = true"
/>
<!-- A3 三维切片(时间/门店/产品):切换即重算;空态显式,不回退总额 -->
<MapDimensionFilter :map-id="selectedMap" @slice="onDimensionSlice" />
<!-- 因果链查看模式提示 -->
<div v-if="chainMode" class="chain-mode-bar">
<span class="chain-ico">
@@ -312,6 +315,7 @@ import MapNodeDetail from "../components/strategy-map/MapNodeDetail.vue"
import MapCompleteness from "../components/strategy-map/MapCompleteness.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"
@@ -573,6 +577,44 @@ const visibleObjectiveCount = computed(() =>
treeGroups.value.reduce((n: number, g: any) => n + g.objectives.length, 0)
)
// ── 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)
@@ -679,9 +721,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
}
+3
View File
@@ -340,6 +340,8 @@
</el-timeline-item>
</el-timeline>
</MyDialog>
<!-- KBFF-v1.1: 按 related_page 关联知识接线 · /maps-review -->
<KnowledgePanel related-page="/maps-review" />
</div>
</template>
@@ -349,6 +351,7 @@ import { useRoute, useRouter } from "vue-router"
import { ElMessage } from "element-plus"
import api from "../api/index"
import MyDialog from '../components/MyDialog.vue'
import KnowledgePanel from '../components/KnowledgePanel.vue'
const route = useRoute()
const $router = useRouter()
+3
View File
@@ -434,6 +434,8 @@
<CostIntelligence />
</el-tab-pane>
</el-tabs>
<!-- KBFF-v1.1: 按 related_page 关联知识接线 · /predict -->
<KnowledgePanel related-page="/predict" />
</div>
</template>
@@ -442,6 +444,7 @@ import { ref, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { predictApi } from '../api/index'
import CostIntelligence from './CostIntelligence.vue'
import KnowledgePanel from '../components/KnowledgePanel.vue'
const activeTab = ref('cvp')