477 lines
14 KiB
Vue
477 lines
14 KiB
Vue
<template>
|
||
<div class="setup-form-wrapper">
|
||
<div v-if="!canGenerate" class="setup-hint">
|
||
<el-alert type="info" :closable="false" title="工艺参数生成提示">
|
||
<div class="hint-text">请先补齐以下基础信息,系统才可以自动匹配推荐的工艺参数:</div>
|
||
<div class="hint-tags">
|
||
<el-tag v-for="label in missingFieldLabels" :key="label" size="mini" effect="dark">
|
||
{{ label }}
|
||
</el-tag>
|
||
</div>
|
||
</el-alert>
|
||
</div>
|
||
|
||
<el-row v-else :gutter="16">
|
||
<el-col>
|
||
<el-form :model="form" v-loading="loading">
|
||
<el-row :gutter="16">
|
||
<el-col :span="8">
|
||
<el-form-item label="开卷机张力">
|
||
<el-input v-model="form.porTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="入口活套张力">
|
||
<el-input v-model="form.celTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="清洗段张力">
|
||
<el-input v-model="form.cleanTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
|
||
<el-col :span="8">
|
||
<el-form-item label="钝化段张力">
|
||
<el-input v-model="form.passivationTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="出口活套张力">
|
||
<el-input v-model="form.cxlTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="卷取机张力">
|
||
<el-input v-model="form.trTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="16">
|
||
<el-col :span="8">
|
||
<el-form-item label="平整机入口张力">
|
||
<el-input v-model="form.levelerEntryTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="平整机出口张力">
|
||
<el-input v-model="form.levelerExitTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="矫直机出口张力">
|
||
<el-input v-model="form.straightenerExitTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<el-row :gutter="16">
|
||
<el-col :span="8">
|
||
<el-form-item label="炉区张力">
|
||
<el-input v-model="form.furTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item label="冷却塔张力">
|
||
<el-input v-model="form.towerTension" @change="syncModal" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</el-col>
|
||
|
||
<el-col v-if="showRecommendationPanel" :span="10">
|
||
<el-card class="recommend-card" shadow="never">
|
||
<div class="recommend-header">
|
||
<div>
|
||
<div class="title">推荐工艺参数</div>
|
||
<div class="subtitle">点击即可填入左侧表单</div>
|
||
</div>
|
||
<el-button type="text" @click="applyAll" :disabled="!hasRecommendation">全部套用</el-button>
|
||
</div>
|
||
|
||
<div v-if="!hasRecommendation" class="empty-recommend">
|
||
<el-empty description="暂无推荐数据" :image-size="100"></el-empty>
|
||
</div>
|
||
|
||
<div v-else class="recommend-groups">
|
||
<div v-for="group in parameterGroups" :key="group.title" class="recommend-group">
|
||
<div class="group-title">{{ group.title }}</div>
|
||
<div class="recommend-items">
|
||
<div
|
||
v-for="item in group.items"
|
||
:key="item.key"
|
||
class="recommend-item"
|
||
:class="{ disabled: !hasValue(recommendation[item.key]) }"
|
||
@click="applyRecommended(item.key)"
|
||
>
|
||
<div class="label">{{ item.label }}</div>
|
||
<div class="value">{{ formatValue(recommendation[item.key]) }}</div>
|
||
<i class="el-icon-top-right"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listTensionAllLine } from '@/api/business/fullLineTension'
|
||
import { listTensionLeveler } from '@/api/business/levelerTension'
|
||
import { listTensionStraightener } from '@/api/business/straightenerTension'
|
||
import { listTensionAnnealingFurnace } from '@/api/business/annealingFurnaceTension'
|
||
import { PARAMETER_GROUPS } from './setupRecommendationConfig'
|
||
|
||
const REQUIRED_FIELDS = ['entryThick', 'yieldPoint', 'steelGrade']
|
||
|
||
const FIELD_LABEL_MAP = {
|
||
entryThick: '入口厚度',
|
||
yieldPoint: '屈服强度',
|
||
steelGrade: '钢种'
|
||
}
|
||
|
||
const createDefaultForm = () => ({
|
||
// 关键基础字段(用于后端入库/关联;同时不影响推荐计算)
|
||
steelGrade: undefined,
|
||
|
||
// 全线张力
|
||
porTension: undefined,
|
||
celTension: undefined,
|
||
cleanTension: undefined,
|
||
passivationTension: undefined,
|
||
cxlTension: undefined,
|
||
trTension: undefined,
|
||
|
||
// 平整机张力
|
||
levelerEntryTension: undefined,
|
||
levelerExitTension: undefined,
|
||
|
||
// 矫直机张力
|
||
straightenerExitTension: undefined,
|
||
|
||
// 退火炉张力
|
||
furTension: undefined,
|
||
towerTension: undefined
|
||
})
|
||
|
||
export default {
|
||
props: {
|
||
income: {
|
||
type: Object,
|
||
required: true
|
||
},
|
||
steelGradeOptions: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
planHistory: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
showRecommendationPanel: {
|
||
type: Boolean,
|
||
default: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
form: createDefaultForm(),
|
||
recommendation: createDefaultForm(),
|
||
loading: false,
|
||
lastSignature: ''
|
||
}
|
||
},
|
||
computed: {
|
||
missingFieldKeys() {
|
||
return REQUIRED_FIELDS.filter((key) => !this.hasValue(this.income && this.income[key]))
|
||
},
|
||
missingFieldLabels() {
|
||
return this.missingFieldKeys.map((key) => FIELD_LABEL_MAP[key])
|
||
},
|
||
canGenerate() {
|
||
return this.missingFieldKeys.length === 0
|
||
},
|
||
hasRecommendation() {
|
||
return Object.values(this.recommendation).some((val) => this.hasValue(val))
|
||
},
|
||
parameterGroups() {
|
||
return PARAMETER_GROUPS
|
||
}
|
||
},
|
||
watch: {
|
||
income: {
|
||
handler() {
|
||
this.handleIncomeChange()
|
||
},
|
||
immediate: true,
|
||
deep: true
|
||
}
|
||
},
|
||
methods: {
|
||
hasValue(value) {
|
||
return !(value === null || value === undefined || value === '')
|
||
},
|
||
formatValue(val) {
|
||
return this.hasValue(val) ? val : '-'
|
||
},
|
||
generateSignature() {
|
||
// 只要这些字段变化,就重新推荐
|
||
return REQUIRED_FIELDS.map((key) => {
|
||
const v = this.income && this.income[key]
|
||
return v === null || v === undefined ? '' : v
|
||
}).join('|')
|
||
},
|
||
async handleIncomeChange() {
|
||
if (!this.canGenerate) {
|
||
this.lastSignature = ''
|
||
this.recommendation = createDefaultForm()
|
||
this.emitRecommendationChange()
|
||
return
|
||
}
|
||
const signature = this.generateSignature()
|
||
if (signature === this.lastSignature) return
|
||
this.lastSignature = signature
|
||
await this.fetchSetup()
|
||
},
|
||
|
||
// 评分排序:优先 steelGrade 匹配,其次厚度差、屈服差、最后按更新时间
|
||
scoreRow(row, cond) {
|
||
const rowSteel = ((row.steelGrade !== undefined && row.steelGrade !== null) ? row.steelGrade : (row.steel_grade !== undefined && row.steel_grade !== null ? row.steel_grade : '')).toString()
|
||
const rowThick = Number(row.thick)
|
||
const rowYield = Number((row.yieldStren !== undefined && row.yieldStren !== null) ? row.yieldStren : row.yield_stren)
|
||
|
||
const condSteel = ((cond.steelGrade === null || cond.steelGrade === undefined) ? '' : cond.steelGrade).toString()
|
||
const condThick = Number(cond.thick)
|
||
const condYield = Number(cond.yieldStren)
|
||
|
||
const steelMatch = condSteel && rowSteel ? (rowSteel === condSteel ? 1 : 0) : 0
|
||
const thickDiff = Number.isFinite(condThick) && Number.isFinite(rowThick) ? Math.abs(rowThick - condThick) : 999
|
||
const yieldDiff = Number.isFinite(condYield) && Number.isFinite(rowYield) ? Math.abs(rowYield - condYield) : 999
|
||
|
||
// 时间越新越好:用负的时间戳作为加分
|
||
const ts = row.updateTime ? new Date(row.updateTime).getTime() : (row.createTime ? new Date(row.createTime).getTime() : 0)
|
||
|
||
// 权重:钢种匹配(大权重),厚度差、屈服差(越小越好),时间(越新越好)
|
||
return steelMatch * 100000 - thickDiff * 1000 - yieldDiff * 10 + (ts ? ts / 1e10 : 0)
|
||
},
|
||
|
||
pickBest(rows, cond) {
|
||
const list = Array.isArray(rows) ? rows : []
|
||
if (!list.length) return null
|
||
const scored = list
|
||
.map((r) => ({ r, s: this.scoreRow(r, cond) }))
|
||
.sort((a, b) => b.s - a.s)
|
||
return scored[0].r
|
||
},
|
||
|
||
async fetchSetup() {
|
||
if (!this.canGenerate) return
|
||
this.loading = true
|
||
this.recommendation = createDefaultForm()
|
||
|
||
const { entryThick, yieldPoint, steelGrade } = this.income
|
||
const cond = {
|
||
steelGrade,
|
||
thick: entryThick,
|
||
yieldStren: yieldPoint
|
||
}
|
||
|
||
try {
|
||
// 方案B:分别拉四张表的 list,然后按已填条件打分选最优
|
||
const query = {
|
||
// list 接口支持条件过滤的话会减少数据量(后端已支持三字段)
|
||
steelGrade,
|
||
thick: entryThick,
|
||
yieldStren: yieldPoint
|
||
}
|
||
|
||
const [allLineRes, levelerRes, straightenerRes, furnaceRes] = await Promise.allSettled([
|
||
listTensionAllLine(query),
|
||
listTensionLeveler(query),
|
||
listTensionStraightener(query),
|
||
listTensionAnnealingFurnace(query)
|
||
])
|
||
|
||
// 统一取 rows(若依 TableDataInfo)
|
||
const allLineRows = allLineRes.status === 'fulfilled' ? (allLineRes.value.rows || []) : []
|
||
const levelerRows = levelerRes.status === 'fulfilled' ? (levelerRes.value.rows || []) : []
|
||
const straightenerRows = straightenerRes.status === 'fulfilled' ? (straightenerRes.value.rows || []) : []
|
||
const furnaceRows = furnaceRes.status === 'fulfilled' ? (furnaceRes.value.rows || []) : []
|
||
|
||
const bestAllLine = this.pickBest(allLineRows, cond)
|
||
const bestLeveler = this.pickBest(levelerRows, cond)
|
||
const bestStraightener = this.pickBest(straightenerRows, cond)
|
||
const bestFurnace = this.pickBest(furnaceRows, cond)
|
||
|
||
// 映射到推荐字段(按 setup_tension_new.sql 的 value1...)
|
||
if (bestAllLine) {
|
||
this.recommendation.porTension = bestAllLine.value1
|
||
this.recommendation.celTension = bestAllLine.value2
|
||
this.recommendation.cleanTension = bestAllLine.value3
|
||
this.recommendation.passivationTension = bestAllLine.value4
|
||
this.recommendation.cxlTension = bestAllLine.value5
|
||
this.recommendation.trTension = bestAllLine.value6
|
||
}
|
||
|
||
if (bestLeveler) {
|
||
this.recommendation.levelerEntryTension = bestLeveler.value1
|
||
this.recommendation.levelerExitTension = bestLeveler.value2
|
||
}
|
||
|
||
if (bestStraightener) {
|
||
this.recommendation.straightenerExitTension = bestStraightener.value1
|
||
}
|
||
|
||
if (bestFurnace) {
|
||
this.recommendation.furTension = bestFurnace.value1
|
||
this.recommendation.towerTension = bestFurnace.value2
|
||
}
|
||
} catch (e) {
|
||
console.error('获取推荐张力参数失败', e)
|
||
} finally {
|
||
this.loading = false
|
||
this.emitRecommendationChange()
|
||
}
|
||
},
|
||
|
||
emitRecommendationChange() {
|
||
this.$emit('recommendation-change', { ...this.recommendation })
|
||
},
|
||
applyRecommended(field) {
|
||
if (!this.hasValue(this.recommendation[field])) return
|
||
this.$set(this.form, field, this.recommendation[field])
|
||
this.syncModal()
|
||
},
|
||
applyAll() {
|
||
if (!this.hasRecommendation) return
|
||
Object.keys(this.recommendation).forEach((key) => {
|
||
if (this.hasValue(this.recommendation[key])) {
|
||
this.$set(this.form, key, this.recommendation[key])
|
||
}
|
||
})
|
||
this.syncModal()
|
||
},
|
||
syncModal() {
|
||
this.$emit('input', this.form)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.setup-form-wrapper {
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.setup-hint {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.hint-text {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
}
|
||
|
||
.hint-tags {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
.recommend-card {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.recommend-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.recommend-header .title {
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.recommend-header .subtitle {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
|
||
.recommend-groups {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding-right: 6px;
|
||
}
|
||
|
||
.recommend-group + .recommend-group {
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.group-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
color: #606266;
|
||
}
|
||
|
||
.recommend-items {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 8px;
|
||
}
|
||
|
||
.recommend-item {
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 4px;
|
||
padding: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
background: #f7f9fc;
|
||
}
|
||
|
||
.recommend-item.disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.recommend-item:not(.disabled):hover {
|
||
border-color: #409eff;
|
||
background: #ecf5ff;
|
||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
|
||
}
|
||
|
||
.recommend-item .label {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
|
||
.recommend-item .value {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.recommend-item i {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 8px;
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.empty-recommend {
|
||
padding: 40px 0;
|
||
}
|
||
</style>
|