feat: CMA P1+P2 Round1 — 成本法对比+杜邦分析+本量利
This commit is contained in:
@@ -207,6 +207,76 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 6: 成本法对比(CMA P1) -->
|
||||
<el-tab-pane label="成本法对比" name="comparison">
|
||||
<div v-loading="compLoading">
|
||||
<div style="display:flex;gap:12px;margin-bottom:16px;align-items:center;">
|
||||
<span style="font-weight:600;font-size:14px;">分析对象:</span>
|
||||
<el-radio-group v-model="compEntity" @change="loadComparison">
|
||||
<el-radio value="hanke">陕西酣客(白酒经销)</el-radio>
|
||||
<el-radio value="bohai">陕西博海科技(IT服务)</el-radio>
|
||||
</el-radio-group>
|
||||
<el-button type="primary" size="small" @click="loadComparison" :loading="compLoading">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="compData.traditional" class="comp-table-wrapper">
|
||||
<el-table :data="compRows" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="metric" label="指标" width="120" fixed>
|
||||
<template #default="{ row }">
|
||||
<span :style="{ fontWeight: row.bold ? 700 : 400 }">{{ row.metric }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="传统成本法" width="160" align="center">
|
||||
<template #header>
|
||||
<div style="text-align:center;">传统成本法</div>
|
||||
<div style="font-size:11px;color:#999;text-align:center;">(账面当前)</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: row.t_color || '#333', fontWeight: row.t_bold ? 700 : 400 }">{{ row.traditional }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变动成本法" width="160" align="center">
|
||||
<template #header>
|
||||
<div style="text-align:center;">变动成本法</div>
|
||||
<div style="font-size:11px;color:#999;text-align:center;">(Model C)</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: row.v_color || '#333', fontWeight: row.v_bold ? 700 : 400 }">{{ row.variable }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="作业成本法" width="160" align="center">
|
||||
<template #header>
|
||||
<div style="text-align:center;">作业成本法</div>
|
||||
<div style="font-size:11px;color:#999;text-align:center;">(ABC)</div>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<span :style="{ color: row.a_color || '#333', fontWeight: row.a_bold ? 700 : 400 }">{{ row.abc }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 注释 -->
|
||||
<el-card style="margin-top:12px;background:#fafafa;" size="small" v-if="compNotes.length > 0">
|
||||
<div v-for="(note, idx) in compNotes" :key="idx" style="font-size:12px;color:#666;padding:2px 0;">
|
||||
{{ note }}
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 决策启示 -->
|
||||
<el-card style="margin-top:12px;border-left:4px solid #409eff;">
|
||||
<template #header><span style="font-weight:600;">📋 决策启示</span></template>
|
||||
<div v-for="insight in compInsights" :key="insight.method" class="insight-row">
|
||||
<div class="insight-method">{{ insight.method }}</div>
|
||||
<div class="insight-conclusion">{{ insight.conclusion }}</div>
|
||||
<div class="insight-decision">{{ insight.decision }}</div>
|
||||
<div class="insight-detail">{{ insight.detail }}</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<el-empty v-else-if="!compLoading" description="选择实体后点击「刷新」加载数据" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 标准成本表单弹窗 -->
|
||||
@@ -283,7 +353,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { costApi } from '../api/index'
|
||||
import MyDialog from '../components/MyDialog.vue'
|
||||
@@ -506,6 +576,88 @@ async function doAllocate() {
|
||||
allocSaving.value = false
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 成本法对比(CMA P1)
|
||||
// ============================
|
||||
const compLoading = ref(false)
|
||||
const compEntity = ref('hanke')
|
||||
const compData = ref<any>({})
|
||||
const compRows = computed(() => {
|
||||
if (!compData.value.traditional) return []
|
||||
const d = compData.value
|
||||
const fmt = (v: any) => {
|
||||
if (v === null || v === undefined) return '--'
|
||||
const n = Number(v)
|
||||
if (n >= 10000) return (n / 10000).toFixed(1) + '亿'
|
||||
return n.toFixed(1) + '万'
|
||||
}
|
||||
const pct = (v: any) => v !== null && v !== undefined ? v.toFixed(2) + '%' : '--'
|
||||
return [
|
||||
{
|
||||
metric: '收入', bold: true,
|
||||
traditional: fmt(d.traditional.revenue),
|
||||
variable: fmt(d.variable.revenue),
|
||||
abc: fmt(d.abc.revenue),
|
||||
},
|
||||
{
|
||||
metric: '成本', bold: true,
|
||||
traditional: fmt(d.traditional.cost),
|
||||
variable: fmt(d.variable.cost),
|
||||
abc: fmt(d.abc.cost),
|
||||
},
|
||||
{
|
||||
metric: '毛利', bold: true,
|
||||
traditional: fmt(d.traditional.gross_profit),
|
||||
variable: fmt(d.variable.gross_profit),
|
||||
abc: fmt(d.abc.gross_profit),
|
||||
t_color: d.traditional.gross_profit < 0 ? '#f56c6c' : '#67c23a',
|
||||
v_color: d.variable.gross_profit > 0 ? '#67c23a' : '#f56c6c',
|
||||
a_color: d.abc.gross_profit > 0 ? '#67c23a' : '#f56c6c',
|
||||
t_bold: true, v_bold: true, a_bold: true,
|
||||
},
|
||||
{
|
||||
metric: '毛利率', bold: false,
|
||||
traditional: pct(d.traditional.gross_margin),
|
||||
variable: pct(d.variable.gross_margin),
|
||||
abc: pct(d.abc.gross_margin),
|
||||
t_color: d.traditional.gross_margin < 0 ? '#f56c6c' : '#67c23a',
|
||||
v_color: d.variable.gross_margin > 10 ? '#67c23a' : '#e6a23c',
|
||||
a_color: d.abc.gross_margin > 10 ? '#67c23a' : '#e6a23c',
|
||||
},
|
||||
{
|
||||
metric: '费用', bold: true,
|
||||
traditional: fmt(d.traditional.expenses),
|
||||
variable: fmt(d.variable.expenses),
|
||||
abc: fmt(d.abc.expenses),
|
||||
},
|
||||
{
|
||||
metric: '净利润', bold: true,
|
||||
traditional: fmt(d.traditional.net_profit),
|
||||
variable: fmt(d.variable.net_profit),
|
||||
abc: fmt(d.abc.net_profit),
|
||||
t_color: d.traditional.net_profit < 0 ? '#f56c6c' : '#67c23a',
|
||||
v_color: d.variable.net_profit < 0 ? '#f56c6c' : '#67c23a',
|
||||
a_color: d.abc.net_profit < 0 ? '#f56c6c' : '#67c23a',
|
||||
t_bold: true, v_bold: true, a_bold: true,
|
||||
},
|
||||
]
|
||||
})
|
||||
const compNotes = computed(() => {
|
||||
const notes = compData.value.notes
|
||||
if (!notes) return []
|
||||
return Object.values(notes)
|
||||
})
|
||||
const compInsights = computed(() => compData.value.insights || [])
|
||||
|
||||
async function loadComparison() {
|
||||
compLoading.value = true
|
||||
try {
|
||||
const r: any = await costApi.comparison({ entity: compEntity.value })
|
||||
compData.value = r.data || r
|
||||
} catch (e) { ElMessage.error('加载对比数据失败') }
|
||||
compLoading.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOverview()
|
||||
loadStandardCosts()
|
||||
|
||||
Reference in New Issue
Block a user