添加数据贯通代码未调试

This commit is contained in:
2026-04-25 18:19:29 +08:00
parent 7fa0a7d38f
commit 73b65962f2
9 changed files with 1197 additions and 0 deletions

View File

@@ -0,0 +1,411 @@
package com.klp.framework.client;
import com.klp.framework.config.SqlServerApiProperties;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* sql-server-api 统一调用客户端。
* <p>
* 对齐 {@code sql-server-api/app.py} 的接口规范:
* <ul>
* <li>GET /health</li>
* <li>GET /heartbeat</li>
* <li>POST /table-schema</li>
* <li>POST /execute-sql</li>
* </ul>
*/
@Component
public class SqlServerApiClient {
private final RestTemplate restTemplate;
private final SqlServerApiProperties properties;
public SqlServerApiClient(RestTemplate sqlServerApiRestTemplate, SqlServerApiProperties properties) {
this.restTemplate = sqlServerApiRestTemplate;
this.properties = properties;
}
public String getBaseUrl() {
return normalizeBaseUrl();
}
public Map<String, Object> health() {
return getForMap("/health");
}
public Map<String, Object> heartbeat() {
return getForMap("/heartbeat");
}
public TableSchemaResponse tableSchema(String tableType, String tableName) {
return postForObject("/table-schema", new TableSchemaRequest(tableType, tableName), TableSchemaResponse.class);
}
public ExecuteSqlResponse executeSql(String tableType, String sql, Map<String, Object> params) {
return postForObject("/execute-sql", new ExecuteSqlRequest(tableType, sql, params), ExecuteSqlResponse.class);
}
public <T> T get(String path, Class<T> responseType) {
return restTemplate.getForObject(buildUri(path), responseType);
}
public <T> T get(String path, Map<String, ?> queryParams, Class<T> responseType) {
URI uri = UriComponentsBuilder.fromHttpUrl(normalizeBaseUrl())
.path(normalizePath(path))
.queryParams(convertToQueryParams(queryParams))
.build(true)
.toUri();
return restTemplate.getForObject(uri, responseType);
}
public <T, R> T post(String path, R requestBody, Class<T> responseType) {
return restTemplate.postForObject(buildUri(path), requestBody, responseType);
}
public static class TableSchemaRequest {
private String tableType;
private String tableName;
public TableSchemaRequest() {
}
public TableSchemaRequest(String tableType, String tableName) {
this.tableType = tableType;
this.tableName = tableName;
}
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
}
public static class ExecuteSqlRequest {
private String tableType;
private String sql;
private Map<String, Object> params;
public ExecuteSqlRequest() {
}
public ExecuteSqlRequest(String tableType, String sql, Map<String, Object> params) {
this.tableType = tableType;
this.sql = sql;
this.params = params;
}
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Map<String, Object> getParams() {
return params;
}
public void setParams(Map<String, Object> params) {
this.params = params;
}
}
public static class TableSchemaResponse {
private String tableType;
private String tableName;
private Schema schema;
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public Schema getSchema() {
return schema;
}
public void setSchema(Schema schema) {
this.schema = schema;
}
public static class Schema {
private List<Column> columns = Collections.emptyList();
public List<Column> getColumns() {
return columns;
}
public void setColumns(List<Column> columns) {
this.columns = columns;
}
}
public static class Column {
private String name;
private String type;
private boolean nullable;
private Object defaultValue;
private boolean primaryKey;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isNullable() {
return nullable;
}
public void setNullable(boolean nullable) {
this.nullable = nullable;
}
public Object getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}
public boolean isPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(boolean primaryKey) {
this.primaryKey = primaryKey;
}
}
}
public static class ExecuteSqlResponse {
private String tableType;
private String resultType;
private Integer rowCount;
private Object result;
public String getTableType() {
return tableType;
}
public void setTableType(String tableType) {
this.tableType = tableType;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public Integer getRowCount() {
return rowCount;
}
public void setRowCount(Integer rowCount) {
this.rowCount = rowCount;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
private Map<String, Object> getForMap(String path) {
ResponseEntity<Map> responseEntity = restTemplate.exchange(buildUri(path), HttpMethod.GET, HttpEntity.EMPTY, Map.class);
Map<String, Object> body = responseEntity.getBody();
return body == null ? Collections.emptyMap() : body;
}
private <T> T postForObject(String path, Object requestBody, Class<T> responseType) {
return restTemplate.postForObject(buildUri(path), requestBody, responseType);
}
private URI buildUri(String path) {
return UriComponentsBuilder.fromHttpUrl(normalizeBaseUrl())
.path(normalizePath(path))
.build(true)
.toUri();
}
private String normalizeBaseUrl() {
if (properties.getBaseUrl() != null && properties.getBaseUrl().trim().length() > 0) {
return properties.getBaseUrl();
}
if (properties.getHost() == null || properties.getHost().trim().length() == 0 || properties.getPort() == null) {
throw new IllegalStateException("sql-server-api 配置缺失,请检查 host/port/base-url");
}
return "http://" + properties.getHost() + ":" + properties.getPort();
}
private String normalizePath(String path) {
if (path == null || path.trim().length() == 0) {
return "";
}
return path.startsWith("/") ? path : "/" + path;
}
public ExecuteSqlResponse queryPlanByCoilId(String coilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where COILID = :coilId",
singletonParam("coilId", coilId)
);
}
public ExecuteSqlResponse queryPlanList() {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN order by INSDATE desc",
emptyParams()
);
}
public ExecuteSqlResponse queryPlanListByProcessCode(String processCode) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where PROCESS_CODE = :processCode order by INSDATE desc",
singletonParam("processCode", processCode)
);
}
public ExecuteSqlResponse queryPlanListByStatus(String status) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where STATUS = :status order by INSDATE desc",
singletonParam("status", status)
);
}
public ExecuteSqlResponse queryPlanByHotCoilId(String hotCoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PDI_PLAN where HOT_COILID = :hotCoilId",
singletonParam("hotCoilId", hotCoilId)
);
}
public ExecuteSqlResponse queryProSegByEncoilId(String encoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PRO_SEG where ENCOILID = :encoilId order by SEGNO",
singletonParam("encoilId", encoilId)
);
}
public ExecuteSqlResponse queryProSegByExcoilId(String excoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.PLTCM_PRO_SEG where EXCOILID = :excoilId order by SEGNO",
singletonParam("excoilId", excoilId)
);
}
public ExecuteSqlResponse queryQualityByExcoilId(String excoilId) {
return executeSql(
"oracle",
"select * from JXPLTCM.V_PLTCM_PDO_QUALITY where EXCOILID = :excoilId order by END_DATE desc",
singletonParam("excoilId", excoilId)
);
}
public ExecuteSqlResponse queryGaugeByMatId(String matId) {
return executeSql(
"oracle",
"select * from JXPLTCM.V_VBDA_GAUGE where MATID = :matId order by XTIME asc",
singletonParam("matId", matId)
);
}
public ExecuteSqlResponse queryShapeByMatId(String matId) {
return executeSql(
"oracle",
"select * from JXPLTCM.V_VBDA_SHAPE where MATID = :matId order by XTIME asc",
singletonParam("matId", matId)
);
}
private org.springframework.util.MultiValueMap<String, String> convertToQueryParams(Map<String, ?> queryParams) {
org.springframework.util.LinkedMultiValueMap<String, String> multiValueMap = new org.springframework.util.LinkedMultiValueMap<>();
if (queryParams == null || queryParams.isEmpty()) {
return multiValueMap;
}
for (Map.Entry<String, ?> entry : queryParams.entrySet()) {
Object value = entry.getValue();
if (value != null) {
multiValueMap.add(entry.getKey(), String.valueOf(value));
}
}
return multiValueMap;
}
private Map<String, Object> singletonParam(String key, Object value) {
java.util.HashMap<String, Object> params = new java.util.HashMap<String, Object>();
if (value != null) {
params.put(key, value);
}
return params;
}
private Map<String, Object> emptyParams() {
return new java.util.HashMap<String, Object>();
}
}

