feat: CMA P1+P2 Round2 — 风险矩阵+COSO+预算方法
This commit is contained in:
@@ -176,6 +176,52 @@
|
||||
<el-table-column label="预警" width="80"><template #default="{ row }"><el-tag v-if="row.alert_level === 'red'" size="small" type="danger">严重</el-tag><el-tag v-else-if="row.alert_level === 'yellow'" size="small" type="warning">关注</el-tag><el-tag v-else size="small" type="success">正常</el-tag></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="编制方法" name="method">
|
||||
<div>
|
||||
<p style="font-size:14px;color:#606266;margin-bottom:16px;">选择预算编制方法 — CMA P1 预算编制(增量预算 vs 零基预算 vs 弹性预算)</p>
|
||||
|
||||
<!-- 方法选择卡片 -->
|
||||
<div style="display:flex;gap:16px;margin-bottom:20px;flex-wrap:wrap;">
|
||||
<div v-for="m in budgetMethods" :key="m.id"
|
||||
:class="['method-card', { selected: selectedMethod === m.id, recommended: m.is_recommended }]"
|
||||
@click="selectedMethod = m.id; loadMethodDetail(m.id)"
|
||||
style="flex:1;min-width:220px;border:2px solid #e8e8e8;border-radius:10px;padding:16px;cursor:pointer;transition:all .2s;position:relative;">
|
||||
<div v-if="m.is_recommended" style="position:absolute;top:-10px;right:12px;background:#e6a23c;color:#fff;font-size:11px;padding:2px 10px;border-radius:8px;">推荐</div>
|
||||
<div style="font-size:12px;color:#999;">{{ m.name_en }}</div>
|
||||
<div style="font-size:18px;font-weight:600;margin:6px 0;">{{ m.name }}</div>
|
||||
<div style="font-size:13px;color:#606266;margin-bottom:8px;">{{ m.pros }} · {{ m.cons }}</div>
|
||||
<div v-if="m.result_value != null" style="font-size:24px;font-weight:700;color:#409eff;">{{ formatNumber(m.result_value) }}<span style="font-size:14px;font-weight:400;color:#999;"> 万</span></div>
|
||||
<div v-if="m.savings" style="font-size:12px;color:#67c23a;">节省 {{ formatNumber(m.savings) }} 万</div>
|
||||
<div v-if="m.variance" style="font-size:12px;" :style="{ color: m.variance > 0 ? '#f56c6c' : '#67c23a' }">差异 {{ m.variance > 0 ? '+' : '' }}{{ formatNumber(m.variance) }} 万</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 方法详情 -->
|
||||
<el-card v-if="selectedMethodDetail" shadow="never" style="margin-bottom:12px;">
|
||||
<template #header>
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<span><strong>{{ selectedMethodDetail.name }}</strong> <span style="font-size:12px;color:#999;">{{ selectedMethodDetail.name_en }}</span></span>
|
||||
<el-tag v-if="selectedMethodDetail.is_recommended" type="warning" effect="dark">推荐</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<p style="margin:0 0 8px;color:#606266;">{{ selectedMethodDetail.detail }}</p>
|
||||
<div style="display:flex;gap:20px;flex-wrap:wrap;">
|
||||
<div><span style="color:#909399;">优点:</span>{{ selectedMethodDetail.pros }}</div>
|
||||
<div><span style="color:#909399;">缺点:</span>{{ selectedMethodDetail.cons }}</div>
|
||||
</div>
|
||||
<div v-if="selectedMethodDetail.result_value != null" style="margin-top:12px;padding:10px 14px;background:#f0f9ff;border-radius:6px;display:flex;align-items:center;gap:12px;">
|
||||
<span style="color:#909399;">预算结果:</span>
|
||||
<span style="font-size:22px;font-weight:700;color:#409eff;">{{ formatNumber(selectedMethodDetail.result_value) }} 万</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<div style="display:flex;gap:8px;margin-top:16px;">
|
||||
<el-button v-if="selectedMethod" type="primary" @click="confirmMethod">确认选择({{ selectedMethodName }})</el-button>
|
||||
<el-button @click="refreshMethodComparison">刷新计算</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<MyDialog v-model="showAddBudget" title="新增预算" :width="500">
|
||||
@@ -449,11 +495,41 @@ async function doDecompose() {
|
||||
decomposing.value = false
|
||||
}
|
||||
|
||||
// ── 预算方法三选一向导 ──
|
||||
const selectedMethod = ref('zero_based')
|
||||
const budgetMethods = ref<any[]>([])
|
||||
const selectedMethodDetail = ref<any>(null)
|
||||
|
||||
const selectedMethodName = computed(() => {
|
||||
return budgetMethods.value.find(m => m.id === selectedMethod.value)?.name || ''
|
||||
})
|
||||
|
||||
function loadMethodDetail(id: string) {
|
||||
selectedMethodDetail.value = budgetMethods.value.find(m => m.id === id) || null
|
||||
}
|
||||
|
||||
async function refreshMethodComparison() {
|
||||
try {
|
||||
const r: any = await budgetApi.methodComparison({ entity: 'hanke' })
|
||||
budgetMethods.value = r.methods || []
|
||||
selectedMethod.value = r.recommended || 'zero_based'
|
||||
loadMethodDetail(selectedMethod.value)
|
||||
} catch { ElMessage.error('加载预算方法对比失败') }
|
||||
}
|
||||
|
||||
function confirmMethod() {
|
||||
const m = selectedMethodDetail.value
|
||||
if (m) {
|
||||
ElMessage.success(`已选择「${m.name}」,预算结果:${m.result_value} 万`)
|
||||
}
|
||||
}
|
||||
|
||||
watch(activeTab, (tab) => {
|
||||
if (tab === 'decompose') loadBudget()
|
||||
else if (tab === 'strategy') loadStrategyBudget()
|
||||
else if (tab === 'versions') loadVersions()
|
||||
else if (tab === 'execution') loadExecutionReport()
|
||||
else if (tab === 'method') refreshMethodComparison()
|
||||
})
|
||||
onMounted(async () => {
|
||||
await loadPublishedMaps()
|
||||
|
||||
Reference in New Issue
Block a user