设备总包项目管理部分
This commit is contained in:
325
ruoyi-ui/src/views/rm/budget/index.vue
Normal file
325
ruoyi-ui/src/views/rm/budget/index.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user