View File

@@ -0,0 +1,23 @@
package com.klp.framework.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
/**
* sql-server-api HTTP 客户端配置
*/
@Configuration
public class SqlServerApiClientConfig {
@Bean
public RestTemplate sqlServerApiRestTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(30))
.build();
}
}

View File

@@ -0,0 +1,51 @@
package com.klp.framework.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* sql-server-api 中间件配置
*/
@Component
@ConfigurationProperties(prefix = "sql-server-api")
public class SqlServerApiProperties {
/**
* 请求地址
*/
private String host;
/**
* 请求端口
*/
private Integer port;
/**
* 基础访问地址
*/
private String baseUrl;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
}

View File

@@ -0,0 +1,306 @@
package com.klp.framework.service;
import com.klp.framework.client.SqlServerApiClient;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 面向业务场景的 sql-server-api 查询服务。
* <p>
* 当前抽象为三个功能:
* <ol>
* <li>计划查询</li>
* <li>钢卷实际 / SEG 查询</li>
* <li>实时数据Gauge / Shape查询</li>
* </ol>
*/
@Service
public class SqlServerApiBusinessService {
private final SqlServerApiClient client;
public SqlServerApiBusinessService(SqlServerApiClient client) {
this.client = client;
}
/**
* 计划列表:查询所有计划,按时间倒序。
* <p>
* 这是后续按 coilId 关联 SEG、实时数据的入口。
*/
public PlanListView getPlanList() {
return PlanListView.fromExecuteSqlResponse(client.queryPlanList());
}
/**
* 计划列表:按工艺流程编码筛选。
*/
public PlanListView getPlanListByProcessCode(String processCode) {
return PlanListView.fromExecuteSqlResponse(client.queryPlanListByProcessCode(processCode));
}
/**
* 计划列表:按状态筛选。
*/
public PlanListView getPlanListByStatus(String status) {
return PlanListView.fromExecuteSqlResponse(client.queryPlanListByStatus(status));
}
/**
* 计划详情:按成品卷号查询单条计划。
*/
public PlanDetailView getPlanByCoilId(String coilId) {
return PlanDetailView.fromExecuteSqlResponse(coilId, client.queryPlanByCoilId(coilId));
}
/**
* 计划详情:按热卷号查询。
*/
public PlanDetailView getPlanByHotCoilId(String hotCoilId) {
return PlanDetailView.fromExecuteSqlResponse(hotCoilId, client.queryPlanByHotCoilId(hotCoilId));
}
/**
* 钢卷实际 SEG 查询:按入口卷号查询。
* <p>
* 返回的是按 SEGNO 排序后的原始行数据。
*/
public SegSeriesView getSegByEncoilId(String encoilId) {
return SegSeriesView.fromExecuteSqlResponse(encoilId, client.queryProSegByEncoilId(encoilId));
}
/**
* 钢卷实际 SEG 查询:按出口卷号查询。
*/
public SegSeriesView getSegByExcoilId(String excoilId) {
return SegSeriesView.fromExecuteSqlResponse(excoilId, client.queryProSegByExcoilId(excoilId));
}
/**
* 钢卷 SEG 视图数据:把同一钢卷下按 SEGNO 排序的多行数据,整理成“一个钢卷 id + 一个属性对应一个列表”的结构。
* <p>
* 例如:厚度、张力、速度都会被整理成数组,方便前端直接画表或曲线。
*/
public SegSeriesView getSegSeriesViewByEncoilId(String encoilId) {
return getSegByEncoilId(encoilId);
}
/**
* 实时数据:厚度/仪表数据。
*/
public SqlServerApiClient.ExecuteSqlResponse getRealtimeGaugeByMatId(String matId) {
return client.queryGaugeByMatId(matId);
}
/**
* 实时数据:板形/形状数据。
*/
public SqlServerApiClient.ExecuteSqlResponse getRealtimeShapeByMatId(String matId) {
return client.queryShapeByMatId(matId);
}
/**
* 实时数据总入口:一次性返回厚度与板形两类数据。
*/
public RealtimeDataBundle getRealtimeData(String matId) {
return new RealtimeDataBundle(
getRealtimeGaugeByMatId(matId),
getRealtimeShapeByMatId(matId)
);
}
private static PlanListView toPlanListView(SqlServerApiClient.ExecuteSqlResponse response) {
return PlanListView.fromExecuteSqlResponse(response);
}
private static List<Map<String, Object>> asRowList(SqlServerApiClient.ExecuteSqlResponse response) {
if (response == null || response.getResult() == null) {
return Collections.emptyList();
}
Object result = response.getResult();
if (result instanceof List) {
List<?> list = (List<?>) result;
List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
for (Object item : list) {
if (item instanceof Map) {
rows.add((Map<String, Object>) item);
}
}
return rows;
}
return Collections.emptyList();
}
private static Number asNumber(Object value) {
if (value instanceof Number) {
return (Number) value;
}
if (value == null) {
return null;
}
try {
return new BigDecimal(String.valueOf(value));
} catch (Exception ex) {
return null;
}
}
private static String asString(Object value) {
return value == null ? null : String.valueOf(value);
}
public static class PlanListView {
private final List<String> coilIds;
private final List<Map<String, Object>> rows;
public PlanListView(List<String> coilIds, List<Map<String, Object>> rows) {
this.coilIds = coilIds;
this.rows = rows;
}
public List<String> getCoilIds() {
return coilIds;
}
public List<Map<String, Object>> getRows() {
return rows;
}
public static PlanListView fromExecuteSqlResponse(SqlServerApiClient.ExecuteSqlResponse response) {
List<Map<String, Object>> rows = asRowList(response);
List<String> coilIds = rows.stream()
.map(row -> asString(row.get("COILID")))
.filter(value -> value != null && value.trim().length() > 0)
.distinct()
.collect(Collectors.toList());
return new PlanListView(coilIds, rows);
}
}
public static class PlanDetailView {
private final String coilId;
private final List<Map<String, Object>> rows;
private final Map<String, Object> firstRow;
public PlanDetailView(String coilId, List<Map<String, Object>> rows, Map<String, Object> firstRow) {
this.coilId = coilId;
this.rows = rows;
this.firstRow = firstRow;
}
public String getCoilId() {
return coilId;
}
public List<Map<String, Object>> getRows() {
return rows;
}
public Map<String, Object> getFirstRow() {
return firstRow;
}
public static PlanDetailView fromExecuteSqlResponse(String coilId, SqlServerApiClient.ExecuteSqlResponse response) {
List<Map<String, Object>> rows = asRowList(response);
Map<String, Object> firstRow = rows.isEmpty() ? Collections.<String, Object>emptyMap() : rows.get(0);
return new PlanDetailView(coilId, rows, firstRow);
}
}
public static class SegSeriesView {
private final String coilId;
private final List<Integer> segNo;
private final Map<String, List<Object>> series;
private final List<Map<String, Object>> rows;
public SegSeriesView(String coilId, List<Integer> segNo, Map<String, List<Object>> series, List<Map<String, Object>> rows) {
this.coilId = coilId;
this.segNo = segNo;
this.series = series;
this.rows = rows;
}
public String getCoilId() {
return coilId;
}
public List<Integer> getSegNo() {
return segNo;
}
public Map<String, List<Object>> getSeries() {
return series;
}
public List<Map<String, Object>> getRows() {
return rows;
}
@SuppressWarnings("unchecked")
public static SegSeriesView fromExecuteSqlResponse(String coilId, SqlServerApiClient.ExecuteSqlResponse response) {
List<Map<String, Object>> rows = asRowList(response);
Collections.sort(rows, new Comparator<Map<String, Object>>() {
@Override
public int compare(Map<String, Object> left, Map<String, Object> right) {
Number leftSeg = asNumber(left.get("SEGNO"));
Number rightSeg = asNumber(right.get("SEGNO"));
int leftValue = leftSeg == null ? Integer.MAX_VALUE : leftSeg.intValue();
int rightValue = rightSeg == null ? Integer.MAX_VALUE : rightSeg.intValue();
if (leftValue > rightValue) {
return 1;
}
if (leftValue < rightValue) {
return -1;
}
return 0;
}
});
List<Integer> segNo = new ArrayList<Integer>();
for (Map<String, Object> row : rows) {
Number seg = asNumber(row.get("SEGNO"));
segNo.add(seg == null ? null : seg.intValue());
}
Map<String, List<Object>> series = new LinkedHashMap<String, List<Object>>();
for (Map<String, Object> row : rows) {
for (Map.Entry<String, Object> entry : row.entrySet()) {
String key = entry.getKey();
if ("SEGNO".equalsIgnoreCase(key)) {
continue;
}
series.computeIfAbsent(key, ignored -> new ArrayList<>()).add(entry.getValue());
}
}
return new SegSeriesView(coilId, segNo, series, rows);
}
}
public static class RealtimeDataBundle {
private final SqlServerApiClient.ExecuteSqlResponse gauge;
private final SqlServerApiClient.ExecuteSqlResponse shape;
public RealtimeDataBundle(SqlServerApiClient.ExecuteSqlResponse gauge,
SqlServerApiClient.ExecuteSqlResponse shape) {
this.gauge = gauge;
this.shape = shape;
}
public SqlServerApiClient.ExecuteSqlResponse getGauge() {
return gauge;
}
public SqlServerApiClient.ExecuteSqlResponse getShape() {
return shape;
}
}
}

