前端新增停机快捷录入,标签打印能力
This commit is contained in:
@@ -83,4 +83,15 @@ export function getStoppageSummary(data) {
|
|||||||
data,
|
data,
|
||||||
url: '/api/stoppage/calc'
|
url: '/api/stoppage/calc'
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询停机类型
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function listStoppageTypes() {
|
||||||
|
return l2Request({
|
||||||
|
method: 'get',
|
||||||
|
url: '/api/stoppage/types'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,8 @@
|
|||||||
<span v-if="currentRow.exitMatId" style="margin-left: 10px; border: 1px solid #d4d4d4; border-radius: 2px; padding: 5px;">
|
<span v-if="currentRow.exitMatId" style="margin-left: 10px; border: 1px solid #d4d4d4; border-radius: 2px; padding: 5px;">
|
||||||
<el-button size="mini" type="text" icon="el-icon-view"
|
<el-button size="mini" type="text" icon="el-icon-view"
|
||||||
@click.stop="handlePrint(currentRow)">Print</el-button>
|
@click.stop="handlePrint(currentRow)">Print</el-button>
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-printer"
|
||||||
|
@click.stop="handlePrintTag(currentRow)">Print Tag</el-button>
|
||||||
<!-- 打印 -->
|
<!-- 打印 -->
|
||||||
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleEdit(currentRow)">Edit</el-button>
|
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleEdit(currentRow)">Edit</el-button>
|
||||||
<!-- 编辑 -->
|
<!-- 编辑 -->
|
||||||
@@ -155,6 +157,17 @@
|
|||||||
<!-- 质保书(单卷) -->
|
<!-- 质保书(单卷) -->
|
||||||
<quality-certificate :detail="formData"></quality-certificate>
|
<quality-certificate :detail="formData"></quality-certificate>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- Print Tag Dialog -->
|
||||||
|
<el-dialog title="Print Tag" :visible.sync="printTagDialogVisible" width="520px" :close-on-click-modal="false" append-to-body>
|
||||||
|
<div ref="printTagRef" class="material-label-container">
|
||||||
|
<MaterialTag v-if="printTagContent" :content="printTagContent" :paper-width-mm="100" :paper-height-mm="80" />
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="printTagDialogVisible = false">Close</el-button>
|
||||||
|
<el-button type="primary" @click="doPrintTag" :disabled="!printTagContent">Print</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -166,6 +179,7 @@ import PdoDataCorrection from './components/DataCorrection.vue'
|
|||||||
import PdoLabelPrint from './components/LabelPrint.vue'
|
import PdoLabelPrint from './components/LabelPrint.vue'
|
||||||
import PdoSummary from './components/PdoSummary.vue'
|
import PdoSummary from './components/PdoSummary.vue'
|
||||||
import QualityCertificate from './components/QualityCertificate.vue'
|
import QualityCertificate from './components/QualityCertificate.vue'
|
||||||
|
import MaterialTag from '../plan/components/MaterialTag.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PdoManagement',
|
name: 'PdoManagement',
|
||||||
@@ -175,7 +189,8 @@ export default {
|
|||||||
PdoDataCorrection, // 注册数据修正组件
|
PdoDataCorrection, // 注册数据修正组件
|
||||||
PdoLabelPrint, // 注册标签打印组件
|
PdoLabelPrint, // 注册标签打印组件
|
||||||
PdoSummary,
|
PdoSummary,
|
||||||
QualityCertificate // 注册质保书组件
|
QualityCertificate, // 注册质保书组件
|
||||||
|
MaterialTag
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -184,6 +199,8 @@ export default {
|
|||||||
total: 0,
|
total: 0,
|
||||||
printOpen: false,
|
printOpen: false,
|
||||||
certificateVisible: false, // 质保书对话框显示状态 Quality certificate dialog visibility state
|
certificateVisible: false, // 质保书对话框显示状态 Quality certificate dialog visibility state
|
||||||
|
printTagDialogVisible: false,
|
||||||
|
printTagContent: null,
|
||||||
tableData: [],
|
tableData: [],
|
||||||
tableLoading: false,
|
tableLoading: false,
|
||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
@@ -287,6 +304,75 @@ export default {
|
|||||||
this.formData = JSON.parse(JSON.stringify(row));
|
this.formData = JSON.parse(JSON.stringify(row));
|
||||||
this.printOpen = true
|
this.printOpen = true
|
||||||
},
|
},
|
||||||
|
handlePrintTag(row) {
|
||||||
|
if (!row) return
|
||||||
|
// 将 PDO 字段映射到 MaterialTag 期望字段
|
||||||
|
this.printTagContent = {
|
||||||
|
planid: row.planId || row.planid || '',
|
||||||
|
coilid: row.exitMatId || row.coilid || '',
|
||||||
|
steelGrade: row.steelGrade || '',
|
||||||
|
status: row.status || '',
|
||||||
|
entryThick: row.entryThick,
|
||||||
|
entryWidth: row.entryWidth,
|
||||||
|
entryLength: row.entryLength,
|
||||||
|
entryWeight: row.entryWeight,
|
||||||
|
exitThick: row.exitThickness,
|
||||||
|
exitWidth: row.exitWidth,
|
||||||
|
exitLength: row.exitLength,
|
||||||
|
exitWeight: row.exitNetWeight,
|
||||||
|
yieldPoint: row.yieldPoint,
|
||||||
|
orderNo: row.orderNo || ''
|
||||||
|
}
|
||||||
|
this.printTagDialogVisible = true
|
||||||
|
},
|
||||||
|
doPrintTag() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.printElementOnly(this.$refs.printTagRef)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
printElementOnly(el) {
|
||||||
|
if (!el) return
|
||||||
|
|
||||||
|
const printWindow = window.open('', '_blank', 'noopener,noreferrer')
|
||||||
|
if (!printWindow) {
|
||||||
|
this.$message && this.$message.error('Failed to open print window')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style'))
|
||||||
|
.map(node => node.outerHTML)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
const html = '<!doctype html>' +
|
||||||
|
'<html>' +
|
||||||
|
'<head>' +
|
||||||
|
'<meta charset="utf-8">' +
|
||||||
|
'<meta name="viewport" content="width=device-width, initial-scale=1">' +
|
||||||
|
'<title>Print</title>' +
|
||||||
|
styles +
|
||||||
|
'<style>' +
|
||||||
|
' @page { size: 100mm 80mm; margin: 0; }' +
|
||||||
|
' html, body { margin: 0; padding: 0; }' +
|
||||||
|
' body { background: #fff; }' +
|
||||||
|
'</style>' +
|
||||||
|
'</head>' +
|
||||||
|
'<body>' +
|
||||||
|
'<div class="material-label-container">' + el.innerHTML + '</div>' +
|
||||||
|
'<script>' +
|
||||||
|
' window.onload = function() {' +
|
||||||
|
' setTimeout(function() {' +
|
||||||
|
' window.print();' +
|
||||||
|
' window.close();' +
|
||||||
|
' }, 50);' +
|
||||||
|
' };' +
|
||||||
|
'<\\/script>' +
|
||||||
|
'</body>' +
|
||||||
|
'</html>'
|
||||||
|
|
||||||
|
printWindow.document.open()
|
||||||
|
printWindow.document.write(html)
|
||||||
|
printWindow.document.close()
|
||||||
|
},
|
||||||
// 处理质保书(单卷) Handle quality certificate (single coil)
|
// 处理质保书(单卷) Handle quality certificate (single coil)
|
||||||
handleQualityCertificate(row) {
|
handleQualityCertificate(row) {
|
||||||
// 深拷贝行数据 Deep copy row data
|
// 深拷贝行数据 Deep copy row data
|
||||||
|
|||||||
269
src/views/l2/plan/components/MaterialTag.vue
Normal file
269
src/views/l2/plan/components/MaterialTag.vue
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<template>
|
||||||
|
<div class="label-container" :style="{ '--print-scale': printScale }">
|
||||||
|
<div class="tag-header">
|
||||||
|
<img class="tag-logo" :src="logoImg" />
|
||||||
|
<span class="tag-title">Galvanizing Line MATERIAL TAG</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="material-label-table">
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Plan ID</td>
|
||||||
|
<td class="value-cell">{{ content.planid || '-' }}</td>
|
||||||
|
<td class="label-cell">Coil ID</td>
|
||||||
|
<td class="value-cell">{{ content.coilid || '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Steel Grade</td>
|
||||||
|
<td class="value-cell">{{ content.steelGrade || '-' }}</td>
|
||||||
|
<td class="label-cell">Status</td>
|
||||||
|
<td class="value-cell">{{ formatStatus(content.status) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Entry Thickness</td>
|
||||||
|
<td class="value-cell">{{ formatMm(content.entryThick) }}</td>
|
||||||
|
<td class="label-cell">Entry Width</td>
|
||||||
|
<td class="value-cell">{{ formatMm(content.entryWidth) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Entry Weight</td>
|
||||||
|
<td class="value-cell">{{ formatT(content.entryWeight) }}</td>
|
||||||
|
<td class="label-cell">Entry Length</td>
|
||||||
|
<td class="value-cell">{{ formatMm(content.entryLength) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Exit Thickness</td>
|
||||||
|
<td class="value-cell">{{ formatMm(content.exitThick) }}</td>
|
||||||
|
<td class="label-cell">Exit Width</td>
|
||||||
|
<td class="value-cell">{{ formatMm(content.exitWidth) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Exit Net Weight</td>
|
||||||
|
<td class="value-cell">{{ formatT(content.exitWeight) }}</td>
|
||||||
|
<td class="label-cell">Exit Length</td>
|
||||||
|
<td class="value-cell">{{ formatMm(content.exitLength) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label-cell">Yield Point</td>
|
||||||
|
<td class="value-cell">{{ content.yieldPoint || '-' }}</td>
|
||||||
|
<td class="label-cell">Order No.</td>
|
||||||
|
<td class="value-cell">{{ content.orderNo || '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import logoImg from '@/assets/logo/logo.png'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MaterialTag',
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
paperWidthMm: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
paperHeightMm: {
|
||||||
|
type: Number,
|
||||||
|
default: 80
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logoImg,
|
||||||
|
printScale: 1,
|
||||||
|
printMediaQuery: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.printMediaQuery = window.matchMedia('print')
|
||||||
|
this.printMediaQuery.addListener(this.handlePrintMediaChange)
|
||||||
|
|
||||||
|
window.addEventListener('beforeprint', this.handleBeforePrint)
|
||||||
|
window.addEventListener('afterprint', this.handleAfterPrint)
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.calculatePrintScale()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.printMediaQuery) {
|
||||||
|
this.printMediaQuery.removeListener(this.handlePrintMediaChange)
|
||||||
|
}
|
||||||
|
window.removeEventListener('beforeprint', this.handleBeforePrint)
|
||||||
|
window.removeEventListener('afterprint', this.handleAfterPrint)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatStatus(status) {
|
||||||
|
const map = {
|
||||||
|
PRODUCING: 'Producing',
|
||||||
|
ONLINE: 'Online',
|
||||||
|
READY: 'Ready',
|
||||||
|
NEW: 'New',
|
||||||
|
PRODUCT: 'Completed'
|
||||||
|
}
|
||||||
|
return map[status] || status || '-'
|
||||||
|
},
|
||||||
|
formatMm(val) {
|
||||||
|
if (val === null || val === undefined || val === '') return '-'
|
||||||
|
return `${val} mm`
|
||||||
|
},
|
||||||
|
formatT(val) {
|
||||||
|
if (val === null || val === undefined || val === '') return '-'
|
||||||
|
return `${val} t`
|
||||||
|
},
|
||||||
|
handlePrintMediaChange(mq) {
|
||||||
|
if (mq.matches) {
|
||||||
|
this.calculatePrintScale()
|
||||||
|
} else {
|
||||||
|
this.resetPrintScale()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleBeforePrint() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.calculatePrintScale()
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
handleAfterPrint() {
|
||||||
|
this.resetPrintScale()
|
||||||
|
},
|
||||||
|
calculatePrintScale() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const container = this.$el
|
||||||
|
if (!container) return
|
||||||
|
|
||||||
|
const dpi = 96
|
||||||
|
const mmToPx = dpi / 25.4
|
||||||
|
|
||||||
|
const paperWidthMm = this.paperWidthMm || 100
|
||||||
|
const paperHeightMm = this.paperHeightMm || 80
|
||||||
|
const marginMm = 2
|
||||||
|
|
||||||
|
const paperWidthPx = paperWidthMm * mmToPx
|
||||||
|
const paperHeightPx = paperHeightMm * mmToPx
|
||||||
|
const marginPx = marginMm * mmToPx
|
||||||
|
|
||||||
|
const rect = container.getBoundingClientRect()
|
||||||
|
let contentWidth = rect.width
|
||||||
|
let contentHeight = rect.height
|
||||||
|
|
||||||
|
if (contentWidth === 0 || contentHeight === 0) {
|
||||||
|
contentWidth = container.scrollWidth || contentWidth
|
||||||
|
contentHeight = container.scrollHeight || contentHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableWidth = paperWidthPx - marginPx * 2
|
||||||
|
const availableHeight = paperHeightPx - marginPx * 2
|
||||||
|
|
||||||
|
const scaleX = contentWidth > 0 ? availableWidth / contentWidth : 1
|
||||||
|
const scaleY = contentHeight > 0 ? availableHeight / contentHeight : 1
|
||||||
|
|
||||||
|
this.printScale = Math.min(scaleX, scaleY, 1)
|
||||||
|
|
||||||
|
container.style.setProperty('--print-scale', this.printScale)
|
||||||
|
container.style.setProperty('--paper-width', `${paperWidthMm}mm`)
|
||||||
|
container.style.setProperty('--paper-height', `${paperHeightMm}mm`)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
resetPrintScale() {
|
||||||
|
this.printScale = 1
|
||||||
|
if (this.$el) {
|
||||||
|
this.$el.style.setProperty('--print-scale', 1)
|
||||||
|
this.$el.style.removeProperty('--paper-width')
|
||||||
|
this.$el.style.removeProperty('--paper-height')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.label-container {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #333;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-logo {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-label-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-label-table td {
|
||||||
|
border: 1px solid #333;
|
||||||
|
padding: 6px 6px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-cell {
|
||||||
|
width: 25%;
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-cell {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
size: 100mm 80mm;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
-webkit-print-color-adjust: exact !important;
|
||||||
|
print-color-adjust: exact !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body > *:not(.material-label-container) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-label-container {
|
||||||
|
transform: scale(var(--print-scale, 1)) !important;
|
||||||
|
transform-origin: top left !important;
|
||||||
|
max-width: var(--paper-width, 100mm) !important;
|
||||||
|
max-height: var(--paper-height, 80mm) !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
|
||||||
|
page-break-inside: avoid !important;
|
||||||
|
break-inside: avoid !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -61,6 +61,9 @@
|
|||||||
<el-button size="mini" type="text" @click.stop="handleDelete(plan)" icon="el-icon-delete">
|
<el-button size="mini" type="text" @click.stop="handleDelete(plan)" icon="el-icon-delete">
|
||||||
Delete <!-- 删除 -->
|
Delete <!-- 删除 -->
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button size="mini" type="text" @click.stop="handlePrintTag(plan)" icon="el-icon-printer">
|
||||||
|
Print Tag
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
@@ -128,6 +131,7 @@
|
|||||||
<div class="plan-actions">
|
<div class="plan-actions">
|
||||||
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(plan)" />
|
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleUpdate(plan)" />
|
||||||
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(plan)" />
|
<el-button size="mini" type="text" icon="el-icon-delete" @click.stop="handleDelete(plan)" />
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-printer" @click.stop="handlePrintTag(plan)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="pendingPlans.length === 0" class="empty-text">
|
<div v-if="pendingPlans.length === 0" class="empty-text">
|
||||||
@@ -229,6 +233,17 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<!-- Print Tag Dialog -->
|
||||||
|
<el-dialog title="Print Tag" :visible.sync="printTagDialogVisible" width="520px" :close-on-click-modal="false" append-to-body>
|
||||||
|
<div ref="printTagRef" class="material-label-container">
|
||||||
|
<MaterialTag v-if="printTagContent" :content="printTagContent" :paper-width-mm="100" :paper-height-mm="80" />
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="printTagDialogVisible = false">Close</el-button>
|
||||||
|
<el-button type="primary" @click="doPrintTag" :disabled="!printTagContent">Print</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 编辑/新增弹窗 Edit/Add Dialog -->
|
<!-- 编辑/新增弹窗 Edit/Add Dialog -->
|
||||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="80%" :close-on-click-modal="false"
|
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="80%" :close-on-click-modal="false"
|
||||||
append-to-body>
|
append-to-body>
|
||||||
@@ -366,6 +381,7 @@ import SetupForm from './components/setupForm.vue'
|
|||||||
import SetupPane from './components/setupPane.vue'
|
import SetupPane from './components/setupPane.vue'
|
||||||
import PlanRecommendPanel from './components/PlanRecommendPanel.vue'
|
import PlanRecommendPanel from './components/PlanRecommendPanel.vue'
|
||||||
import ProcessRecommendPanel from './components/ProcessRecommendPanel.vue'
|
import ProcessRecommendPanel from './components/ProcessRecommendPanel.vue'
|
||||||
|
import MaterialTag from './components/MaterialTag.vue'
|
||||||
|
|
||||||
// 标准日期格式化方法(优化时间处理逻辑,适配接口日期格式)
|
// 标准日期格式化方法(优化时间处理逻辑,适配接口日期格式)
|
||||||
function parseTime(time, format = "{yyyy}-{mm}-{dd} {hh}:{ii}:{ss}") {
|
function parseTime(time, format = "{yyyy}-{mm}-{dd} {hh}:{ii}:{ss}") {
|
||||||
@@ -397,7 +413,7 @@ function parseTime(time, format = "{yyyy}-{mm}-{dd} {hh}:{ii}:{ss}") {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 补零格式化(确保月/日/时/分/秒为两位数)
|
// 补零格式化(确保月/日/时/分/秒为两位数)
|
||||||
return format.replace(/{([ymdhisa])+}/g, (result, key) => {
|
return format.replace(/{([ymdhisa])+}/g, (_result, key) => {
|
||||||
const value = formatObj[key];
|
const value = formatObj[key];
|
||||||
return value.toString().length < 2 ? "0" + value : value;
|
return value.toString().length < 2 ? "0" + value : value;
|
||||||
});
|
});
|
||||||
@@ -409,7 +425,8 @@ export default {
|
|||||||
SetupForm,
|
SetupForm,
|
||||||
SetupPane,
|
SetupPane,
|
||||||
PlanRecommendPanel,
|
PlanRecommendPanel,
|
||||||
ProcessRecommendPanel
|
ProcessRecommendPanel,
|
||||||
|
MaterialTag
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -434,6 +451,8 @@ export default {
|
|||||||
planList: [],
|
planList: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
|
printTagDialogVisible: false,
|
||||||
|
printTagContent: null,
|
||||||
// 弹窗状态(新增/编辑弹窗显隐+标题) Dialog state (add/edit dialog visibility + title)
|
// 弹窗状态(新增/编辑弹窗显隐+标题) Dialog state (add/edit dialog visibility + title)
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogTitle: "Add Plan", // 新增计划
|
dialogTitle: "Add Plan", // 新增计划
|
||||||
@@ -647,6 +666,59 @@ export default {
|
|||||||
if (!this.$refs.setupFormRef) return
|
if (!this.$refs.setupFormRef) return
|
||||||
this.$refs.setupFormRef.applyAll()
|
this.$refs.setupFormRef.applyAll()
|
||||||
},
|
},
|
||||||
|
handlePrintTag(plan) {
|
||||||
|
if (!plan) return
|
||||||
|
this.printTagContent = { ...plan }
|
||||||
|
this.printTagDialogVisible = true
|
||||||
|
},
|
||||||
|
doPrintTag() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.printElementOnly(this.$refs.printTagRef)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
printElementOnly(el) {
|
||||||
|
if (!el) return
|
||||||
|
|
||||||
|
const printWindow = window.open('', '_blank', 'noopener,noreferrer')
|
||||||
|
if (!printWindow) {
|
||||||
|
this.$message && this.$message.error('Failed to open print window')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style'))
|
||||||
|
.map(node => node.outerHTML)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
const html = '<!doctype html>' +
|
||||||
|
'<html>' +
|
||||||
|
'<head>' +
|
||||||
|
'<meta charset="utf-8">' +
|
||||||
|
'<meta name="viewport" content="width=device-width, initial-scale=1">' +
|
||||||
|
'<title>Print</title>' +
|
||||||
|
styles +
|
||||||
|
'<style>' +
|
||||||
|
' @page { size: 100mm 80mm; margin: 0; }' +
|
||||||
|
' html, body { margin: 0; padding: 0; }' +
|
||||||
|
' body { background: #fff; }' +
|
||||||
|
'</style>' +
|
||||||
|
'</head>' +
|
||||||
|
'<body>' +
|
||||||
|
'<div class="material-label-container">' + el.innerHTML + '</div>' +
|
||||||
|
'<script>' +
|
||||||
|
' window.onload = function() {' +
|
||||||
|
' setTimeout(function() {' +
|
||||||
|
' window.print();' +
|
||||||
|
' window.close();' +
|
||||||
|
' }, 50);' +
|
||||||
|
' };' +
|
||||||
|
'<\\/script>' +
|
||||||
|
'</body>' +
|
||||||
|
'</html>'
|
||||||
|
|
||||||
|
printWindow.document.open()
|
||||||
|
printWindow.document.write(html)
|
||||||
|
printWindow.document.close()
|
||||||
|
},
|
||||||
// 获取物料位置映射
|
// 获取物料位置映射
|
||||||
fetchMatMapList() {
|
fetchMatMapList() {
|
||||||
getTrackMatPosition().then(res => {
|
getTrackMatPosition().then(res => {
|
||||||
|
|||||||
@@ -149,32 +149,32 @@ export default {
|
|||||||
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
||||||
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
||||||
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
||||||
{ prop: 'value1', label: 'Pay-off Reel Tension', minWidth: 120 }, // 开卷机张力
|
{ prop: 'value1', label: 'Pay-off Reel Tension (kg)', minWidth: 120 }, // 开卷机张力
|
||||||
{ prop: 'value2', label: 'Entry Loop Tension', minWidth: 120 }, // 入口活套张力
|
{ prop: 'value2', label: 'Entry Loop Tension (kg)', minWidth: 120 }, // 入口活套张力
|
||||||
{ prop: 'value3', label: 'Cleaning Section Tension', minWidth: 120 }, // 清洗段张力
|
{ prop: 'value3', label: 'Cleaning Section Tension (kg)', minWidth: 120 }, // 清洗段张力
|
||||||
{ prop: 'value4', label: 'Passivation Section Tension', minWidth: 120 }, // 钝化段张力
|
{ prop: 'value4', label: 'Passivation Section Tension (kg)', minWidth: 120 }, // 钝化段张力
|
||||||
{ prop: 'value5', label: 'Exit Loop Tension', minWidth: 120 }, // 出口活套张力
|
{ prop: 'value5', label: 'Exit Loop Tension (kg)', minWidth: 120 }, // 出口活套张力
|
||||||
{ prop: 'value6', label: 'Take-up Reel Tension', minWidth: 120 } // 卷取机张力
|
{ prop: 'value6', label: 'Take-up Reel Tension (kg)', minWidth: 120 } // 卷取机张力
|
||||||
],
|
],
|
||||||
schemaLeveler: [
|
schemaLeveler: [
|
||||||
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
||||||
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
||||||
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
||||||
{ prop: 'value1', label: 'Leveler Entry Tension', minWidth: 120 }, // 平整机入口张力
|
{ prop: 'value1', label: 'Leveler Entry Tension (kg)', minWidth: 120 }, // 平整机入口张力
|
||||||
{ prop: 'value2', label: 'Leveler Exit Tension', minWidth: 120 } // 平整机出口张力
|
{ prop: 'value2', label: 'Leveler Exit Tension (kg)', minWidth: 120 } // 平整机出口张力
|
||||||
],
|
],
|
||||||
schemaStraightener: [
|
schemaStraightener: [
|
||||||
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
||||||
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
||||||
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
||||||
{ prop: 'value1', label: 'Straightener Exit Tension', minWidth: 120 } // 矫直机出口张力
|
{ prop: 'value1', label: 'Straightener Exit Tension (kg)', minWidth: 120 } // 矫直机出口张力
|
||||||
],
|
],
|
||||||
schemaAnnealingFurnace: [
|
schemaAnnealingFurnace: [
|
||||||
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
{ prop: 'steelGrade', label: 'Steel Grade', required: true, minWidth: 65 }, // 钢种
|
||||||
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
{ prop: 'thick', label: 'Thickness', required: true, minWidth: 65 }, // 厚度
|
||||||
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
{ prop: 'yieldStren', label: 'Yield Point', required: true, minWidth: 90 }, // 屈服强度
|
||||||
{ prop: 'value1', label: 'Furnace Zone Tension', minWidth: 120 }, // 炉区张力
|
{ prop: 'value1', label: 'Furnace Zone Tension (kg)', minWidth: 120 }, // 炉区张力
|
||||||
{ prop: 'value2', label: 'Cooling Tower Tension', minWidth: 120 } // 冷却塔张力
|
{ prop: 'value2', label: 'Cooling Tower Tension (kg)', minWidth: 120 } // 冷却塔张力
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,6 +54,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="param-groups-row">
|
<div class="param-groups-row">
|
||||||
|
<div class="quick-reason-row" @click.stop>
|
||||||
|
<span class="quick-reason-label">Stop Type:</span>
|
||||||
|
<el-select
|
||||||
|
v-model="item.stopType"
|
||||||
|
size="mini"
|
||||||
|
placeholder="Quick select stop type"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
:disabled="item.reasonSaving"
|
||||||
|
:loading="item.reasonSaving"
|
||||||
|
@change="val => handleQuickReasonChange(item, val)"
|
||||||
|
@click.native.stop
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="opt in quickStopTypeOptions"
|
||||||
|
:key="opt"
|
||||||
|
:label="opt"
|
||||||
|
:value="opt"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
<!-- Basic Info / 基本信息 -->
|
<!-- Basic Info / 基本信息 -->
|
||||||
<div class="param-group">
|
<div class="param-group">
|
||||||
<div class="group-title">Basic Info</div>
|
<div class="group-title">Basic Info</div>
|
||||||
@@ -230,14 +251,12 @@
|
|||||||
<!-- 停机类型 -->
|
<!-- 停机类型 -->
|
||||||
<el-select v-model="formData.stopType" placeholder="Please select stop type" clearable>
|
<el-select v-model="formData.stopType" placeholder="Please select stop type" clearable>
|
||||||
<!-- 请选择停机类型 -->
|
<!-- 请选择停机类型 -->
|
||||||
<el-option label="Planned Stop" value="计划停机"></el-option>
|
<el-option
|
||||||
<!-- 计划停机 -->
|
v-for="opt in stopTypeOptions"
|
||||||
<el-option label="Fault Stop" value="故障停机"></el-option>
|
:key="opt.stopType"
|
||||||
<!-- 故障停机 -->
|
:label="opt.remark"
|
||||||
<el-option label="Maintenance Stop" value="维护停机"></el-option>
|
:value="opt.stopType"
|
||||||
<!-- 维护停机 -->
|
/>
|
||||||
<el-option label="Other" value="其他"></el-option>
|
|
||||||
<!-- 其他 -->
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -258,7 +277,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listStoppage, updateStoppage, deleteStoppage } from '@/api/l2/stop'; // 导入接口
|
import { listStoppage, updateStoppage, deleteStoppage, listStoppageTypes } from '@/api/l2/stop'; // 导入接口
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StoppageManagement',
|
name: 'StoppageManagement',
|
||||||
@@ -267,8 +286,8 @@ export default {
|
|||||||
// Query form data / 查询表单数据
|
// Query form data / 查询表单数据
|
||||||
queryForm: {
|
queryForm: {
|
||||||
// Only keep YYYY-MM-dd format / 只保留年月日YYYY-mm-dd
|
// Only keep YYYY-MM-dd format / 只保留年月日YYYY-mm-dd
|
||||||
startDate: '2023-08-13',
|
startDate: '',
|
||||||
endDate: '2025-08-20'
|
endDate: ''
|
||||||
},
|
},
|
||||||
// Table data / 表格数据
|
// Table data / 表格数据
|
||||||
tableData: [],
|
tableData: [],
|
||||||
@@ -313,14 +332,40 @@ export default {
|
|||||||
// Save button loading state / 保存按钮加载状态
|
// Save button loading state / 保存按钮加载状态
|
||||||
saveLoading: false,
|
saveLoading: false,
|
||||||
// Current row data / 当前操作的行数据
|
// Current row data / 当前操作的行数据
|
||||||
currentRow: {}
|
currentRow: {},
|
||||||
|
// Stop types from backend / 后端停机类型
|
||||||
|
stopTypeOptions: [],
|
||||||
|
// Quick stop type options (from backend) / 快捷停机类型(来自后端)
|
||||||
|
quickStopTypeOptions: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
const now = new Date();
|
||||||
|
const yyyy = now.getFullYear();
|
||||||
|
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
|
const dd = String(now.getDate()).padStart(2, '0');
|
||||||
|
this.queryForm.startDate = `${yyyy}-${mm}-01`;
|
||||||
|
this.queryForm.endDate = `${yyyy}-${mm}-${dd}`;
|
||||||
|
|
||||||
// Query once on page load / 页面加载时默认查询一次
|
// Query once on page load / 页面加载时默认查询一次
|
||||||
this.getStoppageList();
|
this.getStoppageList();
|
||||||
|
this.getStopTypeOptions();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getStopTypeOptions() {
|
||||||
|
listStoppageTypes()
|
||||||
|
.then(res => {
|
||||||
|
const list = (res && res.data) ? res.data : [];
|
||||||
|
this.stopTypeOptions = Array.isArray(list) ? list : [];
|
||||||
|
this.quickStopTypeOptions = this.stopTypeOptions.map(x => x.remark).filter(Boolean);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Failed to fetch stop types:', err);
|
||||||
|
this.stopTypeOptions = [];
|
||||||
|
this.quickStopTypeOptions = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Get stoppage record list / 获取停机记录列表
|
// Get stoppage record list / 获取停机记录列表
|
||||||
getStoppageList() {
|
getStoppageList() {
|
||||||
this.tableLoading = true;
|
this.tableLoading = true;
|
||||||
@@ -330,7 +375,9 @@ export default {
|
|||||||
this.btnLoading = false;
|
this.btnLoading = false;
|
||||||
this.tableData = response.data.map(item => ({
|
this.tableData = response.data.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
deleteLoading: false
|
deleteLoading: false,
|
||||||
|
reasonSaving: false,
|
||||||
|
_lastStopType: item.stopType
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -437,6 +484,32 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleQuickReasonChange(row, val) {
|
||||||
|
if (!row || !row.stopid) return;
|
||||||
|
const newVal = (val === undefined || val === null) ? '' : val;
|
||||||
|
|
||||||
|
const payload = { ...row, stopType: newVal };
|
||||||
|
row.reasonSaving = true;
|
||||||
|
|
||||||
|
updateStoppage(payload)
|
||||||
|
.then(res => {
|
||||||
|
row.reasonSaving = false;
|
||||||
|
if (res && res.code === 200) {
|
||||||
|
row._lastStopType = newVal;
|
||||||
|
this.$message.success('Saved');
|
||||||
|
} else {
|
||||||
|
row.stopType = row._lastStopType;
|
||||||
|
this.$message.error((res && res.msg) || 'Save failed');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
row.reasonSaving = false;
|
||||||
|
row.stopType = row._lastStopType;
|
||||||
|
console.error('Quick stopType save failed:', err);
|
||||||
|
this.$message.error('Save failed, please retry');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Save / 保存
|
// Save / 保存
|
||||||
handleSave() {
|
handleSave() {
|
||||||
this.$refs.formData.validate(valid => {
|
this.$refs.formData.validate(valid => {
|
||||||
@@ -572,6 +645,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.param-group {
|
.param-group {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -642,6 +716,26 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-reason-row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
border-bottom: 1px dashed #e0e0e0;
|
||||||
|
|
||||||
|
.quick-reason-label {
|
||||||
|
color: #777;
|
||||||
|
font-size: 11px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep .el-select {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
Reference in New Issue
Block a user