设备总包项目管理部分
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>
|
||||
225
ruoyi-ui/src/views/rm/drawingDesign/index.vue
Normal file
225
ruoyi-ui/src/views/rm/drawingDesign/index.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>图纸详细设计</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增图纸</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<!-- Search -->
|
||||
<div class="search-row">
|
||||
<el-input v-model="query.drawingName" size="small" placeholder="图纸名称" clearable style="width:160px" @keyup.enter="handleSearch" />
|
||||
<el-select v-model="query.drawingType" size="small" placeholder="图纸类型" clearable style="width:130px">
|
||||
<el-option label="总装图" value="总装图" />
|
||||
<el-option label="部件图" value="部件图" />
|
||||
<el-option label="零件图" value="零件图" />
|
||||
<el-option label="液压原理图" value="液压原理图" />
|
||||
<el-option label="电气原理图" value="电气原理图" />
|
||||
</el-select>
|
||||
<el-select v-model="query.status" size="small" placeholder="状态" clearable style="width:110px">
|
||||
<el-option label="进行中" value="in_progress" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="已审查" value="reviewed" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="drawingName" label="图纸名称" min-width="160" />
|
||||
<el-table-column prop="drawingNo" label="图号" width="110" />
|
||||
<el-table-column prop="drawingType" label="图纸类型" width="100" />
|
||||
<el-table-column prop="version" label="版本" width="60" align="center" />
|
||||
<el-table-column prop="drawer" label="设计人" width="80" />
|
||||
<el-table-column prop="startDate" label="设计日期" width="100" align="center" />
|
||||
<el-table-column prop="endDate" label="完成日期" width="100" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusTag(scope.row.status)" size="mini">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="110" 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-row">
|
||||
<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="small"
|
||||
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="560px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="16">
|
||||
<el-form-item label="图纸名称" prop="drawingName">
|
||||
<el-input v-model="form.drawingName" placeholder="图纸名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="版本" prop="version">
|
||||
<el-input v-model="form.version" placeholder="V1.0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="图号" prop="drawingNo">
|
||||
<el-input v-model="form.drawingNo" placeholder="图号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="图纸类型" prop="drawingType">
|
||||
<el-select v-model="form.drawingType" 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-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="设计人" prop="drawer">
|
||||
<el-input v-model="form.drawer" placeholder="设计人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择" style="width:100%">
|
||||
<el-option label="进行中" value="in_progress" />
|
||||
<el-option label="已完成" value="completed" />
|
||||
<el-option label="已审查" value="reviewed" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="设计日期" prop="startDate">
|
||||
<el-date-picker v-model="form.startDate" type="date" placeholder="选择日期" style="width:100%" value-format="yyyy-MM-dd" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="完成日期" prop="endDate">
|
||||
<el-date-picker v-model="form.endDate" type="date" placeholder="选择日期" style="width:100%" value-format="yyyy-MM-dd" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="文件路径" prop="fileUrl">
|
||||
<el-input v-model="form.fileUrl" placeholder="文件存储路径或 URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDrawingDesign, getDrawingDesign, addDrawingDesign, updateDrawingDesign, delDrawingDesign } from '@/api/rm/drawingDesign'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmDrawingDesign',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
currentProjectId: null,
|
||||
query: { pageNum: 1, pageSize: 10, drawingName: undefined, drawingType: undefined, status: undefined, projectId: undefined },
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
submitting: false,
|
||||
form: {},
|
||||
rules: { drawingName: [{ required: true, message: '请填写图纸名称', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
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
|
||||
listDrawingDesign(this.query).then(res => {
|
||||
this.list = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleSearch() { this.query.pageNum = 1; this.loadList() },
|
||||
handleReset() { this.query = { pageNum: 1, pageSize: 10 }; this.loadList() },
|
||||
handleSizeChange(val) { this.query.pageSize = val; this.loadList() },
|
||||
handlePageChange(val) { this.query.pageNum = val; this.loadList() },
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增图纸'
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.form = { projectId: this.currentProjectId, drawingName: '', drawingNo: '', version: 'V1.0', drawingType: '', drawer: '', startDate: '', endDate: '', fileUrl: '', status: 'in_progress', remark: '' }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑图纸'
|
||||
getDrawingDesign(row.drawingId).then(res => {
|
||||
this.form = res.data
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.drawingName}"?`, '提示', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {
|
||||
delDrawingDesign(row.drawingId).then(() => { this.$message.success('删除成功'); this.loadList() })
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.form.drawingId ? updateDrawingDesign : addDrawingDesign
|
||||
api(this.form).then(() => {
|
||||
this.$message.success(this.form.drawingId ? '更新成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
if (!this.form.drawingId) this.query.pageNum = 1
|
||||
this.loadList()
|
||||
}).finally(() => { this.submitting = false })
|
||||
})
|
||||
},
|
||||
statusTag(s) { return { in_progress: 'info', completed: 'success', reviewed: 'primary' }[s] || 'info' },
|
||||
statusLabel(s) { return { in_progress: '进行中', completed: '已完成', reviewed: '已审查' }[s] || s }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.search-row { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
||||
.pagination-row { margin-top: 10px; display: flex; justify-content: flex-end; }
|
||||
.dialog-footer { text-align: right; }
|
||||
</style>
|
||||
183
ruoyi-ui/src/views/rm/drawingReview/index.vue
Normal file
183
ruoyi-ui/src/views/rm/drawingReview/index.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>图纸审查</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增审查记录</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<!-- Search -->
|
||||
<div class="search-row">
|
||||
<el-input v-model="query.drawingName" size="small" placeholder="图纸名称" clearable style="width:160px" @keyup.enter="handleSearch" />
|
||||
<el-select v-model="query.status" size="small" placeholder="状态" clearable style="width:110px">
|
||||
<el-option label="待审查" value="pending" />
|
||||
<el-option label="通过" value="approved" />
|
||||
<el-option label="驳回" value="rejected" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="drawingName" label="图纸名称" min-width="160" />
|
||||
<el-table-column prop="reviewer" label="审查人" width="90" />
|
||||
<el-table-column prop="reviewDate" label="审查日期" width="100" align="center" />
|
||||
<el-table-column prop="status" label="结果" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusTag(scope.row.status)" size="mini">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reviewOpinion" label="审查意见" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="120" 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-row">
|
||||
<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="small"
|
||||
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" size="small">
|
||||
<el-form-item label="图纸名称" prop="drawingName">
|
||||
<el-input v-model="form.drawingName" placeholder="关联图纸名称" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="审查人" prop="reviewer">
|
||||
<el-input v-model="form.reviewer" placeholder="审查人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="审查日期" prop="reviewDate">
|
||||
<el-date-picker v-model="form.reviewDate" type="date" placeholder="选择日期" style="width:100%" value-format="yyyy-MM-dd" />
|
||||
</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="pending" />
|
||||
<el-option label="通过" value="approved" />
|
||||
<el-option label="驳回" value="rejected" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="审查意见" prop="reviewOpinion">
|
||||
<el-input v-model="form.reviewOpinion" type="textarea" :rows="3" placeholder="审查意见" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listDrawingReview, getDrawingReview, addDrawingReview, updateDrawingReview, delDrawingReview } from '@/api/rm/drawingReview'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmDrawingReview',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
currentProjectId: null,
|
||||
query: { pageNum: 1, pageSize: 10, drawingName: undefined, status: undefined, projectId: undefined },
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
submitting: false,
|
||||
form: {},
|
||||
rules: { drawingName: [{ required: true, message: '请填写图纸名称', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
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
|
||||
listDrawingReview(this.query).then(res => {
|
||||
this.list = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleSearch() { this.query.pageNum = 1; this.loadList() },
|
||||
handleReset() { this.query = { pageNum: 1, pageSize: 10 }; this.loadList() },
|
||||
handleSizeChange(val) { this.query.pageSize = val; this.loadList() },
|
||||
handlePageChange(val) { this.query.pageNum = val; this.loadList() },
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增审查记录'
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.form = { projectId: this.currentProjectId, drawingName: '', reviewer: '', reviewDate: '', status: 'pending', reviewOpinion: '', remark: '' }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑审查记录'
|
||||
getDrawingReview(row.reviewId).then(res => {
|
||||
this.form = res.data
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.drawingName}" 的审查记录?`, '提示', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {
|
||||
delDrawingReview(row.reviewId).then(() => { this.$message.success('删除成功'); this.loadList() })
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.form.reviewId ? updateDrawingReview : addDrawingReview
|
||||
api(this.form).then(() => {
|
||||
this.$message.success(this.form.reviewId ? '更新成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
if (!this.form.reviewId) this.query.pageNum = 1
|
||||
this.loadList()
|
||||
}).finally(() => { this.submitting = false })
|
||||
})
|
||||
},
|
||||
statusTag(s) { return { pending: 'info', approved: 'success', rejected: 'danger' }[s] || 'info' },
|
||||
statusLabel(s) { return { pending: '待审查', approved: '通过', rejected: '驳回' }[s] || s }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.search-row { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
||||
.pagination-row { margin-top: 10px; display: flex; justify-content: flex-end; }
|
||||
.dialog-footer { text-align: right; }
|
||||
</style>
|
||||
204
ruoyi-ui/src/views/rm/layout/index.vue
Normal file
204
ruoyi-ui/src/views/rm/layout/index.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>布局图确定</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-upload2" @click="handleAdd">上传布局图</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<!-- Upload area (matching prototype) -->
|
||||
<div class="upload-area" @click="handleAdd">
|
||||
<div class="upload-icon">📤</div>
|
||||
<div class="upload-text">点击上传布局图文件(DWG / PDF / DXF)</div>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="search-row">
|
||||
<el-input v-model="query.fileName" size="small" placeholder="文件名" clearable style="width:160px" @keyup.enter="handleSearch" />
|
||||
<el-select v-model="query.fileType" size="small" placeholder="文件类型" clearable style="width:110px">
|
||||
<el-option label="PDF" value="PDF" />
|
||||
<el-option label="DWG" value="DWG" />
|
||||
<el-option label="DXF" value="DXF" />
|
||||
</el-select>
|
||||
<el-select v-model="query.status" size="small" placeholder="状态" clearable style="width:100px">
|
||||
<el-option label="待审核" value="pending" />
|
||||
<el-option label="已批准" value="approved" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="fileName" label="文件名" min-width="180" />
|
||||
<el-table-column prop="fileType" label="类型" width="70" align="center" />
|
||||
<el-table-column prop="uploadDate" label="上传日期" width="100" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusTag(scope.row.status)" size="mini">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" 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-row">
|
||||
<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="small"
|
||||
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" size="small">
|
||||
<el-form-item label="文件名" prop="fileName">
|
||||
<el-input v-model="form.fileName" placeholder="如:1380mm轧机平面布置图.dwg" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="文件类型" prop="fileType">
|
||||
<el-select v-model="form.fileType" placeholder="请选择" style="width:100%">
|
||||
<el-option label="PDF" value="PDF" />
|
||||
<el-option label="DWG" value="DWG" />
|
||||
<el-option label="DXF" value="DXF" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="上传日期" prop="uploadDate">
|
||||
<el-date-picker v-model="form.uploadDate" type="date" placeholder="选择日期" style="width:100%" value-format="yyyy-MM-dd" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="版本" prop="version">
|
||||
<el-input v-model="form.version" placeholder="如 V1.0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择" style="width:100%">
|
||||
<el-option label="待审核" value="pending" />
|
||||
<el-option label="已批准" value="approved" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="文件路径" prop="fileUrl">
|
||||
<el-input v-model="form.fileUrl" placeholder="文件存储路径或 URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listLayoutFile, getLayoutFile, addLayoutFile, updateLayoutFile, delLayoutFile } from '@/api/rm/layoutFile'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmLayoutFile',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
currentProjectId: null,
|
||||
query: { pageNum: 1, pageSize: 10, fileName: undefined, fileType: undefined, status: undefined, projectId: undefined },
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
submitting: false,
|
||||
form: {},
|
||||
rules: { fileName: [{ required: true, message: '请填写文件名', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
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
|
||||
listLayoutFile(this.query).then(res => {
|
||||
this.list = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleSearch() { this.query.pageNum = 1; this.loadList() },
|
||||
handleReset() { this.query = { pageNum: 1, pageSize: 10 }; this.loadList() },
|
||||
handleSizeChange(val) { this.query.pageSize = val; this.loadList() },
|
||||
handlePageChange(val) { this.query.pageNum = val; this.loadList() },
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增布局图'
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.form = { projectId: this.currentProjectId, fileName: '', fileType: '', uploadDate: '', version: '', status: 'pending', fileUrl: '', remark: '' }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑布局图'
|
||||
getLayoutFile(row.layoutFileId).then(res => {
|
||||
this.form = res.data
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.fileName}"?`, '提示', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {
|
||||
delLayoutFile(row.layoutFileId).then(() => { this.$message.success('删除成功'); this.loadList() })
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.form.layoutFileId ? updateLayoutFile : addLayoutFile
|
||||
api(this.form).then(() => {
|
||||
this.$message.success(this.form.layoutFileId ? '更新成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
if (!this.form.layoutFileId) this.query.pageNum = 1
|
||||
this.loadList()
|
||||
}).finally(() => { this.submitting = false })
|
||||
})
|
||||
},
|
||||
statusTag(s) { return { pending: 'info', approved: 'success' }[s] || 'info' },
|
||||
statusLabel(s) { return { pending: '待审核', approved: '已批准' }[s] || s }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.upload-area { border: 2px dashed #d0d7de; border-radius: 4px; padding: 20px; text-align: center; cursor: pointer; margin-bottom: 10px; transition: border-color 0.2s; }
|
||||
.upload-area:hover { border-color: #2176ae; }
|
||||
.upload-icon { font-size: 28px; margin-bottom: 6px; }
|
||||
.upload-text { font-size: 12px; color: #666; }
|
||||
.search-row { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
||||
.pagination-row { margin-top: 10px; display: flex; justify-content: flex-end; }
|
||||
.dialog-footer { text-align: right; }
|
||||
</style>
|
||||
237
ruoyi-ui/src/views/rm/manufacturing/index.vue
Normal file
237
ruoyi-ui/src/views/rm/manufacturing/index.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>设备制造进度</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddDevice">新增设备</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<el-table :data="deviceList" v-loading="loading" stripe border highlight-current-row size="small" row-key="deviceId">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="deviceName" label="设备名称" min-width="140" />
|
||||
<el-table-column prop="supplierName" label="供应商" min-width="120" />
|
||||
<el-table-column prop="deliveryDate" label="交货日期" width="100" align="center" />
|
||||
<el-table-column label="制造进度" min-width="160">
|
||||
<template slot-scope="s">
|
||||
<div style="display:flex;align-items:center;gap:6px;">
|
||||
<el-progress :percentage="devicePct(s.row)" :stroke-width="10" style="flex:1;" />
|
||||
<span style="font-size:12px;font-weight:600;white-space:nowrap;">{{ devicePct(s.row) }}%</span>
|
||||
</div>
|
||||
<div style="font-size:10px;color:#888;">{{ doneStageCount(s.row) }}/{{ stageCount(s.row) }} 阶段</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-s-operation" @click="toggleDetail(s.row)">{{ s.row._showDetail ? '收起' : '阶段' }}</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditDevice(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDeleteDevice(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- Stage Detail Panels -->
|
||||
<div v-for="d in deviceList" :key="d.deviceId">
|
||||
<div v-if="d._showDetail" class="stage-detail-panel">
|
||||
<div v-for="(stg, si) in deviceStages(d)" :key="si" class="stage-card">
|
||||
<div class="stage-header" :class="stageHeaderClass(stg)" @click="stg._open = !stg._open">
|
||||
<span>{{ si+1 }}. {{ stg.stageName }} <el-tag :type="stageTag(stg.status)" size="mini" style="margin-left:6px;">{{ stageLabel(stg.status) }}</el-tag></span>
|
||||
<span style="font-size:10px;color:#888;">{{ stg.planEndDate ? '计划: '+stg.planEndDate : '' }} {{ stg.submittedDate ? '· 提交: '+stg.submittedDate : '' }}</span>
|
||||
</div>
|
||||
<div v-show="stg._open" class="stage-body">
|
||||
<div class="stage-field"><label>计划开始:</label>{{ stg.planStartDate || '-' }}</div>
|
||||
<div class="stage-field"><label>计划结束:</label>{{ stg.planEndDate || '-' }}</div>
|
||||
<div class="stage-field"><label>实际开始:</label>{{ stg.actualStart || '-' }}</div>
|
||||
<div class="stage-field"><label>实际结束:</label>{{ stg.actualEnd || '-' }}</div>
|
||||
<div class="stage-field"><label>提交日期:</label>{{ stg.submittedDate || '-' }}</div>
|
||||
<div class="stage-field"><label>制造照片:</label>{{ stg.manufacturingPhotos || '-' }}</div>
|
||||
<div class="stage-field"><label>制造视频:</label>{{ stg.manufacturingVideo || '-' }}</div>
|
||||
<div class="stage-field"><label>材质报告:</label>{{ stg.materialReport || '-' }}</div>
|
||||
<div class="stage-field"><label>精度报告:</label>{{ stg.precisionReport || '-' }}</div>
|
||||
<div style="margin-top:6px;">
|
||||
<el-button size="mini" type="primary" @click="handleEditStage(stg)">编辑阶段</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!deviceStages(d).length" style="text-align:center;padding:16px;color:#aaa;font-size:13px;">暂无阶段数据</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Dialog -->
|
||||
<el-dialog :title="deviceTitle" :visible.sync="deviceVisible" width="520px" append-to-body>
|
||||
<el-form ref="deviceFormRef" :model="deviceForm" :rules="deviceRules" label-width="100px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="设备名称" prop="deviceName"><el-input v-model="deviceForm.deviceName" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="规格型号" prop="spec"><el-input v-model="deviceForm.spec" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="供应商" prop="supplierName"><el-input v-model="deviceForm.supplierName" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="合同编号" prop="contractNo"><el-input v-model="deviceForm.contractNo" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="下单日期"><el-date-picker v-model="deviceForm.orderDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="交货日期" prop="deliveryDate"><el-date-picker v-model="deviceForm.deliveryDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="罚款(元/天)"><el-input-number v-model="deviceForm.penaltyRate" :min="0" :precision="2" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注"><el-input v-model="deviceForm.remark" type="textarea" :rows="2" /></el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="deviceVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="deviceSubmitting" @click="handleDeviceSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Stage Dialog -->
|
||||
<el-dialog title="编辑阶段" :visible.sync="stageVisible" width="560px" append-to-body>
|
||||
<el-form ref="stageFormRef" :model="stageForm" label-width="110px" size="small">
|
||||
<div style="font-weight:600;margin-bottom:8px;font-size:14px;">{{ stageForm.stageName }}</div>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="计划开始"><el-date-picker v-model="stageForm.planStartDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="计划结束"><el-date-picker v-model="stageForm.planEndDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="状态"><el-select v-model="stageForm.status" style="width:100%"><el-option label="待开始" value="0" /><el-option label="进行中" value="1" /><el-option label="已完成" value="2" /><el-option label="逾期" value="3" /></el-select></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="实际开始"><el-date-picker v-model="stageForm.actualStart" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="实际结束"><el-date-picker v-model="stageForm.actualEnd" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="提交日期"><el-date-picker v-model="stageForm.submittedDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="制造照片"><el-input v-model="stageForm.manufacturingPhotos" type="textarea" :rows="1" placeholder="JSON或URL" /></el-form-item>
|
||||
<el-form-item label="制造视频"><el-input v-model="stageForm.manufacturingVideo" placeholder="视频URL" /></el-form-item>
|
||||
<el-form-item label="材质报告"><el-input v-model="stageForm.materialReport" type="textarea" :rows="1" placeholder="JSON" /></el-form-item>
|
||||
<el-form-item label="精度报告"><el-input v-model="stageForm.precisionReport" type="textarea" :rows="1" placeholder="JSON" /></el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="stageVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="stageSubmitting" @click="handleStageSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listMfgDeviceAll, getMfgDevice, addMfgDevice, updateMfgDevice, delMfgDevice } from '@/api/rm/mfgDevice'
|
||||
import { listMfgStageAll, updateMfgStage } from '@/api/rm/mfgDevice'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmManufacturing',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
deviceList: [],
|
||||
stageMap: {},
|
||||
currentProjectId: null,
|
||||
deviceVisible: false, deviceTitle: '', deviceSubmitting: false,
|
||||
deviceForm: {}, deviceRules: { deviceName: [{ required: true, message: '请填写设备名称', trigger: 'blur' }] },
|
||||
stageVisible: false, stageSubmitting: false,
|
||||
stageForm: {},
|
||||
detailMap: {}
|
||||
}
|
||||
},
|
||||
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.loadDevices()
|
||||
}
|
||||
})
|
||||
},
|
||||
loadDevices() {
|
||||
if (!this.currentProjectId) return
|
||||
this.loading = true
|
||||
listMfgDeviceAll({ projectId: this.currentProjectId }).then(r => {
|
||||
this.deviceList = (r.data || []).map(d => ({ ...d, _showDetail: false }))
|
||||
// Load stages for all devices
|
||||
this.deviceList.forEach(d => this.loadStages(d))
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
loadStages(device) {
|
||||
listMfgStageAll({ deviceId: device.deviceId }).then(r => {
|
||||
this.$set(this.stageMap, device.deviceId, (r.data || []).map(s => ({ ...s, _open: false })))
|
||||
})
|
||||
},
|
||||
deviceStages(device) {
|
||||
return this.stageMap[device.deviceId] || []
|
||||
},
|
||||
stageCount(device) {
|
||||
return this.deviceStages(device).length
|
||||
},
|
||||
doneStageCount(device) {
|
||||
return this.deviceStages(device).filter(s => s.status === '2').length
|
||||
},
|
||||
devicePct(device) {
|
||||
const st = this.deviceStages(device)
|
||||
if (!st.length) return 0
|
||||
return Math.round(st.filter(s => s.status === '2').length / st.length * 100)
|
||||
},
|
||||
toggleDetail(device) {
|
||||
device._showDetail = !device._showDetail
|
||||
if (device._showDetail && !this.stageMap[device.deviceId]) {
|
||||
this.loadStages(device)
|
||||
}
|
||||
},
|
||||
handleAddDevice() {
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.deviceTitle = '新增设备'
|
||||
this.deviceForm = { projectId: this.currentProjectId, deviceName: '', spec: '', supplierName: '', contractNo: '', orderDate: '', deliveryDate: '', penaltyRate: 0, remark: '' }
|
||||
this.deviceVisible = true
|
||||
this.$nextTick(() => { this.$refs.deviceFormRef?.clearValidate() })
|
||||
},
|
||||
handleEditDevice(row) {
|
||||
this.deviceTitle = '编辑设备'
|
||||
getMfgDevice(row.deviceId).then(r => { this.deviceForm = r.data; this.deviceVisible = true; this.$nextTick(() => { this.$refs.deviceFormRef?.clearValidate() }) })
|
||||
},
|
||||
handleDeleteDevice(row) {
|
||||
this.$confirm(`确认删除 "${row.deviceName}"?`, '提示', { type: 'warning' }).then(() => { delMfgDevice(row.deviceId).then(() => { this.$message.success('删除成功'); this.loadDevices() }) })
|
||||
},
|
||||
handleDeviceSubmit() {
|
||||
this.$refs.deviceFormRef.validate(v => {
|
||||
if (!v) return; this.deviceSubmitting = true
|
||||
const api = this.deviceForm.deviceId ? updateMfgDevice : addMfgDevice
|
||||
api(this.deviceForm).then(() => { this.$message.success('保存成功'); this.deviceVisible = false; this.loadDevices() }).finally(() => { this.deviceSubmitting = false })
|
||||
})
|
||||
},
|
||||
handleEditStage(stg) {
|
||||
this.stageForm = { ...stg }
|
||||
this.stageVisible = true
|
||||
},
|
||||
handleStageSubmit() {
|
||||
this.stageSubmitting = true
|
||||
updateMfgStage(this.stageForm).then(() => {
|
||||
this.$message.success('阶段已更新')
|
||||
this.stageVisible = false
|
||||
const device = this.deviceList.find(d => d.deviceId === this.stageForm.deviceId)
|
||||
if (device) this.loadStages(device)
|
||||
}).finally(() => { this.stageSubmitting = false })
|
||||
},
|
||||
stageTag(s) { return { '0': 'info', '1': 'primary', '2': 'success', '3': 'danger' }[s] || 'info' },
|
||||
stageLabel(s) { return { '0': '待开始', '1': '进行中', '2': '已完成', '3': '逾期' }[s] || s },
|
||||
stageHeaderClass(stg) {
|
||||
if (stg.status === '3') return 'overdue'
|
||||
if (stg.status === '2') return 'done'
|
||||
if (stg.status === '1') return 'active'
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.dialog-footer { text-align: right; }
|
||||
.stage-detail-panel { background: #f8f9fc; border: 1px solid #e8ecf0; border-radius: 4px; padding: 8px; margin: 4px 0 12px; }
|
||||
.stage-card { margin-bottom: 6px; border: 1px solid #e8ecf0; border-radius: 4px; background: #fff; overflow: hidden; }
|
||||
.stage-header { padding: 8px 10px; font-size: 13px; font-weight: 600; cursor: pointer; display: flex; justify-content: space-between; align-items: center; }
|
||||
.stage-header.done { background: #f0f9eb; }
|
||||
.stage-header.active { background: #ecf5ff; }
|
||||
.stage-header.overdue { background: #fef0f0; }
|
||||
.stage-body { padding: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 16px; font-size: 12px; }
|
||||
.stage-field label { color: #888; }
|
||||
</style>
|
||||
426
ruoyi-ui/src/views/rm/procurement/index.vue
Normal file
426
ruoyi-ui/src/views/rm/procurement/index.vue
Normal file
@@ -0,0 +1,426 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>采购管理</span>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="报价比较" name="quotes" />
|
||||
<el-tab-pane label="合同管理" name="contracts" />
|
||||
<el-tab-pane label="合同模版" name="template" />
|
||||
<el-tab-pane label="采购进度" name="progress" />
|
||||
</el-tabs>
|
||||
|
||||
<!-- ========== 报价比较 Tab ========== -->
|
||||
<div v-if="activeTab === 'quotes'">
|
||||
<div style="margin-bottom:8px;display:flex;gap:6px;flex-wrap:wrap;align-items:center;">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddQuote">新增报价</el-button>
|
||||
</div>
|
||||
<el-table :data="quoteList" v-loading="quoteLoading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="itemName" label="设备项" min-width="140" />
|
||||
<el-table-column prop="supplierName" label="供应商" min-width="140" />
|
||||
<el-table-column prop="unitPrice" label="单价(¥)" width="100" align="right" />
|
||||
<el-table-column prop="totalPrice" label="总价(¥)" width="110" align="right" />
|
||||
<el-table-column prop="deliveryDays" label="交货期" width="80" align="center">
|
||||
<template slot-scope="s">{{ s.row.deliveryDays }}天</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="warrantyMonths" label="质保期" width="80" align="center">
|
||||
<template slot-scope="s">{{ s.row.warrantyMonths }}月</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="s.row.status === '1' ? 'success' : 'info'" size="mini">{{ s.row.status === '1' ? '已选' : '待选' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditQuote(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDeleteQuote(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- ========== 合同管理 Tab ========== -->
|
||||
<div v-if="activeTab === 'contracts'">
|
||||
<div style="margin-bottom:8px;">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddContract">起草合同</el-button>
|
||||
</div>
|
||||
<el-table :data="contractList" v-loading="contractLoading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="contractNo" label="合同编号" width="140" />
|
||||
<el-table-column prop="contractName" label="合同名称" min-width="150" />
|
||||
<el-table-column prop="supplierName" label="供应商" min-width="120" />
|
||||
<el-table-column prop="totalAmount" label="金额(¥)" width="110" align="right" />
|
||||
<el-table-column prop="signDate" label="签订日期" width="100" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag :type="contractTag(s.row.status)" size="mini">{{ contractLabel(s.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template slot-scope="s">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditContract(s.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDeleteContract(s.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- ========== 合同模版 Tab ========== -->
|
||||
<div v-if="activeTab === 'template'">
|
||||
<div class="template-content">
|
||||
<h3 style="text-align:center;margin-bottom:12px;">设备采购合同模版</h3>
|
||||
<p><b>合同编号:</b>__________</p>
|
||||
<p><b>甲方(采购方):</b>昆山德睿福成套设备有限公司</p>
|
||||
<p><b>乙方(供货方):</b>__________</p>
|
||||
<p style="margin-top:10px;"><b>一、设备清单</b></p>
|
||||
<p>设备名称:__________ 规格型号:__________ 数量:__________ 单价:__________</p>
|
||||
<p><b>二、技术标准</b></p>
|
||||
<p>1. 设备设计制造应符合国家相关标准及甲方提供的技术协议要求。</p>
|
||||
<p>2. 安装精度、材质要求详见技术附件。</p>
|
||||
<p><b>三、交货期</b></p>
|
||||
<p>乙方应于 _____ 年 __ 月 __ 日前将设备交付至甲方指定地点。每延迟1天,乙方应按合同总金额的 <b>0.5%</b> 向甲方支付违约金。</p>
|
||||
<p><b>四、质量检验</b></p>
|
||||
<p>1. 乙方应在每个制造阶段向甲方提交进度报告及相关检验数据。</p>
|
||||
<p>2. 未按合同要求上传检验报告的,每项处以合同金额 <b>1%</b> 的罚款。</p>
|
||||
<p>3. 设备到场后,甲方按技术协议进行验收,不合格设备乙方应无偿整改。</p>
|
||||
<p><b>五、质保期</b></p>
|
||||
<p>设备质保期为投产之日起 <b>12个月</b> 或货到之日起 <b>18个月</b>,以先到者为准。</p>
|
||||
<p><b>六、付款方式</b></p>
|
||||
<p>1. 合同签订后支付 <b>30%</b> 预付款;</p>
|
||||
<p>2. 设备制造完成预验收合格后支付 <b>30%</b>;</p>
|
||||
<p>3. 设备到场安装调试验收合格后支付 <b>35%</b>;</p>
|
||||
<p>4. 质保期满后支付剩余 <b>5%</b> 质保金。</p>
|
||||
<p><b>七、争议解决</b></p>
|
||||
<p>双方发生争议应协商解决,协商不成的,提交甲方所在地人民法院诉讼解决。</p>
|
||||
<p style="margin-top:16px;"><b>甲方(盖章):</b>__________ <b>乙方(盖章):</b>__________</p>
|
||||
<p><b>签订日期:</b>_____ 年 __ 月 __ 日</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 采购进度 Tab ========== -->
|
||||
<div v-if="activeTab === 'progress'">
|
||||
<div class="progress-summary">
|
||||
<div class="summary-card"><div class="s-label">采购总进度</div><div class="s-val">{{ overallPct }}%</div></div>
|
||||
<div class="summary-card"><div class="s-label">询价/报价中</div><div class="s-val">{{ inquiringCount }}</div></div>
|
||||
<div class="summary-card"><div class="s-label">制造/运输中</div><div class="s-val">{{ shippingCount }}</div></div>
|
||||
<div class="summary-card"><div class="s-label">已到货</div><div class="s-val">{{ arrivedCount }}</div></div>
|
||||
</div>
|
||||
<div style="margin-bottom:8px;">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddProgress">新增采购进度</el-button>
|
||||
</div>
|
||||
<div v-if="progressList.length === 0" style="text-align:center;padding:40px 0;color:#aaa;">暂无采购进度数据</div>
|
||||
<div v-for="(p, idx) in progressList" :key="idx" class="progress-item">
|
||||
<div class="progress-header">
|
||||
<span><b>{{ p.itemName }}</b> <span style="font-weight:400;font-size:12px;color:#888;">{{ p.supplierName }} {{ p.contractNo ? '· '+p.contractNo : '' }}</span></span>
|
||||
<div>
|
||||
<span style="font-size:12px;font-weight:600;margin-right:10px;">{{ progressPct(p) }}%</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditProgress(p)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDeleteProgress(p)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-steps">
|
||||
<template v-for="(stg, si) in parsedStages(p.stages)">
|
||||
<div v-if="si > 0" :key="'line-'+si" class="step-line" :class="{ done: isStageDone(parsedStages(p.stages), si-1) }" />
|
||||
<div :key="si" class="step" :class="{ done: stg.done, active: isActiveStage(p, si) }">
|
||||
<div class="step-circle">{{ stg.done ? '✓' : si+1 }}</div>
|
||||
<div class="step-label">{{ stg.name }}</div>
|
||||
<div v-if="stg.date" class="step-date">{{ stg.date.slice(5) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="p.remark" style="font-size:11px;color:#888;margin-top:4px;">备注:{{ p.remark }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== Quote Dialog ========== -->
|
||||
<el-dialog :title="quoteTitle" :visible.sync="quoteVisible" width="500px" append-to-body>
|
||||
<el-form ref="quoteFormRef" :model="quoteForm" :rules="quoteRules" label-width="100px" size="small">
|
||||
<el-form-item label="设备项" prop="itemName">
|
||||
<el-input v-model="quoteForm.itemName" placeholder="如:工作辊轴承座" />
|
||||
</el-form-item>
|
||||
<el-form-item label="供应商" prop="supplierName">
|
||||
<el-input v-model="quoteForm.supplierName" placeholder="供应商全称" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="数量" prop="qty"><el-input-number v-model="quoteForm.qty" :min="1" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="单位" prop="unit"><el-input v-model="quoteForm.unit" placeholder="台/套" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="规格" prop="spec"><el-input v-model="quoteForm.spec" placeholder="规格" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="单价(¥)" prop="unitPrice"><el-input-number v-model="quoteForm.unitPrice" :min="0" :precision="2" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="交货期(天)" prop="deliveryDays"><el-input-number v-model="quoteForm.deliveryDays" :min="0" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="质保期(月)" prop="warrantyMonths"><el-input-number v-model="quoteForm.warrantyMonths" :min="0" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="quoteForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="quoteVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="quoteSubmitting" @click="handleQuoteSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ========== Contract Dialog ========== -->
|
||||
<el-dialog :title="contractTitle" :visible.sync="contractVisible" width="560px" append-to-body>
|
||||
<el-form ref="contractFormRef" :model="contractForm" :rules="contractRules" label-width="100px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="合同编号" prop="contractNo"><el-input v-model="contractForm.contractNo" placeholder="如:DRF-CT-2026-001" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="合同名称" prop="contractName"><el-input v-model="contractForm.contractName" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="供应商" prop="supplierName"><el-input v-model="contractForm.supplierName" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="金额(¥)" prop="totalAmount"><el-input-number v-model="contractForm.totalAmount" :min="0" :precision="2" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="签订日期" prop="signDate"><el-date-picker v-model="contractForm.signDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="状态" prop="status"><el-select v-model="contractForm.status" style="width:100%"><el-option label="草稿" value="draft" /><el-option label="审核中" value="review" /><el-option label="已签订" value="signed" /></el-select></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="合同条款" prop="clauses">
|
||||
<el-input v-model="contractForm.clauses" type="textarea" :rows="2" placeholder="主要合同条款" />
|
||||
</el-form-item>
|
||||
<el-form-item label="违约罚款" prop="penaltyClause">
|
||||
<el-input v-model="contractForm.penaltyClause" type="textarea" :rows="2" placeholder="延期/质量违约罚款条款" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件路径" prop="fileUrl">
|
||||
<el-input v-model="contractForm.fileUrl" placeholder="合同文件 URL" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="contractVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="contractSubmitting" @click="handleContractSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ========== Progress Dialog ========== -->
|
||||
<el-dialog :title="progressTitle" :visible.sync="progressVisible" width="520px" append-to-body>
|
||||
<el-form ref="progressFormRef" :model="progressForm" :rules="progressRules" label-width="100px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="采购项" prop="itemName"><el-input v-model="progressForm.itemName" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="供应商" prop="supplierName"><el-input v-model="progressForm.supplierName" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12"><el-form-item label="合同编号" prop="contractNo"><el-input v-model="progressForm.contractNo" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="金额(¥)" prop="amount"><el-input-number v-model="progressForm.amount" :min="0" :precision="2" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="下单日期"><el-date-picker v-model="progressForm.orderDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="预计到货"><el-date-picker v-model="progressForm.expectDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="实际到货"><el-date-picker v-model="progressForm.actualDate" type="date" style="width:100%" value-format="yyyy-MM-dd" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="progressForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="progressVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="progressSubmitting" @click="handleProgressSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listQuoteAll, getQuote, addQuote, updateQuote, delQuote } from '@/api/rm/quote'
|
||||
import { listContractAll, getContract, addContract, updateContract, delContract } from '@/api/rm/contract'
|
||||
import { listProcProgressAll, getProcProgress, addProcProgress, updateProcProgress, delProcProgress } from '@/api/rm/procProgress'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
const PROC_STAGES = ['询价', '报价', '比价', '合同签订', '下单', '制造中', '已发货', '已到货']
|
||||
|
||||
export default {
|
||||
name: 'RmProcurement',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'quotes',
|
||||
currentProjectId: null,
|
||||
// Quotes
|
||||
quoteLoading: false, quoteList: [], quoteVisible: false, quoteTitle: '', quoteSubmitting: false,
|
||||
quoteForm: {}, quoteRules: { itemName: [{ required: true, message: '请填写设备项名称', trigger: 'blur' }], supplierName: [{ required: true, message: '请填写供应商', trigger: 'blur' }] },
|
||||
// Contracts
|
||||
contractLoading: false, contractList: [], contractVisible: false, contractTitle: '', contractSubmitting: false,
|
||||
contractForm: {}, contractRules: { contractNo: [{ required: true, message: '请填写合同编号', trigger: 'blur' }] },
|
||||
// Progress
|
||||
progressList: [], progressVisible: false, progressTitle: '', progressSubmitting: false,
|
||||
progressForm: {}, progressRules: { itemName: [{ required: true, message: '请填写采购项名称', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
overallPct() {
|
||||
if (!this.progressList.length) return 0
|
||||
const total = this.progressList.reduce((s, p) => {
|
||||
const st = this.parsedStages(p.stages)
|
||||
return s + (st.length || 1)
|
||||
}, 0)
|
||||
const done = this.progressList.reduce((s, p) => {
|
||||
const st = this.parsedStages(p.stages)
|
||||
return s + st.filter(x => x.done).length
|
||||
}, 0)
|
||||
return total ? Math.round(done / total * 100) : 0
|
||||
},
|
||||
inquiringCount() {
|
||||
return this.progressList.filter(p => {
|
||||
const st = this.parsedStages(p.stages)
|
||||
return !st.length || !st[0].done
|
||||
}).length
|
||||
},
|
||||
shippingCount() {
|
||||
return this.progressList.filter(p => {
|
||||
const st = this.parsedStages(p.stages)
|
||||
return st.length > 4 && st[4].done && (!st[7] || !st[7].done)
|
||||
}).length
|
||||
},
|
||||
arrivedCount() {
|
||||
return this.progressList.filter(p => {
|
||||
const st = this.parsedStages(p.stages)
|
||||
return st.length > 7 && st[7].done
|
||||
}).length
|
||||
}
|
||||
},
|
||||
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.loadAll()
|
||||
}
|
||||
})
|
||||
},
|
||||
loadAll() {
|
||||
this.loadQuotes(); this.loadContracts(); this.loadProgress()
|
||||
},
|
||||
handleTabClick() { this.loadAll() },
|
||||
parsedStages(stagesStr) {
|
||||
if (!stagesStr) return []
|
||||
try { return JSON.parse(stagesStr) } catch { return [] }
|
||||
},
|
||||
progressPct(p) {
|
||||
const st = this.parsedStages(p.stages)
|
||||
if (!st.length) return 0
|
||||
return Math.round(st.filter(x => x.done).length / st.length * 100)
|
||||
},
|
||||
isStageDone(stages, idx) {
|
||||
return stages[idx] && stages[idx].done
|
||||
},
|
||||
isActiveStage(p, si) {
|
||||
const st = this.parsedStages(p.stages)
|
||||
return !st[si].done && (si === 0 || st[si - 1].done)
|
||||
},
|
||||
// === Quotes ===
|
||||
loadQuotes() {
|
||||
if (!this.currentProjectId) return
|
||||
this.quoteLoading = true
|
||||
listQuoteAll({ projectId: this.currentProjectId }).then(r => { this.quoteList = r.data || [] }).finally(() => { this.quoteLoading = false })
|
||||
},
|
||||
handleAddQuote() {
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.quoteTitle = '新增报价'; this.quoteForm = { projectId: this.currentProjectId, itemName: '', supplierName: '', spec: '', qty: 1, unit: '台', unitPrice: 0, totalPrice: 0, deliveryDays: 30, warrantyMonths: 12, remark: '' }
|
||||
this.quoteVisible = true; this.$nextTick(() => { this.$refs.quoteFormRef?.clearValidate() })
|
||||
},
|
||||
handleEditQuote(row) {
|
||||
this.quoteTitle = '编辑报价'
|
||||
getQuote(row.quoteId).then(r => { this.quoteForm = r.data; this.quoteVisible = true; this.$nextTick(() => { this.$refs.quoteFormRef?.clearValidate() }) })
|
||||
},
|
||||
handleDeleteQuote(row) {
|
||||
this.$confirm('确认删除此报价?', '提示', { type: 'warning' }).then(() => { delQuote(row.quoteId).then(() => { this.$message.success('删除成功'); this.loadQuotes() }) })
|
||||
},
|
||||
handleQuoteSubmit() {
|
||||
this.$refs.quoteFormRef.validate(v => {
|
||||
if (!v) return; this.quoteSubmitting = true
|
||||
const fd = { ...this.quoteForm, totalPrice: this.quoteForm.unitPrice * this.quoteForm.qty }
|
||||
const api = fd.quoteId ? updateQuote : addQuote
|
||||
api(fd).then(() => { this.$message.success('保存成功'); this.quoteVisible = false; this.loadQuotes() }).finally(() => { this.quoteSubmitting = false })
|
||||
})
|
||||
},
|
||||
// === Contracts ===
|
||||
loadContracts() {
|
||||
if (!this.currentProjectId) return
|
||||
this.contractLoading = true
|
||||
listContractAll({ projectId: this.currentProjectId }).then(r => { this.contractList = r.data || [] }).finally(() => { this.contractLoading = false })
|
||||
},
|
||||
handleAddContract() {
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.contractTitle = '起草合同'; this.contractForm = { projectId: this.currentProjectId, contractNo: '', contractName: '', supplierName: '', totalAmount: 0, signDate: '', fileUrl: '', status: 'draft', clauses: '', penaltyClause: '', remark: '' }
|
||||
this.contractVisible = true; this.$nextTick(() => { this.$refs.contractFormRef?.clearValidate() })
|
||||
},
|
||||
handleEditContract(row) {
|
||||
this.contractTitle = '编辑合同'
|
||||
getContract(row.contractId).then(r => { this.contractForm = r.data; this.contractVisible = true; this.$nextTick(() => { this.$refs.contractFormRef?.clearValidate() }) })
|
||||
},
|
||||
handleDeleteContract(row) {
|
||||
this.$confirm('确认删除此合同?', '提示', { type: 'warning' }).then(() => { delContract(row.contractId).then(() => { this.$message.success('删除成功'); this.loadContracts() }) })
|
||||
},
|
||||
handleContractSubmit() {
|
||||
this.$refs.contractFormRef.validate(v => {
|
||||
if (!v) return; this.contractSubmitting = true
|
||||
const api = this.contractForm.contractId ? updateContract : addContract
|
||||
api(this.contractForm).then(() => { this.$message.success('保存成功'); this.contractVisible = false; this.loadContracts() }).finally(() => { this.contractSubmitting = false })
|
||||
})
|
||||
},
|
||||
contractTag(s) { return { draft: 'info', review: 'warning', signed: 'success' }[s] || 'info' },
|
||||
contractLabel(s) { return { draft: '草稿', review: '审核中', signed: '已签订' }[s] || s },
|
||||
// === Progress ===
|
||||
loadProgress() {
|
||||
if (!this.currentProjectId) return
|
||||
listProcProgressAll({ projectId: this.currentProjectId }).then(r => {
|
||||
this.progressList = (r.data || []).map(p => {
|
||||
if (!p.stages) p.stages = JSON.stringify(PROC_STAGES.map((name, i) => ({ name, done: i === 0 && p.currentStage === '0' ? true : i < parseInt(p.currentStage || '0') ? true : false, date: '' })))
|
||||
return p
|
||||
})
|
||||
})
|
||||
},
|
||||
handleAddProgress() {
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.progressTitle = '新增采购进度'
|
||||
this.progressForm = { projectId: this.currentProjectId, itemName: '', supplierName: '', contractNo: '', amount: 0, orderDate: '', expectDate: '', actualDate: '', currentStage: '0', stages: JSON.stringify(PROC_STAGES.map(n => ({ name: n, done: false, date: '' }))), remark: '' }
|
||||
this.progressVisible = true; this.$nextTick(() => { this.$refs.progressFormRef?.clearValidate() })
|
||||
},
|
||||
handleEditProgress(row) {
|
||||
this.progressTitle = '编辑采购进度'
|
||||
getProcProgress(row.progressId).then(r => { this.progressForm = r.data; this.progressVisible = true; this.$nextTick(() => { this.$refs.progressFormRef?.clearValidate() }) })
|
||||
},
|
||||
handleDeleteProgress(row) {
|
||||
this.$confirm('确认删除此采购进度?', '提示', { type: 'warning' }).then(() => { delProcProgress(row.progressId).then(() => { this.$message.success('删除成功'); this.loadProgress() }) })
|
||||
},
|
||||
handleProgressSubmit() {
|
||||
this.$refs.progressFormRef.validate(v => {
|
||||
if (!v) return; this.progressSubmitting = true
|
||||
const api = this.progressForm.progressId ? updateProcProgress : addProcProgress
|
||||
api(this.progressForm).then(() => { this.$message.success('保存成功'); this.progressVisible = false; this.loadProgress() }).finally(() => { this.progressSubmitting = false })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.dialog-footer { text-align: right; }
|
||||
.progress-summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 12px; }
|
||||
.summary-card { text-align: center; padding: 12px; background: #f8f9fc; border-radius: 6px; border: 1px solid #e8ecf0; }
|
||||
.s-label { font-size: 11px; color: #888; }
|
||||
.s-val { font-size: 22px; font-weight: 700; color: #2176ae; margin-top: 2px; }
|
||||
.progress-item { background: #f8f9fc; border: 1px solid #e8ecf0; border-radius: 6px; padding: 10px; margin-bottom: 8px; }
|
||||
.progress-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 13px; }
|
||||
.progress-steps { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; }
|
||||
.step { display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 50px; }
|
||||
.step-circle { width: 24px; height: 24px; border-radius: 50%; background: #e8ecf0; color: #888; font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
|
||||
.step.done .step-circle { background: #67c23a; color: #fff; }
|
||||
.step.active .step-circle { background: #2176ae; color: #fff; }
|
||||
.step-label { font-size: 10px; color: #666; white-space: nowrap; }
|
||||
.step-date { font-size: 9px; color: #999; }
|
||||
.step-line { width: 20px; height: 2px; background: #e8ecf0; margin-bottom: 18px; }
|
||||
.step-line.done { background: #67c23a; }
|
||||
.template-content { font-size: 13px; line-height: 2; padding: 16px; background: #f8f9fc; border: 1px solid #e8ecf0; border-radius: 6px; }
|
||||
</style>
|
||||
374
ruoyi-ui/src/views/rm/project/index.vue
Normal file
374
ruoyi-ui/src/views/rm/project/index.vue
Normal file
@@ -0,0 +1,374 @@
|
||||
<template>
|
||||
<div class="dashboard-page">
|
||||
<!-- Info Cards -->
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card">
|
||||
<div class="label">项目名称</div>
|
||||
<div class="value" style="font-size:14px;color:#1a1a2e;">{{ projectInfo.projectName || '未设置' }}</div>
|
||||
<div class="sub">
|
||||
编号: {{ projectInfo.projectNo || '-' }}
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="openProjectInfoModal" style="color:#2176ae;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card green">
|
||||
<div class="label">已完成阶段</div>
|
||||
<div class="value">{{ doneCount }}</div>
|
||||
<div class="sub">/ {{ totalStages }} 个阶段</div>
|
||||
</div>
|
||||
<div class="stat-card orange">
|
||||
<div class="label">进行中</div>
|
||||
<div class="value">{{ progressCount }}</div>
|
||||
<div class="sub">个阶段</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">项目整体进度</div>
|
||||
<div class="value" style="color:#2176ae;">{{ overallProgress }}%</div>
|
||||
<div class="sub">总包项目全生命周期</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid" style="grid-template-columns:repeat(3,1fr);margin-top:10px;">
|
||||
<div class="stat-card">
|
||||
<div class="label">关联合同</div>
|
||||
<div class="value" style="font-size:13px;">{{ contractCount }}</div>
|
||||
<div class="sub">{{ contractNosText }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">项目日期</div>
|
||||
<div class="value" style="font-size:13px;color:#1a1a2e;">{{ projectInfo.startDate || '未设置' }} ~ {{ projectInfo.endDate || '未设置' }}</div>
|
||||
<div class="sub">项目经理: {{ projectInfo.manager || '未设置' }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">客户</div>
|
||||
<div class="value" style="font-size:13px;color:#1a1a2e;">{{ projectInfo.clientName || '未设置' }}</div>
|
||||
<div class="sub">点击编辑项目信息</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-bar-wrap">
|
||||
<div class="title">📊 项目全生命周期进度</div>
|
||||
<div class="progress-steps">
|
||||
<template v-for="(stage, index) in stages">
|
||||
<div :key="stage.key" :class="['progress-step', stageClass(stage.key)]">
|
||||
<div class="circle">{{ stageIcon(stage.key) }}</div>
|
||||
<div class="step-label">{{ stage.label }}</div>
|
||||
</div>
|
||||
<div :key="'line-'+index" :class="['progress-line', stageLineClass(stage.key)]" v-if="index < stages.length - 1" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stage Overview -->
|
||||
<div style="font-size:12px;font-weight:600;margin-bottom:8px;">📋 各阶段概览(点击进入)</div>
|
||||
<div class="dashboard-grid">
|
||||
<div v-for="stage in stages" :key="stage.key" class="stat-card" style="cursor:pointer;" @click="switchStage(stage.key)">
|
||||
<div class="label">{{ stage.icon }} {{ stage.label }}</div>
|
||||
<div class="value" style="font-size:14px;">{{ stageStatusText(stage.key) }}</div>
|
||||
<div class="sub">点击进入管理</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Project Info Edit Modal -->
|
||||
<el-dialog title="项目信息设置" :visible.sync="dialogVisible" width="600px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编号" prop="projectNo">
|
||||
<el-input v-model="form.projectNo" placeholder="如:DRF-2026-A01" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户名称" prop="clientName">
|
||||
<el-input v-model="form.clientName" placeholder="请输入客户名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目经理" prop="manager">
|
||||
<el-input v-model="form.manager" placeholder="请输入项目经理" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker v-model="form.startDate" type="date" placeholder="选择开始日期" value-format="yyyy-MM-dd" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker v-model="form.endDate" type="date" placeholder="选择结束日期" value-format="yyyy-MM-dd" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</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 { getProject, updateProject, listProject, addProject } from '@/api/rm/project'
|
||||
|
||||
const STAGES = [
|
||||
{ key: 'budget', label: '项目预算', icon: '💰' },
|
||||
{ key: 'tech_plan', label: '技术方案', icon: '📋' },
|
||||
{ key: 'layout', label: '布局图', icon: '🗺️' },
|
||||
{ key: 'tech_review', label: '技术审查', icon: '🔍' },
|
||||
{ key: 'drawing_design', label: '图纸设计', icon: '📐' },
|
||||
{ key: 'drawing_review', label: '图纸审查', icon: '✏️' },
|
||||
{ key: 'procurement', label: '采购管理', icon: '🛒' },
|
||||
{ key: 'manufacturing', label: '制造进度', icon: '🏭' },
|
||||
{ key: 'drawing_compare', label: '图纸比较', icon: '🔄' },
|
||||
{ key: 'doc_lib', label: '资料库', icon: '📁' },
|
||||
{ key: 'site_mod', label: '现场修改', icon: '🔧' },
|
||||
{ key: 'shipping', label: '发货清单', icon: '📦' },
|
||||
{ key: 'manuals', label: '说明书', icon: '📖' },
|
||||
{ key: 'install_prep', label: '安装准备', icon: '🛠️' },
|
||||
{ key: 'install_feedback', label: '问题反馈', icon: '💬' },
|
||||
{ key: 'acceptance', label: '安装验收', icon: '✅' },
|
||||
{ key: 'hot_commissioning', label: '热负荷试车', icon: '🔥' }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'RmProjectDashboard',
|
||||
data() {
|
||||
return {
|
||||
stages: STAGES,
|
||||
projectInfo: {},
|
||||
stageStatus: {},
|
||||
dialogVisible: false,
|
||||
submitting: false,
|
||||
form: {},
|
||||
rules: {
|
||||
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
|
||||
projectNo: [{ required: true, message: '请输入项目编号', trigger: 'blur' }],
|
||||
clientName: [{ required: true, message: '请输入客户名称', trigger: 'blur' }]
|
||||
},
|
||||
// Default project will be fetched from backend
|
||||
currentProjectId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalStages() {
|
||||
return this.stages.length
|
||||
},
|
||||
doneCount() {
|
||||
return Object.values(this.stageStatus).filter(v => v === 'done').length
|
||||
},
|
||||
progressCount() {
|
||||
return Object.values(this.stageStatus).filter(v => v === 'progress').length
|
||||
},
|
||||
overallProgress() {
|
||||
if (this.totalStages === 0) return 0
|
||||
return Math.round((this.doneCount / this.totalStages) * 100)
|
||||
},
|
||||
contractCount() {
|
||||
return '0份'
|
||||
},
|
||||
contractNosText() {
|
||||
return '暂无关联合同'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadProject()
|
||||
},
|
||||
methods: {
|
||||
loadProject() {
|
||||
// Try to get the first project as default
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
return getProject(rows[0].projectId)
|
||||
} else {
|
||||
// No project exists yet, show empty form
|
||||
this.projectInfo = {}
|
||||
return null
|
||||
}
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
this.projectInfo = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
openProjectInfoModal() {
|
||||
// If no project exists, check if we need to create one first
|
||||
if (!this.currentProjectId) {
|
||||
this.$confirm('暂无项目数据,是否先创建一个默认项目?', '提示', { type: 'info' }).then(() => {
|
||||
addProject({
|
||||
projectName: '1380mm六辊可逆轧机设备总包项目',
|
||||
projectNo: 'DRF-2026-001',
|
||||
clientName: '昆山德睿福成套设备有限公司',
|
||||
startDate: '2026-06-01',
|
||||
endDate: '2027-02-28',
|
||||
manager: '工程师',
|
||||
remark: ''
|
||||
}).then(res => {
|
||||
this.$message.success('默认项目已创建')
|
||||
this.loadProject()
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
this.form = { ...this.projectInfo }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
this.form.projectId = this.currentProjectId
|
||||
updateProject(this.form).then(() => {
|
||||
this.$message.success('项目信息已更新')
|
||||
this.dialogVisible = false
|
||||
Object.assign(this.projectInfo, this.form)
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})
|
||||
},
|
||||
stageClass(key) {
|
||||
const st = this.stageStatus[key] || 'pending'
|
||||
return st === 'done' ? 'done' : st === 'progress' ? 'active' : ''
|
||||
},
|
||||
stageLineClass(key) {
|
||||
return (this.stageStatus[key] || 'pending') === 'done' ? 'done' : ''
|
||||
},
|
||||
stageIcon(key) {
|
||||
const st = this.stageStatus[key] || 'pending'
|
||||
return st === 'done' ? '✓' : (STAGES.findIndex(s => s.key === key) + 1)
|
||||
},
|
||||
stageStatusText(key) {
|
||||
const map = { done: '已完成', progress: '进行中', pending: '未开始' }
|
||||
return map[this.stageStatus[key]] || '未开始'
|
||||
},
|
||||
switchStage(key) {
|
||||
this.$message.info(`"${STAGES.find(s => s.key === key).label}" 功能开发中`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-page {
|
||||
padding: 12px 16px;
|
||||
background: #f0f2f5;
|
||||
min-height: calc(100vh - 84px);
|
||||
}
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.stat-card {
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #d0d7de;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
transition: box-shadow 0.15s;
|
||||
}
|
||||
.stat-card:hover {
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.stat-card .label {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.stat-card .value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #2176ae;
|
||||
}
|
||||
.stat-card .sub {
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.stat-card.green .value { color: #27ae60; }
|
||||
.stat-card.orange .value { color: #f39c12; }
|
||||
.stat-card.red .value { color: #e74c3c; }
|
||||
|
||||
/* Progress Bar */
|
||||
.progress-bar-wrap {
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 10px 14px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #d0d7de;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
.progress-bar-wrap .title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.progress-step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 55px;
|
||||
position: relative;
|
||||
}
|
||||
.progress-step .circle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #ddd;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
.progress-step.done .circle { background: #27ae60; }
|
||||
.progress-step.active .circle { background: #2176ae; }
|
||||
.progress-step .step-label {
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
margin-top: 2px;
|
||||
text-align: center;
|
||||
max-width: 55px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.progress-line {
|
||||
height: 2px;
|
||||
background: #ddd;
|
||||
flex: 1;
|
||||
min-width: 10px;
|
||||
}
|
||||
.progress-line.done { background: #27ae60; }
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
198
ruoyi-ui/src/views/rm/techPlan/index.vue
Normal file
198
ruoyi-ui/src/views/rm/techPlan/index.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>技术方案确定</span>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增方案项</el-button>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<!-- Overview row (matching prototype) -->
|
||||
<div class="overview-row">
|
||||
<div class="overview-item">
|
||||
<label>方案整体状态</label>
|
||||
<el-select v-model="overviewStatus" size="small" style="width:140px" @change="loadList">
|
||||
<el-option label="未开始" value="pending" />
|
||||
<el-option label="进行中" value="progress" />
|
||||
<el-option label="已完成" value="done" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="overview-item" style="flex:1;">
|
||||
<label>方案概述</label>
|
||||
<el-input v-model="overviewDesc" size="small" placeholder="技术方案总体描述" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="search-row">
|
||||
<el-input v-model="query.itemName" size="small" placeholder="方案名称" clearable style="width:160px" @keyup.enter="handleSearch" />
|
||||
<el-select v-model="query.status" size="small" placeholder="状态" clearable style="width:110px">
|
||||
<el-option label="未开始" value="pending" />
|
||||
<el-option label="进行中" value="progress" />
|
||||
<el-option label="已完成" value="done" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<el-table :data="list" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="itemName" label="方案名称" min-width="160" />
|
||||
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="owner" label="负责人" width="90" />
|
||||
<el-table-column prop="status" label="状态" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusTag(scope.row.status)" size="mini">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" 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-row">
|
||||
<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="small"
|
||||
@size-change="handleSizeChange" @current-change="handlePageChange" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" size="small">
|
||||
<el-form-item label="方案名称" prop="itemName">
|
||||
<el-input v-model="form.itemName" placeholder="如:主轧机技术方案" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="方案详细描述" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="负责人" prop="owner">
|
||||
<el-input v-model="form.owner" placeholder="负责人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择" style="width:100%">
|
||||
<el-option label="未开始" value="pending" />
|
||||
<el-option label="进行中" value="progress" />
|
||||
<el-option label="已完成" value="done" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="附件" prop="attachmentUrl">
|
||||
<el-input v-model="form.attachmentUrl" placeholder="附件路径或 URL" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTechPlan, getTechPlan, addTechPlan, updateTechPlan, delTechPlan } from '@/api/rm/techPlan'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
export default {
|
||||
name: 'RmTechPlan',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
currentProjectId: null,
|
||||
overviewStatus: 'pending',
|
||||
overviewDesc: '',
|
||||
query: { pageNum: 1, pageSize: 10, itemName: undefined, status: undefined, projectId: undefined },
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
submitting: false,
|
||||
form: {},
|
||||
rules: { itemName: [{ required: true, message: '请填写方案名称', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
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
|
||||
listTechPlan(this.query).then(res => {
|
||||
this.list = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleSearch() { this.query.pageNum = 1; this.loadList() },
|
||||
handleReset() { this.query = { pageNum: 1, pageSize: 10 }; this.loadList() },
|
||||
handleSizeChange(val) { this.query.pageSize = val; this.loadList() },
|
||||
handlePageChange(val) { this.query.pageNum = val; this.loadList() },
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增方案项'
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.form = { projectId: this.currentProjectId, itemName: '', status: 'pending', description: '', owner: '', attachmentUrl: '', remark: '' }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑方案项'
|
||||
getTechPlan(row.planItemId).then(res => {
|
||||
this.form = res.data
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.itemName}"?`, '提示', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {
|
||||
delTechPlan(row.planItemId).then(() => { this.$message.success('删除成功'); this.loadList() })
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.form.planItemId ? updateTechPlan : addTechPlan
|
||||
api(this.form).then(() => {
|
||||
this.$message.success(this.form.planItemId ? '更新成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
if (!this.form.planItemId) this.query.pageNum = 1
|
||||
this.loadList()
|
||||
}).finally(() => { this.submitting = false })
|
||||
})
|
||||
},
|
||||
statusTag(s) { return { pending: 'info', progress: 'warning', done: 'success' }[s] || 'info' },
|
||||
statusLabel(s) { return { pending: '未开始', progress: '进行中', done: '已完成' }[s] || s }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.overview-row { display: flex; gap: 12px; align-items: flex-end; margin-bottom: 10px; }
|
||||
.overview-item label { display: block; font-size: 12px; color: #666; margin-bottom: 3px; }
|
||||
.search-row { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
|
||||
.pagination-row { margin-top: 10px; display: flex; justify-content: flex-end; }
|
||||
.dialog-footer { text-align: right; }
|
||||
</style>
|
||||
336
ruoyi-ui/src/views/rm/techReview/index.vue
Normal file
336
ruoyi-ui/src/views/rm/techReview/index.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<div class="rm-container">
|
||||
<div class="rm-panel">
|
||||
<div class="rm-panel-header">
|
||||
<span>技术审查</span>
|
||||
</div>
|
||||
<div class="rm-panel-body">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="机械审查" name="mechanical" />
|
||||
<el-tab-pane label="电气审查" name="electrical" />
|
||||
<el-tab-pane label="液压审查" name="hydraulic" />
|
||||
<el-tab-pane label="流体审查" name="fluid" />
|
||||
<el-tab-pane label="能源介质审查" name="energy" />
|
||||
<el-tab-pane label="色卡确定" name="color" />
|
||||
</el-tabs>
|
||||
|
||||
<!-- Review Items Tab -->
|
||||
<div v-if="activeTab !== 'color'">
|
||||
<div style="margin-bottom:8px;">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAdd">新增{{ tabLabel(activeTab) }}项</el-button>
|
||||
</div>
|
||||
<el-table :data="reviewList" v-loading="loading" stripe border highlight-current-row size="small">
|
||||
<el-table-column type="index" label="#" width="45" />
|
||||
<el-table-column prop="itemName" label="审查项目" min-width="140" />
|
||||
<el-table-column prop="reviewer" label="审查人" width="80" />
|
||||
<el-table-column prop="reviewDate" label="审查日期" width="100" align="center" />
|
||||
<el-table-column prop="conclusion" label="状态" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="conclusionTag(scope.row.conclusion)" size="mini">{{ conclusionLabel(scope.row.conclusion) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reviewOpinion" label="审查意见" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="thinking" label="思维导入" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="120" 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>
|
||||
|
||||
<!-- Color Card Tab -->
|
||||
<div v-else>
|
||||
<div style="margin-bottom:8px;">
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="handleAddColor">新增色卡</el-button>
|
||||
<span style="font-size:12px;color:#888;margin-left:8px;">共 {{ colorCards.length }} 条色卡</span>
|
||||
</div>
|
||||
<div v-if="colorCards.length === 0" style="text-align:center;padding:40px 0;color:#aaa;font-size:13px;">暂无色卡,点击上方按钮添加</div>
|
||||
<div v-for="(group, gIdx) in colorGroups" :key="gIdx" class="color-group">
|
||||
<div class="color-group-title">{{ group.category || '未分类' }}</div>
|
||||
<div class="color-card-grid">
|
||||
<div v-for="(card, cIdx) in group.items" :key="cIdx" class="color-card-item">
|
||||
<div class="color-swatch" :style="{ background: card.hexValue }" />
|
||||
<div class="color-info">
|
||||
<div class="color-name">{{ card.colorName }}</div>
|
||||
<div class="color-hex">{{ card.hexValue }}</div>
|
||||
<div class="color-standard" v-if="card.standard">{{ card.standardLabel }} {{ card.standard }}</div>
|
||||
<div class="color-usage" v-if="card.usageDesc">{{ card.usageDesc }}</div>
|
||||
</div>
|
||||
<div class="color-actions">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditColor(card)" />
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" @click="handleDeleteColor(card)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Review Item Dialog -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="520px" append-to-body>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="90px" size="small">
|
||||
<el-form-item label="审查项目" prop="itemName">
|
||||
<el-input v-model="form.itemName" placeholder="审查项目名称" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="审查人" prop="reviewer">
|
||||
<el-input v-model="form.reviewer" placeholder="审查人姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="审查日期" prop="reviewDate">
|
||||
<el-date-picker v-model="form.reviewDate" type="date" placeholder="选择日期" style="width:100%" value-format="yyyy-MM-dd" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="结论" prop="conclusion">
|
||||
<el-select v-model="form.conclusion" placeholder="请选择" style="width:100%">
|
||||
<el-option label="待审查" value="pending" />
|
||||
<el-option label="通过" value="approved" />
|
||||
<el-option label="需整改" value="rectify" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="审查意见" prop="reviewOpinion">
|
||||
<el-input v-model="form.reviewOpinion" type="textarea" :rows="2" placeholder="审查意见" />
|
||||
</el-form-item>
|
||||
<el-form-item label="思维导入" prop="thinking">
|
||||
<el-input v-model="form.thinking" type="textarea" :rows="2" placeholder="思考备注" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="1" placeholder="备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="dialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="submitting" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Color Card Dialog -->
|
||||
<el-dialog :title="colorDialogTitle" :visible.sync="colorDialogVisible" width="520px" append-to-body>
|
||||
<el-form ref="colorFormRef" :model="colorForm" :rules="colorRules" label-width="100px" size="small">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="部位/名称" prop="colorName">
|
||||
<el-input v-model="colorForm.colorName" placeholder="如:轧机牌坊" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="HEX颜色值" prop="hexValue">
|
||||
<el-input v-model="colorForm.hexValue" placeholder="#C8CBCE">
|
||||
<template slot="prepend">
|
||||
<span class="color-preview" :style="{ background: colorForm.hexValue, display:'inline-block', width:16, height:16, borderRadius:2, verticalAlign:'middle' }" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标准色号" prop="standard">
|
||||
<el-input v-model="colorForm.standard" placeholder="如 RAL 5005" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标准类型" prop="standardLabel">
|
||||
<el-select v-model="colorForm.standardLabel" placeholder="请选择" style="width:100%">
|
||||
<el-option label="RAL" value="RAL" />
|
||||
<el-option label="Pantone" value="Pantone" />
|
||||
<el-option label="国标" value="GB" />
|
||||
<el-option label="其他" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-select v-model="colorForm.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-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途说明" prop="usageDesc">
|
||||
<el-input v-model="colorForm.usageDesc" placeholder="用途说明" />
|
||||
</el-form-item>
|
||||
<el-form-item label="详细描述" prop="description">
|
||||
<el-input v-model="colorForm.description" type="textarea" :rows="2" placeholder="详细描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序号" prop="sortOrder">
|
||||
<el-input-number v-model="colorForm.sortOrder" :min="0" size="small" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="colorDialogVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="colorSubmitting" @click="handleColorSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTechReviewAll, getTechReview, addTechReview, updateTechReview, delTechReview } from '@/api/rm/techReview'
|
||||
import { listColorCardAll, addColorCard, updateColorCard, delColorCard } from '@/api/rm/colorCard'
|
||||
import { listProject } from '@/api/rm/project'
|
||||
|
||||
const TAB_TYPES = { mechanical: 'mechanical', electrical: 'electrical', hydraulic: 'hydraulic', fluid: 'fluid', energy: 'energy' }
|
||||
const TAB_LABELS = { mechanical: '机械审查', electrical: '电气审查', hydraulic: '液压审查', fluid: '流体审查', energy: '能源介质审查' }
|
||||
|
||||
export default {
|
||||
name: 'RmTechReview',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'mechanical',
|
||||
loading: false,
|
||||
reviewList: [],
|
||||
currentProjectId: null,
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
submitting: false,
|
||||
form: {},
|
||||
rules: { itemName: [{ required: true, message: '请填写审查项目名称', trigger: 'blur' }] },
|
||||
colorCards: [],
|
||||
colorDialogVisible: false,
|
||||
colorDialogTitle: '',
|
||||
colorSubmitting: false,
|
||||
colorForm: {},
|
||||
colorRules: { colorName: [{ required: true, message: '请填写色名', trigger: 'blur' }] }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colorGroups() {
|
||||
const groups = {}
|
||||
this.colorCards.forEach(c => {
|
||||
const key = c.category || '未分类'
|
||||
if (!groups[key]) groups[key] = { category: c.category, items: [] }
|
||||
groups[key].items.push(c)
|
||||
})
|
||||
return Object.values(groups)
|
||||
}
|
||||
},
|
||||
created() { this.loadCurrentProject() },
|
||||
methods: {
|
||||
tabLabel(key) { return TAB_LABELS[key] || key },
|
||||
loadCurrentProject() {
|
||||
listProject({ pageNum: 1, pageSize: 1 }).then(res => {
|
||||
const rows = res.rows || []
|
||||
if (rows.length > 0) {
|
||||
this.currentProjectId = rows[0].projectId
|
||||
this.loadReviews()
|
||||
this.loadColors()
|
||||
}
|
||||
})
|
||||
},
|
||||
loadReviews() {
|
||||
if (!this.currentProjectId) return
|
||||
this.loading = true
|
||||
const type = TAB_TYPES[this.activeTab]
|
||||
listTechReviewAll({ projectId: this.currentProjectId, reviewType: type }).then(res => {
|
||||
this.reviewList = res.data || []
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
loadColors() {
|
||||
if (!this.currentProjectId) return
|
||||
listColorCardAll({ projectId: this.currentProjectId }).then(res => {
|
||||
this.colorCards = res.data || []
|
||||
})
|
||||
},
|
||||
handleTabClick() {
|
||||
if (this.activeTab === 'color') {
|
||||
this.loadColors()
|
||||
} else {
|
||||
this.loadReviews()
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增' + this.tabLabel(this.activeTab) + '项'
|
||||
if (!this.currentProjectId) { this.$message.warning('请先在项目总览中创建项目'); return }
|
||||
this.form = { projectId: this.currentProjectId, reviewType: TAB_TYPES[this.activeTab], itemName: '', reviewer: '', reviewDate: '', conclusion: 'pending', reviewOpinion: '', thinking: '', remark: '' }
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑审查项'
|
||||
getTechReview(row.reviewItemId).then(res => {
|
||||
this.form = res.data
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.formRef?.clearValidate() })
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确认删除 "${row.itemName}"?`, '提示', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {
|
||||
delTechReview(row.reviewItemId).then(() => { this.$message.success('删除成功'); this.loadReviews() })
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitting = true
|
||||
const api = this.form.reviewItemId ? updateTechReview : addTechReview
|
||||
api(this.form).then(() => {
|
||||
this.$message.success(this.form.reviewItemId ? '更新成功' : '新增成功')
|
||||
this.dialogVisible = false
|
||||
this.loadReviews()
|
||||
}).finally(() => { this.submitting = false })
|
||||
})
|
||||
},
|
||||
handleAddColor() {
|
||||
this.colorDialogTitle = '新增色卡'
|
||||
this.colorForm = { projectId: this.currentProjectId, colorName: '', hexValue: '#C8CBCE', standard: '', standardLabel: 'RAL', category: '机械-被动', usageDesc: '', description: '', sortOrder: 0 }
|
||||
this.colorDialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.colorFormRef?.clearValidate() })
|
||||
},
|
||||
handleEditColor(row) {
|
||||
this.colorDialogTitle = '编辑色卡'
|
||||
this.colorForm = { ...row }
|
||||
this.colorDialogVisible = true
|
||||
this.$nextTick(() => { this.$refs.colorFormRef?.clearValidate() })
|
||||
},
|
||||
handleDeleteColor(row) {
|
||||
this.$confirm(`确认删除色卡 "${row.colorName}"?`, '提示', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }).then(() => {
|
||||
delColorCard(row.colorCardId).then(() => { this.$message.success('删除成功'); this.loadColors() })
|
||||
})
|
||||
},
|
||||
handleColorSubmit() {
|
||||
this.$refs.colorFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.colorSubmitting = true
|
||||
const api = this.colorForm.colorCardId ? updateColorCard : addColorCard
|
||||
api(this.colorForm).then(() => {
|
||||
this.$message.success(this.colorForm.colorCardId ? '更新成功' : '新增成功')
|
||||
this.colorDialogVisible = false
|
||||
this.loadColors()
|
||||
}).finally(() => { this.colorSubmitting = false })
|
||||
})
|
||||
},
|
||||
conclusionTag(s) { return { pending: 'info', approved: 'success', rectify: 'warning' }[s] || 'info' },
|
||||
conclusionLabel(s) { return { pending: '待审查', approved: '通过', rectify: '需整改' }[s] || s }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rm-container { padding: 8px; }
|
||||
.rm-panel { background: #fff; border-radius: 4px; border: 1px solid #d0d7de; }
|
||||
.rm-panel-header { padding: 8px 12px; font-size: 14px; font-weight: 600; border-bottom: 1px solid #d0d7de; display: flex; align-items: center; justify-content: space-between; }
|
||||
.rm-panel-body { padding: 8px 12px; }
|
||||
.dialog-footer { text-align: right; }
|
||||
.color-group { margin-bottom: 16px; }
|
||||
.color-group-title { font-size: 13px; font-weight: 600; padding: 6px 0; border-bottom: 1px solid #eee; margin-bottom: 8px; }
|
||||
.color-card-grid { display: flex; flex-wrap: wrap; gap: 10px; }
|
||||
.color-card-item { display: flex; align-items: center; gap: 10px; width: calc(33.33% - 10px); padding: 8px; border: 1px solid #eee; border-radius: 4px; background: #fafbfc; position: relative; min-width: 240px; }
|
||||
.color-swatch { width: 40px; height: 40px; border-radius: 4px; border: 1px solid #ddd; flex-shrink: 0; }
|
||||
.color-info { flex: 1; min-width: 0; }
|
||||
.color-name { font-size: 13px; font-weight: 600; }
|
||||
.color-hex { font-size: 11px; color: #888; font-family: monospace; }
|
||||
.color-standard { font-size: 11px; color: #888; }
|
||||
.color-usage { font-size: 11px; color: #aaa; }
|
||||
.color-actions { position: absolute; top: 4px; right: 4px; display: flex; gap: 2px; }
|
||||
.color-preview { border: 1px solid #ddd; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user