View File

@@ -0,0 +1,62 @@
package com.klp.web.controller.sqlserver;
import com.klp.common.core.domain.R;
import com.klp.framework.service.SqlServerApiBusinessService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* sql-server-api 业务查询接口。
* <p>
* 提供计划、SEG、实时数据三个入口方便前端联动展示。
*/
@RequiredArgsConstructor
@RestController
@RequestMapping("/sql-server-api")
public class SqlServerApiController {
private final SqlServerApiBusinessService businessService;
/**
* 计划列表。
*/
@GetMapping("/plans")
public R<SqlServerApiBusinessService.PlanListView> planList() {
return R.ok(businessService.getPlanList());
}
/**
* 计划详情。
*/
@GetMapping("/plans/{coilId}")
public R<SqlServerApiBusinessService.PlanDetailView> planDetail(@PathVariable String coilId) {
return R.ok(businessService.getPlanByCoilId(coilId));
}
/**
* 钢卷实际 SEG按入口卷号查询。
*/
@GetMapping("/seg/{encoilId}")
public R<SqlServerApiBusinessService.SegSeriesView> segByEncoilId(@PathVariable String encoilId) {
return R.ok(businessService.getSegSeriesViewByEncoilId(encoilId));
}
/**
* 钢卷实际 SEG按出口卷号查询。
*/
@GetMapping("/seg-by-excoil/{excoilId}")
public R<SqlServerApiBusinessService.SegSeriesView> segByExcoilId(@PathVariable String excoilId) {
return R.ok(businessService.getSegByExcoilId(excoilId));
}
/**
* 实时数据:厚度、板形。
*/
@GetMapping("/realtime/{matId}")
public R<SqlServerApiBusinessService.RealtimeDataBundle> realtime(@PathVariable String matId) {
return R.ok(businessService.getRealtimeData(matId));
}
}

