Files
klp-mono/apps/l2/src/views/l2/plan/components/setupForm.vue
2026-01-07 20:15:34 +08:00

477 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div class="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>