Files
klp-oa/klp-ui/src/views/mes/roll/grind/index.vue

1051 lines
41 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="app-container grind-page">
<div class="grind-layout">
<!-- 左侧产线 Tab -->
<div class="line-tabs">
<div
:class="['lt-item', filterLineId === null ? 'lt-item--active' : '']"
@click="handleLineTab(null)"
>全部</div>
<div
v-for="l in sortedProductionLines"
:key="l.lineId"
:class="['lt-item', filterLineId === l.lineId ? 'lt-item--active' : '']"
@click="handleLineTab(l.lineId)"
>{{ l.lineName }}</div>
</div>
<!-- 右侧主体 -->
<div class="grind-main">
<!-- 顶部检索栏 -->
<div class="search-bar">
<el-input
v-model="filterNo"
size="small"
placeholder="辊号检索"
prefix-icon="el-icon-search"
clearable
style="width:180px"
@input="filterRolls"
/>
<el-radio-group v-model="filterType" size="small" @change="filterRolls">
<el-radio-button label="">全部类型</el-radio-button>
<el-radio-button label="WR">WR</el-radio-button>
<el-radio-button label="BR">BR</el-radio-button>
<el-radio-button label="CR">CR</el-radio-button>
</el-radio-group>
<el-radio-group v-model="filterManufacturer" size="small" @change="filterRolls">
<el-radio-button label="">全部厂家</el-radio-button>
<el-radio-button v-for="mfr in manufacturerOptions" :key="mfr" :label="mfr">{{ mfr }}</el-radio-button>
</el-radio-group>
<el-radio-group v-model="filterFrame" size="small" @change="filterRolls">
<el-radio-button label="">全部机架</el-radio-button>
<el-radio-button v-for="f in frameOptions" :key="f" :label="f">{{ f }}</el-radio-button>
</el-radio-group>
</div>
<!-- 下方左右布局 -->
<div class="content-area">
<!-- 左侧轧辊列表 -->
<div class="roll-list-panel">
<el-card shadow="never" class="panel-card">
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-s-order" /> 轧辊列表</span>
<span class="card-count">{{ filteredRolls.length }}</span>
<el-button size="mini" type="text" icon="el-icon-plus" style="margin-left:auto" @click="handleAddRoll">新增轧辊</el-button>
</div>
<div v-loading="rollLoading" class="roll-list">
<div
v-for="r in filteredRolls"
:key="r.rollId"
:class="['roll-item', selectedRollId === r.rollId ? 'roll-item--active' : '']"
@click="selectRoll(r)"
>
<div class="ri-no">{{ r.rollNo }}</div>
<div class="ri-meta">
<el-tag size="mini" :type="r.rollType === 'WR' ? 'primary' : 'warning'">{{ r.rollType }}</el-tag>
<span :class="['ri-status', 'st-' + r.status]">{{ statusLabel(r.status) }}</span>
<span class="ri-dia">φ{{ r.currentDia != null ? r.currentDia : r.initialDia }}</span>
</div>
<div v-if="r.manufacturer" class="ri-manufacturer">{{ r.manufacturer }}</div>
<div v-if="r.frame" class="ri-manufacturer">{{ r.frame }}</div>
<div v-if="filterLineId === null && r.lineName" class="ri-line">{{ r.lineName }}</div>
</div>
<div v-if="!rollLoading && filteredRolls.length === 0" class="roll-empty">暂无数据</div>
</div>
</el-card>
</div>
<!-- 右侧明细 & 磨削台账 -->
<div v-loading="grindLoading" class="detail-panel">
<template v-if="!selectedRoll">
<div class="grind-empty"><i class="el-icon-arrow-left" /> 请从左侧选择一个轧辊</div>
</template>
<template v-else>
<!-- 轧辊基本信息 -->
<el-card shadow="never" class="detail-card" style="margin-bottom:10px">
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-info" /> 轧辊明细</span>
<el-button size="mini" type="text" icon="el-icon-edit" style="margin-left:auto" @click="handleEditRoll(selectedRoll)">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#c5221f" @click="handleDelRoll(selectedRoll)">删除</el-button>
</div>
<div class="roll-header-grid">
<div class="rh-item"><span class="rh-k">轧辊编号</span><span class="rh-v bold">{{ selectedRoll.rollNo }}</span></div>
<div class="rh-item"><span class="rh-k">辊型</span><span class="rh-v">{{ { WR: '工作辊', BR: '支撑辊', CR: '中间辊' }[selectedRoll.rollType] || '—' }}</span></div>
<div class="rh-item"><span class="rh-k">机架</span><span class="rh-v">{{ selectedRoll.frame || '—' }}</span></div>
<div class="rh-item"><span class="rh-k">厂家</span><span class="rh-v">{{ selectedRoll.manufacturer || '—' }}</span></div>
<div class="rh-item"><span class="rh-k">材质</span><span class="rh-v">{{ selectedRoll.material || '—' }}</span></div>
<div class="rh-item"><span class="rh-k">初始辊径</span><span class="rh-v">{{ selectedRoll.initialDia != null ? selectedRoll.initialDia + ' mm' : '—' }}</span></div>
<div class="rh-item"><span class="rh-k">当前辊径</span><span class="rh-v bold accent">{{ effectiveCurrentDia != null ? effectiveCurrentDia + ' mm' : '—' }}</span></div>
<div class="rh-item"><span class="rh-k">最小辊径</span><span class="rh-v">{{ selectedRoll.minDia != null ? selectedRoll.minDia + ' mm' : '—' }}</span></div>
<div class="rh-item"><span class="rh-k">磨削次数</span><span class="rh-v">{{ tableData.length ? tableData.length + ' 次' : '0 次' }}</span></div>
<div class="rh-item"><span class="rh-k">粗糙度</span><span class="rh-v">{{ selectedRoll.roughness != null ? selectedRoll.roughness + ' μm' : '—' }}</span></div>
<div class="rh-item"><span class="rh-k">凸度</span><span class="rh-v">{{ selectedRoll.crown != null ? selectedRoll.crown + ' mm' : '—' }}</span></div>
<div class="rh-item"><span class="rh-k">状态</span>
<span :class="['rh-v', 'st-' + selectedRoll.status]">{{ statusLabel(selectedRoll.status) }}</span>
</div>
<div class="rh-item"><span class="rh-k">制造日期</span><span class="rh-v">{{ selectedRoll.manufactureDate || '—' }}</span></div>
<div class="rh-item"><span class="rh-k">备注</span><span class="rh-v">{{ selectedRoll.remark || '—' }}</span></div>
</div>
</el-card>
<!-- 磨削台账 -->
<el-card shadow="never" class="detail-card" body-style="padding:10px;overflow:auto">
<div slot="header" class="card-header">
<span class="card-title"><i class="el-icon-document" /> 磨削台账</span>
<span style="margin-left:auto;display:flex;gap:8px">
<el-button v-hasPermi="['system:dict:list']" size="mini" type="text" icon="el-icon-setting" @click="openOperatorDict">操作人维护</el-button>
<el-button
type="primary"
size="mini"
icon="el-icon-plus"
:disabled="!!editRow"
@click="startAdd"
>新增磨削记录</el-button>
</span>
</div>
<el-table
:data="tableData"
size="small"
border
style="width:100%"
:row-class-name="rowClassName"
>
<el-table-column label="序号" type="index" width="46" align="center" />
<el-table-column label="磨削时间" align="center" width="200">
<template slot-scope="{row}">
<el-date-picker
v-if="isEditing(row)"
v-model="editRow.grindTime"
type="datetime"
size="mini"
value-format="yyyy-MM-dd HH:mm:ss"
style="width:182px"
placeholder="请选择"
/>
<span v-else>{{ row.grindTime }}</span>
</template>
</el-table-column>
<el-table-column label="班组" align="center" width="100">
<template slot-scope="{row}">
<el-select
v-if="isEditing(row)"
v-model="editRow.team"
size="mini"
placeholder="班组"
clearable
filterable
allow-create
>
<el-option label="甲班" value="甲班" />
<el-option label="乙班" value="乙班" />
</el-select>
<span v-else>{{ row.team || '' }}</span>
</template>
</el-table-column>
<el-table-column label="磨前径(mm)" align="center" width="100">
<template slot-scope="{row}">
<el-input
v-if="isEditing(row)"
v-model="editRow.diaBefore"
size="mini"
style="width:80px"
/>
<span v-else>{{ row.diaBefore }}</span>
</template>
</el-table-column>
<el-table-column label="磨后径(mm)" align="center" width="100">
<template slot-scope="{row}">
<el-input
v-if="isEditing(row)"
v-model="editRow.diaAfter"
size="mini"
style="width:80px"
/>
<span v-else>{{ row.diaAfter }}</span>
</template>
</el-table-column>
<el-table-column label="磨削量(mm)" align="center" width="88">
<template slot-scope="{row}">
<span v-if="isEditing(row)" class="computed-val">
{{ grindAmountOf(editRow) }}
</span>
<span v-else>{{ row.grindAmount }}</span>
</template>
</el-table-column>
<el-table-column label="辊型" align="center" width="100">
<template slot-scope="{row}">
<el-select
v-if="isEditing(row)"
v-model="editRow.rollShape"
size="mini"
style="width:84px"
>
<el-option label="平" value="平" />
<el-option label="凸" value="凸" />
<el-option label="凹" value="凹" />
</el-select>
<span v-else>{{ row.rollShape || '' }}</span>
</template>
</el-table-column>
<el-table-column label="探伤结果" align="center" width="110">
<template slot-scope="{row}">
<el-select
v-if="isEditing(row)"
v-model="editRow.flawResult"
size="mini"
style="width:94px"
>
<el-option label="合格" value="合格" />
<el-option label="不合格" value="不合格" />
</el-select>
<el-tag v-else size="mini" :type="row.flawResult === '合格' ? 'success' : 'danger'">
{{ row.flawResult || '—' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="硬度" align="center" width="80">
<template slot-scope="{row}">
<el-input
v-if="isEditing(row)"
v-model.number="editRow.hardness"
size="mini"
style="width:64px"
/>
<span v-else>{{ row.hardness || '' }}</span>
</template>
</el-table-column>
<el-table-column label="操作者" align="center" width="140">
<template slot-scope="{row}">
<el-select
v-if="isEditing(row)"
v-model="editRow.operator"
size="mini"
placeholder="选填"
clearable
filterable
allow-create
>
<el-option
v-for="item in dict.type.mes_roll_operator"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<span v-else class="remark-text">{{ row.operator || '' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="left" width="120">
<template slot-scope="{row}">
<el-input
v-if="isEditing(row)"
v-model="editRow.remark"
size="mini"
placeholder="选填"
/>
<span v-else class="remark-text">{{ row.remark || '' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100" fixed="right">
<template slot-scope="{row}">
<template v-if="isEditing(row)">
<el-button
size="mini"
type="text"
icon="el-icon-check"
:loading="grindSaving"
@click="saveRow"
>保存</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-close"
style="color:#909399"
@click="cancelEdit"
>取消</el-button>
</template>
<template v-else>
<el-button
size="mini"
type="text"
icon="el-icon-edit"
:disabled="!!editRow"
@click="startEdit(row)"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
style="color:#c5221f"
:disabled="!!editRow"
@click="handleDelete(row)"
>删除</el-button>
</template>
</template>
</el-table-column>
</el-table>
<!-- 月度汇总 -->
<div v-if="grindList.length > 0" class="monthly-wrap">
<div class="monthly-title">
<span>{{ currentYear }} 年月度汇总</span>
<el-button-group size="mini" style="margin-left:8px">
<el-button icon="el-icon-arrow-left" @click="changeYear(-1)" />
<el-button icon="el-icon-arrow-right" @click="changeYear(1)" />
</el-button-group>
</div>
<el-table :data="monthlyList" size="mini" border style="width:100%;margin-top:8px">
<el-table-column label="月份" prop="month" align="center" width="90" />
<el-table-column label="磨削次数" prop="grindCount" align="center" width="90" />
<el-table-column label="累计磨削量(mm)" prop="totalGrindAmount" align="center" min-width="120" />
</el-table>
</div>
</el-card>
</template>
</div>
</div>
</div>
</div>
<!-- 操作人字典维护对话框 -->
<el-dialog title="操作人字典维护" :visible.sync="operatorDictOpen" width="650px" append-to-body :close-on-click-modal="false" @closed="operatorDictList = []">
<div style="margin-bottom:10px">
<el-button type="primary" size="mini" icon="el-icon-plus" @click="handleAddOperator">新增操作人</el-button>
</div>
<el-table :data="operatorDictList" v-loading="operatorDictLoading" size="small" border style="width:100%">
<el-table-column label="姓名" prop="dictLabel" align="center" />
<el-table-column label="状态" align="center" width="100">
<template slot-scope="{row}">
<el-tag :type="row.status === '0' ? 'success' : 'danger'" size="mini">
{{ row.status === '0' ? '在职' : '离职' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="140">
<template slot-scope="{row}">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleEditOperator(row)">编辑</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#c5221f" @click="handleDeleteOperator(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<!-- 操作人新增/修改对话框 -->
<el-dialog :title="operatorFormTitle" :visible.sync="operatorFormOpen" width="400px" append-to-body :close-on-click-modal="false" @close="resetOperatorForm">
<el-form ref="operatorForm" :model="operatorForm" :rules="operatorFormRules" label-width="80px" size="small">
<el-form-item label="姓名" prop="dictLabel">
<el-input v-model="operatorForm.dictLabel" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="operatorForm.status">
<el-radio label="0">在职</el-radio>
<el-radio label="1">离职</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" size="small" @click="submitOperatorForm"> </el-button>
<el-button size="small" @click="operatorFormOpen = false"> </el-button>
</div>
</el-dialog>
<!-- 轧辊新增/修改对话框 -->
<el-dialog
:title="rollFormTitle"
:visible.sync="rollFormOpen"
width="600px"
append-to-body
:close-on-click-modal="false"
@close="resetRollForm"
>
<el-form ref="rollForm" :model="rollForm" :rules="rollFormRules" label-width="60px" size="small">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="所属产线" prop="lineId">
<el-select v-model="rollForm.lineId" placeholder="请选择产线" style="width:100%" clearable>
<el-option v-for="l in productionLines" :key="l.lineId" :label="l.lineName" :value="l.lineId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="辊编号" prop="rollNo">
<el-input v-model="rollForm.rollNo" placeholder="请输入轧辊编号" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="辊型" prop="rollType">
<el-select v-model="rollForm.rollType" placeholder="请选择辊型" style="width:100%">
<el-option label="工作辊 WR" value="WR" />
<el-option label="支撑辊 BR" value="BR" />
<el-option label="中间辊 CR" value="CR" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="厂家" prop="manufacturer">
<el-input v-model="rollForm.manufacturer" placeholder="请输入厂家" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="初始直径" prop="initialDia">
<el-input v-model="rollForm.initialDia" type="number" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="当前直径" prop="currentDia">
<el-input v-model="rollForm.currentDia" type="number" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="最小直径" prop="minDia">
<el-input v-model="rollForm.minDia" type="number" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="材质" prop="material">
<el-input v-model="rollForm.material" placeholder="材质" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="粗糙度" prop="roughness">
<el-input v-model="rollForm.roughness" type="number" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="凸度" prop="crown">
<el-input v-model="rollForm.crown" type="number" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="制造日期" prop="manufactureDate">
<el-date-picker
v-model="rollForm.manufactureDate"
type="date"
value-format="yyyy-MM-dd"
placeholder="请选择制造日期"
style="width:100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="机架" prop="frame">
<el-select v-model="rollForm.frame" placeholder="请输入机架" style="width:100%" clearable filterable allow-create>
<el-option v-for="f in frameOptions" :key="f" :label="f" :value="f" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="rollForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" size="small" @click="submitRollForm"> </el-button>
<el-button size="small" @click="rollFormOpen = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listRollInfo, getRollInfo, addRollInfo, updateRollInfo, delRollInfo } from '@/api/mes/roll/rollInfo'
import { listRollGrind, addRollGrind, updateRollGrind, delRollGrind, getMonthlyStats } from '@/api/mes/roll/rollGrind'
import { listProductionLine } from '@/api/wms/productionLine'
import { listData, addData, updateData, delData } from '@/api/system/dict/data'
import rollLineMixin from '../rollLineMixin'
export default {
name: 'GrindRoom',
mixins: [rollLineMixin],
dicts: ['mes_roll_operator'],
data() {
return {
productionLines: [],
filterLineId: null,
lineTabOrder: [],
rollLoading: false,
allRolls: [],
filteredRolls: [],
filterNo: '',
filterType: '',
filterManufacturer: '',
filterFrame: '',
selectedRollId: null,
selectedRoll: null,
grindLoading: false,
grindList: [],
currentYear: new Date().getFullYear(),
monthlyList: [],
editRow: null,
grindSaving: false,
rollFormOpen: false,
rollFormTitle: '',
rollForm: {},
rollFormRules: {
rollNo: [{ required: true, message: '轧辊编号不能为空', trigger: 'blur' }],
rollType: [{ required: true, message: '请选择辊型', trigger: 'change' }]
},
operatorDictOpen: false,
operatorDictLoading: false,
operatorDictList: [],
operatorFormOpen: false,
operatorFormTitle: '',
operatorForm: {},
operatorFormRules: {
dictLabel: [{ required: true, message: '姓名不能为空', trigger: 'blur' }]
}
}
},
computed: {
currentUserName() {
return this.$store.state.user.name || this.$store.getters.name || ''
},
sortedProductionLines() {
const order = this.lineTabOrder
return [...this.productionLines].sort((a, b) => {
const ai = order.indexOf(a.lineId)
const bi = order.indexOf(b.lineId)
if (ai === -1 && bi === -1) return 0
if (ai === -1) return 1
if (bi === -1) return -1
return ai - bi
})
},
manufacturerOptions() {
const set = new Set()
this.allRolls.forEach(r => {
if (r.manufacturer) set.add(r.manufacturer)
})
return [...set].sort()
},
frameOptions() {
const set = new Set()
this.allRolls.forEach(r => {
if (r.frame) set.add(r.frame)
})
return [...set].sort()
},
effectiveCurrentDia() {
if (this.grindList.length > 0) {
const latest = [...this.grindList].sort((a, b) => {
const ta = a.grindTime ? new Date(a.grindTime).getTime() : 0
const tb = b.grindTime ? new Date(b.grindTime).getTime() : 0
return tb - ta
})[0]
if (latest && latest.diaAfter != null) return parseFloat(latest.diaAfter)
}
return null
},
tableData() {
if (this.editRow && this.editRow.__isNew) {
return [this.editRow, ...this.grindList]
}
return this.grindList
}
},
methods: {
onLineResolved() {
this.filterLineId = this.lineId
const uid = this.$store.state.user?.userId || 0
try { this.lineTabOrder = JSON.parse(localStorage.getItem(`grind_line_order_${uid}`) || '[]') } catch { /* ignore */ }
listProductionLine({ pageNum: 1, pageSize: 100 }).then(res => {
this.productionLines = res.rows || []
})
this.loadRolls()
},
loadRolls() {
this.rollLoading = true
listRollInfo({ pageNum: 1, pageSize: 500, lineId: this.filterLineId }).then(res => {
this.allRolls = res.rows || []
this.filterRolls()
}).finally(() => { this.rollLoading = false })
},
handleLineTab(lineId) {
if (this.filterLineId === lineId) return
this.filterLineId = lineId
if (lineId !== null) {
this.lineTabOrder = [lineId, ...this.lineTabOrder.filter(id => id !== lineId)]
const uid = this.$store.state.user?.userId || 0
localStorage.setItem(`grind_line_order_${uid}`, JSON.stringify(this.lineTabOrder))
}
if (this.editRow) this.cancelEdit()
this.selectedRollId = null
this.selectedRoll = null
this.grindList = []
this.loadRolls()
},
filterRolls() {
this.filteredRolls = this.allRolls.filter(r => {
const matchNo = !this.filterNo || r.rollNo.includes(this.filterNo)
const matchType = !this.filterType || r.rollType === this.filterType
const matchMfr = !this.filterManufacturer || r.manufacturer === this.filterManufacturer
const matchFrame = !this.filterFrame || r.frame === this.filterFrame
return matchNo && matchType && matchMfr && matchFrame
})
},
selectRoll(r) {
if (this.editRow) this.cancelEdit()
this.selectedRollId = r.rollId
getRollInfo(r.rollId).then(res => {
this.selectedRoll = res.data || r
const idx = this.allRolls.findIndex(x => x.rollId === r.rollId)
if (idx !== -1) this.$set(this.allRolls, idx, { ...this.allRolls[idx], ...res.data })
}).catch(() => { this.selectedRoll = r })
this.loadGrindList(r.rollId)
},
loadGrindList(rollId) {
this.grindLoading = true
listRollGrind(rollId).then(res => {
this.grindList = res.data || []
}).finally(() => {
this.grindLoading = false
this.loadMonthlyStats()
})
},
loadMonthlyStats() {
if (!this.selectedRollId) return
getMonthlyStats(this.selectedRollId, this.currentYear).then(res => {
this.monthlyList = (res.data || []).map(r => ({
month: r.month, grindCount: r.grindCount, totalGrindAmount: r.totalGrindAmount
}))
})
},
changeYear(delta) {
this.currentYear += delta
this.loadMonthlyStats()
},
isEditing(row) {
if (!this.editRow) return false
if (row.__isNew && this.editRow.__isNew) return true
return !!row.grindId && row.grindId === this.editRow.grindId
},
rowClassName({ row }) {
return this.isEditing(row) ? 'editing-row' : ''
},
startAdd() {
const now = new Date()
const pad = n => String(n).padStart(2, '0')
const grindTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
const lastHardness = this.grindList
.filter(r => r.hardness != null)
.sort((a, b) => new Date(b.grindTime) - new Date(a.grindTime))
.map(r => r.hardness)[0]
this.editRow = {
__isNew: true,
rollId: this.selectedRollId,
grindTime,
team: undefined,
diaBefore: this.effectiveCurrentDia != null ? this.effectiveCurrentDia : undefined,
diaAfter: undefined,
rollShape: '平',
flawResult: '合格',
hardness: lastHardness,
operator: this.currentUserName,
remark: undefined
}
},
startEdit(row) {
this.editRow = { ...row, operator: row.operator || this.currentUserName }
},
cancelEdit() {
this.editRow = null
},
saveRow() {
const r = this.editRow
if (!r.grindTime) { this.$modal.msgWarning('请填写磨削时间'); return }
if (r.diaBefore == null) { this.$modal.msgWarning('请填写磨前直径'); return }
if (r.diaAfter == null) { this.$modal.msgWarning('请填写磨后直径'); return }
this.grindSaving = true
const isNew = !!r.__isNew
const payload = { ...r }
delete payload.__isNew
const api = isNew ? addRollGrind : updateRollGrind
api(payload).then(() => {
this.$modal.msgSuccess(isNew ? '新增成功' : '修改成功')
this.editRow = null
this.loadGrindList(this.selectedRollId)
getRollInfo(this.selectedRollId).then(res => {
this.selectedRoll = res.data
const idx = this.allRolls.findIndex(x => x.rollId === this.selectedRollId)
if (idx !== -1) this.$set(this.allRolls, idx, { ...this.allRolls[idx], ...res.data })
})
}).finally(() => { this.grindSaving = false })
},
handleDelete(row) {
this.$modal.confirm('确认删除该磨削记录?').then(() => {
return delRollGrind(row.grindId)
}).then(() => {
this.$modal.msgSuccess('已删除')
this.loadGrindList(this.selectedRollId)
})
},
grindAmountOf(row) {
const b = parseFloat(row.diaBefore)
const a = parseFloat(row.diaAfter)
if (!isNaN(b) && !isNaN(a)) return (b - a).toFixed(2)
return '—'
},
statusLabel(s) {
return { Online: '在线', Standby: '备用', Offline: '离线', Scrapped: '报废' }[s] || s
},
// ── 轧辊 CRUD ──────────────────────────────────────
handleAddRoll() {
this.rollFormTitle = '新增轧辊'
this.rollForm = {
lineId: this.filterLineId || null,
rollNo: undefined,
rollType: this.filterType || undefined,
manufacturer: this.filterManufacturer || '',
frame: '',
initialDia: undefined,
currentDia: undefined,
minDia: undefined,
material: '',
roughness: undefined,
crown: undefined,
manufactureDate: '',
remark: '',
status: 'Offline'
}
this.rollFormOpen = true
},
handleEditRoll(row) {
getRollInfo(row.rollId).then(res => {
this.rollFormTitle = '修改轧辊'
const data = res.data || {}
this.rollForm = {
rollId: data.rollId,
lineId: data.lineId,
rollNo: data.rollNo,
rollType: data.rollType,
manufacturer: data.manufacturer || '',
frame: data.frame || '',
initialDia: data.initialDia,
currentDia: data.currentDia,
minDia: data.minDia,
material: data.material || '',
roughness: data.roughness,
crown: data.crown,
manufactureDate: data.manufactureDate || '',
remark: data.remark || '',
status: data.status
}
this.rollFormOpen = true
})
},
handleDelRoll(row) {
this.$modal.confirm(`确认删除轧辊【${row.rollNo}】?此操作不可恢复。`).then(() => {
return delRollInfo(row.rollId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
if (this.selectedRollId === row.rollId) {
this.selectedRollId = null
this.selectedRoll = null
this.grindList = []
}
this.loadRolls()
})
},
submitRollForm() {
this.$refs.rollForm.validate(valid => {
if (!valid) return
const form = { ...this.rollForm }
const numFields = ['initialDia', 'currentDia', 'minDia', 'roughness', 'crown']
numFields.forEach(k => {
if (form[k] != null) form[k] = Number(form[k])
})
const action = form.rollId ? updateRollInfo : addRollInfo
action(form).then(() => {
this.$modal.msgSuccess(form.rollId ? '修改成功' : '新增成功')
this.rollFormOpen = false
this.loadRolls()
if (this.selectedRollId === form.rollId) {
this.selectedRoll = { ...this.selectedRoll, ...form }
}
})
})
},
resetRollForm() {
this.rollForm = {}
this.$nextTick(() => { this.$refs.rollForm && this.$refs.rollForm.clearValidate() })
},
// ── 操作人字典维护 ──────────────────────────────
openOperatorDict() {
this.operatorDictOpen = true
this.loadOperatorDict()
},
loadOperatorDict() {
this.operatorDictLoading = true
listData({ dictType: 'mes_roll_operator', pageNum: 1, pageSize: 999 }).then(res => {
this.operatorDictList = res.rows || []
}).finally(() => { this.operatorDictLoading = false })
},
handleAddOperator() {
this.operatorForm = { dictType: 'mes_roll_operator', dictLabel: '', status: '0' }
this.operatorFormTitle = '新增操作人'
this.$nextTick(() => { this.operatorFormOpen = true })
},
handleEditOperator(row) {
this.operatorForm = { ...row }
this.operatorFormTitle = '修改操作人'
this.$nextTick(() => { this.operatorFormOpen = true })
},
handleDeleteOperator(row) {
this.$modal.confirm(`确认删除操作人【${row.dictLabel}】?`).then(() => {
return delData(row.dictCode)
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.$store.dispatch('dict/removeDict', 'mes_roll_operator')
this.loadOperatorDict()
})
},
submitOperatorForm() {
this.$refs.operatorForm.validate(valid => {
if (!valid) return
const form = { ...this.operatorForm, dictValue: this.operatorForm.dictLabel }
const api = form.dictCode ? updateData : addData
api(form).then(() => {
this.$modal.msgSuccess(form.dictCode ? '修改成功' : '新增成功')
this.operatorFormOpen = false
this.$store.dispatch('dict/removeDict', 'mes_roll_operator')
this.loadOperatorDict()
})
})
},
resetOperatorForm() {
this.operatorForm = {}
this.$nextTick(() => { this.$refs.operatorForm && this.$refs.operatorForm.clearValidate() })
}
}
}
</script>
<style scoped>
.grind-page { background: #f4f5f7; height: 100%; }
.grind-layout { display: flex; gap: 12px; height: 100%; }
/* ── 左侧产线 Tab ── */
.line-tabs {
width: 64px;
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 2px;
padding-top: 2px;
}
.lt-item {
padding: 10px 4px;
text-align: center;
font-size: 12px;
color: #5f6368;
background: #fff;
border: 1px solid #dcdee0;
border-radius: 4px;
cursor: pointer;
word-break: break-all;
line-height: 1.4;
transition: all .15s;
}
.lt-item:hover { background: #f0f6ff; color: #409eff; border-color: #c6d9f5; }
.lt-item--active {
background: #409eff;
color: #fff;
border-color: #409eff;
font-weight: 600;
}
/* ── 右侧主体 ── */
.grind-main {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
/* 顶部检索栏 */
.search-bar {
display: flex;
align-items: center;
gap: 20px;
padding: 10px 14px;
background: #fff;
border: 1px solid #dcdee0;
border-radius: 4px;
flex-shrink: 0;
}
/* 下方内容区:左右分栏 */
.content-area {
flex: 1;
min-height: 0;
display: flex;
gap: 10px;
}
/* 左侧轧辊列表面板 */
.roll-list-panel {
width: 240px;
flex-shrink: 0;
height: calc(100vh - 140px);
overflow-y: auto;
}
/* 右侧明细面板 */
.detail-panel {
flex: 1;
min-width: 0;
overflow-y: auto;
}
/* 明细卡片 */
.detail-card {
border: 1px solid #dcdee0;
border-radius: 4px;
}
.detail-card ::v-deep .el-card__body {
overflow: auto;
}
/* 通用卡片 */
.panel-card {
border: 1px solid #dcdee0;
border-radius: 4px;
height: 100%;
display: flex;
flex-direction: column;
}
.panel-card ::v-deep .el-card__body {
flex: 1;
min-height: 0;
overflow-y: auto;
}
.card-header { display: flex; align-items: center; gap: 8px; }
.card-title { font-size: 13px; font-weight: 600; color: #3d4b5c; }
.card-count { font-size: 11px; color: #9aa0a6; background: #f0f2f5; padding: 1px 8px; border-radius: 10px; margin-left: auto; }
/* 轧辊列表 */
.roll-list { padding: 0; }
.roll-item { padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #f0f2f5; }
.roll-item:hover { background: #f5f7fa; }
.roll-item--active { background: #e8f4ff !important; border-left: 3px solid #409eff; }
.ri-no { font-family: 'Consolas', monospace; font-size: 13px; font-weight: 600; color: #1f2329; }
.ri-meta { display: flex; align-items: center; gap: 6px; margin-top: 3px; }
.ri-dia { font-size: 11px; color: #9aa0a6; }
.ri-manufacturer { font-size: 10px; color: #909399; margin-top: 2px; }
.ri-line { font-size: 10px; color: #b0b3bb; margin-top: 2px; }
.ri-status { font-size: 11px; }
.roll-empty { text-align: center; color: #c0c4cc; padding: 20px 0; font-size: 12px; }
/* 状态色 */
.st-Online { color: #0a7c42; }
.st-Standby { color: #d4860a; }
.st-Offline { color: #9aa0a6; }
.st-Scrapped { color: #c5221f; }
/* 空状态 */
.grind-empty { display: flex; align-items: center; justify-content: center;
height: 300px; color: #c0c4cc; font-size: 14px; gap: 6px; }
/* 辊头信息格 */
.roll-header-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px 16px; }
.rh-item { display: flex; flex-direction: column; gap: 2px; }
.rh-k { font-size: 11px; color: #9aa0a6; }
.rh-v { font-size: 13px; color: #3d4b5c; }
.rh-v.bold { font-weight: 600; }
.rh-v.accent { color: #0a7c42; }
/* 月度汇总 */
.monthly-wrap { margin-top: 16px; border-top: 1px solid #f0f2f5; padding-top: 12px; }
.monthly-title { font-size: 12px; color: #5f6368; display: flex; align-items: center; }
/* 行内编辑 */
.computed-val { color: #409eff; font-weight: 600; font-size: 13px; }
.remark-text { font-size: 12px; color: #5f6368; }
.el-table :deep(input[type=number]::-webkit-inner-spin-button),
.el-table :deep(input[type=number]::-webkit-outer-spin-button) { -webkit-appearance: none; }
.el-table :deep(input[type=number]) { -moz-appearance: textfield; }
</style>
<style>
.el-table .editing-row { background: #fffbf0 !important; }
.el-table .editing-row:hover > td { background: #fffbf0 !important; }
.el-table ::v-deep .cell {
padding: 0;
}
</style>