View File

@@ -65,6 +65,15 @@ user:
# 密码锁定时间默认10分钟 # 密码锁定时间默认10分钟
lockTime: 10 lockTime: 10
# sql-server-api 中间件配置
sql-server-api:
# 请求地址,请替换为实际 IP
host: 127.0.0.1
# 请求端口,请替换为实际端口
port: 8080
# 基础访问地址(可直接注入使用)
base-url: http://${sql-server-api.host}:${sql-server-api.port}
# Spring配置 # Spring配置
spring: spring:
application: application:

View File

@@ -0,0 +1,36 @@
import axios from 'axios'
export default function createTimingFetch(url) {
const request = axios.create({
baseURL: 'http://' + url,
headers: {
'Content-Type': 'application/json'
},
timeout: 10000
})
request.interceptors.response.use(response => response.data)
return {
getPlanList: () => request({
url: '/sql-server-api/plans',
method: 'get'
}),
getPlanDetail: (coilId) => request({
url: `/sql-server-api/plans/${coilId}`,
method: 'get'
}),
getSegByEncoilId: (encoilId) => request({
url: `/sql-server-api/seg/${encoilId}`,
method: 'get'
}),
getSegByExcoilId: (excoilId) => request({
url: `/sql-server-api/seg-by-excoil/${excoilId}`,
method: 'get'
}),
getRealtimeData: (matId) => request({
url: `/sql-server-api/realtime/${matId}`,
method: 'get'
})
}
}

