Files
double-rack/ruoyi-ui/src/views/mill/process.vue
砂糖 e2692c68f2 feat: 新增轧线系统日志、停机和实绩功能模块
- 新增日志、停机和实绩三个功能模块的API接口和页面
- 在计划页面添加完成按钮及相关逻辑
- 更新工艺方案页面,修复ID字段命名问题
- 调整导航栏样式和图标颜色
- 修改代理配置端口为8090
- 更新favicon图标
- 添加package-lock.json文件
2026-05-06 15:13:38 +08:00

433 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="process-page">
<!-- 左侧方案列表 -->
<div class="left-panel">
<div class="panel-header">
<span>工艺方案</span>
<el-button type="primary" size="mini" icon="el-icon-plus" @click="handleAdd">新增</el-button>
</div>
<!-- 搜索 -->
<div class="search-bar">
<el-input v-model="searchKey" placeholder="方案号/合金号" size="mini" clearable
prefix-icon="el-icon-search" @input="loadList" />
</div>
<!-- 列表 -->
<div class="recipe-list">
<div v-for="r in recipeList" :key="r.recipeId"
:class="['recipe-item', { active: selectedId === r.recipeId }]"
@click="handleSelect(r)">
<div class="recipe-item__no">{{ r.recipeNo }}</div>
<div class="recipe-item__info">
<span>{{ r.alloyNo }}</span>
<span>{{ r.inThick }} {{ r.outThick }} mm</span>
<span>{{ r.passCount }} 道次</span>
</div>
</div>
<div v-if="recipeList.length === 0" class="empty-tip">暂无方案</div>
</div>
</div>
<!-- 右侧方案详情 -->
<div class="right-panel">
<div v-if="!form.recipeId && !isNew" class="no-select">
<i class="el-icon-document"></i>
<p>请在左侧选择工艺方案</p>
</div>
<template v-else>
<!-- 顶部操作栏 -->
<div class="detail-header">
<span>{{ isNew ? '新建工艺方案' : form.recipeNo }}</span>
<div class="btn-group">
<el-button size="mini" type="primary" icon="el-icon-check" @click="handleSave">保存</el-button>
<el-button size="mini" icon="el-icon-refresh" @click="handleReset">重置</el-button>
<el-button v-if="!isNew" size="mini" type="danger" icon="el-icon-delete" @click="handleDelete">删除</el-button>
</div>
</div>
<!-- 方案基本信息 -->
<el-form :model="form" :rules="rules" ref="formRef" size="mini" label-width="88px" class="recipe-form">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="方案记录号" prop="recipeNo">
<el-input v-model="form.recipeNo" :disabled="!isNew" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="合金号" prop="alloyNo">
<el-input v-model="form.alloyNo" />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="原料厚度" prop="inThick">
<el-input v-model="form.inThick"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="成品厚度" prop="outThick">
<el-input v-model="form.outThick"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="成品宽度" prop="outWidth">
<el-input v-model="form.outWidth"><template slot="append">mm</template></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 道次详情 -->
<div class="pass-section">
<div class="pass-header">
<span>道次参数</span>
<div class="btn-group">
<el-button size="mini" icon="el-icon-plus" @click="addPass">增加道次</el-button>
<el-button size="mini" icon="el-icon-minus" @click="removeLastPass" :disabled="passList.length===0">删除末道</el-button>
</div>
</div>
<el-table :data="passList" border size="mini" class="pass-table"
:row-class-name="passRowClass" height="calc(100vh - 340px)">
<el-table-column label="道次" prop="passNo" width="50" align="center" fixed>
<template slot-scope="{ row }">
<b>{{ row.passNo }}</b>
</template>
</el-table-column>
<el-table-column label="入口厚度(mm)" width="100" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.inThick" size="mini" @blur="calcPass(row)" />
</template>
</el-table-column>
<el-table-column label="出口厚度(mm)" width="100" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.outThick" size="mini" @blur="calcPass(row)" />
</template>
</el-table-column>
<el-table-column label="宽度(mm)" width="90" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.width" size="mini" />
</template>
</el-table-column>
<el-table-column label="轧制力(kN)" width="90" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.rollForce" size="mini" />
</template>
</el-table-column>
<el-table-column label="入口张力(kN)" width="95" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.inTension" size="mini" />
</template>
</el-table-column>
<el-table-column label="出口张力(kN)" width="95" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.outTension" size="mini" />
</template>
</el-table-column>
<el-table-column label="最高速度(m/min)" width="110" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.maxSpeed" size="mini" />
</template>
</el-table-column>
<el-table-column label="入口单位张力" width="100" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.inUnitTension" size="mini" />
</template>
</el-table-column>
<el-table-column label="出口单位张力" width="100" align="right">
<template slot-scope="{ row }">
<el-input v-model="row.outUnitTension" size="mini" />
</template>
</el-table-column>
<el-table-column label="压下量(mm)" prop="reduction" width="90" align="right">
<template slot-scope="{ row }">
<span class="calc-val">{{ row.reduction }}</span>
</template>
</el-table-column>
<el-table-column label="总压下量(mm)" prop="totalReduction" width="100" align="right">
<template slot-scope="{ row }">
<span class="calc-val">{{ row.totalReduction }}</span>
</template>
</el-table-column>
<el-table-column label="备注" min-width="100">
<template slot-scope="{ row }">
<el-input v-model="row.remark" size="mini" />
</template>
</el-table-column>
</el-table>
</div>
</template>
</div>
</div>
</template>
<script>
import { listRecipe, getRecipeDetail, addRecipe, updateRecipe, delRecipe } from '@/api/mill/recipe'
const emptyPass = (no) => ({
passNo: no, inThick: '', outThick: '', width: '',
rollForce: '', inTension: '', outTension: '',
maxSpeed: '', inUnitTension: '', outUnitTension: '',
reduction: '', totalReduction: '', remark: ''
})
const emptyForm = () => ({
id: null, recipeNo: '', alloyNo: '', passCount: 0,
inThick: '', outThick: '', outWidth: '', status: '0', remark: ''
})
export default {
name: 'MillProcess',
data() {
return {
searchKey: '',
recipeList: [],
selectedId: null,
isNew: false,
form: emptyForm(),
passList: [],
rules: {
recipeNo: [{ required: true, message: '请输入方案记录号', trigger: 'blur' }],
alloyNo: [{ required: true, message: '请输入合金号', trigger: 'blur' }],
inThick: [{ required: true, message: '请输入原料厚度', trigger: 'blur' }],
outThick: [{ required: true, message: '请输入成品厚度', trigger: 'blur' }],
}
}
},
mounted() { this.loadList() },
methods: {
loadList() {
listRecipe({ recipeNo: this.searchKey, alloyNo: this.searchKey }).then(res => {
this.recipeList = res.rows || []
})
},
handleSelect(r) {
this.selectedId = r.recipeId
this.isNew = false
getRecipeDetail(r.recipeId).then(res => {
this.form = { ...res.data }
this.passList = res.data.passList || []
})
},
handleAdd() {
this.isNew = true
this.selectedId = null
this.form = emptyForm()
this.passList = []
},
handleSave() {
this.$refs.formRef.validate(valid => {
if (!valid) return
this.form.passList = this.passList
this.form.passCount = this.passList.length
const api = this.isNew ? addRecipe : updateRecipe
api(this.form).then(() => {
this.$message.success('保存成功')
this.loadList()
this.isNew = false
})
})
},
handleReset() {
if (this.isNew) { this.form = emptyForm(); this.passList = [] }
else this.handleSelect({ id: this.selectedId })
},
handleDelete() {
this.$confirm('确定删除该方案?', '提示', { type: 'warning' }).then(() => {
delRecipe([this.form.recipeId]).then(() => {
this.$message.success('删除成功')
this.form = emptyForm(); this.passList = []
this.selectedId = null; this.isNew = false
this.loadList()
})
})
},
addPass() {
this.passList.push(emptyPass(this.passList.length + 1))
},
removeLastPass() {
if (this.passList.length) this.passList.pop()
},
calcPass(row) {
const inT = parseFloat(row.inThick) || 0
const outT = parseFloat(row.outThick) || 0
row.reduction = outT > 0 ? (inT - outT).toFixed(3) : ''
// 累计总压下量
let total = 0
const base = parseFloat(this.form.inThick) || 0
for (const p of this.passList) {
const pOut = parseFloat(p.outThick) || 0
if (base > 0 && pOut > 0) {
total = parseFloat((base - pOut).toFixed(3))
p.totalReduction = total
}
}
},
passRowClass({ rowIndex }) {
return rowIndex % 2 === 0 ? '' : 'alt-row'
}
}
}
</script>
<style scoped lang="scss">
.process-page {
display: flex;
height: calc(100vh - 84px);
gap: 0;
background: #f0f2f5;
padding: 10px 12px;
box-sizing: border-box;
}
/* ── 左侧面板 ── */
.left-panel {
width: 220px;
flex-shrink: 0;
background: #fff;
border: 1px solid #dde1e6;
border-radius: 3px;
display: flex;
flex-direction: column;
margin-right: 10px;
overflow: hidden;
}
.panel-header {
background: #1c2b3a;
color: #ecf0f1;
padding: 7px 10px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
}
.search-bar {
padding: 6px 8px;
border-bottom: 1px solid #e4e7ed;
flex-shrink: 0;
}
.recipe-list {
flex: 1;
overflow-y: auto;
}
.recipe-item {
padding: 7px 10px;
border-bottom: 1px solid #f0f2f5;
cursor: pointer;
transition: background .15s;
&:hover { background: #f7f9fc; }
&.active { background: #e8f0fb; border-left: 3px solid #1d4e89; }
&__no {
font-size: 12px;
font-weight: 700;
color: #1c2b3a;
}
&__info {
display: flex;
gap: 8px;
font-size: 11px;
color: #7f8c8d;
margin-top: 2px;
}
}
.empty-tip {
text-align: center;
color: #bdc3c7;
padding: 24px 0;
font-size: 12px;
}
/* ── 右侧面板 ── */
.right-panel {
flex: 1;
background: #fff;
border: 1px solid #dde1e6;
border-radius: 3px;
display: flex;
flex-direction: column;
overflow: hidden;
}
.no-select {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #bdc3c7;
font-size: 13px;
i { font-size: 48px; margin-bottom: 10px; }
}
.detail-header {
background: #1c2b3a;
color: #ecf0f1;
padding: 7px 12px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
}
.btn-group { display: flex; gap: 6px; }
.recipe-form {
padding: 10px 12px 4px;
flex-shrink: 0;
border-bottom: 1px solid #e4e7ed;
}
/* ── 道次区域 ── */
.pass-section {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.pass-header {
padding: 6px 12px;
background: #f7f9fc;
border-bottom: 1px solid #e4e7ed;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
font-weight: 700;
color: #1c2b3a;
flex-shrink: 0;
}
.pass-table {
flex: 1;
::v-deep .el-table__row.alt-row td {
background: #f7f9fc !important;
}
::v-deep .el-input__inner {
text-align: right;
padding: 0 4px !important;
}
}
.calc-val {
font-family: 'Courier New', monospace;
font-weight: 600;
color: #1d4e89;
}
</style>