feat: 战略地图收尾 — 自动保存+导出+表迁移+因果链推荐

This commit is contained in:
Hermes CI Fix
2026-07-14 17:32:22 +08:00
parent 5594a572e9
commit 9afcea8f8d
18 changed files with 207 additions and 59 deletions
-51
View File
@@ -13,54 +13,6 @@ declare module 'vue' {
export interface GlobalComponents {
BulletChart: typeof import('./src/components/charts/BulletChart.vue')['default']
ConnectionLines: typeof import('./src/components/strategy/ConnectionLines.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAside: typeof import('element-plus/es')['ElAside']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMain: typeof import('element-plus/es')['ElMain']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
ElSlider: typeof import('element-plus/es')['ElSlider']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTimeline: typeof import('element-plus/es')['ElTimeline']
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload']
GaugeChart: typeof import('./src/components/charts/GaugeChart.vue')['default']
KnowledgePanel: typeof import('./src/components/KnowledgePanel.vue')['default']
KPIListView: typeof import('./src/components/KPIListView.vue')['default']
@@ -74,7 +26,4 @@ declare module 'vue' {
WaterfallChart: typeof import('./src/components/charts/WaterfallChart.vue')['default']
WelcomeGuide: typeof import('./src/components/WelcomeGuide.vue')['default']
}
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
+74 -1
View File
@@ -24,7 +24,9 @@
<input type="radio" v-model="viewDirection" value="causal" /> 🔗 因果
</label>
</span>
<el-button type="primary" @click="saveCanvas">保存</el-button>
<el-button @click="exportImage">📷 导出图片</el-button>
<el-button @click="showCausalityRecommendations">🔗 因果链推荐</el-button>
<el-button type="primary" @click="saveCanvas(false)">保存</el-button>
<el-button type="success" @click="publishMap" v-if="currentMap?.status === 'draft'">发布</el-button>
<el-button @click="showVersions = true; loadVersions()">版本历史</el-button>
<el-button @click="goAlignment" type="info" plain>
@@ -167,6 +169,35 @@
</div>
</div>
<!-- KPI因果链推荐弹窗 -->
<div v-show="showCausalityDialog" class="mc-dialog-overlay" @click.self="showCausalityDialog=false">
<div class="mc-dialog-box" style="width:680px;">
<div class="mc-dialog-header">
<span>🔗 KPI因果链推荐</span>
<button class="mc-dialog-close" @click="showCausalityDialog=false">×</button>
</div>
<div class="mc-dialog-body" style="max-height:400px;overflow-y:auto;">
<div v-if="causalityLoading" style="text-align:center;padding:20px;color:#999;">加载中...</div>
<div v-else-if="causalityRecommendations.length === 0" style="text-align:center;padding:20px;color:#999;">暂无因果链推荐</div>
<div v-else>
<div v-for="(rec,idx) in causalityRecommendations" :key="idx" style="padding:10px 0;border-bottom:1px solid #f0f0f0;display:flex;justify-content:space-between;align-items:center;">
<div>
<strong>{{ rec.source_kpi_code || rec.source_code }}</strong>
<span style="color:#999;margin:0 8px;">→</span>
<strong>{{ rec.target_kpi_code || rec.target_code }}</strong>
<span style="margin-left:8px;font-size:12px;color:#666;">强度: {{ rec.strength || '-' }}</span>
</div>
<button class="mc-btn" @click="applyCausality(rec)">应用</button>
</div>
</div>
</div>
<div class="mc-dialog-footer" v-if="causalityRecommendations.length > 0">
<button class="mc-btn" @click="showCausalityDialog=false">取消</button>
<button class="mc-btn mc-btn-primary" @click="applyAllCausality">一键全部应用</button>
</div>
</div>
</div>
<!-- 版本历史弹窗 -->
<div v-show="showVersions" class="mc-dialog-overlay" @click.self="showVersions=false">
<div class="mc-dialog-box" style="width:620px;">
@@ -935,6 +966,48 @@ async function exportImage() {
ElMessage.error('导出失败')
}
}
// ── KPI因果链推荐 ──
const showCausalityDialog = ref(false)
const causalityRecommendations = ref<any[]>([])
const causalityLoading = ref(false)
async function showCausalityRecommendations() {
if (!currentMap.value) { ElMessage.warning('请先选择或创建战略地图'); return }
causalityLoading.value = true
showCausalityDialog.value = true
try {
const r: any = await api.get('/kpi-causality')
const items = r.data || r.items || r || []
causalityRecommendations.value = Array.isArray(items) ? items : []
} catch (e) {
ElMessage.error('加载因果链失败')
causalityRecommendations.value = []
}
causalityLoading.value = false
}
async function applyCausality(rec: any) {
try {
await api.post(\\`/maps/\\${currentMap.value?.id}/connections\\`, { from: rec.source_kpi_code, to: rec.target_kpi_code })
ElMessage.success('连线已添加')
showCausalityDialog.value = false
loadMap()
} catch { ElMessage.error('添加失败') }
}
async function applyAllCausality() {
let added = 0
for (const rec of causalityRecommendations.value) {
try {
await api.post(\\`/maps/\\${currentMap.value?.id}/connections\\`, { from: rec.source_kpi_code, to: rec.target_kpi_code })
added++
} catch {}
}
ElMessage.success(\\`已添加 \\${added}/\\${causalityRecommendations.value.length} 条因果链\\`)
showCausalityDialog.value = false
loadMap()
}
onMounted(async () => {
const r: any = await mapApi.list()