feat(pdo): 新增PDO管理功能及相关组件
新增PDO管理页面及相关功能组件,包括: 1. 新增数据修正组件DataCorrection.vue 2. 新增标签打印组件LabelPrint.vue 3. 新增统计汇总组件PdoSummary.vue 4. 新增图表展示组件line.vue 5. 实现主页面index.vue布局及功能 6. 新增API接口文件用于业务数据交互 7. 修改lines/index.vue配置,移除baseURL动态获取逻辑
This commit is contained in:
310
klp-ui/src/views/lines/pdo/components/DataCorrection.vue
Normal file
310
klp-ui/src/views/lines/pdo/components/DataCorrection.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<el-form
|
||||
:model="formData"
|
||||
ref="correctionForm"
|
||||
label-width="120px"
|
||||
:rules="formRules"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<!-- 成品卷 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成品卷" prop="exitMatId">
|
||||
<el-input v-model="formData.exitMatId"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 来料卷 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="来料卷" prop="entryMatId">
|
||||
<el-input v-model="formData.entryMatId"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 分切数 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="分切数" prop="subId">
|
||||
<el-input-number v-model="formData.subId" :min="0" :step="1" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 开始位置 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="开始位置" prop="startPosition">
|
||||
<el-input-number v-model="formData.startPosition" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 结束位置 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="结束位置" prop="endPosition">
|
||||
<el-input-number v-model="formData.endPosition" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 计划ID -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="计划ID" prop="planId">
|
||||
<el-input-number v-model="formData.planId" :step="1" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 计划号 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="计划号" prop="planNo">
|
||||
<el-input v-model="formData.planNo"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 产品类型 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="产品类型" prop="prodCode">
|
||||
<el-input v-model="formData.prodCode"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 班号 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="班号" prop="groupNo">
|
||||
<el-input v-model="formData.groupNo"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 组号 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="组号" prop="shiftNo">
|
||||
<el-input v-model="formData.shiftNo"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 状态 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-input v-model="formData.status"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 钢种 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="钢种" prop="steelGrade">
|
||||
<el-input v-model="formData.steelGrade"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 来料厚度 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="来料厚度" prop="entryThick">
|
||||
<el-input-number v-model="formData.entryThick" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 来料宽度 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="来料宽度" prop="entryWidth">
|
||||
<el-input-number v-model="formData.entryWidth" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 来料长度 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="来料长度" prop="entryLength">
|
||||
<el-input-number v-model="formData.entryLength" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 来料重量 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="来料重量" prop="entryWeight">
|
||||
<el-input-number v-model="formData.entryWeight" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 上表面镀锌 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="上表面镀锌" prop="weightTop">
|
||||
<el-input-number v-model="formData.weightTop" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 下表面镀锌 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="下表面镀锌" prop="weightBottom">
|
||||
<el-input-number v-model="formData.weightBottom" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 成品长度 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成品长度" prop="exitLength">
|
||||
<el-input-number v-model="formData.exitLength" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 成品带涂料重量 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成品带涂料重量" prop="exitNetWeight">
|
||||
<el-input-number v-model="formData.exitNetWeight" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 理论重量 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="理论重量" prop="theoryWeight">
|
||||
<el-input-number v-model="formData.theoryWeight" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 实际重量 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="实际重量" prop="actualWeight">
|
||||
<el-input-number v-model="formData.actualWeight" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 成品外径 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成品外径" prop="exitOuterDiameter">
|
||||
<el-input-number v-model="formData.exitOuterDiameter" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 成品厚度 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成品厚度" prop="exitThickness">
|
||||
<el-input-number v-model="formData.exitThickness" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 成品宽度 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="成品宽度" prop="exitWidth">
|
||||
<el-input-number v-model="formData.exitWidth" :step="0.01" style="width: 100%;"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 客户 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="客户" prop="customer">
|
||||
<el-input v-model="formData.customer"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 上线时间 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="上线时间" prop="onlineTime">
|
||||
<el-date-picker v-model="formData.onlineTime" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 开始时间 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker v-model="formData.startTime" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 结束时间 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker v-model="formData.endTime" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 机组号 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="机组号" prop="unitCode">
|
||||
<el-input v-model="formData.unitCode"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 工序号 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="工序号" prop="processCode">
|
||||
<el-input v-model="formData.processCode"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 是否尾卷 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否尾卷" prop="lastFlag">
|
||||
<el-switch v-model="formData.lastFlag"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 是否分卷 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="是否分卷" prop="separateFlag">
|
||||
<el-switch v-model="formData.separateFlag"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 计划来源 -->
|
||||
<el-col :span="8">
|
||||
<el-form-item label="计划来源" prop="planOrigin">
|
||||
<el-select v-model="formData.planOrigin" placeholder="请选择计划来源">
|
||||
<el-option label="L3计划" value="L3"></el-option>
|
||||
<el-option label="人工" value="MANUAL"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="handleSave" :loading="saveLoading">保存</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PdoDataCorrection',
|
||||
props: {
|
||||
// 接收父组件传递的详情数据
|
||||
detail: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
},
|
||||
// 保存回调函数(父组件处理实际保存逻辑)
|
||||
saveCallback: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
// 保存按钮loading状态(由父组件控制)
|
||||
saveLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 内部表单数据(拷贝父组件传递的detail,避免直接修改父组件数据)
|
||||
formData: {},
|
||||
// 表单验证规则
|
||||
formRules: {
|
||||
exitMatId: [{ required: true, message: '请输入成品卷号', trigger: 'blur' }],
|
||||
entryMatId: [{ required: true, message: '请输入来料卷号', trigger: 'blur' }],
|
||||
planNo: [{ required: true, message: '请输入计划号', trigger: 'blur' }],
|
||||
steelGrade: [{ required: true, message: '请输入钢种', trigger: 'blur' }]
|
||||
// 可根据实际需求补充其他字段验证规则
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听detail变化,深拷贝更新内部formData
|
||||
detail: {
|
||||
handler(newVal) {
|
||||
// 深拷贝避免引用类型数据相互影响
|
||||
this.formData = JSON.parse(JSON.stringify(newVal))
|
||||
// 初始化数值类型字段(防止null/undefined导致输入框异常)
|
||||
this.initNumberFields()
|
||||
},
|
||||
immediate: true, // 初始渲染时立即执行
|
||||
deep: true // 深度监听对象内部变化
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化数值类型字段(确保默认值为0而非null/undefined)
|
||||
initNumberFields() {
|
||||
const numberFields = [
|
||||
'subId', 'startPosition', 'endPosition', 'planId', 'entryThick',
|
||||
'entryWidth', 'entryLength', 'entryWeight', 'weightTop', 'weightBottom',
|
||||
'exitLength', 'exitNetWeight', 'theoryWeight', 'actualWeight',
|
||||
'exitOuterDiameter', 'exitThickness', 'exitWidth'
|
||||
]
|
||||
numberFields.forEach(field => {
|
||||
if (this.formData[field] === undefined || this.formData[field] === null) {
|
||||
this.formData[field] = 0
|
||||
}
|
||||
})
|
||||
// 初始化布尔类型字段
|
||||
this.formData.lastFlag = this.formData.lastFlag ?? false
|
||||
this.formData.separateFlag = this.formData.separateFlag ?? false
|
||||
},
|
||||
// 处理保存逻辑(先验证表单,通过后触发父组件回调)
|
||||
handleSave() {
|
||||
this.$refs.correctionForm.validate(valid => {
|
||||
if (valid) {
|
||||
// 触发父组件保存回调,传递内部修改后的表单数据
|
||||
this.saveCallback(this.formData)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
271
klp-ui/src/views/lines/pdo/components/LabelPrint.vue
Normal file
271
klp-ui/src/views/lines/pdo/components/LabelPrint.vue
Normal file
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div class="label-print-container">
|
||||
<!-- 预览区域(与打印内容一致且可编辑) -->
|
||||
<div class="label-preview" v-if="Object.keys(editableData).length">
|
||||
<!-- 给打印区域添加一个唯一ID -->
|
||||
<div id="printContent" class="label-content">
|
||||
<!-- 公司名称 -->
|
||||
<div class="company-name">
|
||||
{{ editableData.companyName }}
|
||||
</div>
|
||||
|
||||
<!-- 内容区域:使用Flexbox布局 -->
|
||||
<div class="content-grid">
|
||||
<div class="grid-item label">钢卷号</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.exitMatId" :border="false" class="editable-input"
|
||||
placeholder="钢卷号"></input>
|
||||
</div>
|
||||
<div class="grid-item label">热卷号</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.entryMatId" :border="false" class="editable-input"
|
||||
placeholder="热卷号"></input>
|
||||
</div>
|
||||
|
||||
<div class="grid-item label">规格</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.spec" :border="false" class="editable-input" placeholder="规格"></input>
|
||||
</div>
|
||||
<div class="grid-item label">材质</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.steelGrade" :border="false" class="editable-input"
|
||||
placeholder="材质"></input>
|
||||
</div>
|
||||
|
||||
<div class="grid-item label">净重</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.actualWeight" :border="false" class="editable-input"
|
||||
placeholder="净重"></input>
|
||||
</div>
|
||||
<div class="grid-item label">生产班组</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.groupNo" :border="false" class="editable-input"
|
||||
placeholder="生产班组"></input>
|
||||
</div>
|
||||
|
||||
<div class="grid-item label">产品名称</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.prodCode" :border="false" class="editable-input"
|
||||
placeholder="产品名称"></input>
|
||||
</div>
|
||||
<div class="grid-item label">生产日期</div>
|
||||
<div class="grid-item value">
|
||||
<input v-model="editableData.productionDate" :border="false" class="editable-input"
|
||||
placeholder="生产日期"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 打印按钮 -->
|
||||
<div class="print-btn-container">
|
||||
<el-button type="primary" @click="handlePrint" icon="el-icon-printer"
|
||||
:disabled="!Object.keys(editableData).length">
|
||||
打印标签
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import printJS from 'print-js';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default {
|
||||
name: 'PdoLabelPrint',
|
||||
props: {
|
||||
detail: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 可编辑的数据对象
|
||||
editableData: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听detail变化,初始化可编辑数据
|
||||
detail: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.initEditableData(newVal);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化可编辑数据
|
||||
initEditableData(detail) {
|
||||
const {
|
||||
exitMatId, entryMatId, exitThickness, exitWidth,
|
||||
actualWeight, steelGrade, groupNo, prodCode
|
||||
} = detail;
|
||||
|
||||
// 格式化规格
|
||||
const spec = exitThickness && exitWidth
|
||||
? `${exitThickness}*${exitWidth}`
|
||||
: '';
|
||||
|
||||
// 格式化生产日期
|
||||
const productionDate = dayjs().format('M月D日');
|
||||
|
||||
// 初始化可编辑数据
|
||||
this.editableData = {
|
||||
companyName: '嘉祥科伦普重工有限公司',
|
||||
exitMatId: exitMatId || '',
|
||||
entryMatId: entryMatId || '',
|
||||
spec: spec || '',
|
||||
steelGrade: steelGrade || '',
|
||||
actualWeight: actualWeight || '',
|
||||
groupNo: groupNo || '',
|
||||
prodCode: prodCode || '',
|
||||
productionDate: productionDate
|
||||
};
|
||||
},
|
||||
|
||||
// 处理打印逻辑
|
||||
handlePrint() {
|
||||
// 定义打印样式,确保打印效果与预览一致
|
||||
const printStyle = `
|
||||
.label-content {
|
||||
width: 200mm;
|
||||
height: 160mm;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: SimSun;
|
||||
}
|
||||
.company-name {
|
||||
text-align: center;
|
||||
padding: 8mm 0;
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
.grid-item {
|
||||
min-height: 15mm;
|
||||
display: flex;
|
||||
border: 1px solid #000;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.label {
|
||||
font-weight: bold;
|
||||
font-size: 12pt;
|
||||
padding-left: 2mm;
|
||||
}
|
||||
.value {
|
||||
font-size: 12pt;
|
||||
}
|
||||
input {
|
||||
border: none !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12pt;
|
||||
padding: 0 2mm;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
|
||||
// 调用printJS进行打印
|
||||
printJS({
|
||||
printable: 'printContent', // 要打印的元素ID
|
||||
type: 'html',
|
||||
header: null, // 不显示默认页眉
|
||||
footer: null, // 不显示默认页脚
|
||||
// style: printStyle, // 应用打印样式
|
||||
scanStyles: true, // 不扫描页面现有样式
|
||||
targetStyles: ['*'], // 允许所有目标样式
|
||||
documentTitle: '标签打印', // 打印文档标题
|
||||
onPrintDialogClose: () => {
|
||||
console.log('打印对话框已关闭');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.label-print-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 预览区域样式 */
|
||||
.label-preview {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 标签内容样式(与打印尺寸等比例) */
|
||||
.label-content {
|
||||
width: 500px; /* 200mm × 2.5 = 500px */
|
||||
height: 400px; /* 160mm × 2.5 = 400px */
|
||||
border: 1px solid #ccc;
|
||||
padding: 25px; /* 10mm × 2.5 = 25px */
|
||||
box-sizing: border-box;
|
||||
font-family: SimSun;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
border: 1px solid white;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 内容网格布局 */
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 0;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
border: 1px solid white;
|
||||
min-height: 40px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
line-height: 60px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 无边框输入框样式 */
|
||||
.editable-input {
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
text-align: inherit;
|
||||
background: transparent;
|
||||
font-size: inherit;
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 60px;
|
||||
box-sizing: border-box;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.editable-input:hover,
|
||||
.editable-input:focus {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.print-btn-container {
|
||||
text-align: left;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
113
klp-ui/src/views/lines/pdo/components/PdoSummary.vue
Normal file
113
klp-ui/src/views/lines/pdo/components/PdoSummary.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<!-- src/views/PdoManagement/components/PdoSummary.vue -->
|
||||
<template>
|
||||
<el-descriptions
|
||||
class="summary-content"
|
||||
:column="1"
|
||||
:style="{ width: '100%' }"
|
||||
size="mini"
|
||||
>
|
||||
<!-- 循环渲染汇总项,从计算属性获取数据 -->
|
||||
<el-descriptions-item
|
||||
v-for="(item, index) in summaryResult"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
label-class="summary-label"
|
||||
>
|
||||
<!-- 根据类型格式化显示(重量/百分比/数量) -->
|
||||
<span :class="item.type === 'rate' ? 'summary-rate' : ''">
|
||||
{{ item.value }}
|
||||
{{ item.unit }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PdoSummary',
|
||||
props: {
|
||||
// 接收表格原始数据(父组件传递)
|
||||
tableData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [] // 默认空数组,避免无数据时报错
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 核心:根据tableData计算汇总结果
|
||||
summaryResult() {
|
||||
// 1. 基础数值汇总(处理字符串转数字,避免NaN)
|
||||
const totalEntryWeight = this.tableData.reduce((sum, item) => {
|
||||
return sum + (Number(item.entryWeight) || 0)
|
||||
}, 0) // 原料总重(来料重量求和)
|
||||
|
||||
const totalExitWeight = this.tableData.reduce((sum, item) => {
|
||||
return sum + (Number(item.exitNetWeight) || 0)
|
||||
}, 0) // 产品总重(成品带涂料重量求和)
|
||||
|
||||
const totalZincRemoved = totalEntryWeight - totalExitWeight // 去锌总理重(原料总重 - 产品总重)
|
||||
|
||||
// 2. 成材率计算(避免除数为0)
|
||||
const yieldRate = totalEntryWeight > 0
|
||||
? ((totalExitWeight / totalEntryWeight) * 100).toFixed(2)
|
||||
: '0.00'
|
||||
|
||||
// 3. 卷数统计(原料/成品卷数默认按表格行数,可根据实际业务调整,如去重entryMatId)
|
||||
const totalEntryCoils = this.tableData.length // 原料总卷数
|
||||
const totalExitCoils = this.tableData.length // 成品总卷数
|
||||
|
||||
// 4. 返回格式化后的汇总项
|
||||
return [
|
||||
{ label: '原料总重', value: totalEntryWeight.toFixed(1), unit: 't', type: 'weight' },
|
||||
{ label: '去锌总理重', value: totalZincRemoved.toFixed(1), unit: 't', type: 'weight' },
|
||||
{ label: '产品总重', value: totalExitWeight.toFixed(1), unit: 't', type: 'weight' },
|
||||
{ label: '成材率', value: yieldRate, unit: '%', type: 'rate' },
|
||||
{ label: '原料总卷数', value: totalEntryCoils, unit: '卷', type: 'count' },
|
||||
{ label: '成品总卷数', value: totalExitCoils, unit: '卷', type: 'count' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.summary-content {
|
||||
padding: 10px;
|
||||
flex: 1;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c0c0c0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
::v-deep .el-descriptions__label {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
::v-deep .el-descriptions__content {
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.summary-rate {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
551
klp-ui/src/views/lines/pdo/components/line.vue
Normal file
551
klp-ui/src/views/lines/pdo/components/line.vue
Normal file
@@ -0,0 +1,551 @@
|
||||
<template>
|
||||
<div class="monitoring-container">
|
||||
<div class="chart-wrapper" v-loading="loading">
|
||||
<!-- 参数选择下拉框 -->
|
||||
<el-select v-model="paramField" class="param-select" @change="handleParamChange" size="mini">
|
||||
<el-option v-for="item in paramFields" :key="item.value" :label="item.label" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
|
||||
<!-- 图表容器 -->
|
||||
<div ref="chart" class="chart-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import createFetch from '@/api/l2/pdo'
|
||||
|
||||
export default {
|
||||
name: 'DeviceMonitoring',
|
||||
props: {
|
||||
enCoilID: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pdoApi: undefined,
|
||||
// 参数列表来源于 DeviceEnum 定义的监测字段
|
||||
paramFields: [
|
||||
{ label: '带钢速度', value: 'stripSpeed' },
|
||||
{ label: '开卷张力1#', value: 'tensionPorBr1' },
|
||||
{ label: '开卷张力2#', value: 'tensionPorBr2' },
|
||||
{ label: '清洗电压', value: 'cleaningVoltage' },
|
||||
{ label: '清洗电流', value: 'cleaningCurrent' },
|
||||
{ label: '碱液浓度', value: 'alkaliConcentration' },
|
||||
{ label: '碱液温度', value: 'alkaliTemperature' },
|
||||
{ label: 'PH炉出口温度', value: 'phfExitStripTemp' },
|
||||
{ label: '加热段出口温度', value: 'rtfExitStripTemp' },
|
||||
{ label: '冷却段出口温度', value: 'jcsExitStripTemp' },
|
||||
{ label: '均衡段出口温度', value: 'scsExitStripTemp' },
|
||||
{ label: '锌锅温度', value: 'potTemperature' },
|
||||
{ label: '锌锅功率', value: 'zincPotPower' },
|
||||
{ label: '燃气消耗', value: 'gasConsumption' },
|
||||
{ label: '冷却塔温度', value: 'coolingTowerStripTemp' },
|
||||
{ label: '光整机张力', value: 'tensionBr5Tm' },
|
||||
{ label: 'TM出口速度', value: 'stripSpeedTmExit' },
|
||||
{ label: '拉矫延伸率', value: 'tlElongation' },
|
||||
{ label: '拉矫张力', value: 'tensionTlBr7' }
|
||||
],
|
||||
paramField: 'stripSpeed',
|
||||
treeProps: {
|
||||
children: 'children',
|
||||
label: 'label'
|
||||
},
|
||||
currentParam: null, // 当前选中的参数
|
||||
chart: null, // 图表实例
|
||||
chartData: [], // 图表数据
|
||||
timeStamps: [], // 时间戳
|
||||
originalTimeStamps: [], // 原始时间戳(用于采样)
|
||||
originalChartData: [], // 原始图表数据(用于采样)
|
||||
timeRange: '1', // 时间范围(分钟)
|
||||
socket: null, // 模拟socket
|
||||
latestValue: 0,
|
||||
maxValue: 0,
|
||||
minValue: 0,
|
||||
avgValue: 0,
|
||||
loading: false,
|
||||
maxXAxisLabels: 40 // X轴最多显示的标签数量
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
enCoilID: {
|
||||
handler(newVal) {
|
||||
console.log('enCoilID 变化:', newVal);
|
||||
if (newVal && this.pdoApi) {
|
||||
this.fetchChartData();
|
||||
} else {
|
||||
this.drawEmptyChart();
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
url: {
|
||||
handler(newVal) {
|
||||
console.log('url 变化:', newVal);
|
||||
if (newVal) {
|
||||
this.pdoApi = createFetch(newVal)
|
||||
} else {
|
||||
this.pdoApi = null
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// 初始化图表
|
||||
this.initChart();
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁图表实例和事件监听
|
||||
if (this.chart) {
|
||||
this.chart.dispose();
|
||||
}
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
},
|
||||
methods: {
|
||||
// 初始化图表
|
||||
initChart() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.chart) {
|
||||
this.chart = echarts.init(this.$refs.chart);
|
||||
// 监听窗口大小变化,调整图表尺寸
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
|
||||
// 初始显示
|
||||
if (this.enCoilID) {
|
||||
this.fetchChartData();
|
||||
} else {
|
||||
this.drawEmptyChart();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 处理窗口大小变化
|
||||
handleResize() {
|
||||
if (this.chart) {
|
||||
this.chart.resize();
|
||||
}
|
||||
},
|
||||
|
||||
// 获取图表数据
|
||||
fetchChartData() {
|
||||
if (!this.enCoilID || !this.paramField || !this.pdoApi) {
|
||||
this.drawNoDataChart();
|
||||
}
|
||||
this.loading = true;
|
||||
this.pdoApi.getSegmentList({
|
||||
enCoilID: this.enCoilID,
|
||||
paramField: this.paramField
|
||||
}).then(res => {
|
||||
// 假设res.data格式为[{ segNo: '时间戳', value: '数值' }]
|
||||
if (res.data && res.data.length) {
|
||||
this.processChartData(res.data);
|
||||
this.drawLineChart();
|
||||
console.log('有数据,绘制折线图')
|
||||
} else {
|
||||
this.drawNoDataChart();
|
||||
console.log('无数据,清空折线图')
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取数据失败:', err);
|
||||
this.drawErrorChart();
|
||||
}).finally(_ => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
// 处理图表数据
|
||||
processChartData(rawData) {
|
||||
// 排序数据(按segNo,假设是时间戳或序号)
|
||||
rawData.sort((a, b) => a.segNo - b.segNo);
|
||||
|
||||
// 保存原始数据
|
||||
this.originalTimeStamps = rawData.map(item => item.segNo);
|
||||
this.originalChartData = rawData.map(item => parseFloat(item.value));
|
||||
|
||||
// 根据数据量决定是否采样
|
||||
// if (rawData.length > this.maxXAxisLabels) {
|
||||
// // 数据采样 - 均匀采样
|
||||
// this.sampleData(rawData);
|
||||
// } else {
|
||||
// 数据量适中,直接使用全部数据
|
||||
this.timeStamps = this.originalTimeStamps;
|
||||
this.chartData = this.originalChartData;
|
||||
// }
|
||||
|
||||
// 计算统计值(基于原始数据)
|
||||
if (this.originalChartData.length) {
|
||||
this.latestValue = this.originalChartData[this.originalChartData.length - 1];
|
||||
this.maxValue = Math.max(...this.originalChartData);
|
||||
this.minValue = Math.min(...this.originalChartData);
|
||||
this.avgValue = this.originalChartData.reduce((sum, val) => sum + val, 0) / this.originalChartData.length;
|
||||
}
|
||||
},
|
||||
|
||||
// 数据采样方法
|
||||
sampleData(rawData) {
|
||||
const total = rawData.length;
|
||||
const sampleInterval = Math.ceil(total / this.maxXAxisLabels);
|
||||
|
||||
// 初始化采样数组
|
||||
this.timeStamps = [];
|
||||
this.chartData = [];
|
||||
|
||||
// 均匀采样
|
||||
for (let i = 0; i < total; i += sampleInterval) {
|
||||
this.timeStamps.push(rawData[i].segNo);
|
||||
this.chartData.push(parseFloat(rawData[i].value));
|
||||
}
|
||||
|
||||
// 确保最后一个数据点被包含
|
||||
if (this.timeStamps[this.timeStamps.length - 1] !== rawData[total - 1].segNo) {
|
||||
this.timeStamps.push(rawData[total - 1].segNo);
|
||||
this.chartData.push(parseFloat(rawData[total - 1].value));
|
||||
}
|
||||
},
|
||||
|
||||
// 绘制折线图
|
||||
drawLineChart() {
|
||||
if (!this.chart) return;
|
||||
|
||||
// 清理旧图层,避免“暂无数据”残留
|
||||
this.chart.clear();
|
||||
|
||||
const option = {
|
||||
backgroundColor: 'transparent',
|
||||
graphic: [], // 清除占位/无数据图层
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'line'
|
||||
},
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||
borderColor: '#d4d4d4',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#333'
|
||||
},
|
||||
formatter: (params) => {
|
||||
const param = params[0];
|
||||
return `
|
||||
序号: ${param.name}<br>
|
||||
值: ${param.value.toFixed(2)}<br>
|
||||
单位: ${this.getParamUnit()}
|
||||
`;
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '10%', // 增加底部空间,防止标签被截断
|
||||
top: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
// data: this.timeStamps,
|
||||
// axisLine: {
|
||||
// lineStyle: {
|
||||
// color: '#d4d4d4'
|
||||
// }
|
||||
// },
|
||||
// axisLabel: {
|
||||
// color: '#666',
|
||||
// rotate: 45,
|
||||
// // 动态计算标签显示间隔
|
||||
// interval: (index, value) => {
|
||||
// // 如果数据量大于maxXAxisLabels,按采样后的间隔显示
|
||||
// return index % Math.max(1, Math.ceil(this.timeStamps.length / this.maxXAxisLabels)) === 0;
|
||||
// }
|
||||
// },
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: this.getParamUnit(),
|
||||
nameLocation: 'middle',
|
||||
nameGap: 30,
|
||||
nameTextStyle: {
|
||||
color: '#666'
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#d4d4d4'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#666'
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#e8e8e8',
|
||||
type: 'dashed'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: this.getParamLabel(),
|
||||
type: 'line',
|
||||
data: this.chartData,
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 4,
|
||||
lineStyle: {
|
||||
color: '#666',
|
||||
width: 2
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#999'
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 0, color: 'rgba(153, 153, 153, 0.3)' },
|
||||
{ offset: 1, color: 'rgba(153, 153, 153, 0.05)' }
|
||||
]
|
||||
}
|
||||
},
|
||||
markPoint: {
|
||||
data: [
|
||||
{ type: 'max', name: '最大值' },
|
||||
{ type: 'min', name: '最小值' }
|
||||
],
|
||||
itemStyle: {
|
||||
color: '#999'
|
||||
},
|
||||
label: {
|
||||
color: '#333'
|
||||
}
|
||||
},
|
||||
markLine: {
|
||||
data: [
|
||||
{ type: 'average', name: '平均值' }
|
||||
],
|
||||
lineStyle: {
|
||||
color: '#999',
|
||||
type: 'dashed'
|
||||
},
|
||||
label: {
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.chart.setOption(option, true);
|
||||
},
|
||||
|
||||
// 绘制空图表(未选择实绩)
|
||||
drawEmptyChart() {
|
||||
if (!this.chart) return;
|
||||
|
||||
const option = {
|
||||
graphic: {
|
||||
elements: [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
style: {
|
||||
text: '请选择实绩',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
fill: '#999'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: []
|
||||
};
|
||||
|
||||
this.chart.setOption(option);
|
||||
},
|
||||
|
||||
// 绘制无数据图表
|
||||
drawNoDataChart() {
|
||||
if (!this.chart) return;
|
||||
|
||||
// 先清除原有图表内容
|
||||
this.chart.clear();
|
||||
|
||||
const option = {
|
||||
graphic: {
|
||||
elements: [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
style: {
|
||||
text: '暂无数据',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
fill: '#999'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: []
|
||||
};
|
||||
|
||||
this.chart.setOption(option);
|
||||
},
|
||||
|
||||
// 绘制错误图表
|
||||
drawErrorChart() {
|
||||
if (!this.chart) return;
|
||||
|
||||
const option = {
|
||||
graphic: {
|
||||
elements: [
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
style: {
|
||||
text: '数据加载失败',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
fill: '#f56c6c'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: []
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: []
|
||||
};
|
||||
|
||||
this.chart.setOption(option);
|
||||
},
|
||||
|
||||
// 处理参数变更
|
||||
handleParamChange() {
|
||||
// 清空现有数据
|
||||
this.chartData = [];
|
||||
this.timeStamps = [];
|
||||
this.originalTimeStamps = [];
|
||||
this.originalChartData = [];
|
||||
|
||||
// 重新获取数据
|
||||
if (this.enCoilID) {
|
||||
this.fetchChartData();
|
||||
}
|
||||
},
|
||||
|
||||
// 获取参数标签
|
||||
getParamLabel() {
|
||||
const param = this.paramFields.find(item => item.value === this.paramField);
|
||||
return param ? param.label : this.paramField;
|
||||
},
|
||||
|
||||
// 获取参数单位
|
||||
getParamUnit() {
|
||||
// 根据不同参数返回不同单位
|
||||
switch (this.paramField) {
|
||||
case 'stripSpeed':
|
||||
return 'm/s';
|
||||
case 'tensionPorBr1':
|
||||
case 'tensionPorBr2':
|
||||
return 'N';
|
||||
case 'cleaningVoltage':
|
||||
return 'V';
|
||||
case 'cleaningCurrent':
|
||||
return 'A';
|
||||
case 'alkaliConcentration':
|
||||
return '%';
|
||||
case 'alkaliTemperature':
|
||||
case 'phfExitStripTemp':
|
||||
case 'rtfExitStripTemp':
|
||||
case 'jcsExitStripTemp':
|
||||
case 'scsExitStripTemp':
|
||||
case 'potTemperature':
|
||||
case 'coolingTowerStripTemp':
|
||||
return '°C';
|
||||
case 'zincPotPower':
|
||||
return 'kW';
|
||||
case 'gasConsumption':
|
||||
return 'm³/h';
|
||||
case 'tensionBr5Tm':
|
||||
case 'tensionTlBr7':
|
||||
return 'N';
|
||||
case 'stripSpeedTmExit':
|
||||
return 'm/s';
|
||||
case 'tlElongation':
|
||||
return '%';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.monitoring-container {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.param-select {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 10;
|
||||
width: 150px;
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
border-color: #d4d4d4;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
579
klp-ui/src/views/lines/pdo/index.vue
Normal file
579
klp-ui/src/views/lines/pdo/index.vue
Normal file
@@ -0,0 +1,579 @@
|
||||
<template>
|
||||
<div class="pdo-container">
|
||||
<!-- 查询表单区域 -->
|
||||
<div class="pdo-header">
|
||||
<el-form :inline="true" :model="queryForm" ref="queryForm" label-width="100px" size="small">
|
||||
<el-form-item label="钢卷号" prop="coilid">
|
||||
<el-input v-model="queryForm.coilid" placeholder="请输入钢卷号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker v-model="queryForm.startDate" type="date" placeholder="选择开始日期" value-format="yyyy-MM-dd"
|
||||
clearable></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker v-model="queryForm.endDate" type="date" placeholder="选择结束日期" value-format="yyyy-MM-dd"
|
||||
clearable></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :loading="btnLoading" icon="el-icon-search">查询</el-button>
|
||||
<el-button @click="handleReset" icon="el-icon-refresh">重置</el-button>
|
||||
<el-button type="success" @click="handleAdd" icon="el-icon-plus">补录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 卡片网格布局 -->
|
||||
<div v-loading="tableLoading" class="card-grid-container">
|
||||
<el-row :gutter="10">
|
||||
<el-col v-for="(item, index) in tableData" :key="index" :xs="24" :sm="12" :md="8" :lg="6" :xl="5"
|
||||
class="card-col">
|
||||
<el-card class="parameter-card" shadow="never" :body-style="{ padding: '8px' }"
|
||||
:class="{ 'card-selected': currentRow.exitMatId === item.exitMatId }" @click.native="handleRowClick(item)">
|
||||
<div slot="header" class="card-header">
|
||||
<div class="card-title">成品卷: {{ item.exitMatId || '-' }}</div>
|
||||
<div class="card-subtitle">{{ item.entryMatId || '-' }} | {{ item.planNo || '-' }}</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="param-groups-row">
|
||||
<!-- 基本信息 -->
|
||||
<div class="param-group">
|
||||
<div class="group-title">基本信息</div>
|
||||
<div class="param-list">
|
||||
<div class="param-line">
|
||||
<span class="param-label">状态:</span>
|
||||
<span class="param-value">{{ item.status || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">钢种:</span>
|
||||
<span class="param-value">{{ item.steelGrade || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">产品类型:</span>
|
||||
<span class="param-value">{{ item.prodCode || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">客户:</span>
|
||||
<span class="param-value">{{ item.customer || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 来料信息 -->
|
||||
<div class="param-group">
|
||||
<div class="group-title">来料信息</div>
|
||||
<div class="param-list">
|
||||
<div class="param-line">
|
||||
<span class="param-label">厚度:</span>
|
||||
<span class="param-value">{{ item.entryThick || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">宽度:</span>
|
||||
<span class="param-value">{{ item.entryWidth || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">长度:</span>
|
||||
<span class="param-value">{{ item.entryLength || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">重量:</span>
|
||||
<span class="param-value">{{ item.entryWeight || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成品信息 -->
|
||||
<div class="param-group">
|
||||
<div class="group-title">成品信息</div>
|
||||
<div class="param-list">
|
||||
<div class="param-line">
|
||||
<span class="param-label">厚度:</span>
|
||||
<span class="param-value">{{ item.exitThickness || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">宽度:</span>
|
||||
<span class="param-value">{{ item.exitWidth || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">长度:</span>
|
||||
<span class="param-value">{{ item.exitLength || '-' }}</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<span class="param-label">重量:</span>
|
||||
<span class="param-value">{{ item.exitNetWeight || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<el-button size="mini" type="text" icon="el-icon-view" @click.stop="handlePrint(item)">打印</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click.stop="handleEdit(item)">操作</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" :loading="item.deleteLoading"
|
||||
@click.stop="handleDelete(item)">删除</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-if="tableData.length === 0 && !tableLoading" class="empty-data">
|
||||
<el-empty description="暂无数据"></el-empty>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计汇总和图表区域 -->
|
||||
<div class="statistics-container">
|
||||
<div class="statistics-header">
|
||||
<div class="selected-info" v-if="currentRow && currentRow.entryMatId">
|
||||
<i class="el-icon-check"></i>
|
||||
<span class="selected-label">已选中:</span>
|
||||
<span class="selected-value">成品卷 {{ currentRow.exitMatId }}</span>
|
||||
<span class="selected-detail" v-if="currentRow.entryMatId">(来料卷: {{ currentRow.entryMatId }})</span>
|
||||
</div>
|
||||
<div class="selected-info empty" v-else>
|
||||
<i class="el-icon-info"></i>
|
||||
<span>请选择上方卡片查看详情</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-row :gutter="15" class="statistics-content">
|
||||
<el-col :span="4" class="summary-col">
|
||||
<div class="summary-wrapper">
|
||||
<div class="summary-header">统计汇总</div>
|
||||
<pdo-summary :table-data="tableData" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="20" class="chart-col">
|
||||
<div class="chart-wrapper">
|
||||
<line-chart :enCoilID="currentRow.entryMatId" :url="activeUrl" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 编辑/新增弹窗:根据isAdd条件渲染内容 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="80%" :close-on-click-modal="false">
|
||||
<!-- 补录(新增):仅显示数据修正组件,无tab -->
|
||||
<div v-if="isAdd">
|
||||
<pdo-data-correction :detail="formData" :save-callback="handleSave"
|
||||
:save-loading="saveLoading"></pdo-data-correction>
|
||||
</div>
|
||||
|
||||
|
||||
<pdo-data-correction :detail="formData" :save-callback="handleSave"
|
||||
:save-loading="saveLoading"></pdo-data-correction>
|
||||
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="标签打印" :visible.sync="printOpen" width="600px" :close-on-click-modal="false">
|
||||
<div style="display: flex; justify-content: center; align-items: center;">
|
||||
<pdo-label-print :detail="formData"></pdo-label-print>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import createFetch from '@/api/l2/pdo'
|
||||
import LineChart from './components/line.vue'
|
||||
// 引入封装的两个组件
|
||||
import PdoDataCorrection from './components/DataCorrection.vue'
|
||||
import PdoLabelPrint from './components/LabelPrint.vue'
|
||||
import PdoSummary from './components/PdoSummary.vue'
|
||||
|
||||
export default {
|
||||
name: 'PdoManagement',
|
||||
dicts: ['pdo_plan_origin'],
|
||||
components: {
|
||||
LineChart,
|
||||
PdoDataCorrection, // 注册数据修正组件
|
||||
PdoLabelPrint, // 注册标签打印组件
|
||||
PdoSummary
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeUrl: '140.143.206.120:18081',
|
||||
queryForm: { coilid: '', startDate: '', endDate: '' },
|
||||
printOpen: false,
|
||||
tableData: [],
|
||||
tableLoading: false,
|
||||
btnLoading: false,
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新增实绩',
|
||||
// 新增:区分「补录(新增)」和「编辑」的状态标识
|
||||
isAdd: false,
|
||||
// 传递给子组件的详情数据(父组件仅做数据中转,不直接修改)
|
||||
formData: {},
|
||||
saveLoading: false,
|
||||
currentRow: {},
|
||||
socketType: [
|
||||
{ value: 'alarm', label: '报警' },
|
||||
{ value: 'track_position', label: '跟踪位置' },
|
||||
{ value: 'track_measure', label: '跟踪测量' },
|
||||
{ value: 'track_signal', label: '跟踪信号' },
|
||||
{ value: 'track_matmap', label: '跟踪料号' },
|
||||
{ value: 'calc_setup_result', label: '计算结果' }
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pdoApi = createFetch(this.activeUrl)
|
||||
this.getPdoList()
|
||||
},
|
||||
methods: {
|
||||
// 获取列表数据(保持不变)
|
||||
getPdoList() {
|
||||
this.tableLoading = true
|
||||
this.pdoApi.getPdoList(this.queryForm).then(res => {
|
||||
this.tableData = res.data.map(item => ({ ...item, deleteLoading: false }))
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
this.$message.error('获取数据失败')
|
||||
}).finally(() => {
|
||||
this.tableLoading = false
|
||||
this.btnLoading = false
|
||||
})
|
||||
},
|
||||
// 查询(保持不变)
|
||||
handleQuery() {
|
||||
this.btnLoading = true;
|
||||
this.getPdoList()
|
||||
},
|
||||
// 重置(保持不变)
|
||||
handleReset() {
|
||||
this.$refs.queryForm.resetFields();
|
||||
this.getPdoList()
|
||||
},
|
||||
// 行点击(保持不变)
|
||||
handleRowClick(row) {
|
||||
this.currentRow = row;
|
||||
},
|
||||
// 格式化时间
|
||||
formatTime(time) {
|
||||
if (!time) return '-';
|
||||
return this.parseTime(time, '{y}-{m}-{d} {h}:{i}:{s}');
|
||||
},
|
||||
// 补录(新增):设置isAdd=true,初始化空表单
|
||||
handleAdd() {
|
||||
this.isAdd = true; // 标记为「补录」
|
||||
this.dialogTitle = '新增实绩';
|
||||
// 初始化空表单数据(传递给子组件)
|
||||
this.formData = {
|
||||
subId: 0,
|
||||
startPosition: 0,
|
||||
endPosition: 0,
|
||||
planId: 0,
|
||||
entryThick: 0,
|
||||
entryWidth: 0,
|
||||
entryLength: 0,
|
||||
entryWeight: 0,
|
||||
weightTop: 0,
|
||||
weightBottom: 0,
|
||||
exitLength: 0,
|
||||
exitNetWeight: 0,
|
||||
theoryWeight: 0,
|
||||
actualWeight: 0,
|
||||
exitOuterDiameter: 0,
|
||||
exitThickness: 0,
|
||||
exitWidth: 0,
|
||||
lastFlag: false,
|
||||
separateFlag: false
|
||||
};
|
||||
this.dialogVisible = true
|
||||
},
|
||||
// 编辑:设置isAdd=false,赋值行数据
|
||||
handleEdit(row) {
|
||||
this.isAdd = false; // 标记为「编辑」
|
||||
this.dialogTitle = '编辑实绩';
|
||||
// 深拷贝行数据(避免直接修改表格数据)
|
||||
this.formData = JSON.parse(JSON.stringify(row));
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handlePrint(row) {
|
||||
// 深拷贝行数据(避免直接修改表格数据)
|
||||
this.formData = JSON.parse(JSON.stringify(row));
|
||||
this.printOpen = true
|
||||
},
|
||||
// 删除(保持不变)
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定删除成品卷 ${row.exitMatId}?`, '确认删除', { type: 'danger' }).then(() => {
|
||||
row.deleteLoading = true
|
||||
this.pdoApi.deletePdo(row.exitMatId, row.planId).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$message.success('删除成功');
|
||||
this.getPdoList()
|
||||
} else this.$message.error(res.msg || '删除失败')
|
||||
}).finally(() => row.deleteLoading = false)
|
||||
})
|
||||
},
|
||||
// 保存回调(子组件触发,处理实际API调用)
|
||||
handleSave(formData) {
|
||||
this.saveLoading = true
|
||||
// 根据是否有id判断是新增还是编辑
|
||||
const request = formData.id ? this.pdoApi.updatePdo(formData) : this.pdoApi.addPdo(formData)
|
||||
request.then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$message.success('保存成功');
|
||||
this.dialogVisible = false;
|
||||
this.getPdoList() // 刷新列表
|
||||
} else this.$message.error(res.msg || '保存失败')
|
||||
}).finally(() => {
|
||||
this.saveLoading = false
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pdo-container {
|
||||
height: calc(100vh - 84px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
background: #f5f5f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pdo-header {
|
||||
background: #ffffff;
|
||||
border: 1px solid #d4d4d4;
|
||||
border-radius: 2px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-grid-container {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding-right: 5px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #c0c0c0;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-col {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.parameter-card {
|
||||
border: 1px solid #d4d4d4;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.card-selected {
|
||||
border-color: #999;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
::v-deep .el-card__header {
|
||||
padding: 6px 8px;
|
||||
background: #f8f8f8;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #999;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
.card-title {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
margin-bottom: 2px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
.param-groups-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.param-group {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.group-title {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
margin-bottom: 4px;
|
||||
background: #f5f5f5;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
margin-left: -4px;
|
||||
margin-right: -4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.param-list {
|
||||
.param-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px dotted #e8e8e8;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.param-label {
|
||||
color: #777;
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.param-value {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
margin-top: 6px;
|
||||
|
||||
.el-button {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-data {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.statistics-container {
|
||||
flex: 0 0 auto;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d4d4d4;
|
||||
border-radius: 2px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
.statistics-header {
|
||||
padding: 8px 15px;
|
||||
background: #f8f8f8;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
|
||||
.selected-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
|
||||
&.empty {
|
||||
color: #999;
|
||||
|
||||
i {
|
||||
color: #bbb;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
color: #67c23a;
|
||||
margin-right: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.selected-label {
|
||||
color: #666;
|
||||
margin-right: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.selected-value {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.selected-detail {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.statistics-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.summary-col {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.summary-wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid #e8e8e8;
|
||||
padding-right: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.summary-header {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.chart-col {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
height: 100%;
|
||||
padding-left: 15px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user