init: 管理会计OS初始代码
包含前后端完整代码: - 前端:Vue3+Vite+ElementPlus - 后端:FastAPI+SQLAlchemy - 模块:驾驶舱/KPI/战略地图/预警/预算/成本/预测/改善行动 - 当前版本:v1.0.0
This commit is contained in:
@@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>成本分析</h3>
|
||||
|
||||
<el-tabs v-model="activeTab" style="margin-top:16px;">
|
||||
<!-- Tab 1: 成本总览 -->
|
||||
<el-tab-pane label="成本总览" name="overview">
|
||||
<div style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap;align-items:center;">
|
||||
<el-select v-model="overviewPeriod" placeholder="期间" style="width:110px;" @change="loadOverview">
|
||||
<el-option v-for="p in periodOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadOverview">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" style="margin-bottom:16px;">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div class="stat-card"><div class="stat-label">总成本</div><div class="stat-value">{{ formatMoney(overviewData.total_cost || 0) }}</div></div></el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div class="stat-card"><div class="stat-label">ERP同步成本</div><div class="stat-value" style="color:#409eff;">{{ formatMoney(overviewData.erp_cost || 0) }}</div></div></el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div class="stat-card"><div class="stat-label">手工录入成本</div><div class="stat-value" style="color:#e6a23c;">{{ formatMoney(overviewData.manual_cost || 0) }}</div></div></el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div class="stat-card"><div class="stat-label">产品数</div><div class="stat-value" style="color:#67c23a;">{{ (overviewData.products || []).length }}</div></div></el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 成本结构 -->
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>成本结构(按类型)</template>
|
||||
<div v-if="(overviewData.structure || []).length > 0">
|
||||
<div v-for="s in overviewData.structure" :key="s.cost_type" style="margin-bottom:12px;">
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:4px;">
|
||||
<span>{{ costTypeLabel(s.cost_type) }}</span>
|
||||
<span>{{ formatMoney(s.amount) }}({{ s.ratio }}%)</span>
|
||||
</div>
|
||||
<el-progress :percentage="s.ratio" :stroke-width="14" :color="costTypeColor(s.cost_type)" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center;color:#999;padding:20px;">暂无成本数据,请先在「成本录入」中添加</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>各产品成本</template>
|
||||
<div v-if="(overviewData.products || []).length > 0">
|
||||
<div v-for="p in overviewData.products" :key="p.product_code" style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #f5f5f5;font-size:13px;">
|
||||
<span>{{ p.product_name || p.product_code }}</span>
|
||||
<span style="font-weight:500;">{{ formatMoney(p.total_cost) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center;color:#999;padding:20px;">暂无产品数据</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 成本趋势 -->
|
||||
<el-card style="margin-top:16px;">
|
||||
<template #header>成本趋势(近6个月)</template>
|
||||
<div v-if="(overviewData.trend || []).length > 0" style="display:flex;align-items:flex-end;gap:12px;height:180px;padding:20px 0;overflow-x:auto;">
|
||||
<div v-for="(t, idx) in overviewData.trend" :key="idx" style="display:flex;flex-direction:column;align-items:center;min-width:80px;">
|
||||
<div class="trend-bar" :style="{ height: trendHeight(t.total_cost, maxTrend) + 'px' }" :title="formatMoney(t.total_cost)"></div>
|
||||
<span style="font-size:11px;color:#909399;margin-top:6px;">{{ t.period }}</span>
|
||||
<span style="font-size:10px;">{{ formatMoney(t.total_cost) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center;color:#999;padding:20px;">暂无趋势数据</div>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 2: 标准成本管理 -->
|
||||
<el-tab-pane label="标准成本" name="standard">
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button type="primary" @click="showStdForm = true; stdForm = {}">+ 新建标准成本</el-button>
|
||||
<el-select v-model="stdProductFilter" placeholder="产品" clearable style="width:150px;margin-left:8px;" @change="loadStandardCosts">
|
||||
<el-option v-for="p in productOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-table :data="standardCosts" v-loading="stdLoading" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="product_code" label="产品编码" width="100" />
|
||||
<el-table-column prop="product_name" label="产品名称" width="120" />
|
||||
<el-table-column prop="cost_type" label="成本类型" width="90">
|
||||
<template #default="{ row }"><el-tag size="small" :type="costTypeTag(row.cost_type)">{{ costTypeLabel(row.cost_type) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="item_name" label="项目名称" min-width="140" />
|
||||
<el-table-column prop="standard_quantity" label="标准用量" width="90" align="right" />
|
||||
<el-table-column prop="unit" label="单位" width="60" />
|
||||
<el-table-column prop="standard_price" label="标准单价" width="90" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.standard_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="standard_cost" label="标准成本" width="100" align="right">
|
||||
<template #default="{ row }"><span style="font-weight:500;">{{ formatMoney(row.standard_cost) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本" width="60" />
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="editStd(row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteStd(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 3: 成本录入 -->
|
||||
<el-tab-pane label="成本录入" name="entry">
|
||||
<div style="margin-bottom:12px;">
|
||||
<el-button type="primary" @click="showActualForm = true; actualForm = {}">+ 录入实际成本</el-button>
|
||||
<el-select v-model="entryPeriod" placeholder="期间" style="width:110px;margin-left:8px;" @change="loadActualCosts">
|
||||
<el-option v-for="p in periodOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-table :data="actualCosts" v-loading="actualLoading" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="period" label="期间" width="80" />
|
||||
<el-table-column prop="product_code" label="产品编码" width="90" />
|
||||
<el-table-column prop="product_name" label="产品名称" width="120" />
|
||||
<el-table-column prop="cost_type" label="成本类型" width="90">
|
||||
<template #default="{ row }"><el-tag size="small" :type="costTypeTag(row.cost_type)">{{ costTypeLabel(row.cost_type) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="item_name" label="项目" min-width="120" />
|
||||
<el-table-column prop="actual_quantity" label="实际用量" width="90" align="right" />
|
||||
<el-table-column prop="actual_price" label="实际单价" width="90" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.actual_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="actual_cost" label="实际成本" width="100" align="right">
|
||||
<template #default="{ row }"><span style="font-weight:500;">{{ formatMoney(row.actual_cost) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="source" label="来源" width="70" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 4: 量差价差分析 -->
|
||||
<el-tab-pane label="量差价差" name="variance">
|
||||
<div style="display:flex;gap:12px;margin-bottom:16px;align-items:center;">
|
||||
<el-select v-model="varProduct" placeholder="选择产品" style="width:200px;">
|
||||
<el-option v-for="p in productOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
<el-select v-model="varPeriod" placeholder="期间" style="width:110px;">
|
||||
<el-option v-for="p in periodOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadVariance" :loading="varLoading">分析</el-button>
|
||||
</div>
|
||||
|
||||
<div v-if="varianceData.summary">
|
||||
<el-row :gutter="16" style="margin-bottom:16px;">
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat-card"><div class="stat-label">标准成本</div><div class="stat-value" style="color:#409eff;">{{ formatMoney(varianceData.summary.total_standard_cost) }}</div></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat-card"><div class="stat-label">实际成本</div><div class="stat-value" style="color:#e6a23c;">{{ formatMoney(varianceData.summary.total_actual_cost) }}</div></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat-card"><div class="stat-label">总差异</div><div class="stat-value" :style="{ color: varianceData.summary.total_variance > 0 ? '#f56c6c' : '#67c23a' }">{{ formatMoney(varianceData.summary.total_variance) }}</div></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat-card"><div class="stat-label">差异率</div><div class="stat-value" :style="{ color: varianceData.summary.variance_rate > 0 ? '#f56c6c' : '#67c23a' }">{{ varianceData.summary.variance_rate }}%</div></div></el-card></el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="varianceData.items" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="cost_type" label="类型" width="80">
|
||||
<template #default="{ row }"><el-tag size="small" :type="costTypeTag(row.cost_type)">{{ costTypeLabel(row.cost_type) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="item_name" label="项目" width="140" />
|
||||
<el-table-column prop="standard_cost" label="标准" width="100" align="right"><template #default="{ row }">{{ formatMoney(row.standard_cost) }}</template></el-table-column>
|
||||
<el-table-column prop="actual_cost" label="实际" width="100" align="right"><template #default="{ row }">{{ formatMoney(row.actual_cost) }}</template></el-table-column>
|
||||
<el-table-column prop="qty_variance" label="量差" width="100" align="right">
|
||||
<template #default="{ row }"><span :style="{ color: row.qty_variance > 0 ? '#f56c6c' : '#67c23a' }">{{ formatMoney(row.qty_variance) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price_variance" label="价差" width="100" align="right">
|
||||
<template #default="{ row }"><span :style="{ color: row.price_variance > 0 ? '#f56c6c' : '#67c23a' }">{{ formatMoney(row.price_variance) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total_variance" label="总差异" width="100" align="right">
|
||||
<template #default="{ row }"><span :style="{ color: row.total_variance > 0 ? '#f56c6c' : '#67c23a', fontWeight:500 }">{{ formatMoney(row.total_variance) }}</span></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty v-else-if="!varLoading" description="请选择产品后点击「分析」" style="padding:40px 0;" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 5: ABC作业成本 -->
|
||||
<el-tab-pane label="ABC作业成本" name="abc">
|
||||
<el-tabs v-model="abcTab">
|
||||
<el-tab-pane label="作业中心" name="activities">
|
||||
<div style="margin-bottom:12px;"><el-button type="primary" @click="showAbcForm = true; abcForm = {}">+ 新建作业中心</el-button></div>
|
||||
<el-table :data="abcActivities" v-loading="abcLoading" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="activity_code" label="编码" width="100" />
|
||||
<el-table-column prop="activity_name" label="作业名称" width="160" />
|
||||
<el-table-column prop="cost_driver" label="成本动因" width="120" />
|
||||
<el-table-column prop="driver_unit" label="动因单位" width="70" />
|
||||
<el-table-column prop="total_cost" label="总成本" width="100" align="right"><template #default="{ row }">{{ formatMoney(row.total_cost) }}</template></el-table-column>
|
||||
<el-table-column prop="driver_volume" label="动因总量" width="90" align="right" />
|
||||
<el-table-column prop="driver_rate" label="分配率" width="90" align="right">{{ formatMoney(row.driver_rate) }}</el-table-column>
|
||||
<el-table-column label="操作" width="80"><template #default="{ row }"><el-button size="small" @click="editAbc(row)">编辑</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="成本分配" name="allocations">
|
||||
<div style="margin-bottom:12px;display:flex;gap:8px;">
|
||||
<el-button type="primary" @click="showAllocForm = true; allocForm = {}">+ 执行分配</el-button>
|
||||
<el-select v-model="allocPeriod" placeholder="期间" style="width:110px;" @change="loadAllocations">
|
||||
<el-option v-for="p in periodOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-table :data="abcAllocations" v-loading="allocLoading" border stripe size="small" style="width:100%;">
|
||||
<el-table-column prop="period" label="期间" width="80" />
|
||||
<el-table-column prop="activity_id" label="作业ID" width="70" />
|
||||
<el-table-column prop="product_code" label="产品" width="90" />
|
||||
<el-table-column prop="product_name" label="产品名" width="120" />
|
||||
<el-table-column prop="driver_consumed" label="动因消耗" width="90" align="right" />
|
||||
<el-table-column prop="allocated_cost" label="分配成本" width="100" align="right"><template #default="{ row }">{{ formatMoney(row.allocated_cost) }}</template></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 标准成本表单弹窗 -->
|
||||
<MyDialog v-model="showStdForm" :title="stdForm.id ? '编辑标准成本' : '新建标准成本'" :width="550">
|
||||
<el-form :model="stdForm" label-width="110px">
|
||||
<el-form-item label="产品编码"><el-input v-model="stdForm.product_code" /></el-form-item>
|
||||
<el-form-item label="产品名称"><el-input v-model="stdForm.product_name" /></el-form-item>
|
||||
<el-form-item label="成本类型">
|
||||
<el-select v-model="stdForm.cost_type"><el-option label="材料" value="material" /><el-option label="人工" value="labor" /><el-option label="制造费用" value="overhead" /></el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称"><el-input v-model="stdForm.item_name" /></el-form-item>
|
||||
<el-form-item label="标准用量"><el-input-number v-model="stdForm.standard_quantity" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
<el-form-item label="单位"><el-input v-model="stdForm.unit" style="width:150px;" /></el-form-item>
|
||||
<el-form-item label="标准单价"><el-input-number v-model="stdForm.standard_price" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showStdForm = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveStd" :loading="stdSaving">保存</el-button>
|
||||
</template>
|
||||
</MyDialog>
|
||||
|
||||
<!-- 实际成本录入弹窗 -->
|
||||
<MyDialog v-model="showActualForm" title="录入实际成本" :width="550">
|
||||
<el-form :model="actualForm" label-width="110px">
|
||||
<el-form-item label="期间"><el-input v-model="actualForm.period" placeholder="2026-05" /></el-form-item>
|
||||
<el-form-item label="产品编码"><el-input v-model="actualForm.product_code" /></el-form-item>
|
||||
<el-form-item label="产品名称"><el-input v-model="actualForm.product_name" /></el-form-item>
|
||||
<el-form-item label="成本类型"><el-select v-model="actualForm.cost_type"><el-option label="材料" value="material" /><el-option label="人工" value="labor" /><el-option label="制造费用" value="overhead" /></el-select></el-form-item>
|
||||
<el-form-item label="项目名称"><el-input v-model="actualForm.item_name" /></el-form-item>
|
||||
<el-form-item label="实际用量"><el-input-number v-model="actualForm.actual_quantity" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
<el-form-item label="实际单价"><el-input-number v-model="actualForm.actual_price" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showActualForm = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveActual" :loading="actualSaving">保存</el-button>
|
||||
</template>
|
||||
</MyDialog>
|
||||
|
||||
<!-- ABC作业中心弹窗 -->
|
||||
<MyDialog v-model="showAbcForm" :title="abcForm.id ? '编辑作业中心' : '新建作业中心'" :width="500">
|
||||
<el-form :model="abcForm" label-width="110px">
|
||||
<el-form-item label="作业编码"><el-input v-model="abcForm.activity_code" /></el-form-item>
|
||||
<el-form-item label="作业名称"><el-input v-model="abcForm.activity_name" /></el-form-item>
|
||||
<el-form-item label="成本动因"><el-input v-model="abcForm.cost_driver" /></el-form-item>
|
||||
<el-form-item label="动因单位"><el-input v-model="abcForm.driver_unit" /></el-form-item>
|
||||
<el-form-item label="总成本"><el-input-number v-model="abcForm.total_cost" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
<el-form-item label="动因总量"><el-input-number v-model="abcForm.driver_volume" :min="0" style="width:200px;" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAbcForm = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveAbc" :loading="abcSaving">保存</el-button>
|
||||
</template>
|
||||
</MyDialog>
|
||||
|
||||
<!-- ABC分配弹窗 -->
|
||||
<MyDialog v-model="showAllocForm" title="执行ABC成本分配" :width="450">
|
||||
<el-form :model="allocForm" label-width="110px">
|
||||
<el-form-item label="作业中心">
|
||||
<el-select v-model="allocForm.activity_id" style="width:250px;">
|
||||
<el-option v-for="a in abcActivities" :key="a.id" :label="a.activity_name" :value="a.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="期间"><el-input v-model="allocForm.period" placeholder="2026-05" /></el-form-item>
|
||||
<el-form-item label="产品编码"><el-input v-model="allocForm.product_code" /></el-form-item>
|
||||
<el-form-item label="产品名称"><el-input v-model="allocForm.product_name" /></el-form-item>
|
||||
<el-form-item label="动因消耗量"><el-input-number v-model="allocForm.driver_consumed" :min="0" :precision="2" style="width:200px;" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAllocForm = false">取消</el-button>
|
||||
<el-button type="primary" @click="doAllocate" :loading="allocSaving">执行分配</el-button>
|
||||
</template>
|
||||
</MyDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { costApi } from '../api/index'
|
||||
import MyDialog from '../components/MyDialog.vue'
|
||||
|
||||
const activeTab = ref('overview')
|
||||
const abcTab = ref('activities')
|
||||
|
||||
// ── 期间选项 ──
|
||||
const now = new Date()
|
||||
const periodOptions = computed(() => {
|
||||
const opts: string[] = []
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
|
||||
opts.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`)
|
||||
}
|
||||
return opts
|
||||
})
|
||||
|
||||
// ── 工具函数 ──
|
||||
const costTypeMap: Record<string, string> = { material: '材料', labor: '人工', overhead: '制造费用' }
|
||||
const costTypeTagMap: Record<string, string> = { material: 'primary', labor: 'success', overhead: 'warning' }
|
||||
function costTypeLabel(t: string) { return costTypeMap[t] || t }
|
||||
function costTypeTag(t: string) { return costTypeTagMap[t] || 'info' }
|
||||
function formatMoney(v: any) {
|
||||
if (v === null || v === undefined) return '--'
|
||||
return Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 成本总览
|
||||
// ============================
|
||||
const overviewPeriod = ref(periodOptions.value[0] || now.getFullYear() + '-' + String(now.getMonth()+1).padStart(2,'0'))
|
||||
const overviewData = ref<any>({})
|
||||
const productOptions = ref<string[]>([])
|
||||
const maxTrend = ref(0)
|
||||
|
||||
async function loadOverview() {
|
||||
try {
|
||||
const r: any = await costApi.dashboard({ period: overviewPeriod.value })
|
||||
overviewData.value = r.data || r
|
||||
// 提取产品列表
|
||||
const products = overviewData.value.products || []
|
||||
productOptions.value = [...new Set(products.map((p: any) => p.product_code))]
|
||||
// 计算趋势最高值
|
||||
const trend = overviewData.value.overview?.trend || []
|
||||
if (trend.length > 0) {
|
||||
maxTrend.value = Math.max(...trend.map((t: any) => t.total_cost), 1)
|
||||
}
|
||||
} catch (e) { ElMessage.error('加载成本总览失败') }
|
||||
}
|
||||
|
||||
function trendHeight(val: number, max: number) {
|
||||
if (!max) return 0
|
||||
return Math.max(4, (val / max) * 140)
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 标准成本管理
|
||||
// ============================
|
||||
const stdLoading = ref(false)
|
||||
const standardCosts = ref<any[]>([])
|
||||
const showStdForm = ref(false)
|
||||
const stdForm = ref<any>({})
|
||||
const stdSaving = ref(false)
|
||||
const stdProductFilter = ref('')
|
||||
|
||||
async function loadStandardCosts() {
|
||||
stdLoading.value = true
|
||||
try {
|
||||
const params: any = {}
|
||||
if (stdProductFilter.value) params.product_code = stdProductFilter.value
|
||||
const r: any = await costApi.listStandardCosts(params)
|
||||
standardCosts.value = r.data || []
|
||||
} catch (e) { ElMessage.error('加载标准成本失败') }
|
||||
stdLoading.value = false
|
||||
}
|
||||
|
||||
function editStd(row: any) {
|
||||
stdForm.value = { ...row }
|
||||
showStdForm.value = true
|
||||
}
|
||||
|
||||
async function saveStd() {
|
||||
stdSaving.value = true
|
||||
try {
|
||||
if (stdForm.value.id) {
|
||||
await costApi.updateStandardCost(stdForm.value.id, stdForm.value)
|
||||
ElMessage.success('已更新')
|
||||
} else {
|
||||
await costApi.createStandardCost(stdForm.value)
|
||||
ElMessage.success('已创建')
|
||||
}
|
||||
showStdForm.value = false
|
||||
loadStandardCosts()
|
||||
} catch (e) { ElMessage.error('保存失败') }
|
||||
stdSaving.value = false
|
||||
}
|
||||
|
||||
async function deleteStd(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除「${row.item_name}」的标准成本?`, '确认')
|
||||
await costApi.deleteStandardCost(row.id)
|
||||
ElMessage.success('已删除')
|
||||
loadStandardCosts()
|
||||
} catch (e: any) { if (e !== 'cancel') ElMessage.error('删除失败') }
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 成本录入
|
||||
// ============================
|
||||
const actualLoading = ref(false)
|
||||
const actualCosts = ref<any[]>([])
|
||||
const entryPeriod = ref(overviewPeriod.value)
|
||||
const showActualForm = ref(false)
|
||||
const actualForm = ref<any>({})
|
||||
const actualSaving = ref(false)
|
||||
|
||||
async function loadActualCosts() {
|
||||
actualLoading.value = true
|
||||
try {
|
||||
const params: any = { period: entryPeriod.value }
|
||||
const r: any = await costApi.listActualCosts(params)
|
||||
actualCosts.value = r.data || []
|
||||
} catch (e) { ElMessage.error('加载实际成本失败') }
|
||||
actualLoading.value = false
|
||||
}
|
||||
|
||||
async function saveActual() {
|
||||
actualSaving.value = true
|
||||
try {
|
||||
await costApi.createActualCost(actualForm.value)
|
||||
ElMessage.success('已录入')
|
||||
showActualForm.value = false
|
||||
loadActualCosts()
|
||||
} catch (e) { ElMessage.error('保存失败') }
|
||||
actualSaving.value = false
|
||||
}
|
||||
|
||||
// ============================
|
||||
// 量差价差分析
|
||||
// ============================
|
||||
const varProduct = ref('')
|
||||
const varPeriod = ref(overviewPeriod.value)
|
||||
const varLoading = ref(false)
|
||||
const varianceData = ref<any>({})
|
||||
|
||||
async function loadVariance() {
|
||||
if (!varProduct.value) { ElMessage.warning('请选择产品'); return }
|
||||
varLoading.value = true
|
||||
try {
|
||||
const r: any = await costApi.variance({ product_code: varProduct.value, period: varPeriod.value })
|
||||
varianceData.value = r
|
||||
} catch (e) { ElMessage.error('加载差异分析失败') }
|
||||
varLoading.value = false
|
||||
}
|
||||
|
||||
// ============================
|
||||
// ABC作业成本
|
||||
// ============================
|
||||
const abcLoading = ref(false)
|
||||
const abcActivities = ref<any[]>([])
|
||||
const showAbcForm = ref(false)
|
||||
const abcForm = ref<any>({})
|
||||
const abcSaving = ref(false)
|
||||
|
||||
async function loadAbcActivities() {
|
||||
abcLoading.value = true
|
||||
try {
|
||||
const r: any = await costApi.listAbcActivities()
|
||||
abcActivities.value = r.data || []
|
||||
} catch (e) { /* ignore */ }
|
||||
abcLoading.value = false
|
||||
}
|
||||
|
||||
function editAbc(row: any) {
|
||||
abcForm.value = { ...row }
|
||||
showAbcForm.value = true
|
||||
}
|
||||
|
||||
async function saveAbc() {
|
||||
abcSaving.value = true
|
||||
try {
|
||||
await costApi.createAbcActivity(abcForm.value)
|
||||
ElMessage.success('已保存')
|
||||
showAbcForm.value = false
|
||||
loadAbcActivities()
|
||||
} catch (e) { ElMessage.error('保存失败') }
|
||||
abcSaving.value = false
|
||||
}
|
||||
|
||||
// ABC分配
|
||||
const showAllocForm = ref(false)
|
||||
const allocForm = ref<any>({})
|
||||
const allocPeriod = ref(overviewPeriod.value)
|
||||
const allocLoading = ref(false)
|
||||
const abcAllocations = ref<any[]>([])
|
||||
const allocSaving = ref(false)
|
||||
|
||||
async function loadAllocations() {
|
||||
allocLoading.value = true
|
||||
try {
|
||||
const r: any = await costApi.listAbcAllocations({ period: allocPeriod.value })
|
||||
abcAllocations.value = r.data || []
|
||||
} catch (e) { /* ignore */ }
|
||||
allocLoading.value = false
|
||||
}
|
||||
|
||||
async function doAllocate() {
|
||||
if (!allocForm.value.activity_id || !allocForm.value.product_code) {
|
||||
ElMessage.warning('请填写必要信息')
|
||||
return
|
||||
}
|
||||
allocSaving.value = true
|
||||
try {
|
||||
const r: any = await costApi.doAbcAllocate(allocForm.value)
|
||||
ElMessage.success(`分配完成:${r.allocated_cost || ''}`)
|
||||
showAllocForm.value = false
|
||||
loadAllocations()
|
||||
} catch (e) { ElMessage.error('分配失败') }
|
||||
allocSaving.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOverview()
|
||||
loadStandardCosts()
|
||||
loadActualCosts()
|
||||
loadAbcActivities()
|
||||
loadAllocations()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat-card { text-align: center; padding: 8px 0; }
|
||||
.stat-label { font-size: 13px; color: #909399; margin-bottom: 8px; }
|
||||
.stat-value { font-size: 24px; font-weight: bold; color: #303133; }
|
||||
.trend-bar { width: 40px; border-radius: 4px 4px 0 0; background: linear-gradient(to top, #409eff, #79bbff); transition: height 0.3s; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user