View File

@@ -0,0 +1,189 @@
<template>
<div class="timing-page acid-page">
<el-card class="page-card" shadow="never">
<div slot="header" class="card-header">
<span>酸轧实绩页</span>
<el-tag type="success" size="mini">Plan + Seg + Quality</el-tag>
</div>
<el-form :inline="true" :model="queryForm" size="mini" class="query-form">
<el-form-item label="计划号">
<el-input v-model="queryForm.coilId" placeholder="输入 coilId" clearable style="width: 220px;" />
</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>
<el-row :gutter="16">
<el-col :span="8">
<el-card shadow="never" class="sub-card">
<div slot="header" class="sub-header">计划列表</div>
<el-table
:data="planRows"
height="560"
size="mini"
highlight-current-row
@row-click="handlePlanRowClick"
>
<el-table-column prop="COILID" label="COILID" min-width="140" />
<el-table-column prop="HOT_COILID" label="热卷号" min-width="140" />
<el-table-column prop="STATUS" label="状态" width="90" />
</el-table>
</el-card>
</el-col>
<el-col :span="16">
<el-card shadow="never" class="sub-card">
<div slot="header" class="sub-header">计划详情</div>
<el-empty v-if="!selectedPlan" description="请选择一条计划" />
<el-descriptions v-else :column="3" border size="mini">
<el-descriptions-item label="COILID">{{ selectedPlan.COILID }}</el-descriptions-item>
<el-descriptions-item label="HOT_COILID">{{ selectedPlan.HOT_COILID }}</el-descriptions-item>
<el-descriptions-item label="STATUS">{{ selectedPlan.STATUS }}</el-descriptions-item>
<el-descriptions-item label="ENTRY_THICK">{{ selectedPlan.ENTRY_THICK }}</el-descriptions-item>
<el-descriptions-item label="ENTRY_WIDTH">{{ selectedPlan.ENTRY_WIDTH }}</el-descriptions-item>
<el-descriptions-item label="ENTRY_WEIGHT">{{ selectedPlan.ENTRY_WEIGHT }}</el-descriptions-item>
<el-descriptions-item label="EXIT_THICK">{{ selectedPlan.EXIT_THICK }}</el-descriptions-item>
<el-descriptions-item label="EXIT_WIDTH">{{ selectedPlan.EXIT_WIDTH }}</el-descriptions-item>
<el-descriptions-item label="EXIT_LENGTH">{{ selectedPlan.EXIT_LENGTH }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card shadow="never" class="sub-card" style="margin-top: 16px;">
<div slot="header" class="sub-header">SEG 实绩</div>
<el-alert
v-if="!segView"
title="请选择计划后自动加载对应 SEG"
type="info"
:closable="false"
show-icon
/>
<template v-else>
<el-row :gutter="16">
<el-col :span="8">
<el-card shadow="never">
<div slot="header">SEGNO</div>
<div class="seg-list">
<el-tag
v-for="item in segView.segNo"
:key="item"
size="mini"
class="seg-tag"
>
{{ item }}
</el-tag>
</div>
</el-card>
</el-col>
<el-col :span="16">
<el-card shadow="never">
<div slot="header">属性数组</div>
<el-table :data="seriesTable" size="mini" height="240" border>
<el-table-column prop="key" label="属性" width="180" />
<el-table-column prop="values" label="数组值" min-width="300">
<template slot-scope="scope">
<el-tag v-for="(item, index) in scope.row.values" :key="index" size="mini" style="margin-right: 4px; margin-bottom: 4px;">
{{ item }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</template>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script>
import createTimingFetch from '@/api/l2/timing'
export default {
name: 'TimingAcidPage',
props: {
baseURL: { type: String, required: true }
},
data() {
return {
fetchApi: null,
loading: false,
queryForm: { coilId: '' },
planRows: [],
selectedPlan: null,
segView: null,
seriesTable: []
}
},
created() {
this.fetchApi = createTimingFetch(this.baseURL)
this.loadPlanList()
},
methods: {
async loadPlanList() {
this.loading = true
try {
const res = await this.fetchApi.getPlanList()
const rows = res?.data?.rows || res?.rows || []
this.planRows = rows
} finally {
this.loading = false
}
},
async handleSearch() {
if (!this.queryForm.coilId) {
return this.loadPlanList()
}
this.loading = true
try {
const res = await this.fetchApi.getPlanDetail(this.queryForm.coilId)
const row = res?.data?.firstRow || res?.firstRow || null
this.selectedPlan = row
if (row && row.ENCOILID) {
await this.loadSeg(row.ENCOILID)
}
} finally {
this.loading = false
}
},
async handlePlanRowClick(row) {
this.selectedPlan = row
if (row?.COILID) {
const detail = await this.fetchApi.getPlanDetail(row.COILID)
const plan = detail?.data?.firstRow || detail?.firstRow || row
this.selectedPlan = plan
const encoilId = plan.ENCOILID || plan.ENCOILID || plan.COILID
if (encoilId) await this.loadSeg(encoilId)
}
},
async loadSeg(encoilId) {
const res = await this.fetchApi.getSegByEncoilId(encoilId)
const view = res?.data || res
this.segView = view
this.seriesTable = Object.entries(view?.series || {}).map(([key, values]) => ({ key, values }))
},
handleReset() {
this.queryForm.coilId = ''
this.selectedPlan = null
this.segView = null
this.seriesTable = []
this.loadPlanList()
}
}
}
</script>
<style scoped>
.timing-page { padding: 16px; }
.page-card { border-radius: 12px; }
.card-header, .sub-header { display: flex; align-items: center; justify-content: space-between; font-weight: 600; }
.sub-card { border-radius: 10px; }
.query-form { margin-bottom: 12px; }
.seg-list { display: flex; flex-wrap: wrap; gap: 6px; }
.seg-tag { margin-right: 0; }
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="timing-page realtime-page">
<el-card shadow="never" class="page-card">
<div slot="header" class="card-header">
<span>实时跟踪页</span>
<el-tag type="warning" size="mini">Gauge + Shape</el-tag>
</div>
<el-form :inline="true" :model="queryForm" size="mini" class="query-form">
<el-form-item label="MATID">
<el-input v-model="queryForm.matId" placeholder="输入 MATID" clearable style="width: 240px;" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-refresh" :loading="loading" @click="handleQuery">获取实时数据</el-button>
<el-button icon="el-icon-delete" @click="handleReset">清空</el-button>
</el-form-item>
</el-form>
<el-empty v-if="!realtimeData" description="请输入 MATID 后查询实时数据" />
<template v-else>
<el-row :gutter="16">
<el-col :span="12">
<el-card shadow="never" class="sub-card">
<div slot="header">Gauge 厚度数据</div>
<el-table :data="gaugeRows" size="mini" height="520" border>
<el-table-column prop="XTIME" label="XTIME" width="120" />
<el-table-column prop="XLOCATION" label="XLOCATION" width="100" />
<el-table-column prop="THICK0" label="THICK0" width="90" />
<el-table-column prop="THICK1" label="THICK1" width="90" />
<el-table-column prop="THICK4" label="THICK4" width="90" />
<el-table-column prop="THICK5" label="THICK5" width="90" />
<el-table-column prop="EXIT_SPEED" label="EXIT_SPEED" width="100" />
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" class="sub-card">
<div slot="header">Shape 板形数据</div>
<el-table :data="shapeRows" size="mini" height="520" border>
<el-table-column prop="XTIME" label="XTIME" width="120" />
<el-table-column prop="XLOCATION" label="XLOCATION" width="100" />
<el-table-column prop="HIGHZONEID" label="HIGHZONEID" width="100" />
<el-table-column prop="LOWZONEID" label="LOWZONEID" width="100" />
<el-table-column prop="EXIT_SPEED" label="EXIT_SPEED" width="100" />
<el-table-column prop="ROLLFORCE" label="ROLLFORCE" width="100" />
<el-table-column prop="ABSDEVIATION" label="ABSDEVIATION" width="110" />
</el-table>
</el-card>
</el-col>
</el-row>
</template>
</el-card>
</div>
</template>
<script>
import createTimingFetch from '@/api/l2/timing'
export default {
name: 'TimingRealtimePage',
props: {
baseURL: { type: String, required: true }
},
data() {
return {
fetchApi: null,
loading: false,
queryForm: { matId: '' },
realtimeData: null,
gaugeRows: [],
shapeRows: []
}
},
created() {
this.fetchApi = createTimingFetch(this.baseURL)
},
methods: {
async handleQuery() {
if (!this.queryForm.matId) {
this.$message.warning('请输入 MATID')
return
}
this.loading = true
try {
const res = await this.fetchApi.getRealtimeData(this.queryForm.matId)
this.realtimeData = res?.data || res
this.gaugeRows = this.realtimeData?.gauge?.result || []
this.shapeRows = this.realtimeData?.shape?.result || []
} finally {
this.loading = false
}
},
handleReset() {
this.queryForm.matId = ''
this.realtimeData = null
this.gaugeRows = []
this.shapeRows = []
}
}
}
</script>
<style scoped>
.timing-page { padding: 16px; }
.page-card { border-radius: 12px; }
.card-header { display: flex; align-items: center; justify-content: space-between; font-weight: 600; }
.query-form { margin-bottom: 12px; }
.sub-card { border-radius: 10px; }
</style>