停机时长修复
This commit is contained in:
@@ -413,6 +413,35 @@ public class SqlServerApiClient {
|
||||
return executeSql("oracle", sql.toString(), params);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryExcoilList(int page, int pageSize) {
|
||||
int endRow = page * pageSize;
|
||||
int startRow = endRow - pageSize;
|
||||
Map<String, Object> params = new java.util.HashMap<>();
|
||||
params.put("startRow", startRow);
|
||||
params.put("endRow", endRow);
|
||||
return executeSql(
|
||||
"oracle",
|
||||
"select * from (select t.*, ROWNUM rn from (select * from JXPLTCM.PLTCM_PDO_EXCOIL order by END_DATE desc) t where ROWNUM <= :endRow) where rn > :startRow",
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryExcoilCount() {
|
||||
return executeSql(
|
||||
"oracle",
|
||||
"select count(*) as total from JXPLTCM.PLTCM_PDO_EXCOIL",
|
||||
emptyParams()
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryPresetSetupByCoilId(String coilId) {
|
||||
return executeSql(
|
||||
"oracle",
|
||||
"select * from JXPLTCM.PLTCM_PRESET_SETUP where COILID = :coilId",
|
||||
singletonParam("coilId", coilId)
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryProSegByExcoilId(String excoilId) {
|
||||
return executeSql(
|
||||
"oracle",
|
||||
|
||||
@@ -126,6 +126,32 @@ public class SqlServerApiBusinessService {
|
||||
return client.queryShapeByMatId(matId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 出口卷实绩列表(分页),来自 PLTCM_PDO_EXCOIL。
|
||||
*/
|
||||
public ExcoilPageView getExcoilList(int page, int pageSize) {
|
||||
return new ExcoilPageView(asRowList(client.queryExcoilList(page, pageSize)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 出口卷实绩总数。
|
||||
*/
|
||||
public long getExcoilCount() {
|
||||
List<Map<String, Object>> rows = asRowList(client.queryExcoilCount());
|
||||
if (rows.isEmpty()) return 0L;
|
||||
Number n = asNumber(rows.get(0).get("total"));
|
||||
return n == null ? 0L : n.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 工艺预设参数,按钢卷号查询(PLTCM_PRESET_SETUP)。
|
||||
*/
|
||||
public PresetSetupView getPresetSetupByCoilId(String coilId) {
|
||||
List<Map<String, Object>> rows = asRowList(client.queryPresetSetupByCoilId(coilId));
|
||||
Map<String, Object> firstRow = rows.isEmpty() ? Collections.<String, Object>emptyMap() : rows.get(0);
|
||||
return new PresetSetupView(coilId, firstRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时数据总入口:一次性返回厚度与板形两类数据。
|
||||
*/
|
||||
@@ -361,6 +387,36 @@ public class SqlServerApiBusinessService {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExcoilPageView {
|
||||
private final List<Map<String, Object>> rows;
|
||||
|
||||
public ExcoilPageView(List<Map<String, Object>> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getRows() {
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PresetSetupView {
|
||||
private final String coilId;
|
||||
private final Map<String, Object> data;
|
||||
|
||||
public PresetSetupView(String coilId, Map<String, Object> data) {
|
||||
this.coilId = coilId;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getCoilId() {
|
||||
return coilId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RollListView {
|
||||
private final List<Map<String, Object>> rows;
|
||||
|
||||
|
||||
@@ -155,6 +155,34 @@ public class SqlServerApiController {
|
||||
return R.ok(businessService.getRollHistoryList(page, pageSize, rollId, standId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 出口卷实绩列表(分页),来自 PLTCM_PDO_EXCOIL。
|
||||
*/
|
||||
@GetMapping("/excoil")
|
||||
public R<SqlServerApiBusinessService.ExcoilPageView> excoilList(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "50") int pageSize) {
|
||||
return R.ok(businessService.getExcoilList(page, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 出口卷实绩总数。
|
||||
*/
|
||||
@GetMapping("/excoil/count")
|
||||
public R<Map<String, Long>> excoilCount() {
|
||||
Map<String, Long> result = new HashMap<>();
|
||||
result.put("total", businessService.getExcoilCount());
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工艺预设参数,按计划钢卷号查询(PLTCM_PRESET_SETUP)。
|
||||
*/
|
||||
@GetMapping("/preset-setup/{coilId}")
|
||||
public R<SqlServerApiBusinessService.PresetSetupView> presetSetup(@PathVariable String coilId) {
|
||||
return R.ok(businessService.getPresetSetupByCoilId(coilId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 换辊历史总条数。
|
||||
*/
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"dayjs": "^1.11.18",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"echarts": "5.4.0",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"element-ui": "2.15.12",
|
||||
"exceljs": "^4.4.0",
|
||||
"file-saver": "2.0.5",
|
||||
|
||||
@@ -117,3 +117,28 @@ export function getRollHistoryCount(rollId, standId) {
|
||||
params: { rollId, standId }
|
||||
})
|
||||
}
|
||||
|
||||
// 出口卷实绩列表(分页),PLTCM_PDO_EXCOIL
|
||||
export function getExcoilList(page = 1, pageSize = 50) {
|
||||
return request({
|
||||
url: '/sql-server-api/excoil',
|
||||
method: 'get',
|
||||
params: { page, pageSize }
|
||||
})
|
||||
}
|
||||
|
||||
// 出口卷实绩总数
|
||||
export function getExcoilCount() {
|
||||
return request({
|
||||
url: '/sql-server-api/excoil/count',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 工艺预设参数,按计划钢卷号查询,PLTCM_PRESET_SETUP
|
||||
export function getPresetSetupByCoilId(coilId) {
|
||||
return request({
|
||||
url: '/sql-server-api/preset-setup/' + coilId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,960 @@
|
||||
<template>
|
||||
<div class="actual-container">
|
||||
<!-- 顶部实绩列表 (PLTCM_PDO_EXCOIL) -->
|
||||
<div class="top-section">
|
||||
<el-table
|
||||
ref="excoilTable"
|
||||
:data="excoilRows"
|
||||
size="mini"
|
||||
highlight-current-row
|
||||
border
|
||||
:height="topTableHeight"
|
||||
style="width:100%"
|
||||
@row-click="handleRowClick"
|
||||
>
|
||||
<el-table-column label="子卷号" min-width="110" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.EXCOILID || row.excoilid }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="热卷号" min-width="100" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.HOT_COILID || row.hot_coilid || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="班" width="40" align="center">
|
||||
<template slot-scope="{ row }">{{ row.SHIFT || row.shift || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="组" width="40" align="center">
|
||||
<template slot-scope="{ row }">{{ row.CREW || row.crew || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="钢种" min-width="80" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.ORDER_QUALITY || row.order_quality || row.GRADE || row.grade || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来料厚度" width="68" align="right">
|
||||
<template slot-scope="{ row }">{{ row.ENTRY_THICK || row.entry_thick || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出口厚度" width="68" align="right">
|
||||
<template slot-scope="{ row }">{{ row.EXIT_THICK || row.exit_thick || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="偏差上限" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ row.EXIT_POS_DEV || row.exit_pos_dev || '0' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="偏差下限" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ row.EXIT_NEG_DEV || row.exit_neg_dev || '0' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来料宽度" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ row.ENTRY_WIDTH || row.entry_width || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出口宽度" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ row.EXIT_WIDTH || row.exit_width || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来料重量" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ row.USED_ENTRY_WEIGHT || row.used_entry_weight || row.ENTRY_WEIGHT || row.entry_weight || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="称重重量" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ row.MEAS_EXIT_WEIGHT || row.meas_exit_weight || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="包装要求" width="60">
|
||||
<template slot-scope="{ row }">{{ row.PARK_TYPE || row.park_type || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="切边要求" width="60">
|
||||
<template slot-scope="{ row }">{{ row.SIDE_TRIM || row.side_trim || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成品质量" width="58" align="right">
|
||||
<template slot-scope="{ row }">{{ row.QUALITY || row.quality || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成品长度" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ row.EXIT_LENGTH || row.exit_length || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="吨钢长度" width="60" align="right">
|
||||
<template slot-scope="{ row }">{{ calcLengthPerTon(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下线时间" width="140">
|
||||
<template slot-scope="{ row }">{{ formatDate(row.END_DATE || row.end_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="68" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag type="primary" size="mini" effect="plain">{{ row.STATUS || row.status || '产出' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="table-pagination">
|
||||
<el-pagination
|
||||
small layout="total, prev, pager, next"
|
||||
:total="pagination.total"
|
||||
:page-size="pagination.pageSize"
|
||||
:current-page="pagination.page"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部图表区域 -->
|
||||
<div class="bottom-section">
|
||||
<div class="chart-section">
|
||||
<el-tabs v-model="activeTab" size="small" class="chart-tabs" @tab-click="handleTabSwitch">
|
||||
|
||||
<!-- ① 趋势参数:左树形目录 + 右单图 -->
|
||||
<el-tab-pane label="趋势参数" name="trend">
|
||||
<div v-if="!selectedRow" class="no-data-hint">请在上方选择钢卷</div>
|
||||
<div v-else-if="segLoading" class="no-data-hint">加载中…</div>
|
||||
<div v-else-if="!segData" class="no-data-hint">暂无 SEG 数据</div>
|
||||
<div v-else class="trend-layout">
|
||||
<!-- 左侧目录树 -->
|
||||
<div class="trend-tree">
|
||||
<div v-for="group in trendGroups" :key="group.label" class="tree-group">
|
||||
<div class="tree-group-label" @click="toggleGroup(group.label)">
|
||||
<i :class="expandedGroups[group.label] ? 'el-icon-caret-bottom' : 'el-icon-caret-right'" />
|
||||
{{ group.label }}
|
||||
</div>
|
||||
<div v-show="expandedGroups[group.label]" class="tree-children">
|
||||
<div
|
||||
v-for="item in group.children"
|
||||
:key="item.col"
|
||||
class="tree-item"
|
||||
:class="{ active: selectedTrendParam && selectedTrendParam.col === item.col }"
|
||||
@click="selectTrendParam(item)"
|
||||
>{{ item.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧图表 -->
|
||||
<div class="trend-chart-area">
|
||||
<div v-if="!selectedTrendParam" class="no-data-hint">← 点击左侧参数查看曲线</div>
|
||||
<!-- 保持 DOM 存在,仅用 v-show 控制显示,避免 ref 失效 -->
|
||||
<div ref="trendSingleChart" :style="{ display: selectedTrendParam ? 'block' : 'none', height: '100%', width: '100%' }" />
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ② 厚度曲线 -->
|
||||
<el-tab-pane label="厚度曲线" name="thickness">
|
||||
<div v-if="!selectedRow" class="no-data-hint">请在上方选择钢卷</div>
|
||||
<div v-else-if="realtimeLoading" class="no-data-hint">加载中…</div>
|
||||
<div v-else-if="!gaugeRows || !gaugeRows.length" class="no-data-hint">暂无厚度数据</div>
|
||||
<div v-else class="charts-scroll charts-grid">
|
||||
<div ref="chartGauge1" class="chart-box" />
|
||||
<div ref="chartGauge2" class="chart-box" />
|
||||
<div ref="chartGauge3" class="chart-box" />
|
||||
<div ref="chartGauge4" class="chart-box" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ③ 带钢板形 热力图 -->
|
||||
<el-tab-pane label="带钢板形" name="flatness3d">
|
||||
<div v-if="!selectedRow" class="no-data-hint">请在上方选择钢卷</div>
|
||||
<div v-else-if="realtimeLoading" class="no-data-hint">加载中…</div>
|
||||
<div v-else-if="!shapeRows || !shapeRows.length" class="no-data-hint">暂无板形数据</div>
|
||||
<div v-else class="charts-scroll">
|
||||
<div ref="chartFlatness3d" class="chart-box chart-box-tall" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ④ 板形曲线:一行两图 -->
|
||||
<el-tab-pane label="板形曲线" name="flatness">
|
||||
<div v-if="!selectedRow" class="no-data-hint">请在上方选择钢卷</div>
|
||||
<div v-else-if="realtimeLoading" class="no-data-hint">加载中…</div>
|
||||
<div v-else-if="!shapeRows || !shapeRows.length" class="no-data-hint">暂无板形数据</div>
|
||||
<div v-else class="charts-scroll charts-grid">
|
||||
<div ref="chartFlatDev" class="chart-box" />
|
||||
<div ref="chartTilt" class="chart-box" />
|
||||
<div ref="chartWrBend" class="chart-box" />
|
||||
<div ref="chartIrBend" class="chart-box" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 右侧查找面板 -->
|
||||
<div class="search-panel">
|
||||
<div class="panel-title">查找</div>
|
||||
<div class="search-type-group">
|
||||
<el-radio v-model="searchType" label="coil">按钢卷号</el-radio>
|
||||
<div v-if="searchType === 'coil'" class="search-field">
|
||||
<span class="search-label">钢卷号:</span>
|
||||
<el-input v-model="searchCoilId" size="mini" style="width:140px" placeholder="EXCOILID" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-type-group">
|
||||
<el-radio v-model="searchType" label="time">按时间</el-radio>
|
||||
<template v-if="searchType === 'time'">
|
||||
<div class="search-field">
|
||||
<span class="search-label">开始时间:</span>
|
||||
<el-date-picker v-model="searchStartDate" type="datetime" size="mini" style="width:160px"
|
||||
value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始时间" />
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<span class="search-label">结束时间:</span>
|
||||
<el-date-picker v-model="searchEndDate" type="datetime" size="mini" style="width:160px"
|
||||
value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" size="mini" :loading="excoilLoading" @click="handleFindSearch">查找</el-button>
|
||||
<el-button size="mini" @click="handleFindReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import 'echarts-gl'
|
||||
import {
|
||||
getExcoilList,
|
||||
getExcoilCount,
|
||||
getTimingSegByEncoilId,
|
||||
getTimingRealtimeData
|
||||
} from '@/api/l2/timing'
|
||||
|
||||
// 趋势参数树结构,对应 PLTCM_PRO_SEG 列名
|
||||
const TREND_GROUPS = [
|
||||
{
|
||||
label: '张力',
|
||||
children: [
|
||||
{ label: '开卷张力', col: 'PORTENS' },
|
||||
{ label: '入口活套张力', col: 'ENLTENS' },
|
||||
{ label: '拉矫张力', col: 'TLTENS' },
|
||||
{ label: '酸洗张力', col: 'PLTENS' },
|
||||
{ label: '出口活套张力', col: 'CXLTENS' },
|
||||
{ label: '圆盘剪张力', col: 'TRIMTENS' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '速度',
|
||||
children: [
|
||||
{ label: '开卷速度', col: 'PORSPEED' },
|
||||
{ label: '酸洗速度', col: 'PLSPEED' },
|
||||
{ label: '圆盘剪速度', col: 'TRIMSPEED' },
|
||||
{ label: '轧机入口速度', col: 'MILLENTRYSPEED' },
|
||||
{ label: '轧机出口速度', col: 'MILLEXITSPEED' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '拉矫机',
|
||||
children: [
|
||||
{ label: '1#插入量', col: 'TLMESH1' },
|
||||
{ label: '2#插入量', col: 'TLMESH2' },
|
||||
{ label: '3#插入量', col: 'TLMESH3' },
|
||||
{ label: '延伸率', col: 'TLELONG' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '酸洗段',
|
||||
children: [
|
||||
{ label: '1#温度', col: 'TK1TEMP' },
|
||||
{ label: '2#温度', col: 'TK2TEMP' },
|
||||
{ label: '3#温度', col: 'TK3TEMP' },
|
||||
{ label: '漂洗温度', col: 'RINSETEMP' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// V_VBDA_GAUGE 厚度曲线:4 个图,列名来自 DDL
|
||||
const GAUGE_COLS = [
|
||||
{ col: 'THICK0', title: '入口测厚仪 [mm]' },
|
||||
{ col: 'THICK1', title: '1架出口厚度 [mm]' },
|
||||
{ col: 'THICK4', title: '末架出口厚度 [mm]' },
|
||||
{ col: 'EXIT_SPEED', title: '轧制速度 [m/min]' }
|
||||
]
|
||||
|
||||
// V_VBDA_SHAPE 板形曲线:4 个图,列名来自 DDL
|
||||
const SHAPE_SCALAR_COLS = [
|
||||
{ col: 'ABSDEVIATION', title: '总板形偏差 [IU]' },
|
||||
{ col: 'TILT', title: '末架倾斜量 [mm]' },
|
||||
{ col: 'WRBEND', title: '工作辊弯辊力 [kN]' },
|
||||
{ col: 'IRBEND', title: '中间辊弯辊力 [kN]' }
|
||||
]
|
||||
|
||||
/**
|
||||
* 从数据数组计算合理的 Y 轴范围(带 15% 上下边距)。
|
||||
* echarts 默认 scale 在数据变化幅度极小时会把轴拉到很大范围,导致曲线看起来是横线。
|
||||
*/
|
||||
function calcYRange(vals) {
|
||||
const nums = vals.filter(v => v != null && isFinite(Number(v))).map(Number)
|
||||
if (!nums.length) return {}
|
||||
const min = Math.min(...nums)
|
||||
const max = Math.max(...nums)
|
||||
if (min === max) {
|
||||
const base = Math.abs(min) || 1
|
||||
return { min: parseFloat((min - base * 0.2).toFixed(4)), max: parseFloat((max + base * 0.2).toFixed(4)) }
|
||||
}
|
||||
const pad = (max - min) * 0.15
|
||||
return {
|
||||
min: parseFloat((min - pad).toFixed(4)),
|
||||
max: parseFloat((max + pad).toFixed(4))
|
||||
}
|
||||
}
|
||||
|
||||
function makeLine(title, xData, yData) {
|
||||
const range = calcYRange(yData)
|
||||
return {
|
||||
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal' }, top: 4, left: 8 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { top: 36, bottom: 28, left: 8, right: 16, containLabel: true },
|
||||
xAxis: {
|
||||
type: 'category', data: xData,
|
||||
name: 'pos(m)', nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: range.min,
|
||||
max: range.max,
|
||||
nameTextStyle: { fontSize: 10 },
|
||||
axisLabel: { fontSize: 10 }
|
||||
},
|
||||
dataZoom: [
|
||||
{ type: 'inside', xAxisIndex: 0, zoomOnMouseWheel: true, moveOnMouseMove: true },
|
||||
{ type: 'inside', yAxisIndex: 0, zoomOnMouseWheel: false, moveOnMouseWheel: true }
|
||||
],
|
||||
series: [{
|
||||
name: title, type: 'line', smooth: false, symbol: 'none',
|
||||
lineStyle: { width: 1 }, data: yData
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
function getRowVal(row, col) {
|
||||
const v = row[col] !== undefined ? row[col] : row[col.toLowerCase()]
|
||||
return v == null ? null : Number(v)
|
||||
}
|
||||
|
||||
function xLocData(rows) {
|
||||
return rows.map(r => {
|
||||
const v = r.XLOCATION !== undefined ? r.XLOCATION : r.xlocation
|
||||
return v == null ? '' : Number(v).toFixed(1)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'ActualPerformance',
|
||||
data() {
|
||||
return {
|
||||
excoilLoading: false,
|
||||
segLoading: false,
|
||||
realtimeLoading: false,
|
||||
excoilRows: [],
|
||||
selectedRow: null,
|
||||
segData: null,
|
||||
gaugeRows: null,
|
||||
shapeRows: null,
|
||||
activeTab: 'trend',
|
||||
// 趋势参数树状态
|
||||
trendGroups: TREND_GROUPS,
|
||||
expandedGroups: { '张力': true, '速度': true, '拉矫机': true, '酸洗段': true },
|
||||
selectedTrendParam: null,
|
||||
trendChartInst: null,
|
||||
// 查找
|
||||
searchType: 'coil',
|
||||
searchCoilId: '',
|
||||
searchStartDate: '',
|
||||
searchEndDate: '',
|
||||
pagination: { page: 1, pageSize: 50, total: 0 },
|
||||
topTableHeight: 'calc(40vh - 80px)',
|
||||
chartInstances: [],
|
||||
resizeHandler: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadExcoilCount()
|
||||
this.loadExcoilList()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.disposeAllCharts()
|
||||
},
|
||||
methods: {
|
||||
async loadExcoilCount() {
|
||||
try {
|
||||
const res = await getExcoilCount()
|
||||
this.pagination.total = res?.data?.total ?? 0
|
||||
} catch (_) {}
|
||||
},
|
||||
async loadExcoilList() {
|
||||
this.excoilLoading = true
|
||||
try {
|
||||
const res = await getExcoilList(this.pagination.page, this.pagination.pageSize)
|
||||
this.excoilRows = res?.data?.rows || []
|
||||
} finally {
|
||||
this.excoilLoading = false
|
||||
}
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pagination.page = page
|
||||
this.loadExcoilList()
|
||||
},
|
||||
async handleRowClick(row) {
|
||||
this.selectedRow = row
|
||||
this.segData = null
|
||||
this.gaugeRows = null
|
||||
this.shapeRows = null
|
||||
this.selectedTrendParam = null
|
||||
this.disposeAllCharts()
|
||||
|
||||
// PLTCM_PRO_SEG 用 ENCOILID 查(主键前缀)
|
||||
const encoilId = row.ENCOILID || row.encoilid
|
||||
// V_VBDA_GAUGE / V_VBDA_SHAPE 用 EXCOILID 作为 MATID
|
||||
const excoilId = row.EXCOILID || row.excoilid
|
||||
|
||||
await Promise.all([
|
||||
encoilId ? this.loadSeg(encoilId) : Promise.resolve(),
|
||||
excoilId ? this.loadRealtime(excoilId) : Promise.resolve()
|
||||
])
|
||||
|
||||
await this.$nextTick()
|
||||
// 加载完成后自动选中第一个趋势参数
|
||||
if (this.activeTab === 'trend' && this.segData) {
|
||||
this.selectTrendParam(TREND_GROUPS[0].children[0])
|
||||
} else {
|
||||
this.renderCurrentTab()
|
||||
}
|
||||
},
|
||||
|
||||
async loadSeg(encoilId) {
|
||||
this.segLoading = true
|
||||
try {
|
||||
const res = await getTimingSegByEncoilId(encoilId)
|
||||
const rows = res?.data?.rows || []
|
||||
this.segData = rows.length ? (res?.data?.series || null) : null
|
||||
} catch (_) {
|
||||
} finally {
|
||||
this.segLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
async loadRealtime(excoilId) {
|
||||
this.realtimeLoading = true
|
||||
try {
|
||||
const res = await getTimingRealtimeData(excoilId)
|
||||
const g = res?.data?.gauge?.result
|
||||
const s = res?.data?.shape?.result
|
||||
this.gaugeRows = Array.isArray(g) ? g : null
|
||||
this.shapeRows = Array.isArray(s) ? s : null
|
||||
} catch (_) {
|
||||
} finally {
|
||||
this.realtimeLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// ── 趋势参数树 ──────────────────────────────
|
||||
toggleGroup(label) {
|
||||
this.$set(this.expandedGroups, label, !this.expandedGroups[label])
|
||||
},
|
||||
isGroupExpanded(label) {
|
||||
return !!this.expandedGroups[label]
|
||||
},
|
||||
selectTrendParam(item) {
|
||||
this.selectedTrendParam = item
|
||||
this.$nextTick(() => this.renderTrendSingleChart())
|
||||
},
|
||||
renderTrendSingleChart() {
|
||||
if (!this.selectedTrendParam || !this.segData) return
|
||||
const el = this.$refs.trendSingleChart
|
||||
if (!el) return
|
||||
// 复用已有实例,避免重复 init
|
||||
if (!this.trendChartInst || this.trendChartInst.isDisposed()) {
|
||||
this.trendChartInst = echarts.init(el)
|
||||
// 滚轮缩放支持
|
||||
const resizeFn = () => this.trendChartInst && !this.trendChartInst.isDisposed() && this.trendChartInst.resize()
|
||||
window.addEventListener('resize', resizeFn)
|
||||
this._trendResizeFn = resizeFn
|
||||
}
|
||||
const x = this.segX()
|
||||
const yData = this.seg(this.selectedTrendParam.col)
|
||||
this.trendChartInst.setOption(makeLine(this.selectedTrendParam.label, x, yData), true)
|
||||
},
|
||||
|
||||
// ── Tab 切换 ────────────────────────────────
|
||||
handleTabSwitch() {
|
||||
this.$nextTick(() => {
|
||||
if (this.activeTab === 'trend' && this.selectedTrendParam && this.segData) {
|
||||
this.renderTrendSingleChart()
|
||||
} else {
|
||||
this.renderCurrentTab()
|
||||
}
|
||||
})
|
||||
},
|
||||
renderCurrentTab() {
|
||||
this.disposeSideCharts()
|
||||
if (this.activeTab === 'thickness' && this.gaugeRows?.length) this.renderGaugeCharts()
|
||||
if (this.activeTab === 'flatness3d' && this.shapeRows?.length) this.renderFlatness3d()
|
||||
if (this.activeTab === 'flatness' && this.shapeRows?.length) this.renderFlatnessCharts()
|
||||
},
|
||||
|
||||
// ── 销毁 ────────────────────────────────────
|
||||
disposeSideCharts() {
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler)
|
||||
this.resizeHandler = null
|
||||
}
|
||||
this.chartInstances.forEach(c => { if (c && !c.isDisposed()) c.dispose() })
|
||||
this.chartInstances = []
|
||||
},
|
||||
disposeAllCharts() {
|
||||
this.disposeSideCharts()
|
||||
if (this._trendResizeFn) {
|
||||
window.removeEventListener('resize', this._trendResizeFn)
|
||||
this._trendResizeFn = null
|
||||
}
|
||||
if (this.trendChartInst && !this.trendChartInst.isDisposed()) {
|
||||
this.trendChartInst.dispose()
|
||||
this.trendChartInst = null
|
||||
}
|
||||
},
|
||||
|
||||
// ── SEG 数据辅助 ─────────────────────────────
|
||||
seg(col) {
|
||||
const s = this.segData
|
||||
// Oracle 返回大写列名,兼容小写
|
||||
const arr = s[col] !== undefined ? s[col] : (s[col.toLowerCase()] || [])
|
||||
return arr.map(v => v == null ? null : Number(Number(v).toFixed(3)))
|
||||
},
|
||||
segX() {
|
||||
const s = this.segData
|
||||
const arr = s['STARTPOS'] !== undefined ? s['STARTPOS'] : (s['startpos'] || [])
|
||||
return arr.map(v => v == null ? '' : Number(v).toFixed(1))
|
||||
},
|
||||
|
||||
// ── 图表初始化 ───────────────────────────────
|
||||
makeChart(ref, option) {
|
||||
const el = this.$refs[ref]
|
||||
if (!el) return null
|
||||
const chart = echarts.init(el)
|
||||
chart.setOption(option)
|
||||
return chart
|
||||
},
|
||||
setupResize() {
|
||||
this.resizeHandler = () => this.chartInstances.forEach(c => {
|
||||
if (c && !c.isDisposed()) c.resize()
|
||||
})
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
|
||||
// ── 厚度曲线 (V_VBDA_GAUGE) ──────────────────
|
||||
renderGaugeCharts() {
|
||||
const rows = this.gaugeRows
|
||||
if (!rows || !rows.length) return
|
||||
const xData = xLocData(rows)
|
||||
const refs = ['chartGauge1', 'chartGauge2', 'chartGauge3', 'chartGauge4']
|
||||
const charts = refs.map((ref, i) => {
|
||||
const { col, title } = GAUGE_COLS[i]
|
||||
const yData = rows.map(r => {
|
||||
const v = getRowVal(r, col)
|
||||
return v == null ? null : parseFloat(v.toFixed(4))
|
||||
})
|
||||
return this.makeChart(ref, makeLine(title, xData, yData))
|
||||
})
|
||||
this.chartInstances = charts.filter(Boolean)
|
||||
this.setupResize()
|
||||
},
|
||||
|
||||
// ── 带钢板形 3D 线图 (V_VBDA_SHAPE) ────────────
|
||||
// 每个通道画一条独立 line3D,通道之间不连面,形成镂空效果
|
||||
renderFlatness3d() {
|
||||
const rows = this.shapeRows
|
||||
if (!rows || !rows.length) return
|
||||
const firstRow = rows[0]
|
||||
const high = parseInt(getRowVal(firstRow, 'HIGHZONEID')) || 26
|
||||
const low = parseInt(getRowVal(firstRow, 'LOWZONEID')) || 1
|
||||
const numZones = Math.min(Math.max(high - low + 1, 1), 26)
|
||||
const zoneCols = Array.from({ length: numZones }, (_, i) =>
|
||||
`VALUES${String(low + i).padStart(2, '0')}`
|
||||
)
|
||||
// X 方向降采样,最多 200 个点
|
||||
const step = Math.max(1, Math.floor(rows.length / 200))
|
||||
const sampled = rows.filter((_, i) => i % step === 0)
|
||||
const numX = sampled.length
|
||||
|
||||
// X 轴标签(位置,单位 m)
|
||||
const xLabels = sampled.map(r => {
|
||||
const v = r.XLOCATION !== undefined ? r.XLOCATION : r.xlocation
|
||||
return v == null ? '' : Number(v).toFixed(0)
|
||||
})
|
||||
|
||||
// 收集值域用于 visualMap
|
||||
let minV = Infinity, maxV = -Infinity
|
||||
sampled.forEach(row => {
|
||||
zoneCols.forEach(col => {
|
||||
const v = getRowVal(row, col)
|
||||
if (v != null) {
|
||||
if (v < minV) minV = v
|
||||
if (v > maxV) maxV = v
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!isFinite(minV)) { minV = -30; maxV = 30 }
|
||||
const absMax = Math.max(Math.abs(minV), Math.abs(maxV))
|
||||
|
||||
// ① 沿 X 方向网格线(每通道一条,按 Z 值着色)
|
||||
const channelLines = zoneCols.map((col, yi) => ({
|
||||
type: 'line3D',
|
||||
coordinateSystem: 'cartesian3D',
|
||||
data: sampled.map((row, xi) => {
|
||||
const v = getRowVal(row, col)
|
||||
return v == null ? null : [xi, yi, parseFloat(v.toFixed(2))]
|
||||
}).filter(Boolean),
|
||||
lineStyle: { width: 2, opacity: 1 }
|
||||
}))
|
||||
|
||||
// ② 沿 Y 方向网格线(每隔若干位置连通各通道,按 Z 值着色)
|
||||
const xStride = Math.max(1, Math.floor(numX / 60))
|
||||
const crossLines = []
|
||||
for (let xi = 0; xi < numX; xi += xStride) {
|
||||
const pts = zoneCols.map((col, yi) => {
|
||||
const v = getRowVal(sampled[xi], col)
|
||||
return v == null ? null : [xi, yi, parseFloat(v.toFixed(2))]
|
||||
}).filter(Boolean)
|
||||
if (pts.length > 1) {
|
||||
crossLines.push({
|
||||
type: 'line3D',
|
||||
coordinateSystem: 'cartesian3D',
|
||||
data: pts,
|
||||
lineStyle: { width: 1.5, opacity: 1 }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const series = [...channelLines, ...crossLines]
|
||||
|
||||
const option = {
|
||||
title: { text: '实测平直度 [IU]', textStyle: { fontSize: 13, fontWeight: 'normal' }, top: 6, left: 10 },
|
||||
tooltip: {},
|
||||
visualMap: {
|
||||
show: true,
|
||||
dimension: 2,
|
||||
min: -absMax,
|
||||
max: absMax,
|
||||
calculable: true,
|
||||
orient: 'vertical',
|
||||
right: 10,
|
||||
top: 'center',
|
||||
textStyle: { fontSize: 10 },
|
||||
inRange: {
|
||||
// 负值红色 → 零值绿色 → 正值蓝紫
|
||||
color: ['#8B0000','#CC2200','#E84C00','#F46D43',
|
||||
'#FDAE61','#FEE08B',
|
||||
'#66BD63','#1A9850','#006837',
|
||||
'#3288BD','#5E4FA2','#762A83']
|
||||
}
|
||||
},
|
||||
grid3D: {
|
||||
boxWidth: 200,
|
||||
boxHeight: 60,
|
||||
boxDepth: 80,
|
||||
viewControl: {
|
||||
projection: 'orthographic',
|
||||
autoRotate: false,
|
||||
rotateSensitivity: 1,
|
||||
zoomSensitivity: 1
|
||||
},
|
||||
light: {
|
||||
main: { intensity: 1.2, shadow: false },
|
||||
ambient: { intensity: 0.3 }
|
||||
}
|
||||
},
|
||||
xAxis3D: {
|
||||
type: 'value',
|
||||
name: '位置',
|
||||
min: 0,
|
||||
max: numX - 1,
|
||||
nameTextStyle: { fontSize: 10 },
|
||||
axisLabel: {
|
||||
fontSize: 9,
|
||||
formatter: v => xLabels[Math.round(v)] || ''
|
||||
}
|
||||
},
|
||||
yAxis3D: {
|
||||
type: 'value',
|
||||
name: '通道',
|
||||
min: 0,
|
||||
max: numZones - 1,
|
||||
nameTextStyle: { fontSize: 10 },
|
||||
axisLabel: {
|
||||
fontSize: 9,
|
||||
formatter: v => String(low + Math.round(v))
|
||||
}
|
||||
},
|
||||
zAxis3D: {
|
||||
type: 'value',
|
||||
name: 'IU',
|
||||
nameTextStyle: { fontSize: 10 },
|
||||
axisLabel: { fontSize: 9 }
|
||||
},
|
||||
series
|
||||
}
|
||||
|
||||
const el = this.$refs.chartFlatness3d
|
||||
if (!el) return
|
||||
const chart = echarts.init(el)
|
||||
chart.setOption(option)
|
||||
this.chartInstances = [chart]
|
||||
this.setupResize()
|
||||
},
|
||||
|
||||
// ── 板形曲线 (V_VBDA_SHAPE) ──────────────────
|
||||
renderFlatnessCharts() {
|
||||
const rows = this.shapeRows
|
||||
if (!rows || !rows.length) return
|
||||
const xData = xLocData(rows)
|
||||
const refs = ['chartFlatDev', 'chartTilt', 'chartWrBend', 'chartIrBend']
|
||||
const charts = refs.map((ref, i) => {
|
||||
const { col, title } = SHAPE_SCALAR_COLS[i]
|
||||
const yData = rows.map(r => {
|
||||
const v = getRowVal(r, col)
|
||||
return v == null ? null : parseFloat(v.toFixed(3))
|
||||
})
|
||||
return this.makeChart(ref, makeLine(title, xData, yData))
|
||||
})
|
||||
this.chartInstances = charts.filter(Boolean)
|
||||
this.setupResize()
|
||||
},
|
||||
|
||||
// ── 查找 ─────────────────────────────────────
|
||||
handleFindSearch() {
|
||||
if (this.searchType === 'coil' && this.searchCoilId) {
|
||||
const found = this.excoilRows.find(r =>
|
||||
(r.EXCOILID || r.excoilid || '').includes(this.searchCoilId)
|
||||
)
|
||||
if (found) {
|
||||
this.$refs.excoilTable && this.$refs.excoilTable.setCurrentRow(found)
|
||||
this.handleRowClick(found)
|
||||
} else {
|
||||
this.$message.info('当前页未找到该卷号,请翻页查找')
|
||||
}
|
||||
} else {
|
||||
this.pagination.page = 1
|
||||
this.loadExcoilList()
|
||||
}
|
||||
},
|
||||
handleFindReset() {
|
||||
this.searchCoilId = ''
|
||||
this.searchStartDate = ''
|
||||
this.searchEndDate = ''
|
||||
this.selectedRow = null
|
||||
this.segData = null
|
||||
this.gaugeRows = null
|
||||
this.shapeRows = null
|
||||
this.selectedTrendParam = null
|
||||
this.disposeAllCharts()
|
||||
this.pagination.page = 1
|
||||
this.loadExcoilCount()
|
||||
this.loadExcoilList()
|
||||
},
|
||||
|
||||
calcLengthPerTon(row) {
|
||||
const len = parseFloat(row.EXIT_LENGTH || row.exit_length)
|
||||
const wt = parseFloat(row.MEAS_EXIT_WEIGHT || row.meas_exit_weight)
|
||||
if (!len || !wt || wt === 0) return '—'
|
||||
return (len / wt).toFixed(2)
|
||||
},
|
||||
formatDate(v) {
|
||||
if (!v) return '—'
|
||||
return String(v).replace('T', ' ').substring(0, 19)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.actual-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 8px;
|
||||
background: #f0f2f5;
|
||||
gap: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.top-section {
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 4px 8px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.bottom-section {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.chart-tabs {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
|
||||
::v-deep .el-tabs__content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
::v-deep .el-tab-pane {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 趋势参数:左树 + 右图 ── */
|
||||
.trend-layout {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.trend-tree {
|
||||
width: 140px;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
border-right: 1px solid #ebeef5;
|
||||
padding: 4px 0;
|
||||
|
||||
&::-webkit-scrollbar { width: 3px; }
|
||||
&::-webkit-scrollbar-thumb { background: #dcdfe6; border-radius: 2px; }
|
||||
}
|
||||
|
||||
.tree-group {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-group-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 5px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover { background: #f5f7fa; }
|
||||
|
||||
i { font-size: 10px; color: #909399; }
|
||||
}
|
||||
|
||||
.tree-children { padding-left: 4px; }
|
||||
|
||||
.tree-item {
|
||||
padding: 4px 8px 4px 18px;
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
margin: 1px 4px;
|
||||
|
||||
&:hover { background: #ecf5ff; color: #409eff; }
|
||||
|
||||
&.active {
|
||||
background: #ecf5ff;
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.trend-chart-area {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
padding: 4px 4px 4px 8px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* ── 其他图表 ── */
|
||||
.charts-scroll {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
|
||||
&::-webkit-scrollbar { width: 4px; }
|
||||
&::-webkit-scrollbar-thumb { background: #dcdfe6; border-radius: 2px; }
|
||||
}
|
||||
|
||||
/* 一行两图 */
|
||||
.charts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
align-content: start;
|
||||
|
||||
.chart-box { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.chart-box-tall { height: 480px; }
|
||||
|
||||
/* ── 查找面板 ── */
|
||||
.search-panel {
|
||||
width: 210px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.search-type-group { display: flex; flex-direction: column; gap: 8px; }
|
||||
|
||||
.search-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.search-label { font-size: 11px; color: #909399; }
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.no-data-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
color: #c0c4cc;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,483 +1,628 @@
|
||||
<template>
|
||||
<div class="app-container" style="width: 100%; height: 100%;">
|
||||
<div class="plan-container">
|
||||
<!-- 顶部筛选栏 -->
|
||||
<div class="filter-bar">
|
||||
<el-input
|
||||
v-model="queryForm.coilId"
|
||||
placeholder="热卷号 / 成品卷号"
|
||||
placeholder="冷卷号 / 热卷号"
|
||||
clearable
|
||||
size="small"
|
||||
style="width: 220px"
|
||||
style="width: 200px"
|
||||
@keyup.enter.native="handleSearch"
|
||||
/>
|
||||
<el-select v-model="queryForm.status" placeholder="状态" clearable size="small" style="width: 110px">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="新建" value="NEW" />
|
||||
<el-option label="准备好" value="READY" />
|
||||
<el-option label="在线" value="ONLINE" />
|
||||
<el-option label="生产中" value="PRODUCING" />
|
||||
<el-option label="产出" value="PRODUCT" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" :loading="loading" @click="handleSearch">查询</el-button>
|
||||
<el-button size="small" @click="handleReset">重置</el-button>
|
||||
<span class="total-hint">共 {{ pagination.total }} 条</span>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="12" class="main-row">
|
||||
<!-- 左侧计划列表 -->
|
||||
<el-col :span="9">
|
||||
<div class="left-card">
|
||||
<!-- 计划表格 -->
|
||||
<div class="table-wrap">
|
||||
<el-table
|
||||
ref="planTable"
|
||||
:data="planRows"
|
||||
size="mini"
|
||||
highlight-current-row
|
||||
border
|
||||
:height="tableHeight"
|
||||
@row-click="handlePlanRowClick"
|
||||
style="width: 100%"
|
||||
@row-click="handleRowClick"
|
||||
>
|
||||
<el-table-column prop="hot_coilid" label="热卷号" show-overflow-tooltip />
|
||||
<el-table-column prop="coilid" label="原料卷号" show-overflow-tooltip />
|
||||
<el-table-column label="状态">
|
||||
<el-table-column type="selection" width="36" />
|
||||
<el-table-column prop="SEQID" label="序号" width="52" align="center">
|
||||
<template slot-scope="{ row }">{{ row.SEQID || row.seqid }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="冷卷号" min-width="110" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.EXIT_COILID || row.exit_coilid || row.COILID || row.coilid }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="热卷号" min-width="100" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.HOT_COILID || row.hot_coilid }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="钢种" min-width="80" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.ORDER_QUALITY || row.order_quality }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来料厚度" width="72" align="right">
|
||||
<template slot-scope="{ row }">{{ row.ENTRY_THICK || row.entry_thick }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品厚度" width="72" align="right">
|
||||
<template slot-scope="{ row }">{{ row.EXIT_THICK || row.exit_thick }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="偏差上限" width="72" align="right">
|
||||
<template slot-scope="{ row }">{{ calcPosDev(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="偏差下限" width="72" align="right">
|
||||
<template slot-scope="{ row }">{{ calcNegDev(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来料宽度" width="72" align="right">
|
||||
<template slot-scope="{ row }">{{ row.ENTRY_WIDTH || row.entry_width }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品宽度" width="72" align="right">
|
||||
<template slot-scope="{ row }">{{ row.EXIT_WIDTH || row.exit_width }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="包装要求" width="72">
|
||||
<template slot-scope="{ row }">{{ row.PARK_TYPE || row.park_type || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="切边要求" width="72">
|
||||
<template slot-scope="{ row }">{{ formatTrimming(row.TRIMMING !== undefined ? row.TRIMMING : row.trimming) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="轧制模式" width="82">
|
||||
<template slot-scope="{ row }">{{ formatStratMode(row.STRAT_MODE !== undefined ? row.STRAT_MODE : row.strat_mode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分卷数" width="56" align="center">
|
||||
<template slot-scope="{ row }">{{ row.SPLIT_NUM || row.split_num }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下工序" width="68">
|
||||
<template slot-scope="{ row }">{{ formatNextUnit(row.NEXT_UNIT || row.next_unit) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划时间" width="140">
|
||||
<template slot-scope="{ row }">{{ formatDate(row.ONLINE_DATE || row.online_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="68" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<span :class="['status-dot', statusClass(row.status)]" />
|
||||
<span class="status-text">{{ row.status || '—' }}</span>
|
||||
<el-tag :type="statusTagType(row.STATUS || row.status)" size="mini" effect="plain">
|
||||
{{ formatStatus(row.STATUS || row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="厚×宽">
|
||||
<template slot-scope="{ row }">{{ row.entry_thick }}×{{ row.entry_width }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="exit_thick" label="出口厚" />
|
||||
<el-table-column prop="entry_weight" label="重量(t)" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-bar">
|
||||
<el-pagination
|
||||
small
|
||||
layout="total, prev, pager, next"
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="pagination.total"
|
||||
:page-size="pagination.pageSize"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:current-page="pagination.page"
|
||||
@current-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧详情 + 图表 -->
|
||||
<el-col :span="15">
|
||||
<div v-if="!selectedPlan" class="empty-hint">
|
||||
<el-empty description="选择左侧计划" />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 底部详情区域 -->
|
||||
<div class="bottom-panels">
|
||||
<!-- 计划详细 -->
|
||||
<div class="detail-panel">
|
||||
<div class="panel-title">计划详细</div>
|
||||
<template v-if="selectedRow">
|
||||
<div class="detail-grid">
|
||||
<div v-for="f in planFields" :key="f.key" class="detail-cell">
|
||||
<span class="cell-label">{{ f.label }}</span>
|
||||
<span class="cell-value">{{ selectedPlan[f.key] != null ? selectedPlan[f.key] : '—' }}</span>
|
||||
<div class="detail-row">
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">钢卷号</span>
|
||||
<span class="cell-value">{{ r('EXIT_COILID','exit_coilid') || r('COILID','coilid') }}</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">来料厚度</span>
|
||||
<span class="cell-value">{{ r('ENTRY_THICK','entry_thick') }}</span>
|
||||
<span class="cell-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">来料宽度</span>
|
||||
<span class="cell-value">{{ r('ENTRY_WIDTH','entry_width') }}</span>
|
||||
<span class="cell-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">1#分卷</span>
|
||||
<span class="cell-value">{{ r('EXIT_VALUE1','exit_value1','0') }}</span>
|
||||
<span class="cell-unit">[t]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="perf-header">
|
||||
实轧实绩
|
||||
<span v-if="hasPerfData" class="perf-count">({{ perfSegCount }} 段)</span>
|
||||
<el-tag v-if="perfLoading" size="mini" type="info" style="margin-left:8px">加载中…</el-tag>
|
||||
<div class="detail-row">
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">钢种</span>
|
||||
<span class="cell-value">{{ r('ORDER_QUALITY','order_quality') }}</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">产品厚度</span>
|
||||
<span class="cell-value">{{ r('EXIT_THICK','exit_thick') }}</span>
|
||||
<span class="cell-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">产品宽度</span>
|
||||
<span class="cell-value">{{ r('EXIT_WIDTH','exit_width') }}</span>
|
||||
<span class="cell-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">2#分卷</span>
|
||||
<span class="cell-value">{{ r('EXIT_VALUE2','exit_value2','0') }}</span>
|
||||
<span class="cell-unit">[t]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">轧制模式</span>
|
||||
<span class="cell-value">{{ formatStratMode(r('STRAT_MODE','strat_mode')) }}</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">偏差上限</span>
|
||||
<span class="cell-value">{{ calcPosDev(selectedRow) }}</span>
|
||||
<span class="cell-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">分卷数</span>
|
||||
<span class="cell-value">{{ r('SPLIT_NUM','split_num') }}</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">3#分卷</span>
|
||||
<span class="cell-value">{{ r('EXIT_VALUE3','exit_value3','0') }}</span>
|
||||
<span class="cell-unit">[t]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">状态</span>
|
||||
<span class="cell-value">{{ formatStatus(r('STATUS','status')) }}</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">偏差下限</span>
|
||||
<span class="cell-value">{{ calcNegDev(selectedRow) }}</span>
|
||||
<span class="cell-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">下工序</span>
|
||||
<span class="cell-value">{{ formatNextUnit(r('NEXT_UNIT','next_unit')) }}</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">4#分卷</span>
|
||||
<span class="cell-value">{{ r('EXIT_VALUE4','exit_value4','0') }}</span>
|
||||
<span class="cell-unit">[t]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">下达时间</span>
|
||||
<span class="cell-value" style="font-size:11px">{{ formatDate(r('ONLINE_DATE','online_date')) }}</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">来料重量</span>
|
||||
<span class="cell-value">{{ r('ENTRY_WEIGHT','entry_weight') }}</span>
|
||||
<span class="cell-unit">[t]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">来料外径</span>
|
||||
<span class="cell-value">{{ r('ENTRY_OUTER_DIAMETER','entry_outer_diameter') }}</span>
|
||||
<span class="cell-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">5#分卷</span>
|
||||
<span class="cell-value">{{ r('EXIT_VALUE5','exit_value5','0') }}</span>
|
||||
<span class="cell-unit">[t]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-cell" /><div class="detail-cell" /><div class="detail-cell" />
|
||||
<div class="detail-cell">
|
||||
<span class="cell-label">6#分卷</span>
|
||||
<span class="cell-value">{{ r('EXIT_VALUE6','exit_value6','0') }}</span>
|
||||
<span class="cell-unit">[t]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasPerfData" class="charts-wrap">
|
||||
<div ref="chartSpeed" class="chart-box" />
|
||||
<div ref="chartMillSpeed" class="chart-box" />
|
||||
<div ref="chartTension" class="chart-box" />
|
||||
<div ref="chartPorSpeed" class="chart-box" />
|
||||
<div ref="chartPorTens" class="chart-box" />
|
||||
<div ref="chartTrim" class="chart-box" />
|
||||
<div ref="chartTemp" class="chart-box" />
|
||||
<div ref="chartMesh" class="chart-box" />
|
||||
<div ref="chartElong" class="chart-box" />
|
||||
</div>
|
||||
|
||||
<el-empty v-else-if="!perfLoading" description="暂无实绩数据" :image-size="56" style="margin-top: 24px" />
|
||||
</template>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-else class="empty-hint">← 点击上方表格行查看详情</div>
|
||||
</div>
|
||||
|
||||
<!-- 设定参数(PLTCM_PRESET_SETUP) -->
|
||||
<div class="settings-panel">
|
||||
<div class="panel-title">
|
||||
设定参数
|
||||
<el-tag v-if="setupLoading" size="mini" type="info" style="margin-left:6px">加载中…</el-tag>
|
||||
</div>
|
||||
<el-tabs v-model="settingsTab" size="small" class="settings-tabs">
|
||||
<!-- 入口段 -->
|
||||
<el-tab-pane label="入口段" name="entry">
|
||||
<div v-if="!selectedRow" class="empty-hint">请先选择计划</div>
|
||||
<div v-else class="param-grid">
|
||||
<div class="param-row">
|
||||
<span class="param-label">开卷机单位张力</span>
|
||||
<span class="param-value">{{ s('POR_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">入口活套单位张力</span>
|
||||
<span class="param-value">{{ s('CEL_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">矫直机1#辊插入量</span>
|
||||
<span class="param-value">{{ s('FLAT_MESH_1') }}</span>
|
||||
<span class="param-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">矫直机2#辊插入量</span>
|
||||
<span class="param-value">{{ s('FLAT_MESH_2') }}</span>
|
||||
<span class="param-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">矫直机3#辊插入量</span>
|
||||
<span class="param-value">{{ s('FLAT_MESH_3') }}</span>
|
||||
<span class="param-unit">[mm]</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 工艺段 -->
|
||||
<el-tab-pane label="工艺段" name="process">
|
||||
<div v-if="!selectedRow" class="empty-hint">请先选择计划</div>
|
||||
<div v-else class="param-grid">
|
||||
<div class="param-row">
|
||||
<span class="param-label">拉弯矫直机单位张力</span>
|
||||
<span class="param-value">{{ s('TLV_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">拉弯矫直机延伸率</span>
|
||||
<span class="param-value">{{ s('TLV_ELONG') }}</span>
|
||||
<span class="param-unit">[%]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">弯曲辊1#弯辊量</span>
|
||||
<span class="param-value">{{ s('TLV_MESH_1') }}</span>
|
||||
<span class="param-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">弯曲辊2#弯辊量</span>
|
||||
<span class="param-value">{{ s('TLV_MESH_2') }}</span>
|
||||
<span class="param-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">弯曲辊3#弯辊量</span>
|
||||
<span class="param-value">{{ s('TLV_MESH_3') }}</span>
|
||||
<span class="param-unit">[mm]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">酸洗处理段单位张力</span>
|
||||
<span class="param-value">{{ s('CPL_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">处理段最大速度</span>
|
||||
<span class="param-value">{{ s('CPL_MAX_SPEED') }}</span>
|
||||
<span class="param-unit">[m/min]</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 出口段 -->
|
||||
<el-tab-pane label="出口段" name="exit">
|
||||
<div v-if="!selectedRow" class="empty-hint">请先选择计划</div>
|
||||
<div v-else class="param-grid">
|
||||
<div class="param-row">
|
||||
<span class="param-label">酸洗出口张力辊张力</span>
|
||||
<span class="param-value">{{ s('TR_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">切边段单位张力</span>
|
||||
<span class="param-value">{{ s('TRIM_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">联机活套单位张力</span>
|
||||
<span class="param-value">{{ s('TEL_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
<div class="param-row">
|
||||
<span class="param-label">出口活套单位张力</span>
|
||||
<span class="param-value">{{ s('CXL_TEN') }}</span>
|
||||
<span class="param-unit">[N/mm²]</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
import {
|
||||
getTimingPlanList,
|
||||
getTimingPlanCount,
|
||||
getTimingPlanDetail,
|
||||
getTimingSegByEncoilId
|
||||
getPresetSetupByCoilId
|
||||
} from '@/api/l2/timing'
|
||||
|
||||
const PLAN_FIELDS = [
|
||||
{ key: 'status', label: '状态' },
|
||||
{ key: 'process_code', label: '工艺编码' },
|
||||
{ key: 'entry_thick', label: '入口厚度(mm)' },
|
||||
{ key: 'entry_width', label: '入口宽度(mm)' },
|
||||
{ key: 'entry_weight', label: '入口重量(t)' },
|
||||
{ key: 'exit_thick', label: '出口厚度(mm)' },
|
||||
{ key: 'exit_width', label: '出口宽度(mm)' },
|
||||
{ key: 'exit_length', label: '出口长度(m)' }
|
||||
]
|
||||
|
||||
const STATUS_CLASS = {
|
||||
READY: 'status-ready',
|
||||
ONLINE: 'status-online',
|
||||
PRODUCING: 'status-producing',
|
||||
PRODUCT: 'status-product'
|
||||
}
|
||||
|
||||
function makeLine(name, data) {
|
||||
return { name, type: 'line', smooth: true, symbol: 'none', data }
|
||||
}
|
||||
|
||||
function baseOption(title, xData, series, yName) {
|
||||
return {
|
||||
title: { text: title, textStyle: { fontSize: 12, fontWeight: 'normal' }, top: 4, left: 8 },
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { top: 4, right: 8, textStyle: { fontSize: 11 } },
|
||||
grid: { top: 36, bottom: 28, left: 8, right: 8, containLabel: true },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xData,
|
||||
name: 'pos(m)',
|
||||
nameTextStyle: { fontSize: 10 },
|
||||
axisLabel: { fontSize: 10 }
|
||||
},
|
||||
yAxis: { type: 'value', name: yName, nameTextStyle: { fontSize: 10 }, axisLabel: { fontSize: 10 } },
|
||||
series
|
||||
}
|
||||
}
|
||||
const STATUS_MAP = { NEW: '新建', READY: '准备好', ONLINE: '在线', PRODUCING: '生产中', PRODUCT: '产出' }
|
||||
const STATUS_TAG = { NEW: 'info', READY: 'success', ONLINE: 'warning', PRODUCING: 'warning', PRODUCT: 'primary' }
|
||||
const NEXT_UNIT_MAP = { 'C000': '冷硬卷', 'G000': '连退', 'Z000': '镀锌', 'A000': '退火' }
|
||||
|
||||
export default {
|
||||
name: 'TimingAcidPage',
|
||||
name: 'PlanManagement',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
perfLoading: false,
|
||||
queryForm: { coilId: '' },
|
||||
setupLoading: false,
|
||||
queryForm: { coilId: '', status: '' },
|
||||
planRows: [],
|
||||
selectedPlan: null,
|
||||
perfSeries: null,
|
||||
perfSegCount: 0,
|
||||
planFields: PLAN_FIELDS,
|
||||
pagination: { page: 1, pageSize: 50, total: 0 },
|
||||
tableHeight: 'calc(100vh - 210px)'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasPerfData() {
|
||||
return this.perfSeries && this.perfSegCount > 0
|
||||
selectedRow: null,
|
||||
setupData: {},
|
||||
settingsTab: 'exit',
|
||||
pagination: { page: 1, pageSize: 20, total: 0 },
|
||||
tableHeight: 'calc(100vh - 430px)'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// plain instance property — Vue 2 does NOT proxy underscore-prefixed names
|
||||
this.chartInstances = []
|
||||
this.resizeHandler = null
|
||||
this.loadPlanCount()
|
||||
this.loadPlanList()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.disposeCharts()
|
||||
this.loadCount()
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
statusClass(status) {
|
||||
return STATUS_CLASS[status] || 'status-default'
|
||||
},
|
||||
async loadPlanCount() {
|
||||
async loadCount() {
|
||||
try {
|
||||
const res = await getTimingPlanCount()
|
||||
this.pagination.total = res?.data?.total ?? 0
|
||||
} catch (_) {}
|
||||
},
|
||||
async loadPlanList() {
|
||||
async loadList() {
|
||||
this.loading = true
|
||||
try {
|
||||
const { page, pageSize } = this.pagination
|
||||
const res = await getTimingPlanList(page, pageSize)
|
||||
const res = await getTimingPlanList(this.pagination.page, this.pagination.pageSize)
|
||||
this.planRows = res?.data?.rows || []
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async handleRowClick(row) {
|
||||
this.selectedRow = row
|
||||
this.setupData = {}
|
||||
// COILID is the plan's primary key → matches PLTCM_PRESET_SETUP.COILID
|
||||
const coilId = row.COILID || row.coilid
|
||||
if (!coilId) return
|
||||
this.setupLoading = true
|
||||
try {
|
||||
const res = await getPresetSetupByCoilId(coilId)
|
||||
this.setupData = res?.data?.data || {}
|
||||
} catch (_) {
|
||||
} finally {
|
||||
this.setupLoading = false
|
||||
}
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pagination.page = page
|
||||
this.loadPlanList()
|
||||
this.loadList()
|
||||
},
|
||||
async handleSearch() {
|
||||
if (!this.queryForm.coilId) return this.loadPlanList()
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getTimingPlanDetail(this.queryForm.coilId)
|
||||
const row = res?.data?.firstRow || null
|
||||
if (row) {
|
||||
this.selectedPlan = row
|
||||
await this.loadPerf(row)
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
handleSizeChange(size) {
|
||||
this.pagination.pageSize = size
|
||||
this.pagination.page = 1
|
||||
this.loadList()
|
||||
},
|
||||
async handlePlanRowClick(row) {
|
||||
this.selectedPlan = row
|
||||
this.perfSeries = null
|
||||
this.perfSegCount = 0
|
||||
this.disposeCharts()
|
||||
await this.loadPerf(row)
|
||||
},
|
||||
async loadPerf(plan) {
|
||||
const encoilId = plan.encoilid || plan.coilid
|
||||
if (!encoilId) return
|
||||
this.perfLoading = true
|
||||
try {
|
||||
const res = await getTimingSegByEncoilId(encoilId)
|
||||
const keys = Object.keys(res?.data?.series || {})
|
||||
console.log(keys)
|
||||
|
||||
const series = res?.data?.series || null
|
||||
const rows = res?.data?.rows || []
|
||||
this.perfSegCount = rows.length
|
||||
this.perfSeries = series
|
||||
if (series && rows.length) {
|
||||
await this.$nextTick()
|
||||
this.renderCharts(series)
|
||||
}
|
||||
} catch (_) {
|
||||
this.perfSeries = null
|
||||
this.perfSegCount = 0
|
||||
} finally {
|
||||
this.perfLoading = false
|
||||
}
|
||||
},
|
||||
disposeCharts() {
|
||||
if (this.resizeHandler) {
|
||||
window.removeEventListener('resize', this.resizeHandler)
|
||||
this.resizeHandler = null
|
||||
}
|
||||
if (this.chartInstances && this.chartInstances.length) {
|
||||
this.chartInstances.forEach(c => { if (c) c.dispose() })
|
||||
this.chartInstances = []
|
||||
}
|
||||
},
|
||||
renderCharts(series) {
|
||||
this.disposeCharts()
|
||||
const pick = key => (series[key] || []).map(v => v == null ? null : Number(v).toFixed(2))
|
||||
const xData = (series.startpos || []).map(v => v == null ? '' : Number(v).toFixed(1))
|
||||
|
||||
const c1 = echarts.init(this.$refs.chartSpeed)
|
||||
c1.setOption(baseOption(
|
||||
'速度趋势 (m/min)', xData,
|
||||
[
|
||||
makeLine('轧制速度 plspeed', pick('plspeed')),
|
||||
makeLine('剪切速度 trimspeed', pick('trimspeed'))
|
||||
],
|
||||
'm/min'
|
||||
))
|
||||
|
||||
const c2 = echarts.init(this.$refs.chartMillSpeed)
|
||||
c2.setOption(baseOption(
|
||||
'轧机速度 (m/min)', xData,
|
||||
[
|
||||
makeLine('入口速度 millentryspeed', pick('millentryspeed')),
|
||||
makeLine('出口速度 millexitspeed', pick('millexitspeed'))
|
||||
],
|
||||
'm/min'
|
||||
))
|
||||
|
||||
const c3 = echarts.init(this.$refs.chartTension)
|
||||
c3.setOption(baseOption(
|
||||
'张力趋势 (N)', xData,
|
||||
[
|
||||
makeLine('出口张力 pltens', pick('pltens')),
|
||||
makeLine('入口张力 enltens', pick('enltens')),
|
||||
makeLine('cxltens', pick('cxltens'))
|
||||
],
|
||||
'N'
|
||||
))
|
||||
|
||||
const c4 = echarts.init(this.$refs.chartPorSpeed)
|
||||
c4.setOption(baseOption(
|
||||
'开卷机速度 (m/min)', xData,
|
||||
[
|
||||
makeLine('开卷速度 porspeed', pick('porspeed')),
|
||||
makeLine('最大 porspeedmax', pick('porspeedmax')),
|
||||
makeLine('最小 porspeedmin', pick('porspeedmin'))
|
||||
],
|
||||
'm/min'
|
||||
))
|
||||
|
||||
const c5 = echarts.init(this.$refs.chartPorTens)
|
||||
c5.setOption(baseOption(
|
||||
'开卷机张力 (N)', xData,
|
||||
[
|
||||
makeLine('开卷张力 portens', pick('portens')),
|
||||
makeLine('最大 portensmax', pick('portensmax')),
|
||||
makeLine('最小 portensmin', pick('portensmin'))
|
||||
],
|
||||
'N'
|
||||
))
|
||||
|
||||
const c6 = echarts.init(this.$refs.chartTrim)
|
||||
c6.setOption(baseOption(
|
||||
'剪切参数', xData,
|
||||
[
|
||||
makeLine('剪切张力 trimtens', pick('trimtens')),
|
||||
makeLine('剪切宽度 trimwidth', pick('trimwidth')),
|
||||
makeLine('trtens', pick('trtens'))
|
||||
],
|
||||
''
|
||||
))
|
||||
|
||||
const c7 = echarts.init(this.$refs.chartTemp)
|
||||
c7.setOption(baseOption(
|
||||
'温度趋势 (℃)', xData,
|
||||
[
|
||||
makeLine('1号测温 tk1temp', pick('tk1temp')),
|
||||
makeLine('2号测温 tk2temp', pick('tk2temp')),
|
||||
makeLine('3号测温 tk3temp', pick('tk3temp')),
|
||||
makeLine('4号测温 tk4temp', pick('tk4temp')),
|
||||
makeLine('漂洗温度 rinsetemp', pick('rinsetemp'))
|
||||
],
|
||||
'℃'
|
||||
))
|
||||
|
||||
const c8 = echarts.init(this.$refs.chartMesh)
|
||||
c8.setOption(baseOption(
|
||||
'轧辊参数', xData,
|
||||
[
|
||||
makeLine('轧辊1 tlmesh1', pick('tlmesh1')),
|
||||
makeLine('轧辊2 tlmesh2', pick('tlmesh2')),
|
||||
makeLine('轧辊3 tlmesh3', pick('tlmesh3'))
|
||||
],
|
||||
''
|
||||
))
|
||||
|
||||
const c9 = echarts.init(this.$refs.chartElong)
|
||||
c9.setOption(baseOption(
|
||||
'延伸率', xData,
|
||||
[
|
||||
makeLine('延伸率 tlelong', pick('tlelong')),
|
||||
makeLine('总张力 tltens', pick('tltens')),
|
||||
makeLine('teltens', pick('teltens'))
|
||||
],
|
||||
''
|
||||
))
|
||||
|
||||
this.chartInstances = [c1, c2, c3, c4, c5, c6, c7, c8, c9]
|
||||
this.resizeHandler = () => this.chartInstances.forEach(c => { if (c) c.resize() })
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
handleSearch() {
|
||||
this.pagination.page = 1
|
||||
this.loadList()
|
||||
},
|
||||
handleReset() {
|
||||
this.queryForm.coilId = ''
|
||||
this.selectedPlan = null
|
||||
this.perfSeries = null
|
||||
this.perfSegCount = 0
|
||||
this.disposeCharts()
|
||||
this.queryForm = { coilId: '', status: '' }
|
||||
this.selectedRow = null
|
||||
this.setupData = {}
|
||||
this.pagination.page = 1
|
||||
this.loadPlanList()
|
||||
this.loadCount()
|
||||
this.loadList()
|
||||
},
|
||||
// get from selectedRow (upper or lower case key)
|
||||
r(upper, lower, fallback = '—') {
|
||||
if (!this.selectedRow) return fallback
|
||||
const v = this.selectedRow[upper] !== undefined ? this.selectedRow[upper] : this.selectedRow[lower]
|
||||
return v != null ? v : fallback
|
||||
},
|
||||
// get from setupData (PLTCM_PRESET_SETUP columns are uppercase from Oracle)
|
||||
s(col) {
|
||||
const v = this.setupData[col] !== undefined ? this.setupData[col] : this.setupData[col.toLowerCase()]
|
||||
return v != null ? Number(v).toFixed(2) : '—'
|
||||
},
|
||||
formatDate(v) {
|
||||
if (!v) return '—'
|
||||
return String(v).replace('T', ' ').substring(0, 19)
|
||||
},
|
||||
formatStatus(s) { return STATUS_MAP[s] || s || '—' },
|
||||
statusTagType(s) { return STATUS_TAG[s] || '' },
|
||||
formatNextUnit(v) { return NEXT_UNIT_MAP[v] || v || '—' },
|
||||
formatTrimming(v) {
|
||||
if (v === 1 || v === '1') return '净边料'
|
||||
if (v === 0 || v === '0') return '不切边'
|
||||
return v || '—'
|
||||
},
|
||||
formatStratMode(v) {
|
||||
const n = parseInt(v)
|
||||
if (n === 0) return '延伸率模式'
|
||||
if (n === 1) return '压下率模式'
|
||||
return v || '—'
|
||||
},
|
||||
calcPosDev(row) {
|
||||
const exit = parseFloat(row.EXIT_THICK || row.exit_thick || 0)
|
||||
const max = parseFloat(row.MAX_EXIT_THICK || row.max_exit_thick || exit)
|
||||
return isNaN(max - exit) ? '—' : (max - exit).toFixed(3)
|
||||
},
|
||||
calcNegDev(row) {
|
||||
const exit = parseFloat(row.EXIT_THICK || row.exit_thick || 0)
|
||||
const min = parseFloat(row.MIN_EXIT_THICK || row.min_exit_thick || exit)
|
||||
return isNaN(min - exit) ? '—' : (min - exit).toFixed(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.plan-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 8px;
|
||||
background: #f0f2f5;
|
||||
gap: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.main-row {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.left-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
flex-shrink: 0;
|
||||
|
||||
.total-hint {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 80px;
|
||||
.table-wrap {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
padding: 4px 8px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
.bottom-panels {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
flex: 3;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings-panel {
|
||||
flex: 2;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.detail-grid { display: flex; flex-direction: column; }
|
||||
|
||||
.detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border-top: 1px solid #ebeef5;
|
||||
border-left: 1px solid #ebeef5;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.detail-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 10px;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
gap: 6px;
|
||||
border-right: 1px solid #ebeef5;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
&:last-child { border-right: none; }
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-bottom: 3px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.perf-header {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
.cell-unit { font-size: 11px; color: #909399; }
|
||||
|
||||
.settings-tabs {
|
||||
flex: 1;
|
||||
::v-deep .el-tabs__content { padding: 8px 0 0; }
|
||||
}
|
||||
|
||||
.param-grid { display: flex; flex-direction: column; gap: 0; }
|
||||
|
||||
.param-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 8px;
|
||||
border-bottom: 1px solid #f0f2f5;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.perf-count {
|
||||
font-weight: normal;
|
||||
color: #909399;
|
||||
.param-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
width: 130px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.charts-wrap {
|
||||
/* display: flex; */
|
||||
/* flex-direction: column;
|
||||
gap: 12px; */
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 310px);
|
||||
.param-value {
|
||||
font-size: 13px;
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.param-unit { font-size: 11px; color: #909399; }
|
||||
|
||||
/* 状态指示点 */
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
.empty-hint {
|
||||
color: #c0c4cc;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.status-text {
|
||||
font-size: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.status-ready { background: #909399; }
|
||||
.status-online { background: #67c23a; }
|
||||
.status-producing { background: #e6a23c; }
|
||||
.status-product { background: #409eff; }
|
||||
.status-default { background: #c0c4cc; }
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,11 @@
|
||||
<span slot="title">品质</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="performance">
|
||||
<i class="el-icon-coin"></i>
|
||||
<i class="el-icon-date"></i>
|
||||
<span slot="title">计划</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="actualPerformance">
|
||||
<i class="el-icon-data-analysis"></i>
|
||||
<span slot="title">实绩</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="rollConfig">
|
||||
@@ -42,13 +46,9 @@
|
||||
<i class="el-icon-warning"></i>
|
||||
<span slot="title">停机</span>
|
||||
</el-menu-item>
|
||||
<!-- <el-menu-item index="realTime">
|
||||
<i class="el-icon-time"></i>
|
||||
<span slot="title">实时</span>
|
||||
</el-menu-item> -->
|
||||
</el-menu>
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="flex: 1; overflow: hidden;">
|
||||
<component :is="currentComponent" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,7 +61,7 @@ import Report from './components/Report.vue';
|
||||
import Shipping from './components/Shipping.vue';
|
||||
import Quality from './components/Quality.vue';
|
||||
import Performance from './components/Performance.vue';
|
||||
import RealTime from './components/RealTime.vue';
|
||||
import ActualPerformance from './components/ActualPerformance.vue';
|
||||
import RollConfig from '@/views/timing/roll/index.vue';
|
||||
import RollHistory from '@/views/timing/roll/history.vue';
|
||||
import Stoppage from '@/views/timing/stoppage/index.vue';
|
||||
@@ -75,7 +75,7 @@ export default {
|
||||
Shipping,
|
||||
Quality,
|
||||
Performance,
|
||||
RealTime,
|
||||
ActualPerformance,
|
||||
RollConfig,
|
||||
RollHistory,
|
||||
Stoppage
|
||||
@@ -94,7 +94,7 @@ export default {
|
||||
shipping: 'Shipping',
|
||||
quality: 'Quality',
|
||||
performance: 'Performance',
|
||||
realTime: 'RealTime',
|
||||
actualPerformance: 'ActualPerformance',
|
||||
rollConfig: 'RollConfig',
|
||||
rollHistory: 'RollHistory',
|
||||
stoppage: 'Stoppage',
|
||||
|
||||
Reference in New Issue
Block a user