Merge branch '0.8.X' of http://49.232.154.205:10100/DeXun/klp-oa into 0.8.X
This commit is contained in:
@@ -33,6 +33,14 @@ export function getTimingSegByEncoilId(encoilId) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计划详情 + SEG 实绩合并,后端一次返回,避免前端二次请求
|
||||||
|
export function getPlanWithSeg(encoilId) {
|
||||||
|
return request({
|
||||||
|
url: '/sql-server-api/plans-with-seg/' + encoilId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function getTimingSegByHotcoilId(encoilId) {
|
export function getTimingSegByHotcoilId(encoilId) {
|
||||||
return request({
|
return request({
|
||||||
url: '/sql-server-api/seg-by-hotcoil/' + encoilId,
|
url: '/sql-server-api/seg-by-hotcoil/' + encoilId,
|
||||||
|
|||||||
@@ -15,3 +15,7 @@ export function countProcessCoilRecord(versionId) {
|
|||||||
export function upsertProcessCoilRecord(data) {
|
export function upsertProcessCoilRecord(data) {
|
||||||
return request({ url: '/wms/processCoilRecord/upsert', method: 'post', data })
|
return request({ url: '/wms/processCoilRecord/upsert', method: 'post', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function batchRebindCoilRecord(data) {
|
||||||
|
return request({ url: '/wms/processCoilRecord/batchRebind', method: 'post', data })
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,3 +42,4 @@ export function delProcessSpec(specId) {
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,8 +91,7 @@ import * as echarts from 'echarts'
|
|||||||
import {
|
import {
|
||||||
getTimingPlanList,
|
getTimingPlanList,
|
||||||
getTimingPlanCount,
|
getTimingPlanCount,
|
||||||
getTimingPlanDetail,
|
getPlanWithSeg
|
||||||
getTimingSegByEncoilId
|
|
||||||
} from '@/api/l2/timing'
|
} from '@/api/l2/timing'
|
||||||
|
|
||||||
const PLAN_FIELDS = [
|
const PLAN_FIELDS = [
|
||||||
@@ -157,7 +156,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
// plain instance property — Vue 2 does NOT proxy underscore-prefixed names
|
// plain instance properties — Vue 2 does NOT proxy underscore-prefixed names
|
||||||
this.chartInstances = []
|
this.chartInstances = []
|
||||||
this.resizeHandler = null
|
this.resizeHandler = null
|
||||||
this.loadPlanCount()
|
this.loadPlanCount()
|
||||||
@@ -194,43 +193,36 @@ export default {
|
|||||||
if (!this.queryForm.coilId) return this.loadPlanList()
|
if (!this.queryForm.coilId) return this.loadPlanList()
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
const res = await getTimingPlanDetail(this.queryForm.coilId)
|
await this._loadFull(this.queryForm.coilId)
|
||||||
const row = res?.data?.firstRow || null
|
|
||||||
if (row) {
|
|
||||||
this.selectedPlan = row
|
|
||||||
await this.loadPerf(row)
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handlePlanRowClick(row) {
|
async handlePlanRowClick(row) {
|
||||||
this.selectedPlan = row
|
const encoilId = row.encoilid || row.coilid
|
||||||
|
if (!encoilId) return
|
||||||
|
this.disposeCharts()
|
||||||
this.perfSeries = null
|
this.perfSeries = null
|
||||||
this.perfSegCount = 0
|
this.perfSegCount = 0
|
||||||
this.disposeCharts()
|
|
||||||
await this.loadPerf(row)
|
|
||||||
},
|
|
||||||
async loadPerf(plan) {
|
|
||||||
const encoilId = plan.encoilid || plan.coilid
|
|
||||||
if (!encoilId) return
|
|
||||||
this.perfLoading = true
|
this.perfLoading = true
|
||||||
try {
|
try {
|
||||||
const res = await getTimingSegByEncoilId(encoilId)
|
await this._loadFull(encoilId)
|
||||||
|
} finally {
|
||||||
|
this.perfLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async _loadFull(encoilId) {
|
||||||
|
const res = await getPlanWithSeg(encoilId)
|
||||||
|
const plan = res?.data?.plan || res?.data?.firstRow || null
|
||||||
const series = res?.data?.series || null
|
const series = res?.data?.series || null
|
||||||
const rows = res?.data?.rows || []
|
const rows = res?.data?.rows || []
|
||||||
|
if (plan) this.selectedPlan = plan
|
||||||
this.perfSegCount = rows.length
|
this.perfSegCount = rows.length
|
||||||
this.perfSeries = series
|
this.perfSeries = series
|
||||||
if (series && rows.length) {
|
if (series && rows.length) {
|
||||||
await this.$nextTick()
|
await this.$nextTick()
|
||||||
this.renderCharts(series)
|
this.renderCharts(series)
|
||||||
}
|
}
|
||||||
} catch (_) {
|
|
||||||
this.perfSeries = null
|
|
||||||
this.perfSegCount = 0
|
|
||||||
} finally {
|
|
||||||
this.perfLoading = false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
disposeCharts() {
|
disposeCharts() {
|
||||||
if (this.resizeHandler) {
|
if (this.resizeHandler) {
|
||||||
|
|||||||
953
klp-ui/src/views/wms/processSpec/coilSpecBind.vue
Normal file
953
klp-ui/src/views/wms/processSpec/coilSpecBind.vue
Normal file
@@ -0,0 +1,953 @@
|
|||||||
|
<template>
|
||||||
|
<div class="coil-bind-page">
|
||||||
|
<!-- 筛选 + 操作一体栏 -->
|
||||||
|
<div class="filter-bar">
|
||||||
|
<el-form :inline="true" :model="query" size="small" class="filter-form">
|
||||||
|
<el-form-item label="出口卷号">
|
||||||
|
<el-input
|
||||||
|
v-model="query.coilId"
|
||||||
|
placeholder="模糊搜索"
|
||||||
|
clearable
|
||||||
|
style="width:150px"
|
||||||
|
@keyup.enter.native="handleSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="入口卷号">
|
||||||
|
<el-input
|
||||||
|
v-model="query.enCoilId"
|
||||||
|
placeholder="模糊搜索"
|
||||||
|
clearable
|
||||||
|
style="width:150px"
|
||||||
|
@keyup.enter.native="handleSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="异常">
|
||||||
|
<el-select v-model="query.hasAnomaly" placeholder="全部" clearable style="width:88px">
|
||||||
|
<el-option label="有异常" :value="1" />
|
||||||
|
<el-option label="正常" :value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" :loading="loading" @click="handleSearch">查询</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 右侧:始终可见的操作区 -->
|
||||||
|
<div class="toolbar-action">
|
||||||
|
<transition name="sel-fade">
|
||||||
|
<span v-if="selected.length" class="sel-badge">
|
||||||
|
已选 <b>{{ selected.length }}</b> 条
|
||||||
|
<el-button type="text" size="mini" style="color:#909399;padding:0 4px" @click="$refs.recordTable.clearSelection()">清除</el-button>
|
||||||
|
</span>
|
||||||
|
</transition>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
icon="el-icon-connection"
|
||||||
|
:disabled="!selected.length"
|
||||||
|
@click="openBindDialog"
|
||||||
|
>重新绑定规程方案</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<div class="table-wrap" v-loading="loading">
|
||||||
|
<el-table
|
||||||
|
ref="recordTable"
|
||||||
|
:data="rows"
|
||||||
|
size="small"
|
||||||
|
border
|
||||||
|
:height="tableHeight"
|
||||||
|
@selection-change="onSelectionChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="44" fixed />
|
||||||
|
<el-table-column type="index" width="44" label="序" align="center" fixed />
|
||||||
|
<el-table-column prop="coilId" label="出口卷号" width="160" fixed show-overflow-tooltip />
|
||||||
|
<el-table-column prop="enCoilId" label="入口卷号" width="160" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{ row }">{{ row.enCoilId || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="当前规程方案" min-width="220" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<template v-if="row.specName || row.specCode">
|
||||||
|
<span class="tag-spec">{{ row.specCode || '—' }}</span>
|
||||||
|
<span class="tag-spec-name">{{ row.specName }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else class="dim">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="版本" width="110">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<template v-if="row.versionCode">
|
||||||
|
<span class="tag-version">{{ row.versionCode }}</span>
|
||||||
|
<el-tag
|
||||||
|
v-if="row.isActive === 1"
|
||||||
|
type="success"
|
||||||
|
size="mini"
|
||||||
|
effect="dark"
|
||||||
|
style="border-radius:8px;margin-left:4px"
|
||||||
|
>生效</el-tag>
|
||||||
|
</template>
|
||||||
|
<span v-else class="dim">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="异常" width="76" align="center">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<el-tag
|
||||||
|
v-if="row.hasAnomaly === 1"
|
||||||
|
type="danger"
|
||||||
|
size="mini"
|
||||||
|
effect="plain"
|
||||||
|
disable-transitions
|
||||||
|
>{{ row.anomalyCnt }} 项</el-tag>
|
||||||
|
<el-tag v-else type="success" size="mini" effect="plain" disable-transitions>正常</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="检测时间" width="152">
|
||||||
|
<template slot-scope="{ row }">{{ fmtTime(row.processTime) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="记录时间" width="152">
|
||||||
|
<template slot-scope="{ row }">{{ fmtTime(row.createTime) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip>
|
||||||
|
<template slot-scope="{ row }">{{ row.remark || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="72" align="center" fixed="right">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<el-button type="text" size="mini" @click.stop="openDetail(row)">详情</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部分页栏 -->
|
||||||
|
<div class="bottom-bar">
|
||||||
|
<el-pagination
|
||||||
|
small
|
||||||
|
layout="total, sizes, prev, pager, next"
|
||||||
|
:total="total"
|
||||||
|
:page-size="query.pageSize"
|
||||||
|
:page-sizes="[20, 50, 100]"
|
||||||
|
:current-page="query.pageNum"
|
||||||
|
@size-change="v => { query.pageSize = v; query.pageNum = 1; loadRows() }"
|
||||||
|
@current-change="v => { query.pageNum = v; loadRows() }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════
|
||||||
|
钢卷参数详情 抽屉
|
||||||
|
══════════════════════════════════════ -->
|
||||||
|
<el-drawer
|
||||||
|
:visible.sync="detailDrawerVisible"
|
||||||
|
:title="detailRow ? ('钢卷参数详情 · ' + detailRow.coilId) : '钢卷参数详情'"
|
||||||
|
direction="rtl"
|
||||||
|
size="780px"
|
||||||
|
append-to-body
|
||||||
|
@close="onDetailClose"
|
||||||
|
>
|
||||||
|
<div class="detail-wrap" v-loading="detailLoading">
|
||||||
|
<!-- 基本信息卡 -->
|
||||||
|
<div class="detail-info-card" v-if="detailRow">
|
||||||
|
<div class="dic-row">
|
||||||
|
<span class="dic-label">出口卷号</span>
|
||||||
|
<span class="dic-val">{{ detailRow.coilId || '—' }}</span>
|
||||||
|
<span class="dic-label">入口卷号</span>
|
||||||
|
<span class="dic-val">{{ detailRow.enCoilId || '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dic-row">
|
||||||
|
<span class="dic-label">规程方案</span>
|
||||||
|
<span class="dic-val">
|
||||||
|
<span v-if="detailRow.specCode" class="tag-spec">{{ detailRow.specCode }}</span>
|
||||||
|
<span v-if="detailRow.specName" class="tag-spec-name">{{ detailRow.specName }}</span>
|
||||||
|
<span v-if="!detailRow.specCode && !detailRow.specName" class="dim">—</span>
|
||||||
|
</span>
|
||||||
|
<span class="dic-label">版本</span>
|
||||||
|
<span class="dic-val">
|
||||||
|
<span v-if="detailRow.versionCode" class="tag-version">{{ detailRow.versionCode }}</span>
|
||||||
|
<span v-else class="dim">—</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="dic-row">
|
||||||
|
<span class="dic-label">检测时间</span>
|
||||||
|
<span class="dic-val">{{ fmtTime(detailRow.processTime) }}</span>
|
||||||
|
<span class="dic-label">异常状态</span>
|
||||||
|
<span class="dic-val">
|
||||||
|
<el-tag v-if="detailRow.hasAnomaly === 1" type="danger" size="mini" effect="plain">{{ detailRow.anomalyCnt }} 项异常</el-tag>
|
||||||
|
<el-tag v-else type="success" size="mini" effect="plain">正常</el-tag>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 参数对比表 -->
|
||||||
|
<div v-if="!detailLoading && detailParams.length === 0" class="detail-empty">
|
||||||
|
暂无参数数据
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-for="group in detailParamGroups"
|
||||||
|
:key="group.planId"
|
||||||
|
class="param-group"
|
||||||
|
>
|
||||||
|
<div class="param-group-hd">
|
||||||
|
<span class="pg-segment">{{ group.segmentName || group.segmentType || '—' }}</span>
|
||||||
|
<span class="pg-point">{{ group.pointName }}
|
||||||
|
<span v-if="group.pointCode" class="pg-code">{{ group.pointCode }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
:data="group.params"
|
||||||
|
size="mini"
|
||||||
|
border
|
||||||
|
:row-class-name="paramRowClass"
|
||||||
|
>
|
||||||
|
<el-table-column label="参数名称" prop="paramName" min-width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column label="编码" prop="paramCode" width="90" show-overflow-tooltip />
|
||||||
|
<el-table-column label="单位" prop="unit" width="56" align="center">
|
||||||
|
<template slot-scope="{ row }">{{ row.unit || '—' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="设定目标" width="84" align="right">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<span :class="row._hasAnomaly ? 'val-anomaly' : ''">{{ fmtNum(row.targetValue) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="下限" width="72" align="right">
|
||||||
|
<template slot-scope="{ row }">{{ fmtNum(row.lowerLimit) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="上限" width="72" align="right">
|
||||||
|
<template slot-scope="{ row }">{{ fmtNum(row.upperLimit) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="实际最大" width="84" align="right">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<span v-if="row._hasAnomaly" :class="row._anomalyType === 'UPPER' ? 'val-danger' : 'val-ok'">{{ fmtNum(row._actualMax) }}</span>
|
||||||
|
<span v-else class="dim">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="实际最小" width="84" align="right">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<span v-if="row._hasAnomaly" :class="row._anomalyType === 'LOWER' ? 'val-danger' : 'val-ok'">{{ fmtNum(row._actualMin) }}</span>
|
||||||
|
<span v-else class="dim">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="偏差" width="72" align="right">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<span v-if="row._hasAnomaly" class="val-danger">
|
||||||
|
{{ fmtNum(row._anomalyType === 'UPPER' ? row._deviationMax : row._deviationMin) }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="dim">—</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" width="72" align="center">
|
||||||
|
<template slot-scope="{ row }">
|
||||||
|
<el-tag v-if="row._hasAnomaly" type="danger" size="mini" effect="plain">
|
||||||
|
{{ row._anomalyType === 'UPPER' ? '超上限' : row._anomalyType === 'LOWER' ? '低于下限' : '异常' }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-else type="success" size="mini" effect="plain">正常</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════════
|
||||||
|
重新绑定 对话框
|
||||||
|
══════════════════════════════════════ -->
|
||||||
|
<el-dialog
|
||||||
|
title="重新绑定规程方案"
|
||||||
|
:visible.sync="bindDialogVisible"
|
||||||
|
width="960px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="resetBindDialog"
|
||||||
|
>
|
||||||
|
<!-- ① 摘要条 — 始终可见 -->
|
||||||
|
<div :class="['summary-bar', selectedVersion ? 'summary-bar--ready' : 'summary-bar--idle']">
|
||||||
|
<template v-if="selectedVersion">
|
||||||
|
<i class="el-icon-success summary-icon" />
|
||||||
|
<span>
|
||||||
|
将把
|
||||||
|
<b class="s-count">{{ selected.length }}</b>
|
||||||
|
条记录重新绑定至规程
|
||||||
|
<span class="s-spec">{{ selectedSpec.specName }}</span>
|
||||||
|
<span class="s-sep">›</span>
|
||||||
|
版本
|
||||||
|
<span class="s-version">{{ selectedVersion.versionCode }}</span>
|
||||||
|
</span>
|
||||||
|
<div class="summary-remove-opt">
|
||||||
|
<el-checkbox v-model="removeOldRecord" :disabled="!singleSourceVersion">
|
||||||
|
同时从原版本移除
|
||||||
|
<span v-if="!singleSourceVersion" class="dim" style="margin-left:4px">(选中记录来自多个版本时不可用)</span>
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<i class="el-icon-info summary-icon summary-icon--idle" />
|
||||||
|
<span class="s-idle">请先在下方选择目标规程,再选择版本后点击「确认绑定」</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ② 左右选择区 -->
|
||||||
|
<div class="bind-body">
|
||||||
|
<!-- 左:规程列表 -->
|
||||||
|
<div class="spec-col">
|
||||||
|
<div class="col-hd">规程列表</div>
|
||||||
|
<div class="spec-filter">
|
||||||
|
<el-input
|
||||||
|
v-model="specKeyword"
|
||||||
|
placeholder="搜索名称 / 编码"
|
||||||
|
size="mini"
|
||||||
|
clearable
|
||||||
|
prefix-icon="el-icon-search"
|
||||||
|
@input="filterSpecs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="spec-list" v-loading="specLoading">
|
||||||
|
<div
|
||||||
|
v-for="spec in filteredSpecList"
|
||||||
|
:key="spec.specId"
|
||||||
|
:class="['spec-item', { 'spec-item--active': selectedSpec && selectedSpec.specId === spec.specId }]"
|
||||||
|
@click="selectSpec(spec)"
|
||||||
|
>
|
||||||
|
<div class="spec-item__name">{{ spec.specName }}</div>
|
||||||
|
<div class="spec-item__code">{{ spec.specCode }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!specLoading && !filteredSpecList.length" class="list-empty">暂无规程</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-divider" />
|
||||||
|
|
||||||
|
<!-- 右:版本列表 -->
|
||||||
|
<div class="version-col">
|
||||||
|
<template v-if="!selectedSpec">
|
||||||
|
<div class="version-placeholder">
|
||||||
|
<i class="el-icon-back" style="font-size:18px;color:#c0c4cc;margin-right:6px" />
|
||||||
|
请在左侧选择规程
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="col-hd">
|
||||||
|
<span class="spec-name-label">{{ selectedSpec.specName }}</span>
|
||||||
|
<span class="spec-code-chip">{{ selectedSpec.specCode }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectedSpec.remark" class="spec-remark">{{ selectedSpec.remark }}</div>
|
||||||
|
<div class="version-list" v-loading="versionLoading">
|
||||||
|
<div v-if="!versionLoading && !versionList.length" class="list-empty">
|
||||||
|
该规程暂无版本,请先在规程管理中创建
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="ver in versionList"
|
||||||
|
:key="ver.versionId"
|
||||||
|
:class="['version-card', {
|
||||||
|
'version-card--selected': selectedVersion && selectedVersion.versionId === ver.versionId,
|
||||||
|
'version-card--obsolete': ver.status === 'OBSOLETE'
|
||||||
|
}]"
|
||||||
|
@click="ver.status !== 'OBSOLETE' && selectVersion(ver)"
|
||||||
|
>
|
||||||
|
<div class="vc-body">
|
||||||
|
<div class="vc-row1">
|
||||||
|
<span class="vc-code">{{ ver.versionCode }}</span>
|
||||||
|
<el-tag :type="versionStatusType(ver.status)" size="mini" effect="plain" style="border-radius:10px;margin-left:6px">
|
||||||
|
{{ versionStatusLabel(ver.status) }}
|
||||||
|
</el-tag>
|
||||||
|
<el-tag v-if="ver.isActive === 1" type="success" size="mini" effect="dark" style="border-radius:10px;margin-left:4px">
|
||||||
|
当前生效
|
||||||
|
</el-tag>
|
||||||
|
<span v-if="ver.status === 'OBSOLETE'" class="obsolete-note">(已作废,不可选)</span>
|
||||||
|
</div>
|
||||||
|
<div class="vc-row2">
|
||||||
|
创建于 {{ (ver.createTime || '').substring(0, 16) || '—' }}
|
||||||
|
<template v-if="ver.updateTime && ver.updateTime !== ver.createTime">
|
||||||
|
· 更新于 {{ (ver.updateTime || '').substring(0, 16) }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-if="ver.remark" class="vc-remark">{{ ver.remark }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="vc-check">
|
||||||
|
<i v-if="selectedVersion && selectedVersion.versionId === ver.versionId" class="el-icon-circle-check" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div slot="footer" class="bind-footer">
|
||||||
|
<el-button size="small" @click="bindDialogVisible = false">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
:disabled="!selectedVersion"
|
||||||
|
:loading="bindLoading"
|
||||||
|
@click="confirmBind"
|
||||||
|
>确认绑定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listProcessCoilRecord, batchRebindCoilRecord } from '@/api/wms/processCoilRecord'
|
||||||
|
import { listProcessSpec } from '@/api/wms/processSpec'
|
||||||
|
import { listProcessSpecVersion } from '@/api/wms/processSpecVersion'
|
||||||
|
import { listProcessPlan } from '@/api/wms/processPlan'
|
||||||
|
import { listProcessPlanParam } from '@/api/wms/processPlanParam'
|
||||||
|
import { listAllProcessAnomaly } from '@/api/wms/processAnomaly'
|
||||||
|
|
||||||
|
const VERSION_STATUS = [
|
||||||
|
{ value: 'DRAFT', label: '草稿' },
|
||||||
|
{ value: 'PUBLISHED', label: '已发布' },
|
||||||
|
{ value: 'OBSOLETE', label: '已作废' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CoilSpecBind',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
rows: [],
|
||||||
|
total: 0,
|
||||||
|
selected: [],
|
||||||
|
query: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
coilId: '',
|
||||||
|
enCoilId: '',
|
||||||
|
hasAnomaly: null
|
||||||
|
},
|
||||||
|
tableHeight: 'calc(100vh - 218px)',
|
||||||
|
|
||||||
|
detailDrawerVisible: false,
|
||||||
|
detailLoading: false,
|
||||||
|
detailRow: null,
|
||||||
|
detailParams: [],
|
||||||
|
|
||||||
|
bindDialogVisible: false,
|
||||||
|
removeOldRecord: false,
|
||||||
|
specLoading: false,
|
||||||
|
specList: [],
|
||||||
|
filteredSpecList: [],
|
||||||
|
specKeyword: '',
|
||||||
|
selectedSpec: null,
|
||||||
|
versionLoading: false,
|
||||||
|
versionList: [],
|
||||||
|
selectedVersion: null,
|
||||||
|
bindLoading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
detailParamGroups() {
|
||||||
|
const planMap = {}
|
||||||
|
for (const p of this.detailParams) {
|
||||||
|
const key = p._planId
|
||||||
|
if (!planMap[key]) {
|
||||||
|
planMap[key] = {
|
||||||
|
planId: p._planId,
|
||||||
|
segmentType: p._segmentType,
|
||||||
|
segmentName: p._segmentName,
|
||||||
|
pointName: p._pointName,
|
||||||
|
pointCode: p._pointCode,
|
||||||
|
sortOrder: p._sortOrder,
|
||||||
|
params: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
planMap[key].params.push(p)
|
||||||
|
}
|
||||||
|
return Object.values(planMap).sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
|
||||||
|
},
|
||||||
|
// 选中行是否全来自同一个版本(决定是否可以"移除旧记录")
|
||||||
|
singleSourceVersion() {
|
||||||
|
if (!this.selected.length) return false
|
||||||
|
const vids = [...new Set(this.selected.map(r => r.versionId))]
|
||||||
|
return vids.length === 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.loadRows()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadRows() {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const params = { ...this.query }
|
||||||
|
if (!params.coilId) delete params.coilId
|
||||||
|
if (!params.enCoilId) delete params.enCoilId
|
||||||
|
if (params.hasAnomaly === null) delete params.hasAnomaly
|
||||||
|
const res = await listProcessCoilRecord(params)
|
||||||
|
this.rows = res.rows || []
|
||||||
|
this.total = res.total || 0
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.query.pageNum = 1
|
||||||
|
this.loadRows()
|
||||||
|
},
|
||||||
|
handleReset() {
|
||||||
|
this.query = { pageNum: 1, pageSize: 20, coilId: '', enCoilId: '', hasAnomaly: null }
|
||||||
|
this.loadRows()
|
||||||
|
},
|
||||||
|
onSelectionChange(val) {
|
||||||
|
this.selected = val
|
||||||
|
if (!this.singleSourceVersion) this.removeOldRecord = false
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── 绑定对话框 ──────────────────────────────
|
||||||
|
openBindDialog() {
|
||||||
|
this.specKeyword = ''
|
||||||
|
this.selectedSpec = null
|
||||||
|
this.selectedVersion = null
|
||||||
|
this.versionList = []
|
||||||
|
this.removeOldRecord = false
|
||||||
|
this.bindDialogVisible = true
|
||||||
|
this.loadSpecs()
|
||||||
|
},
|
||||||
|
resetBindDialog() {
|
||||||
|
this.selectedSpec = null
|
||||||
|
this.selectedVersion = null
|
||||||
|
this.versionList = []
|
||||||
|
this.specKeyword = ''
|
||||||
|
this.removeOldRecord = false
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadSpecs() {
|
||||||
|
this.specLoading = true
|
||||||
|
try {
|
||||||
|
const res = await listProcessSpec({ pageNum: 1, pageSize: 200 })
|
||||||
|
this.specList = res.rows || []
|
||||||
|
this.filteredSpecList = this.specList
|
||||||
|
} finally {
|
||||||
|
this.specLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filterSpecs() {
|
||||||
|
const kw = (this.specKeyword || '').trim().toLowerCase()
|
||||||
|
this.filteredSpecList = kw
|
||||||
|
? this.specList.filter(s =>
|
||||||
|
(s.specName || '').toLowerCase().includes(kw) ||
|
||||||
|
(s.specCode || '').toLowerCase().includes(kw)
|
||||||
|
)
|
||||||
|
: this.specList
|
||||||
|
},
|
||||||
|
selectSpec(spec) {
|
||||||
|
this.selectedSpec = spec
|
||||||
|
this.selectedVersion = null
|
||||||
|
this.versionList = []
|
||||||
|
this.loadVersions(spec.specId)
|
||||||
|
},
|
||||||
|
async loadVersions(specId) {
|
||||||
|
this.versionLoading = true
|
||||||
|
try {
|
||||||
|
const res = await listProcessSpecVersion({ specId, pageNum: 1, pageSize: 200 })
|
||||||
|
this.versionList = res.rows || []
|
||||||
|
} finally {
|
||||||
|
this.versionLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectVersion(ver) {
|
||||||
|
this.selectedVersion = ver
|
||||||
|
},
|
||||||
|
|
||||||
|
async confirmBind() {
|
||||||
|
if (!this.selectedVersion || !this.selected.length) return
|
||||||
|
this.bindLoading = true
|
||||||
|
try {
|
||||||
|
const coilIds = this.selected.map(r => r.coilId)
|
||||||
|
const oldVersionId = (this.removeOldRecord && this.singleSourceVersion)
|
||||||
|
? this.selected[0].versionId
|
||||||
|
: null
|
||||||
|
await batchRebindCoilRecord({
|
||||||
|
coilIds,
|
||||||
|
newVersionId: this.selectedVersion.versionId,
|
||||||
|
oldVersionId
|
||||||
|
})
|
||||||
|
this.$modal.msgSuccess(
|
||||||
|
`已成功将 ${this.selected.length} 条记录绑定至【${this.selectedSpec.specName} / ${this.selectedVersion.versionCode}】`
|
||||||
|
)
|
||||||
|
this.bindDialogVisible = false
|
||||||
|
this.$refs.recordTable.clearSelection()
|
||||||
|
this.loadRows()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
this.bindLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── 详情抽屉 ──────────────────────────────────
|
||||||
|
openDetail(row) {
|
||||||
|
this.detailRow = row
|
||||||
|
this.detailParams = []
|
||||||
|
this.detailDrawerVisible = true
|
||||||
|
this.loadDetailData(row)
|
||||||
|
},
|
||||||
|
onDetailClose() {
|
||||||
|
this.detailRow = null
|
||||||
|
this.detailParams = []
|
||||||
|
},
|
||||||
|
async loadDetailData(row) {
|
||||||
|
if (!row.versionId) return
|
||||||
|
this.detailLoading = true
|
||||||
|
try {
|
||||||
|
const [plansRes, anomalyRes] = await Promise.all([
|
||||||
|
listProcessPlan({ versionId: row.versionId, pageNum: 1, pageSize: 200 }),
|
||||||
|
listAllProcessAnomaly({ coilId: row.coilId, versionId: row.versionId })
|
||||||
|
])
|
||||||
|
const plans = plansRes.rows || []
|
||||||
|
const anomalyList = anomalyRes.data || []
|
||||||
|
const anomalyMap = {}
|
||||||
|
for (const a of anomalyList) {
|
||||||
|
anomalyMap[a.paramCode] = a
|
||||||
|
}
|
||||||
|
|
||||||
|
const allParams = []
|
||||||
|
const paramPromises = plans.map(plan =>
|
||||||
|
listProcessPlanParam({ planId: plan.planId, pageNum: 1, pageSize: 500 })
|
||||||
|
.then(res => {
|
||||||
|
const params = res.rows || []
|
||||||
|
for (const p of params) {
|
||||||
|
const anomaly = anomalyMap[p.paramCode] || null
|
||||||
|
allParams.push({
|
||||||
|
...p,
|
||||||
|
_planId: plan.planId,
|
||||||
|
_segmentType: plan.segmentType,
|
||||||
|
_segmentName: plan.segmentName,
|
||||||
|
_pointName: plan.pointName,
|
||||||
|
_pointCode: plan.pointCode,
|
||||||
|
_sortOrder: plan.sortOrder,
|
||||||
|
_hasAnomaly: !!anomaly,
|
||||||
|
_anomalyType: anomaly ? anomaly.anomalyType : null,
|
||||||
|
_actualMax: anomaly ? anomaly.actualMax : null,
|
||||||
|
_actualMin: anomaly ? anomaly.actualMin : null,
|
||||||
|
_deviationMax: anomaly ? anomaly.deviationMax : null,
|
||||||
|
_deviationMin: anomaly ? anomaly.deviationMin : null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
await Promise.all(paramPromises)
|
||||||
|
this.detailParams = allParams
|
||||||
|
} finally {
|
||||||
|
this.detailLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
paramRowClass({ row }) {
|
||||||
|
return row._hasAnomaly ? 'row-anomaly' : ''
|
||||||
|
},
|
||||||
|
fmtNum(val) {
|
||||||
|
if (val === null || val === undefined || val === '') return '—'
|
||||||
|
const n = Number(val)
|
||||||
|
return isNaN(n) ? val : n.toFixed(3).replace(/\.?0+$/, '')
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── 工具 ──────────────────────────────────
|
||||||
|
fmtTime(val) {
|
||||||
|
if (!val) return '—'
|
||||||
|
return String(val).substring(0, 16)
|
||||||
|
},
|
||||||
|
versionStatusType(status) {
|
||||||
|
return { DRAFT: '', PUBLISHED: 'success', OBSOLETE: 'info' }[status] || ''
|
||||||
|
},
|
||||||
|
versionStatusLabel(status) {
|
||||||
|
return (VERSION_STATUS.find(s => s.value === status) || {}).label || status || '—'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* ── 页面 ── */
|
||||||
|
.coil-bind-page {
|
||||||
|
padding: 12px 16px;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 筛选 + 操作一体栏 ── */
|
||||||
|
.filter-bar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
}
|
||||||
|
.filter-form { flex: 1; min-width: 0; }
|
||||||
|
.filter-bar .el-form-item { margin-bottom: 6px; }
|
||||||
|
.toolbar-action {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
.sel-badge {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #606266;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.sel-badge b { color: #5F7BA0; font-size: 15px; margin: 0 2px; }
|
||||||
|
.sel-fade-enter-active, .sel-fade-leave-active { transition: opacity .2s; }
|
||||||
|
.sel-fade-enter, .sel-fade-leave-to { opacity: 0; }
|
||||||
|
|
||||||
|
/* ── 表格 ── */
|
||||||
|
.table-wrap {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.tag-spec {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 7px;
|
||||||
|
border-radius: 3px 0 0 3px;
|
||||||
|
background: #ecf5ff;
|
||||||
|
color: #3a6ea8;
|
||||||
|
font-size: 11px;
|
||||||
|
border: 1px solid #b3d8ff;
|
||||||
|
}
|
||||||
|
.tag-spec-name {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 7px;
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
background: #f5f7fa;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 11px;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
.tag-version {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 8px;
|
||||||
|
background: #f0f9eb;
|
||||||
|
color: #4a8a2a;
|
||||||
|
font-size: 11px;
|
||||||
|
border: 1px solid #c2e7b0;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.dim { font-size: 12px; color: #c0c4cc; }
|
||||||
|
|
||||||
|
/* ── 底部分页 ── */
|
||||||
|
.bottom-bar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 摘要条 ── */
|
||||||
|
.summary-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
transition: background .2s, border-color .2s;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.summary-bar--idle { background: #f4f4f5; border: 1px solid #e9e9eb; color: #909399; }
|
||||||
|
.summary-bar--ready { background: #ecf5ff; border: 1px solid #b3d8ff; color: #303133; }
|
||||||
|
.summary-icon { font-size: 16px; flex-shrink: 0; color: #5F7BA0; margin-top: 2px; }
|
||||||
|
.summary-icon--idle { color: #c0c4cc; }
|
||||||
|
.s-count { font-size: 15px; color: #5F7BA0; margin: 0 2px; }
|
||||||
|
.s-spec { font-weight: 600; color: #303133; margin: 0 3px; }
|
||||||
|
.s-sep { color: #c0c4cc; margin: 0 3px; }
|
||||||
|
.s-version { font-weight: 600; color: #5F7BA0; }
|
||||||
|
.s-idle { color: #909399; }
|
||||||
|
.summary-remove-opt {
|
||||||
|
margin-left: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 对话框主体 ── */
|
||||||
|
.bind-body {
|
||||||
|
display: flex;
|
||||||
|
height: 460px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.col-hd {
|
||||||
|
padding: 10px 14px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
background: #fafafa;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.col-divider { width: 1px; background: #ebeef5; flex-shrink: 0; }
|
||||||
|
|
||||||
|
/* 左:规程列 */
|
||||||
|
.spec-col {
|
||||||
|
width: 256px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.spec-filter { padding: 8px 10px; border-bottom: 1px solid #f0f2f5; flex-shrink: 0; }
|
||||||
|
.spec-list { flex: 1; overflow-y: auto; padding: 4px 0; }
|
||||||
|
.spec-item {
|
||||||
|
padding: 9px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
transition: background .12s;
|
||||||
|
}
|
||||||
|
.spec-item:hover { background: #f5f7fa; }
|
||||||
|
.spec-item--active { background: #ecf5ff !important; border-left-color: #5F7BA0; }
|
||||||
|
.spec-item__name { font-size: 13px; font-weight: 500; color: #303133; line-height: 1.4; }
|
||||||
|
.spec-item__code { font-size: 11px; color: #909399; margin-top: 2px; }
|
||||||
|
|
||||||
|
/* 右:版本列 */
|
||||||
|
.version-col {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.spec-name-label { font-size: 13px; font-weight: 600; color: #303133; }
|
||||||
|
.spec-code-chip {
|
||||||
|
font-size: 11px; color: #909399;
|
||||||
|
background: #f0f2f5; padding: 1px 8px; border-radius: 10px;
|
||||||
|
}
|
||||||
|
.spec-remark {
|
||||||
|
font-size: 12px; color: #909399;
|
||||||
|
padding: 5px 14px; background: #fafbfc;
|
||||||
|
border-bottom: 1px solid #f0f2f5; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.version-placeholder {
|
||||||
|
flex: 1; display: flex; align-items: center; justify-content: center;
|
||||||
|
color: #c0c4cc; font-size: 13px;
|
||||||
|
}
|
||||||
|
.version-list {
|
||||||
|
flex: 1; overflow-y: auto; padding: 10px 12px;
|
||||||
|
display: flex; flex-direction: column; gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 版本卡片 */
|
||||||
|
.version-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color .15s, box-shadow .15s, background .15s;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.version-card:hover:not(.version-card--obsolete) {
|
||||||
|
border-color: #5F7BA0;
|
||||||
|
box-shadow: 0 2px 8px rgba(95,123,160,.12);
|
||||||
|
}
|
||||||
|
.version-card--selected {
|
||||||
|
border-color: #5F7BA0 !important;
|
||||||
|
background: #f0f7ff !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(95,123,160,.18) !important;
|
||||||
|
}
|
||||||
|
.version-card--obsolete { opacity: .5; cursor: not-allowed; background: #fafafa; }
|
||||||
|
.vc-body { flex: 1; min-width: 0; }
|
||||||
|
.vc-row1 { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; margin-bottom: 5px; }
|
||||||
|
.vc-code { font-size: 14px; font-weight: 600; color: #303133; }
|
||||||
|
.obsolete-note { font-size: 11px; color: #c0c4cc; margin-left: 4px; }
|
||||||
|
.vc-row2 { font-size: 11px; color: #c0c4cc; margin-bottom: 3px; }
|
||||||
|
.vc-remark { font-size: 12px; color: #909399; margin-top: 4px; word-break: break-all; }
|
||||||
|
.vc-check { font-size: 18px; color: #5F7BA0; flex-shrink: 0; margin-left: 10px; align-self: center; }
|
||||||
|
|
||||||
|
/* 底部 */
|
||||||
|
.bind-footer { display: flex; justify-content: flex-end; gap: 8px; }
|
||||||
|
.list-empty { padding: 32px 0; text-align: center; font-size: 13px; color: #c0c4cc; }
|
||||||
|
|
||||||
|
/* ── 详情抽屉 ── */
|
||||||
|
.detail-wrap {
|
||||||
|
padding: 16px 20px 32px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.detail-info-card {
|
||||||
|
background: #f8f9fb;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.dic-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.dic-row:last-child { margin-bottom: 0; }
|
||||||
|
.dic-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 56px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.dic-val {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #303133;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.param-group { margin-bottom: 20px; }
|
||||||
|
.param-group-hd {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
.pg-segment {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #fff;
|
||||||
|
background: #5F7BA0;
|
||||||
|
padding: 1px 9px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.pg-point { font-size: 13px; font-weight: 600; color: #303133; }
|
||||||
|
.pg-code { font-size: 11px; color: #909399; margin-left: 5px; }
|
||||||
|
.val-danger { color: #f56c6c; font-weight: 600; }
|
||||||
|
.val-ok { color: #67c23a; }
|
||||||
|
.val-anomaly { color: #e6a23c; font-weight: 600; }
|
||||||
|
.detail-empty {
|
||||||
|
padding: 48px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
::v-deep .row-anomaly td { background: #fff8f8 !important; }
|
||||||
|
::v-deep .el-drawer__header { padding: 16px 20px 12px; font-size: 15px; font-weight: 600; color: #303133; margin-bottom: 0; border-bottom: 1px solid #f0f2f5; }
|
||||||
|
::v-deep .el-drawer__body { overflow: hidden; }
|
||||||
|
|
||||||
|
/* 覆盖 */
|
||||||
|
::v-deep .el-button--primary {
|
||||||
|
color: #fff !important; background: #5F7BA0 !important; border-color: #5F7BA0 !important;
|
||||||
|
}
|
||||||
|
::v-deep .el-button--primary:hover,
|
||||||
|
::v-deep .el-button--primary:focus {
|
||||||
|
background: #4d6a8e !important; border-color: #4d6a8e !important;
|
||||||
|
}
|
||||||
|
::v-deep .el-button--primary.is-disabled { opacity: .5; }
|
||||||
|
::v-deep .el-table .el-table__row:hover td { background: #f5f9ff !important; }
|
||||||
|
</style>
|
||||||
@@ -7,6 +7,7 @@ import com.klp.common.core.page.TableDataInfo;
|
|||||||
import com.klp.domain.bo.WmsProcessCoilRecordBo;
|
import com.klp.domain.bo.WmsProcessCoilRecordBo;
|
||||||
import com.klp.domain.vo.WmsProcessCoilRecordVo;
|
import com.klp.domain.vo.WmsProcessCoilRecordVo;
|
||||||
import com.klp.service.IWmsProcessCoilRecordService;
|
import com.klp.service.IWmsProcessCoilRecordService;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
@@ -42,10 +43,28 @@ public class WmsProcessCoilRecordController extends BaseController {
|
|||||||
return R.ok(wmsProcessCoilRecordService.countByVersion(versionId));
|
return R.ok(wmsProcessCoilRecordService.countByVersion(versionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增或更新(幂等接口,前端每次点击钢卷行时调用) */
|
/** 新增或更新(幂等接口) */
|
||||||
@PostMapping("/upsert")
|
@PostMapping("/upsert")
|
||||||
public R<Void> upsert(@RequestBody WmsProcessCoilRecordBo bo) {
|
public R<Void> upsert(@RequestBody WmsProcessCoilRecordBo bo) {
|
||||||
wmsProcessCoilRecordService.upsert(bo);
|
wmsProcessCoilRecordService.upsert(bo);
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量重绑:将所选钢卷从 oldVersionId 迁移到 newVersionId。
|
||||||
|
* oldVersionId 可为空(此时只新增,不删除旧记录)。
|
||||||
|
*/
|
||||||
|
@PostMapping("/batchRebind")
|
||||||
|
public R<Void> batchRebind(@RequestBody BatchRebindRequest req) {
|
||||||
|
wmsProcessCoilRecordService.batchRebind(req.getCoilIds(), req.getNewVersionId(), req.getOldVersionId());
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
static class BatchRebindRequest {
|
||||||
|
private List<String> coilIds;
|
||||||
|
private Long newVersionId;
|
||||||
|
/** 可为空;非空时先删除该版本下的旧记录 */
|
||||||
|
private Long oldVersionId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,12 @@ public class WmsProcessCoilRecordVo {
|
|||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
private String remark;
|
private String remark;
|
||||||
|
|
||||||
|
// ── 连接字段(来自 wms_process_spec_version / wms_process_spec) ──
|
||||||
|
private String versionCode;
|
||||||
|
private Integer isActive;
|
||||||
|
private String versionStatus;
|
||||||
|
private Long specId;
|
||||||
|
private String specCode;
|
||||||
|
private String specName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
package com.klp.mapper;
|
package com.klp.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.klp.common.core.mapper.BaseMapperPlus;
|
import com.klp.common.core.mapper.BaseMapperPlus;
|
||||||
import com.klp.domain.WmsProcessCoilRecord;
|
import com.klp.domain.WmsProcessCoilRecord;
|
||||||
|
import com.klp.domain.bo.WmsProcessCoilRecordBo;
|
||||||
import com.klp.domain.vo.WmsProcessCoilRecordVo;
|
import com.klp.domain.vo.WmsProcessCoilRecordVo;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本钢卷服役记录 Mapper
|
* 版本钢卷服役记录 Mapper
|
||||||
*/
|
*/
|
||||||
public interface WmsProcessCoilRecordMapper extends BaseMapperPlus<WmsProcessCoilRecordMapper, WmsProcessCoilRecord, WmsProcessCoilRecordVo> {
|
public interface WmsProcessCoilRecordMapper extends BaseMapperPlus<WmsProcessCoilRecordMapper, WmsProcessCoilRecord, WmsProcessCoilRecordVo> {
|
||||||
|
|
||||||
|
/** 带版本/规程名称的分页列表(JOIN 查询) */
|
||||||
|
IPage<WmsProcessCoilRecordVo> selectVoPageJoin(Page<WmsProcessCoilRecordVo> page, @Param("bo") WmsProcessCoilRecordBo bo);
|
||||||
|
|
||||||
|
/** 带版本/规程名称的不分页列表 */
|
||||||
|
List<WmsProcessCoilRecordVo> selectVoListJoin(@Param("bo") WmsProcessCoilRecordBo bo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,20 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public interface IWmsProcessCoilRecordService {
|
public interface IWmsProcessCoilRecordService {
|
||||||
|
|
||||||
|
/** 分页列表,携带版本/规程名称(JOIN) */
|
||||||
TableDataInfo<WmsProcessCoilRecordVo> queryPageList(WmsProcessCoilRecordBo bo, PageQuery pageQuery);
|
TableDataInfo<WmsProcessCoilRecordVo> queryPageList(WmsProcessCoilRecordBo bo, PageQuery pageQuery);
|
||||||
|
|
||||||
List<WmsProcessCoilRecordVo> queryList(WmsProcessCoilRecordBo bo);
|
List<WmsProcessCoilRecordVo> queryList(WmsProcessCoilRecordBo bo);
|
||||||
|
|
||||||
/** 按版本统计服役钢卷总数(含异常数) */
|
/** 按版本统计服役钢卷总数 */
|
||||||
long countByVersion(Long versionId);
|
long countByVersion(Long versionId);
|
||||||
|
|
||||||
/** 新增或更新(按 version_id + coil_id 唯一键) */
|
/** 新增或更新(按 version_id + coil_id 唯一键) */
|
||||||
void upsert(WmsProcessCoilRecordBo bo);
|
void upsert(WmsProcessCoilRecordBo bo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量重绑:将指定钢卷的服役记录从旧版本迁移到新版本。
|
||||||
|
* oldVersionId 非空时先删除旧记录,再写入新版本记录。
|
||||||
|
*/
|
||||||
|
void batchRebind(List<String> coilIds, Long newVersionId, Long oldVersionId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.klp.mapper.WmsProcessCoilRecordMapper;
|
|||||||
import com.klp.service.IWmsProcessCoilRecordService;
|
import com.klp.service.IWmsProcessCoilRecordService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -27,13 +28,13 @@ public class WmsProcessCoilRecordServiceImpl implements IWmsProcessCoilRecordSer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TableDataInfo<WmsProcessCoilRecordVo> queryPageList(WmsProcessCoilRecordBo bo, PageQuery pageQuery) {
|
public TableDataInfo<WmsProcessCoilRecordVo> queryPageList(WmsProcessCoilRecordBo bo, PageQuery pageQuery) {
|
||||||
Page<WmsProcessCoilRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), buildQueryWrapper(bo));
|
Page<WmsProcessCoilRecordVo> page = pageQuery.build();
|
||||||
return TableDataInfo.build(result);
|
return TableDataInfo.build(baseMapper.selectVoPageJoin(page, bo));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<WmsProcessCoilRecordVo> queryList(WmsProcessCoilRecordBo bo) {
|
public List<WmsProcessCoilRecordVo> queryList(WmsProcessCoilRecordBo bo) {
|
||||||
return baseMapper.selectVoList(buildQueryWrapper(bo));
|
return baseMapper.selectVoListJoin(bo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -60,6 +61,24 @@ public class WmsProcessCoilRecordServiceImpl implements IWmsProcessCoilRecordSer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void batchRebind(List<String> coilIds, Long newVersionId, Long oldVersionId) {
|
||||||
|
for (String coilId : coilIds) {
|
||||||
|
if (oldVersionId != null) {
|
||||||
|
baseMapper.delete(Wrappers.<WmsProcessCoilRecord>lambdaQuery()
|
||||||
|
.eq(WmsProcessCoilRecord::getVersionId, oldVersionId)
|
||||||
|
.eq(WmsProcessCoilRecord::getCoilId, coilId));
|
||||||
|
}
|
||||||
|
WmsProcessCoilRecordBo bo = new WmsProcessCoilRecordBo();
|
||||||
|
bo.setVersionId(newVersionId);
|
||||||
|
bo.setCoilId(coilId);
|
||||||
|
bo.setHasAnomaly(0);
|
||||||
|
bo.setAnomalyCnt(0);
|
||||||
|
upsert(bo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private LambdaQueryWrapper<WmsProcessCoilRecord> buildQueryWrapper(WmsProcessCoilRecordBo bo) {
|
private LambdaQueryWrapper<WmsProcessCoilRecord> buildQueryWrapper(WmsProcessCoilRecordBo bo) {
|
||||||
LambdaQueryWrapper<WmsProcessCoilRecord> lqw = Wrappers.lambdaQuery();
|
LambdaQueryWrapper<WmsProcessCoilRecord> lqw = Wrappers.lambdaQuery();
|
||||||
lqw.eq(bo.getVersionId() != null, WmsProcessCoilRecord::getVersionId, bo.getVersionId());
|
lqw.eq(bo.getVersionId() != null, WmsProcessCoilRecord::getVersionId, bo.getVersionId());
|
||||||
|
|||||||
@@ -18,4 +18,58 @@
|
|||||||
<result property="remark" column="remark"/>
|
<result property="remark" column="remark"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap type="com.klp.domain.vo.WmsProcessCoilRecordVo" id="WmsProcessCoilRecordVoJoinResult">
|
||||||
|
<result property="recordId" column="record_id"/>
|
||||||
|
<result property="versionId" column="version_id"/>
|
||||||
|
<result property="coilId" column="coil_id"/>
|
||||||
|
<result property="enCoilId" column="en_coil_id"/>
|
||||||
|
<result property="hasAnomaly" column="has_anomaly"/>
|
||||||
|
<result property="anomalyCnt" column="anomaly_cnt"/>
|
||||||
|
<result property="processTime" column="process_time"/>
|
||||||
|
<result property="createTime" column="create_time"/>
|
||||||
|
<result property="remark" column="remark"/>
|
||||||
|
<result property="versionCode" column="version_code"/>
|
||||||
|
<result property="isActive" column="is_active"/>
|
||||||
|
<result property="versionStatus" column="version_status"/>
|
||||||
|
<result property="specId" column="spec_id"/>
|
||||||
|
<result property="specCode" column="spec_code"/>
|
||||||
|
<result property="specName" column="spec_name"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="joinSpec">
|
||||||
|
FROM wms_process_coil_record r
|
||||||
|
LEFT JOIN wms_process_spec_version v ON r.version_id = v.version_id AND v.del_flag = 0
|
||||||
|
LEFT JOIN wms_process_spec s ON v.spec_id = s.spec_id AND s.del_flag = 0
|
||||||
|
WHERE r.del_flag = 0
|
||||||
|
<if test="bo.versionId != null">
|
||||||
|
AND r.version_id = #{bo.versionId}
|
||||||
|
</if>
|
||||||
|
<if test="bo.coilId != null and bo.coilId != ''">
|
||||||
|
AND r.coil_id LIKE CONCAT('%', #{bo.coilId}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="bo.enCoilId != null and bo.enCoilId != ''">
|
||||||
|
AND r.en_coil_id LIKE CONCAT('%', #{bo.enCoilId}, '%')
|
||||||
|
</if>
|
||||||
|
<if test="bo.hasAnomaly != null">
|
||||||
|
AND r.has_anomaly = #{bo.hasAnomaly}
|
||||||
|
</if>
|
||||||
|
ORDER BY r.process_time DESC
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="selectVoPageJoin" resultMap="WmsProcessCoilRecordVoJoinResult">
|
||||||
|
SELECT r.record_id, r.version_id, r.coil_id, r.en_coil_id,
|
||||||
|
r.has_anomaly, r.anomaly_cnt, r.process_time, r.create_time, r.remark,
|
||||||
|
v.version_code, v.is_active, v.status AS version_status,
|
||||||
|
s.spec_id, s.spec_code, s.spec_name
|
||||||
|
<include refid="joinSpec"/>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectVoListJoin" resultMap="WmsProcessCoilRecordVoJoinResult">
|
||||||
|
SELECT r.record_id, r.version_id, r.coil_id, r.en_coil_id,
|
||||||
|
r.has_anomaly, r.anomaly_cnt, r.process_time, r.create_time, r.remark,
|
||||||
|
v.version_code, v.is_active, v.status AS version_status,
|
||||||
|
s.spec_id, s.spec_code, s.spec_name
|
||||||
|
<include refid="joinSpec"/>
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user