326 lines
11 KiB
Vue
326 lines
11 KiB
Vue
<template>
|
|
<div class="app-container">
|
|
<el-card shadow="never" class="module-panel">
|
|
<div slot="header" class="module-header">
|
|
<span>💰 项目预算管理</span>
|
|
<div class="header-actions">
|
|
<el-button size="small" icon="el-icon-upload2" @click="handleArchive">💾 存档当前预算</el-button>
|
|
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">+ 新增预算项</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="summary-row">
|
|
<div class="stat-card">
|
|
<div class="label">预算总额</div>
|
|
<div class="value">¥{{ totalBudget }}</div>
|
|
</div>
|
|
<div class="stat-card orange">
|
|
<div class="label">实际支出</div>
|
|
<div class="value">¥{{ totalSpent }}</div>
|
|
</div>
|
|
<div class="stat-card" :class="budgetBalanceClass">
|
|
<div class="label">预算余量</div>
|
|
<div class="value">¥{{ totalBalance }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<el-table :data="budgetList" v-loading="loading" stripe border highlight-current-row>
|
|
<el-table-column type="index" label="#" width="50" />
|
|
<el-table-column prop="category" label="类别" width="120" />
|
|
<el-table-column prop="item" label="项目名称" min-width="180" />
|
|
<el-table-column prop="budgetAmount" label="预算金额(¥)" width="140" align="right">
|
|
<template slot-scope="scope">{{ formatMoney(scope.row.budgetAmount) }}</template>
|
|
</el-table-column>
|
|
<el-table-column prop="spentAmount" label="实际金额(¥)" width="140" align="right">
|
|
<template slot-scope="scope">{{ formatMoney(scope.row.spentAmount) }}</template>
|
|
</el-table-column>
|
|
<el-table-column prop="status" label="状态" width="110" align="center">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="statusTag(scope.row.status)" size="small">
|
|
{{ statusLabel(scope.row.status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="150" fixed="right">
|
|
<template slot-scope="scope">
|
|
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
|
|
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<div class="pagination-wrapper">
|
|
<el-pagination :current-page="query.pageNum" :page-sizes="[10, 20, 50, 100]" :page-size="query.pageSize"
|
|
:total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
|
|
@current-change="handlePageChange" />
|
|
</div>
|
|
</el-card>
|
|
|
|
<!-- Add/Edit Dialog -->
|
|
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="550px" append-to-body>
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px" size="small">
|
|
<el-row :gutter="16">
|
|
<el-col :span="12">
|
|
<el-form-item label="类别" prop="category">
|
|
<el-select v-model="form.category" placeholder="请选择类别" style="width:100%">
|
|
<el-option label="机械设备" value="机械设备" />
|
|
<el-option label="电气设备" value="电气设备" />
|
|
<el-option label="液压设备" value="液压设备" />
|
|
<el-option label="流体设备" value="流体设备" />
|
|
<el-option label="能源介质" value="能源介质" />
|
|
<el-option label="安装费用" value="安装费用" />
|
|
<el-option label="其他" value="其他" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="项目名称" prop="item">
|
|
<el-input v-model="form.item" placeholder="如:主轧机牌坊" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="16">
|
|
<el-col :span="12">
|
|
<el-form-item label="预算金额(¥)" prop="budgetAmount">
|
|
<el-input v-model="form.budgetAmount" type="number" placeholder="预算金额" />
|
|
</el-form-item>
|
|
</el-col>
|
|
<el-col :span="12">
|
|
<el-form-item label="实际金额(¥)" prop="spentAmount">
|
|
<el-input v-model="form.spentAmount" type="number" placeholder="实际金额" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="16">
|
|
<el-col :span="12">
|
|
<el-form-item label="状态" prop="status">
|
|
<el-select v-model="form.status" placeholder="请选择状态" style="width:100%">
|
|
<el-option label="草稿" value="draft" />
|
|
<el-option label="审核中" value="review" />
|
|
<el-option label="已批准" value="approved" />
|
|
<el-option label="已驳回" value="rejected" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
<el-row :gutter="16">
|
|
<el-col :span="24">
|
|
<el-form-item label="备注" prop="remark">
|
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息" />
|
|
</el-form-item>
|
|
</el-col>
|
|
</el-row>
|
|
</el-form>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
<el-button type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { listBudget, getBudget, addBudget, updateBudget, delBudget } from '@/api/rm/budget'
|
|
import { listProject } from '@/api/rm/project'
|
|
|
|
export default {
|
|
name: 'RmBudget',
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
budgetList: [],
|
|
total: 0,
|
|
currentProjectId: null,
|
|
query: {
|
|
pageNum: 1,
|
|
pageSize: 10,
|
|
projectId: undefined
|
|
},
|
|
dialogVisible: false,
|
|
dialogTitle: '',
|
|
submitting: false,
|
|
form: {},
|
|
rules: {
|
|
category: [{ required: true, message: '请选择类别', trigger: 'change' }],
|
|
item: [{ required: true, message: '请填写项目名称', trigger: 'blur' }],
|
|
budgetAmount: [{ required: true, message: '请填写预算金额', trigger: 'blur' }],
|
|
projectId: [{ required: true, message: '缺少项目ID', trigger: 'blur' }]
|
|
},
|
|
}
|
|
},
|
|
computed: {
|
|
totalBudget() {
|
|
return this.formatMoney(this.budgetList.reduce((s, b) => s + (Number(b.budgetAmount) || 0), 0))
|
|
},
|
|
totalSpent() {
|
|
return this.formatMoney(this.budgetList.reduce((s, b) => s + (Number(b.spentAmount) || 0), 0))
|
|
},
|
|
totalBalance() {
|
|
const budget = this.budgetList.reduce((s, b) => s + (Number(b.budgetAmount) || 0), 0)
|
|
const spent = this.budgetList.reduce((s, b) => s + (Number(b.spentAmount) || 0), 0)
|
|
return this.formatMoney(budget - spent)
|
|
},
|
|
budgetBalanceClass() {
|
|
const balance = this.budgetList.reduce((s, b) => s + (Number(b.budgetAmount) || 0), 0)
|
|
- this.budgetList.reduce((s, b) => s + (Number(b.spentAmount) || 0), 0)
|
|
return balance < 0 ? 'red' : 'green'
|
|
}
|
|
},
|
|
created() {
|
|
this.loadCurrentProject()
|
|
},
|
|
methods: {
|
|
loadCurrentProject() {
|
|
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
|
const rows = res.rows || []
|
|
if (rows.length > 0) {
|
|
this.currentProjectId = rows[0].projectId
|
|
this.query.projectId = rows[0].projectId
|
|
this.loadList()
|
|
}
|
|
})
|
|
},
|
|
loadList() {
|
|
this.loading = true
|
|
listBudget(this.query).then(res => {
|
|
this.budgetList = res.rows || []
|
|
this.total = res.total || 0
|
|
}).finally(() => {
|
|
this.loading = false
|
|
})
|
|
},
|
|
handleSizeChange(val) {
|
|
this.query.pageSize = val
|
|
this.loadList()
|
|
},
|
|
handlePageChange(val) {
|
|
this.query.pageNum = val
|
|
this.loadList()
|
|
},
|
|
handleAdd() {
|
|
if (!this.currentProjectId) {
|
|
this.$message.warning('请先在项目总览中创建项目')
|
|
return
|
|
}
|
|
this.dialogTitle = '新增预算项'
|
|
this.form = {
|
|
projectId: this.currentProjectId,
|
|
category: '',
|
|
item: '',
|
|
budgetAmount: '',
|
|
spentAmount: '',
|
|
status: 'draft',
|
|
remark: ''
|
|
}
|
|
this.dialogVisible = true
|
|
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
|
},
|
|
handleEdit(row) {
|
|
this.dialogTitle = '编辑预算项'
|
|
getBudget(row.budgetId).then(res => {
|
|
this.form = res.data
|
|
this.dialogVisible = true
|
|
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
|
})
|
|
},
|
|
handleDelete(row) {
|
|
this.$confirm(`确认删除预算项 "${row.item}" 吗?`, '提示', { type: 'warning' }).then(() => {
|
|
delBudget(row.budgetId).then(() => {
|
|
this.$message.success('删除成功')
|
|
this.loadList()
|
|
})
|
|
})
|
|
},
|
|
handleSubmit() {
|
|
this.$refs.formRef.validate(valid => {
|
|
if (!valid) return
|
|
this.submitting = true
|
|
const api = this.form.budgetId ? updateBudget : addBudget
|
|
api(this.form).then(() => {
|
|
this.$message.success(this.form.budgetId ? '更新成功' : '新增成功')
|
|
this.dialogVisible = false
|
|
if (!this.form.budgetId) {
|
|
this.query.pageNum = 1
|
|
}
|
|
this.loadList()
|
|
}).finally(() => {
|
|
this.submitting = false
|
|
})
|
|
})
|
|
},
|
|
handleArchive() {
|
|
this.$message.info('存档功能待实现')
|
|
},
|
|
formatMoney(val) {
|
|
if (val === null || val === undefined || val === '') return '0.00'
|
|
return Number(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
},
|
|
statusTag(status) {
|
|
const map = { draft: 'info', review: 'warning', approved: 'success', rejected: 'danger' }
|
|
return map[status] || 'info'
|
|
},
|
|
statusLabel(status) {
|
|
const map = { draft: '草稿', review: '审核中', approved: '已批准', rejected: '已驳回' }
|
|
return map[status] || status
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.module-panel {
|
|
border: none;
|
|
border-radius: 4px;
|
|
}
|
|
.module-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
}
|
|
.header-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
.summary-row {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.stat-card {
|
|
flex: 1;
|
|
min-width: 140px;
|
|
background: #fff;
|
|
border-radius: 5px;
|
|
padding: 10px 14px;
|
|
border: 1px solid #d0d7de;
|
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
|
}
|
|
.stat-card .label {
|
|
font-size: 11px;
|
|
color: #666;
|
|
margin-bottom: 4px;
|
|
}
|
|
.stat-card .value {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: #2176ae;
|
|
}
|
|
.stat-card.green .value { color: #27ae60; }
|
|
.stat-card.orange .value { color: #f39c12; }
|
|
.stat-card.red .value { color: #e74c3c; }
|
|
.pagination-wrapper {
|
|
margin-top: 16px;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
.dialog-footer {
|
|
text-align: right;
|
|
}
|
|
</style>
|