数据贯通完成,规程重构
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package com.klp.framework.client;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.klp.framework.config.SqlServerApiProperties;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -73,7 +74,9 @@ public class SqlServerApiClient {
|
||||
}
|
||||
|
||||
public static class TableSchemaRequest {
|
||||
@JsonProperty("table_type")
|
||||
private String tableType;
|
||||
@JsonProperty("table_name")
|
||||
private String tableName;
|
||||
|
||||
public TableSchemaRequest() {
|
||||
@@ -102,6 +105,7 @@ public class SqlServerApiClient {
|
||||
}
|
||||
|
||||
public static class ExecuteSqlRequest {
|
||||
@JsonProperty("table_type")
|
||||
private String tableType;
|
||||
private String sql;
|
||||
private Map<String, Object> params;
|
||||
@@ -311,10 +315,23 @@ public class SqlServerApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryPlanList() {
|
||||
public ExecuteSqlResponse queryPlanList(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 JXPLTCM.PLTCM_PDI_PLAN order by INSDATE desc",
|
||||
"select * from (select t.*, ROWNUM rn from (select * from JXPLTCM.PLTCM_PDI_PLAN order by INSDATE desc) t where ROWNUM <= :endRow) where rn > :startRow",
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
public ExecuteSqlResponse queryPlanCount() {
|
||||
return executeSql(
|
||||
"oracle",
|
||||
"select count(*) as total from JXPLTCM.PLTCM_PDI_PLAN",
|
||||
emptyParams()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class SqlServerApiClientConfig {
|
||||
public RestTemplate sqlServerApiRestTemplate(RestTemplateBuilder builder) {
|
||||
return builder
|
||||
.setConnectTimeout(Duration.ofSeconds(5))
|
||||
.setReadTimeout(Duration.ofSeconds(30))
|
||||
.setReadTimeout(Duration.ofSeconds(60))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,24 @@ public class SqlServerApiBusinessService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 计划列表:查询所有计划,按时间倒序。
|
||||
* <p>
|
||||
* 这是后续按 coilId 关联 SEG、实时数据的入口。
|
||||
* 计划列表(分页):按时间倒序,page 从 1 开始。
|
||||
*/
|
||||
public PlanListView getPlanList() {
|
||||
return PlanListView.fromExecuteSqlResponse(client.queryPlanList());
|
||||
public PlanListView getPlanList(int page, int pageSize) {
|
||||
return PlanListView.fromExecuteSqlResponse(client.queryPlanList(page, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计划总数。
|
||||
*/
|
||||
public long getPlanCount() {
|
||||
SqlServerApiClient.ExecuteSqlResponse resp = client.queryPlanCount();
|
||||
List<Map<String, Object>> rows = asRowList(resp);
|
||||
if (rows.isEmpty()) {
|
||||
return 0L;
|
||||
}
|
||||
Object total = rows.get(0).get("total");
|
||||
Number n = asNumber(total);
|
||||
return n == null ? 0L : n.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.klp.web.controller.sqlserver;
|
||||
package com.klp.framework.sqlserver;
|
||||
|
||||
import com.klp.common.core.domain.R;
|
||||
import com.klp.framework.service.SqlServerApiBusinessService;
|
||||
@@ -6,8 +6,12 @@ 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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* sql-server-api 业务查询接口。
|
||||
* <p>
|
||||
@@ -21,11 +25,24 @@ public class SqlServerApiController {
|
||||
private final SqlServerApiBusinessService businessService;
|
||||
|
||||
/**
|
||||
* 计划列表。
|
||||
* 计划列表(分页)。
|
||||
* page 从 1 开始,默认第 1 页,每页 20 条。
|
||||
*/
|
||||
@GetMapping("/plans")
|
||||
public R<SqlServerApiBusinessService.PlanListView> planList() {
|
||||
return R.ok(businessService.getPlanList());
|
||||
public R<SqlServerApiBusinessService.PlanListView> planList(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int pageSize) {
|
||||
return R.ok(businessService.getPlanList(page, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计划总数,用于前端分页器。
|
||||
*/
|
||||
@GetMapping("/plans/count")
|
||||
public R<Map<String, Long>> planCount() {
|
||||
Map<String, Long> result = new HashMap<>();
|
||||
result.put("total", businessService.getPlanCount());
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,12 @@ klp:
|
||||
# 开发环境文件存储目录
|
||||
directory-path: testDirectory
|
||||
|
||||
--- # sql-server-api 中间件配置(开发/测试环境)
|
||||
sql-server-api:
|
||||
host: 140.143.206.120
|
||||
port: 15000
|
||||
base-url: http://${sql-server-api.host}:${sql-server-api.port}
|
||||
|
||||
--- # OEE 聚合(klp-da)多服务地址配置
|
||||
da:
|
||||
oee:
|
||||
|
||||
@@ -7,6 +7,12 @@ klp:
|
||||
# 生产环境文件存储目录
|
||||
directory-path: /home/ubuntu/oa/folder
|
||||
|
||||
--- # sql-server-api 中间件配置(生产环境)
|
||||
sql-server-api:
|
||||
host: 192.168.0.219
|
||||
port: 15000
|
||||
base-url: http://${sql-server-api.host}:${sql-server-api.port}
|
||||
|
||||
--- # OEE 聚合(klp-da)多服务地址配置
|
||||
da:
|
||||
oee:
|
||||
|
||||
@@ -66,14 +66,6 @@ user:
|
||||
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:
|
||||
application:
|
||||
|
||||
@@ -1,36 +1,50 @@
|
||||
import axios from 'axios'
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default function createTimingFetch(url) {
|
||||
const request = axios.create({
|
||||
baseURL: 'http://' + url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 10000
|
||||
// 计划列表(分页)
|
||||
export function getTimingPlanList(page = 1, pageSize = 20) {
|
||||
return request({
|
||||
url: '/sql-server-api/plans',
|
||||
method: 'get',
|
||||
params: { page, pageSize }
|
||||
})
|
||||
}
|
||||
|
||||
// 计划总数
|
||||
export function getTimingPlanCount() {
|
||||
return request({
|
||||
url: '/sql-server-api/plans/count',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 计划详情
|
||||
export function getTimingPlanDetail(coilId) {
|
||||
return request({
|
||||
url: '/sql-server-api/plans/' + coilId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 钢卷实际 SEG,按入口卷号查询
|
||||
export function getTimingSegByEncoilId(encoilId) {
|
||||
return request({
|
||||
url: '/sql-server-api/seg/' + encoilId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 钢卷实际 SEG,按出口卷号查询
|
||||
export function getTimingSegByExcoilId(excoilId) {
|
||||
return request({
|
||||
url: '/sql-server-api/seg-by-excoil/' + excoilId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 实时数据:Gauge + Shape
|
||||
export function getTimingRealtimeData(matId) {
|
||||
return request({
|
||||
url: '/sql-server-api/realtime/' + matId,
|
||||
method: 'get'
|
||||
})
|
||||
|
||||
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'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +1,204 @@
|
||||
<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>
|
||||
<div class="acid-view">
|
||||
<div class="filter-bar">
|
||||
<el-input
|
||||
v-model="queryForm.coilId"
|
||||
placeholder="热卷号 / 成品卷号"
|
||||
clearable
|
||||
size="small"
|
||||
style="width: 220px"
|
||||
@keyup.enter.native="handleSearch"
|
||||
/>
|
||||
<el-button size="small" type="primary" :loading="loading" @click="handleSearch">查询</el-button>
|
||||
<el-button size="small" @click="handleReset">重置</el-button>
|
||||
</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="12" class="main-row">
|
||||
<!-- 左侧计划列表 -->
|
||||
<el-col :span="9">
|
||||
<div class="left-card">
|
||||
<el-table
|
||||
ref="planTable"
|
||||
:data="planRows"
|
||||
size="mini"
|
||||
highlight-current-row
|
||||
:height="tableHeight"
|
||||
@row-click="handlePlanRowClick"
|
||||
>
|
||||
<el-table-column prop="hot_coilid" label="热卷号" show-overflow-tooltip />
|
||||
<el-table-column prop="coilid" label="原料卷号" show-overflow-tooltip />
|
||||
<el-table-column label="状态">
|
||||
<template slot-scope="{ row }">
|
||||
<span :class="['status-dot', statusClass(row.status)]" width="70px"/>
|
||||
<span class="status-text">{{ row.status || '—' }}</span>
|
||||
</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>
|
||||
|
||||
<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
|
||||
<div class="pagination-bar">
|
||||
<el-pagination
|
||||
small
|
||||
layout="total, prev, pager, next"
|
||||
:total="pagination.total"
|
||||
:page-size="pagination.pageSize"
|
||||
:current-page="pagination.page"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧详情 + 图表 -->
|
||||
<el-col :span="15">
|
||||
<div v-if="!selectedPlan" class="empty-hint">
|
||||
<el-empty description="选择左侧计划" :image-size="72" />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<el-empty v-else-if="!perfLoading" description="暂无实绩数据" :image-size="56" style="margin-top: 24px" />
|
||||
</template>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import createTimingFetch from '@/api/l2/timing'
|
||||
import * as echarts from 'echarts'
|
||||
import {
|
||||
getTimingPlanList,
|
||||
getTimingPlanCount,
|
||||
getTimingPlanDetail,
|
||||
getTimingSegByEncoilId
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TimingAcidPage',
|
||||
props: {
|
||||
baseURL: { type: String, required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetchApi: null,
|
||||
loading: false,
|
||||
perfLoading: false,
|
||||
queryForm: { coilId: '' },
|
||||
planRows: [],
|
||||
selectedPlan: null,
|
||||
segView: null,
|
||||
seriesTable: []
|
||||
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
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchApi = createTimingFetch(this.baseURL)
|
||||
// plain instance property — Vue 2 does NOT proxy underscore-prefixed names
|
||||
this.chartInstances = []
|
||||
this.resizeHandler = null
|
||||
this.loadPlanCount()
|
||||
this.loadPlanList()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.disposeCharts()
|
||||
},
|
||||
methods: {
|
||||
statusClass(status) {
|
||||
return STATUS_CLASS[status] || 'status-default'
|
||||
},
|
||||
async loadPlanCount() {
|
||||
try {
|
||||
const res = await getTimingPlanCount()
|
||||
this.pagination.total = res?.data?.total ?? 0
|
||||
} catch (_) {}
|
||||
},
|
||||
async loadPlanList() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await this.fetchApi.getPlanList()
|
||||
const rows = res?.data?.rows || res?.rows || []
|
||||
this.planRows = rows
|
||||
const { page, pageSize } = this.pagination
|
||||
const res = await getTimingPlanList(page, pageSize)
|
||||
this.planRows = res?.data?.rows || []
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handlePageChange(page) {
|
||||
this.pagination.page = page
|
||||
this.loadPlanList()
|
||||
},
|
||||
async handleSearch() {
|
||||
if (!this.queryForm.coilId) {
|
||||
return this.loadPlanList()
|
||||
}
|
||||
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)
|
||||
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
|
||||
@@ -153,25 +206,86 @@ export default {
|
||||
},
|
||||
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)
|
||||
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 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
|
||||
}
|
||||
},
|
||||
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 }))
|
||||
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('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'
|
||||
))
|
||||
|
||||
this.chartInstances = [c1, c2, c3]
|
||||
this.resizeHandler = () => this.chartInstances.forEach(c => { if (c) c.resize() })
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
handleReset() {
|
||||
this.queryForm.coilId = ''
|
||||
this.selectedPlan = null
|
||||
this.segView = null
|
||||
this.seriesTable = []
|
||||
this.perfSeries = null
|
||||
this.perfSegCount = 0
|
||||
this.disposeCharts()
|
||||
this.pagination.page = 1
|
||||
this.loadPlanList()
|
||||
}
|
||||
}
|
||||
@@ -179,11 +293,115 @@ export default {
|
||||
</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; }
|
||||
.acid-view {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.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;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 80px;
|
||||
}
|
||||
|
||||
.pagination-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border-top: 1px solid #ebeef5;
|
||||
border-left: 1px solid #ebeef5;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 10px;
|
||||
border-right: 1px solid #ebeef5;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.cell-label {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.cell-value {
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.perf-header {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.perf-count {
|
||||
font-weight: normal;
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.charts-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 310px);
|
||||
}
|
||||
|
||||
.chart-box {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
/* 状态指示点 */
|
||||
.status-dot {
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.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>
|
||||
|
||||
@@ -55,16 +55,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import createTimingFetch from '@/api/l2/timing'
|
||||
import { getTimingRealtimeData } from '@/api/l2/timing'
|
||||
|
||||
export default {
|
||||
name: 'TimingRealtimePage',
|
||||
props: {
|
||||
baseURL: { type: String, required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetchApi: null,
|
||||
loading: false,
|
||||
queryForm: { matId: '' },
|
||||
realtimeData: null,
|
||||
@@ -72,9 +68,6 @@ export default {
|
||||
shapeRows: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchApi = createTimingFetch(this.baseURL)
|
||||
},
|
||||
methods: {
|
||||
async handleQuery() {
|
||||
if (!this.queryForm.matId) {
|
||||
@@ -83,10 +76,10 @@ export default {
|
||||
}
|
||||
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 || []
|
||||
const res = await getTimingRealtimeData(this.queryForm.matId)
|
||||
this.realtimeData = res && res.data ? res.data : res
|
||||
this.gaugeRows = this.realtimeData && this.realtimeData.gauge ? this.realtimeData.gauge.result || [] : []
|
||||
this.shapeRows = this.realtimeData && this.realtimeData.shape ? this.realtimeData.shape.result || [] : []
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -1,87 +1,80 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="88px">
|
||||
<el-form-item label="规程编号" prop="specCode">
|
||||
<el-input v-model="queryParams.specCode" placeholder="规程编号" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规程名称" prop="specName">
|
||||
<el-input v-model="queryParams.specName" placeholder="规程名称" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规程类型" prop="specType">
|
||||
<el-select v-model="queryParams.specType" placeholder="全部" clearable>
|
||||
<el-option v-for="item in specTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产线" prop="lineId">
|
||||
<el-select v-model="queryParams.lineId" placeholder="全部" clearable filterable>
|
||||
<el-option
|
||||
v-for="line in lineOptions"
|
||||
:key="line.lineId"
|
||||
:label="formatLineOption(line)"
|
||||
:value="line.lineId"
|
||||
<div class="spec-page">
|
||||
<!-- 规程类型 tabs -->
|
||||
<div class="type-tab-bar">
|
||||
<span
|
||||
v-for="t in specTypeTab"
|
||||
:key="t.value"
|
||||
:class="['type-tab', { active: activeSpecType === t.value }]"
|
||||
@click="switchSpecType(t.value)"
|
||||
>{{ t.label }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 产线 tabs -->
|
||||
<div class="line-tab-bar">
|
||||
<span
|
||||
:class="['line-tab', { active: activeLineId === '' }]"
|
||||
@click="switchLine('')"
|
||||
>全部</span>
|
||||
<span
|
||||
v-for="line in lineOptions"
|
||||
:key="line.lineId"
|
||||
:class="['line-tab', { active: activeLineId === line.lineId }]"
|
||||
@click="switchLine(line.lineId)"
|
||||
>{{ line.lineName }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-button type="primary" size="mini" icon="el-icon-plus" @click="handleAdd">新增</el-button>
|
||||
<el-button size="mini" icon="el-icon-edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
||||
<el-button size="mini" icon="el-icon-delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<el-input
|
||||
v-model="queryParams.specName"
|
||||
size="small"
|
||||
placeholder="规程名称"
|
||||
clearable
|
||||
style="width:180px; margin-right:8px"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
<el-button size="mini" type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button size="mini" @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="dataList"
|
||||
size="small"
|
||||
highlight-current-row
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" />
|
||||
<el-table-column label="规程编号" prop="specCode" show-overflow-tooltip />
|
||||
<el-table-column label="规程名称" prop="specName" show-overflow-tooltip />
|
||||
<el-table-column label="产品类型" prop="productType" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" prop="createTime" />
|
||||
<el-table-column label="启用" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-switch
|
||||
:value="row.isEnabled === 1"
|
||||
active-color="#5F7BA0"
|
||||
@change="toggleEnabled(row, $event)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品类型" prop="productType">
|
||||
<el-input v-model="queryParams.productType" placeholder="产品类型" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="isEnabled">
|
||||
<el-select v-model="queryParams.isEnabled" placeholder="全部" clearable>
|
||||
<el-option label="启用" :value="1" />
|
||||
<el-option label="禁用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<KLPTable v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="规程编号" align="center" prop="specCode" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="规程名称" align="center" prop="specName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="规程类型" align="center" prop="specType" width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatSpecType(scope.row.specType) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产线" align="center" min-width="160" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ getLineName(scope.row.lineId) }}
|
||||
<el-table-column label="操作" align="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="mini" @click="goVersionManage(row)">版本与方案</el-button>
|
||||
<el-button type="text" size="mini" @click="handleUpdate(row)">修改</el-button>
|
||||
<el-button type="text" size="mini" class="btn-danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产品类型" align="center" prop="productType" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="是否启用" align="center" prop="isEnabled" width="90">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.common_swicth" :value="scope.row.isEnabled" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-document" @click="goVersionManage(scope.row)">版本与方案</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
@@ -91,45 +84,43 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="open" width="560px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<!-- 新增/修改 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="open" width="520px" append-to-body @close="reset">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="88px" size="small">
|
||||
<el-form-item label="规程编号" prop="specCode">
|
||||
<el-input v-model="form.specCode" placeholder="唯一编号" maxlength="64" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="规程名称" prop="specName">
|
||||
<el-input v-model="form.specName" placeholder="规程名称" maxlength="200" show-word-limit />
|
||||
<el-input v-model="form.specName" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="规程类型" prop="specType">
|
||||
<el-select v-model="form.specType" placeholder="请选择">
|
||||
<el-option v-for="item in specTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
<el-select v-model="form.specType" style="width:100%">
|
||||
<el-option v-for="t in specTypeOptions" :key="t.value" :label="t.label" :value="t.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产线" prop="lineId">
|
||||
<el-select v-model="form.lineId" placeholder="请选择产线" filterable style="width: 100%">
|
||||
<el-select v-model="form.lineId" filterable placeholder="请选择" style="width:100%">
|
||||
<el-option
|
||||
v-for="line in lineOptions"
|
||||
:key="line.lineId"
|
||||
:label="formatLineOption(line)"
|
||||
:label="line.lineCode ? line.lineName + '(' + line.lineCode + ')' : line.lineName"
|
||||
:value="line.lineId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品类型" prop="productType">
|
||||
<el-input v-model="form.productType" placeholder="可选" maxlength="100" />
|
||||
<el-input v-model="form.productType" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="isEnabled">
|
||||
<el-radio-group v-model="form.isEnabled">
|
||||
<el-radio :label="1">启用</el-radio>
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
<el-switch v-model="form.isEnabled" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="备注" maxlength="500" show-word-limit rows="2" />
|
||||
<el-input v-model="form.remark" type="textarea" rows="2" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="open = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="btnLoading" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -139,184 +130,255 @@
|
||||
import { listProcessSpec, getProcessSpec, delProcessSpec, updateProcessSpec, addProcessSpec } from '@/api/wms/processSpec'
|
||||
import { listProductionLine } from '@/api/wms/productionLine'
|
||||
|
||||
const SPEC_TYPES = [
|
||||
{ label: '工艺规程', value: 'PROCESS' },
|
||||
{ label: '标准', value: 'STANDARD' }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'ProcessSpec',
|
||||
dicts: ['common_swicth'],
|
||||
data() {
|
||||
return {
|
||||
buttonLoading: false,
|
||||
loading: true,
|
||||
loading: false,
|
||||
btnLoading: false,
|
||||
total: 0,
|
||||
dataList: [],
|
||||
ids: [],
|
||||
single: true,
|
||||
multiple: true,
|
||||
showSearch: true,
|
||||
total: 0,
|
||||
dataList: [],
|
||||
title: '',
|
||||
open: false,
|
||||
dialogTitle: '',
|
||||
lineOptions: [],
|
||||
specTypeOptions: [
|
||||
{ label: '工艺规程', value: 'PROCESS' },
|
||||
{ label: '标准', value: 'STANDARD' }
|
||||
],
|
||||
specTypeTab: [{ label: '全部', value: '' }, ...SPEC_TYPES],
|
||||
specTypeOptions: SPEC_TYPES,
|
||||
activeSpecType: '',
|
||||
activeLineId: '',
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
specCode: undefined,
|
||||
specName: undefined,
|
||||
specType: undefined,
|
||||
lineId: undefined,
|
||||
productType: undefined,
|
||||
isEnabled: undefined
|
||||
lineId: undefined
|
||||
},
|
||||
form: {},
|
||||
rules: {
|
||||
specCode: [{ required: true, message: '规程编号不能为空', trigger: 'blur' }],
|
||||
specName: [{ required: true, message: '规程名称不能为空', trigger: 'blur' }],
|
||||
specType: [{ required: true, message: '规程类型不能为空', trigger: 'change' }],
|
||||
lineId: [{ required: true, message: '产线不能为空', trigger: 'change' }]
|
||||
specType: [{ required: true, message: '请选择规程类型', trigger: 'change' }],
|
||||
lineId: [{ required: true, message: '请选择产线', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadLineOptions()
|
||||
listProductionLine({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.lineOptions = res.rows || []
|
||||
})
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
formatSpecType(value) {
|
||||
const hit = this.specTypeOptions.find((x) => x.value === value)
|
||||
return hit ? hit.label : value
|
||||
},
|
||||
formatLineOption(line) {
|
||||
if (!line) {
|
||||
return ''
|
||||
}
|
||||
return line.lineCode ? `${line.lineName}(${line.lineCode})` : line.lineName
|
||||
},
|
||||
getLineName(lineId) {
|
||||
if (lineId == null) {
|
||||
return ''
|
||||
}
|
||||
const hit = this.lineOptions.find((o) => o.lineId === lineId)
|
||||
return hit ? this.formatLineOption(hit) : lineId
|
||||
},
|
||||
loadLineOptions() {
|
||||
listProductionLine({ pageNum: 1, pageSize: 500 }).then((res) => {
|
||||
this.lineOptions = res.rows || []
|
||||
}).catch((err) => {
|
||||
console.error('加载产线列表失败', err)
|
||||
})
|
||||
},
|
||||
getList() {
|
||||
this.loading = true
|
||||
listProcessSpec(this.queryParams).then((response) => {
|
||||
this.dataList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
}).catch((err) => {
|
||||
console.error('加载规程列表失败', err)
|
||||
this.loading = false
|
||||
})
|
||||
listProcessSpec(this.queryParams).then(res => {
|
||||
this.dataList = res.rows || []
|
||||
this.total = res.total || 0
|
||||
}).finally(() => { this.loading = false })
|
||||
},
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
switchSpecType(val) {
|
||||
this.activeSpecType = val
|
||||
this.queryParams.specType = val || undefined
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
reset() {
|
||||
this.form = {
|
||||
specId: undefined,
|
||||
specCode: undefined,
|
||||
specName: undefined,
|
||||
specType: 'PROCESS',
|
||||
lineId: undefined,
|
||||
productType: undefined,
|
||||
isEnabled: 1,
|
||||
remark: undefined
|
||||
}
|
||||
this.resetForm('form')
|
||||
switchLine(lineId) {
|
||||
this.activeLineId = lineId
|
||||
this.queryParams.lineId = lineId || undefined
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.queryParams.specName = undefined
|
||||
this.handleQuery()
|
||||
},
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map((item) => item.specId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
handleSelectionChange(sel) {
|
||||
this.ids = sel.map(r => r.specId)
|
||||
this.single = sel.length !== 1
|
||||
this.multiple = !sel.length
|
||||
},
|
||||
reset() {
|
||||
this.form = { specId: undefined, specCode: undefined, specName: undefined, specType: 'PROCESS', lineId: undefined, productType: undefined, isEnabled: 1, remark: undefined }
|
||||
this.$refs.form && this.$refs.form.clearValidate()
|
||||
},
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.dialogTitle = '新增规程'
|
||||
this.open = true
|
||||
this.title = '添加规程'
|
||||
},
|
||||
handleUpdate(row) {
|
||||
this.loading = true
|
||||
this.reset()
|
||||
const specId = row.specId || this.ids
|
||||
getProcessSpec(specId).then((response) => {
|
||||
this.loading = false
|
||||
this.form = response.data
|
||||
const specId = row ? row.specId : this.ids[0]
|
||||
getProcessSpec(specId).then(res => {
|
||||
this.form = res.data || {}
|
||||
this.dialogTitle = '修改规程'
|
||||
this.open = true
|
||||
this.title = '修改规程'
|
||||
}).catch((err) => {
|
||||
console.error('获取规程详情失败', err)
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
this.buttonLoading = true
|
||||
const req = this.form.specId != null ? updateProcessSpec(this.form) : addProcessSpec(this.form)
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
this.btnLoading = true
|
||||
const req = this.form.specId ? updateProcessSpec(this.form) : addProcessSpec(this.form)
|
||||
req.then(() => {
|
||||
this.$modal.msgSuccess(this.form.specId != null ? '修改成功' : '新增成功')
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
}).catch((err) => {
|
||||
console.error('保存规程失败', err)
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false
|
||||
})
|
||||
}).finally(() => { this.btnLoading = false })
|
||||
})
|
||||
},
|
||||
handleDelete(row) {
|
||||
const specIds = row.specId || this.ids
|
||||
this.$modal.confirm('是否确认删除选中的规程数据?').then(() => {
|
||||
const ids = row ? row.specId : this.ids
|
||||
this.$modal.confirm('确认删除所选规程?').then(() => {
|
||||
this.loading = true
|
||||
return delProcessSpec(specIds)
|
||||
return delProcessSpec(ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.getList()
|
||||
}).catch(() => {}).finally(() => { this.loading = false })
|
||||
},
|
||||
handleExport() {
|
||||
this.download('wms/processSpec/export', {
|
||||
...this.queryParams
|
||||
}, `processSpec_${new Date().getTime()}.xlsx`)
|
||||
toggleEnabled(row, val) {
|
||||
const updated = { ...row, isEnabled: val ? 1 : 0 }
|
||||
updateProcessSpec(updated).then(() => {
|
||||
row.isEnabled = updated.isEnabled
|
||||
}).catch(() => {})
|
||||
},
|
||||
goVersionManage(row) {
|
||||
const specId = row.specId
|
||||
if (specId == null || specId === '') {
|
||||
this.$modal.msgWarning('无法获取规程ID,请刷新列表后重试')
|
||||
return
|
||||
}
|
||||
// 固定落在「…/processSpec/version」,避免列表为 …/processSpec/list 时拼成 …/list/version 导致路由不匹配、query 丢失
|
||||
const pathCurrent = this.$route.path.replace(/\/$/, '')
|
||||
const m = pathCurrent.match(/^(.*\/processSpec)(?:\/.*)?$/)
|
||||
const base = m ? m[1] : pathCurrent
|
||||
this.$router.push({
|
||||
path: `${base}/version`,
|
||||
query: { specId: String(specId) }
|
||||
})
|
||||
this.$router.push({ path: `${base}/version`, query: { specId: String(row.specId) } })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.spec-page {
|
||||
padding: 16px 20px;
|
||||
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* ── 双色主题:默认=白底灰边,激活/主操作=深藏青 #5F7BA0 ── */
|
||||
|
||||
.type-tab-bar {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: 10px;
|
||||
width: fit-content;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.type-tab {
|
||||
padding: 5px 14px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-right: 1px solid #dcdfe6;
|
||||
transition: color .15s, background .15s;
|
||||
user-select: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.type-tab:last-child { border-right: none; }
|
||||
.type-tab:hover { color: #5F7BA0; }
|
||||
|
||||
.type-tab.active {
|
||||
color: #fff;
|
||||
background: #5F7BA0;
|
||||
}
|
||||
|
||||
.line-tab-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.line-tab {
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
transition: color .15s, background .15s, border-color .15s;
|
||||
user-select: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.line-tab:hover { color: #5F7BA0; border-color: #5F7BA0; }
|
||||
|
||||
.line-tab.active {
|
||||
color: #fff;
|
||||
background: #5F7BA0;
|
||||
border-color: #5F7BA0;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* el-button:主操作类 → 深藏青;默认类 → 白底灰边 */
|
||||
::v-deep .el-button--primary {
|
||||
color: #fff !important;
|
||||
background: #5F7BA0 !important;
|
||||
border-color: #5F7BA0 !important;
|
||||
}
|
||||
::v-deep .el-button--primary:hover,
|
||||
::v-deep .el-button--primary:focus {
|
||||
background: #4d6a8e !important;
|
||||
border-color: #4d6a8e !important;
|
||||
}
|
||||
::v-deep .el-button--primary:active { background: #4a6585 !important; border-color: #4a6585 !important; }
|
||||
::v-deep .el-button--primary.is-disabled { opacity: .5; }
|
||||
|
||||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger):not(.el-button--info) {
|
||||
color: #606266 !important;
|
||||
background: #fff !important;
|
||||
border-color: #dcdfe6 !important;
|
||||
}
|
||||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger):not(.el-button--info):hover {
|
||||
color: #5F7BA0 !important;
|
||||
border-color: #5F7BA0 !important;
|
||||
}
|
||||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger):not(.el-button--info).is-disabled { opacity: .5; }
|
||||
|
||||
::v-deep .el-button--text { background: transparent !important; border-color: transparent !important; }
|
||||
::v-deep .el-button--text.btn-danger { color: #f56c6c !important; }
|
||||
|
||||
.btn-danger { color: #f56c6c; }
|
||||
</style>
|
||||
|
||||
533
klp-ui/src/views/wms/processSpec/planSpec.vue
Normal file
533
klp-ui/src/views/wms/processSpec/planSpec.vue
Normal file
@@ -0,0 +1,533 @@
|
||||
<template>
|
||||
<div class="plan-spec-page" v-loading="pageLoading">
|
||||
<!-- 头部 -->
|
||||
<div class="page-header">
|
||||
<el-button type="text" icon="el-icon-arrow-left" @click="goBack">返回</el-button>
|
||||
<span class="page-title">方案详情</span>
|
||||
<span v-if="versionCode" class="version-badge">版本 {{ versionCode }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 可配置 / 不可配置 切换 -->
|
||||
<div class="config-tabs">
|
||||
<span
|
||||
:class="['config-tab', { active: configMode === 'configurable' }]"
|
||||
@click="configMode = 'configurable'"
|
||||
>可配置</span>
|
||||
<span
|
||||
:class="['config-tab', { active: configMode === 'readonly' }]"
|
||||
@click="configMode = 'readonly'"
|
||||
>不可配置</span>
|
||||
</div>
|
||||
|
||||
<div class="main-layout">
|
||||
<!-- 左侧段分组 -->
|
||||
<div class="left-tree">
|
||||
<div
|
||||
:class="['tree-item', { active: activeSegment === '' }]"
|
||||
@click="activeSegment = ''"
|
||||
>全部</div>
|
||||
<div
|
||||
v-for="seg in segmentOptions"
|
||||
:key="seg.value"
|
||||
:class="['tree-item', { active: activeSegment === seg.value }]"
|
||||
@click="activeSegment = seg.value"
|
||||
>
|
||||
<span class="tree-arrow">›</span> {{ seg.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<div class="right-content">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<span class="search-label">点位名称:</span>
|
||||
<el-input
|
||||
v-model="filterName"
|
||||
placeholder="请输入"
|
||||
size="small"
|
||||
style="width:200px"
|
||||
clearable
|
||||
@keyup.enter.native="applyFilter"
|
||||
/>
|
||||
<el-button size="mini" type="primary" @click="applyFilter">查询</el-button>
|
||||
<el-button size="mini" @click="resetFilter">重置</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
style="margin-left:auto"
|
||||
@click="openPlanDialog()"
|
||||
>新建方案点位</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 方案点位表 -->
|
||||
<el-table
|
||||
v-loading="planLoading"
|
||||
:data="filteredPlans"
|
||||
size="small"
|
||||
highlight-current-row
|
||||
@current-change="onPlanSelect"
|
||||
>
|
||||
<el-table-column label="序号" type="index" align="center" />
|
||||
<el-table-column label="父级名称" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ segLabel(row.segmentType) }} › {{ row.segmentName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="点位名称" prop="pointName" show-overflow-tooltip />
|
||||
<el-table-column label="实际值ID" prop="actualValueId" show-overflow-tooltip />
|
||||
<el-table-column label="L1设定值ID" prop="l1SetValueId" show-overflow-tooltip />
|
||||
<el-table-column label="设定值" prop="targetValue" align="center" />
|
||||
<el-table-column label="下限" prop="lowerLimit" align="center" />
|
||||
<el-table-column label="上限" prop="upperLimit" align="center" />
|
||||
<el-table-column label="操作" align="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="mini" @click.stop="openPlanDialog(row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click.stop="openParamDialog(row)">参数</el-button>
|
||||
<el-button type="text" size="mini" class="btn-danger" @click.stop="removePlan(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 方案参数面板 -->
|
||||
<template v-if="selectedPlan">
|
||||
<div class="param-header">
|
||||
<span>{{ selectedPlan.pointName || selectedPlan.pointCode }} — 参数</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openParamDialog()">新建参数</el-button>
|
||||
</div>
|
||||
<el-table v-loading="paramLoading" :data="paramList" size="small">
|
||||
<el-table-column label="参数编码" prop="paramCode" show-overflow-tooltip />
|
||||
<el-table-column label="参数名称" prop="paramName" show-overflow-tooltip />
|
||||
<el-table-column label="设定值" prop="targetValue" align="center" />
|
||||
<el-table-column label="下限" prop="lowerLimit" align="center" />
|
||||
<el-table-column label="上限" prop="upperLimit" align="center" />
|
||||
<el-table-column label="单位" prop="unit" align="center" />
|
||||
<el-table-column label="操作" align="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="mini" @click="openParamDialog(null, row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" class="btn-danger" @click="removeParam(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 方案点位 dialog -->
|
||||
<el-dialog :title="planTitle" :visible.sync="planOpen" width="520px" append-to-body @close="planForm = {}">
|
||||
<el-form ref="planFormRef" :model="planForm" :rules="planRules" label-width="90px" size="small">
|
||||
<el-form-item label="段类型" prop="segmentType">
|
||||
<el-select v-model="planForm.segmentType" style="width:100%">
|
||||
<el-option v-for="s in segmentOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="段名称" prop="segmentName">
|
||||
<el-input v-model="planForm.segmentName" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点位名称" prop="pointName">
|
||||
<el-input v-model="planForm.pointName" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点位编码" prop="pointCode">
|
||||
<el-input v-model="planForm.pointCode" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="实际值ID" prop="actualValueId">
|
||||
<el-input v-model="planForm.actualValueId" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="L1设定值ID" prop="l1SetValueId">
|
||||
<el-input v-model="planForm.l1SetValueId" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设定值" prop="targetValue">
|
||||
<el-input v-model="planForm.targetValue" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下限" prop="lowerLimit">
|
||||
<el-input v-model="planForm.lowerLimit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上限" prop="upperLimit">
|
||||
<el-input v-model="planForm.upperLimit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="planForm.sortOrder" :min="0" controls-position="right" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="planForm.remark" type="textarea" rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="planOpen = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="planSubmitLoading" @click="submitPlan">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 方案参数 dialog -->
|
||||
<el-dialog :title="paramTitle" :visible.sync="paramOpen" width="480px" append-to-body @close="paramForm = {}">
|
||||
<el-form ref="paramFormRef" :model="paramForm" :rules="paramRules" label-width="90px" size="small">
|
||||
<el-form-item label="参数编码" prop="paramCode">
|
||||
<el-input v-model="paramForm.paramCode" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数名称" prop="paramName">
|
||||
<el-input v-model="paramForm.paramName" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设定值">
|
||||
<el-input v-model="paramForm.targetValue" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下限">
|
||||
<el-input v-model="paramForm.lowerLimit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上限">
|
||||
<el-input v-model="paramForm.upperLimit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位">
|
||||
<el-input v-model="paramForm.unit" maxlength="32" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="paramForm.remark" type="textarea" rows="2" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="paramOpen = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="paramSubmitLoading" @click="submitParam">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listProcessPlan, addProcessPlan, updateProcessPlan, delProcessPlan } from '@/api/wms/processPlan'
|
||||
import { listProcessPlanParam, addProcessPlanParam, updateProcessPlanParam, delProcessPlanParam } from '@/api/wms/processPlanParam'
|
||||
|
||||
const SEGMENTS = [
|
||||
{ label: '入口段', value: 'INLET' },
|
||||
{ label: '工艺段', value: 'PROCESS' },
|
||||
{ label: '出口段', value: 'OUTLET' }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'ProcessSpecPlanSpec',
|
||||
data() {
|
||||
return {
|
||||
pageLoading: false,
|
||||
versionId: undefined,
|
||||
versionCode: '',
|
||||
specId: undefined,
|
||||
configMode: 'configurable',
|
||||
activeSegment: '',
|
||||
filterName: '',
|
||||
appliedFilterName: '',
|
||||
segmentOptions: SEGMENTS,
|
||||
planList: [],
|
||||
planLoading: false,
|
||||
selectedPlan: null,
|
||||
paramList: [],
|
||||
paramLoading: false,
|
||||
planOpen: false,
|
||||
planTitle: '',
|
||||
planSubmitLoading: false,
|
||||
planForm: {},
|
||||
planRules: {
|
||||
segmentType: [{ required: true, message: '请选择段类型', trigger: 'change' }],
|
||||
pointName: [{ required: true, message: '点位名称不能为空', trigger: 'blur' }],
|
||||
pointCode: [{ required: true, message: '点位编码不能为空', trigger: 'blur' }]
|
||||
},
|
||||
paramOpen: false,
|
||||
paramTitle: '',
|
||||
paramSubmitLoading: false,
|
||||
paramForm: {},
|
||||
paramRules: {
|
||||
paramCode: [{ required: true, message: '参数编码不能为空', trigger: 'blur' }],
|
||||
paramName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredPlans() {
|
||||
return this.planList.filter(p => {
|
||||
const segOk = !this.activeSegment || p.segmentType === this.activeSegment
|
||||
const nameOk = !this.appliedFilterName || (p.pointName || '').includes(this.appliedFilterName)
|
||||
return segOk && nameOk
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: { immediate: true, handler() { this.syncFromRoute() } }
|
||||
},
|
||||
methods: {
|
||||
syncFromRoute() {
|
||||
const q = this.$route.query
|
||||
this.versionId = q.versionId || undefined
|
||||
this.versionCode = q.versionCode || ''
|
||||
this.specId = q.specId || undefined
|
||||
if (this.versionId) this.loadPlans()
|
||||
},
|
||||
goBack() { this.$router.go(-1) },
|
||||
segLabel(val) {
|
||||
const hit = SEGMENTS.find(s => s.value === val)
|
||||
return hit ? hit.label : val || ''
|
||||
},
|
||||
loadPlans() {
|
||||
this.planLoading = true
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
listProcessPlan({ versionId: this.versionId, pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.planList = res.rows || []
|
||||
}).catch(e => console.error(e)).finally(() => { this.planLoading = false })
|
||||
},
|
||||
loadParams(planId) {
|
||||
this.paramLoading = true
|
||||
listProcessPlanParam({ planId, pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.paramList = res.rows || []
|
||||
}).catch(e => console.error(e)).finally(() => { this.paramLoading = false })
|
||||
},
|
||||
onPlanSelect(row) {
|
||||
if (!row) return
|
||||
this.selectedPlan = row
|
||||
this.loadParams(row.planId)
|
||||
},
|
||||
applyFilter() { this.appliedFilterName = this.filterName },
|
||||
resetFilter() { this.filterName = ''; this.appliedFilterName = '' },
|
||||
openPlanDialog(row) {
|
||||
this.planForm = row
|
||||
? { ...row }
|
||||
: {
|
||||
versionId: this.versionId,
|
||||
segmentType: 'PROCESS',
|
||||
segmentName: undefined,
|
||||
pointName: undefined,
|
||||
pointCode: undefined,
|
||||
actualValueId: undefined,
|
||||
l1SetValueId: undefined,
|
||||
targetValue: undefined,
|
||||
lowerLimit: undefined,
|
||||
upperLimit: undefined,
|
||||
sortOrder: 0,
|
||||
remark: undefined
|
||||
}
|
||||
this.planTitle = row ? '编辑方案点位' : '新建方案点位'
|
||||
this.planOpen = true
|
||||
this.$nextTick(() => this.$refs.planFormRef && this.$refs.planFormRef.clearValidate())
|
||||
},
|
||||
submitPlan() {
|
||||
this.$refs.planFormRef.validate(ok => {
|
||||
if (!ok) return
|
||||
this.planSubmitLoading = true
|
||||
const req = this.planForm.planId ? updateProcessPlan(this.planForm) : addProcessPlan(this.planForm)
|
||||
req.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.planOpen = false
|
||||
this.loadPlans()
|
||||
}).catch(e => console.error(e)).finally(() => { this.planSubmitLoading = false })
|
||||
})
|
||||
},
|
||||
removePlan(row) {
|
||||
this.$modal.confirm('确认删除该方案点位?').then(() => {
|
||||
return delProcessPlan(row.planId)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
if (this.selectedPlan && this.selectedPlan.planId === row.planId) {
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
}
|
||||
this.loadPlans()
|
||||
}).catch(() => {})
|
||||
},
|
||||
openParamDialog(planRow, paramRow) {
|
||||
const targetPlan = planRow || this.selectedPlan
|
||||
if (!targetPlan) { this.$message.warning('请先选择一个方案点位'); return }
|
||||
if (!this.selectedPlan || this.selectedPlan.planId !== targetPlan.planId) {
|
||||
this.selectedPlan = targetPlan
|
||||
this.loadParams(targetPlan.planId)
|
||||
}
|
||||
this.paramForm = paramRow
|
||||
? { ...paramRow }
|
||||
: {
|
||||
planId: targetPlan.planId,
|
||||
paramCode: undefined,
|
||||
paramName: undefined,
|
||||
targetValue: undefined,
|
||||
lowerLimit: undefined,
|
||||
upperLimit: undefined,
|
||||
unit: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
this.paramTitle = paramRow ? '编辑方案参数' : '新建方案参数'
|
||||
this.paramOpen = true
|
||||
this.$nextTick(() => this.$refs.paramFormRef && this.$refs.paramFormRef.clearValidate())
|
||||
},
|
||||
submitParam() {
|
||||
this.$refs.paramFormRef.validate(ok => {
|
||||
if (!ok) return
|
||||
this.paramSubmitLoading = true
|
||||
const req = this.paramForm.paramId ? updateProcessPlanParam(this.paramForm) : addProcessPlanParam(this.paramForm)
|
||||
req.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.paramOpen = false
|
||||
this.loadParams(this.selectedPlan.planId)
|
||||
}).catch(e => console.error(e)).finally(() => { this.paramSubmitLoading = false })
|
||||
})
|
||||
},
|
||||
removeParam(row) {
|
||||
this.$modal.confirm('确认删除该参数?').then(() => {
|
||||
return delProcessPlanParam(row.paramId)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.loadParams(this.selectedPlan.planId)
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.plan-spec-page {
|
||||
padding: 16px 20px;
|
||||
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
background: #f0f2f5;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.config-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: 14px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.config-tab {
|
||||
padding: 5px 20px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
background: #fff;
|
||||
border: none;
|
||||
border-right: 1px solid #dcdfe6;
|
||||
transition: color .15s, background .15s;
|
||||
user-select: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.config-tab:last-child { border-right: none; }
|
||||
.config-tab:hover { color: #5F7BA0; }
|
||||
|
||||
.config-tab.active {
|
||||
color: #fff;
|
||||
background: #5F7BA0;
|
||||
}
|
||||
|
||||
.main-layout {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.left-tree {
|
||||
width: 120px;
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid #ebeef5;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.tree-item {
|
||||
padding: 9px 16px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
transition: all .15s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-item:hover { background: #f5f7fa; }
|
||||
|
||||
.tree-item.active {
|
||||
background: #5F7BA0;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tree-item.active .tree-arrow { color: rgba(255,255,255,.6); }
|
||||
|
||||
.tree-arrow {
|
||||
margin-right: 4px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.search-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.param-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0 8px;
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.el-table { border-radius: 0; }
|
||||
|
||||
::v-deep .el-button--primary {
|
||||
color: #fff !important;
|
||||
background: #5F7BA0 !important;
|
||||
border-color: #5F7BA0 !important;
|
||||
}
|
||||
::v-deep .el-button--primary:hover,
|
||||
::v-deep .el-button--primary:focus { background: #4d6a8e !important; border-color: #4d6a8e !important; }
|
||||
::v-deep .el-button--primary:active { background: #4a6585 !important; border-color: #4a6585 !important; }
|
||||
::v-deep .el-button--primary.is-disabled { opacity: .5; }
|
||||
|
||||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger) {
|
||||
color: #606266 !important;
|
||||
background: #fff !important;
|
||||
border-color: #dcdfe6 !important;
|
||||
}
|
||||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger):hover {
|
||||
color: #5F7BA0 !important;
|
||||
border-color: #5F7BA0 !important;
|
||||
}
|
||||
|
||||
::v-deep .el-button--text { background: transparent !important; border-color: transparent !important; }
|
||||
::v-deep .el-button--text.btn-danger { color: #f56c6c !important; }
|
||||
|
||||
.btn-danger { color: #f56c6c; }
|
||||
</style>
|
||||
@@ -1,192 +1,97 @@
|
||||
<template>
|
||||
<div class="app-container" v-loading="pageLoading">
|
||||
<el-page-header v-if="specInfo.specId" @back="goBack" :content="'规程版本与方案 — ' + (specInfo.specName || '') + '(' + (specInfo.specCode || '') + ')'" />
|
||||
<el-card v-else shadow="never" class="mb8">
|
||||
<div slot="header">请选择规程</div>
|
||||
<p class="text-muted mb8" style="color: #909399; font-size: 13px">从「规程管理」列表进入时会自动带上规程;从菜单直接进入时请先选择规程。</p>
|
||||
<el-form inline size="small" @submit.native.prevent>
|
||||
<el-form-item label="规程">
|
||||
<el-select
|
||||
v-model="specPickerId"
|
||||
filterable
|
||||
clearable
|
||||
placeholder="请选择规程"
|
||||
style="min-width: 280px"
|
||||
:loading="specPickerLoading"
|
||||
>
|
||||
<el-option v-for="s in specPickerOptions" :key="s.specId" :label="(s.specCode || '') + ' — ' + (s.specName || '')" :value="String(s.specId)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :disabled="!specPickerId" @click="applySpecPicker">进入维护</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div class="ver-page" v-loading="pageLoading">
|
||||
<!-- 头部 -->
|
||||
<div class="page-header">
|
||||
<el-button type="text" icon="el-icon-arrow-left" @click="goBack">返回</el-button>
|
||||
<span class="page-title" v-if="specInfo.specName">
|
||||
{{ specInfo.specName }}
|
||||
<span class="spec-code">{{ specInfo.specCode }}</span>
|
||||
</span>
|
||||
<el-button
|
||||
v-if="specInfo.specId"
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
style="margin-left:auto"
|
||||
@click="openVersionDialog()"
|
||||
>新建版本</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 无 specId 时选择规程 -->
|
||||
<div v-if="!specInfo.specId" class="pick-card">
|
||||
<p class="pick-hint">从「规程管理」列表进入会自动带上规程;也可在下方手动选择。</p>
|
||||
<el-select
|
||||
v-model="specPickerId"
|
||||
filterable clearable
|
||||
placeholder="选择规程"
|
||||
style="width:300px; margin-right:8px"
|
||||
:loading="specPickerLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="s in specPickerOptions"
|
||||
:key="s.specId"
|
||||
:label="(s.specCode || '') + ' — ' + (s.specName || '')"
|
||||
:value="String(s.specId)"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" size="small" :disabled="!specPickerId" @click="applySpecPicker">进入维护</el-button>
|
||||
</div>
|
||||
|
||||
<template v-if="specInfo.specId">
|
||||
<el-card shadow="never" class="mb8">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>规程版本</span>
|
||||
<el-button style="float: right; padding: 3px 10px" type="primary" size="mini" icon="el-icon-plus" @click="openVersionDialog()">新建版本</el-button>
|
||||
</div>
|
||||
<KLPTable
|
||||
:data="versionList"
|
||||
highlight-current-row
|
||||
@row-click="onVersionRowClick"
|
||||
>
|
||||
<el-table-column label="版本号" prop="versionCode" min-width="120" align="center" />
|
||||
<el-table-column label="是否生效" prop="isActive" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.isActive === 1" type="success" size="mini">生效</el-tag>
|
||||
<span v-else>否</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" width="120" align="center" />
|
||||
<el-table-column label="创建时间" prop="createTime" width="170" align="center" />
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click.stop="activateVersion(scope.row)">设为生效</el-button>
|
||||
<el-button type="text" size="mini" @click.stop="openVersionDialog(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click.stop="removeVersion(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
</el-card>
|
||||
<!-- 版本列表 -->
|
||||
<div class="section-title">规程版本</div>
|
||||
<el-table
|
||||
:data="versionList"
|
||||
size="small"
|
||||
highlight-current-row
|
||||
@row-click="onVersionRowClick"
|
||||
>
|
||||
<el-table-column label="版本号" prop="versionCode" />
|
||||
<el-table-column label="状态" prop="status" />
|
||||
<el-table-column label="创建时间" prop="createTime" />
|
||||
<el-table-column label="生效" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-switch
|
||||
:value="row.isActive === 1"
|
||||
active-color="#5F7BA0"
|
||||
@click.native.stop
|
||||
@change="handleActiveChange(row, $event)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="mini" @click.stop="goPlanSpec(row)">方案点位</el-button>
|
||||
<el-button type="text" size="mini" @click.stop="openVersionDialog(row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" class="btn-danger" @click.stop="removeVersion(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-card shadow="never" v-if="selectedVersion">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>方案点位(版本 {{ selectedVersion.versionCode }})</span>
|
||||
<el-button style="float: right; padding: 3px 10px" type="primary" size="mini" icon="el-icon-plus" @click="openPlanDialog()">新建方案点位</el-button>
|
||||
</div>
|
||||
<KLPTable
|
||||
v-loading="planLoading"
|
||||
:data="planList"
|
||||
highlight-current-row
|
||||
@row-click="onPlanRowClick"
|
||||
>
|
||||
<el-table-column label="段类型" prop="segmentType" width="110" align="center" />
|
||||
<el-table-column label="段名称" prop="segmentName" min-width="100" show-overflow-tooltip align="center" />
|
||||
<el-table-column label="点位名称" prop="pointName" min-width="120" show-overflow-tooltip align="center" />
|
||||
<el-table-column label="点位编码" prop="pointCode" min-width="120" align="center" />
|
||||
<el-table-column label="排序" prop="sortOrder" width="80" align="center" />
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click.stop="openPlanDialog(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click.stop="removePlan(scope.row)">删除</el-button>
|
||||
<el-button type="text" size="mini" @click.stop="onPlanRowClick(scope.row)">维护参数</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" v-if="selectedVersion && selectedPlan" class="mb8">
|
||||
<div slot="header" class="clearfix">
|
||||
<span>方案参数({{ selectedPlan.pointName || selectedPlan.pointCode }})</span>
|
||||
<el-button style="float: right; padding: 3px 10px" type="primary" size="mini" icon="el-icon-plus" @click="openParamDialog()">新建参数</el-button>
|
||||
</div>
|
||||
<KLPTable v-loading="paramLoading" :data="paramList">
|
||||
<el-table-column label="参数编码" prop="paramCode" min-width="100" align="center" />
|
||||
<el-table-column label="参数名称" prop="paramName" min-width="120" show-overflow-tooltip align="center" />
|
||||
<el-table-column label="设定值" prop="targetValue" width="100" align="center" />
|
||||
<el-table-column label="下限" prop="lowerLimit" width="90" align="center" />
|
||||
<el-table-column label="上限" prop="upperLimit" width="90" align="center" />
|
||||
<el-table-column label="单位" prop="unit" width="80" align="center" />
|
||||
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openParamDialog(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" @click="removeParam(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</KLPTable>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="specInfo.specId && !selectedVersion" shadow="never" class="mb8">
|
||||
<el-empty description="请在上方选择一个规程版本以维护方案点位" />
|
||||
</el-card>
|
||||
<el-empty v-if="!versionList.length && !pageLoading" description="暂无版本,请新建" style="padding:40px 0" />
|
||||
</template>
|
||||
|
||||
<!-- 版本 -->
|
||||
<el-dialog :title="versionTitle" :visible.sync="versionOpen" width="520px" append-to-body @close="versionForm = {}">
|
||||
<el-form ref="versionFormRef" :model="versionForm" :rules="versionRules" label-width="100px">
|
||||
<!-- 新建/编辑版本 -->
|
||||
<el-dialog :title="versionTitle" :visible.sync="versionOpen" width="480px" append-to-body @close="versionForm = {}">
|
||||
<el-form ref="versionFormRef" :model="versionForm" :rules="versionRules" label-width="88px" size="small">
|
||||
<el-form-item label="版本号" prop="versionCode">
|
||||
<el-input v-model="versionForm.versionCode" maxlength="64" placeholder="如 V1.0" />
|
||||
<el-input v-model="versionForm.versionCode" placeholder="如 V1.0" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="versionForm.status" placeholder="请选择" style="width: 100%">
|
||||
<el-select v-model="versionForm.status" style="width:100%">
|
||||
<el-option v-for="s in statusOptions" :key="s" :label="s" :value="s" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="保存后生效" prop="isActive">
|
||||
<el-form-item label="保存后生效">
|
||||
<el-switch v-model="versionForm.isActive" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="versionForm.remark" type="textarea" rows="2" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="versionSubmitLoading" type="primary" @click="submitVersion">确 定</el-button>
|
||||
<el-button @click="versionOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 方案点位 -->
|
||||
<el-dialog :title="planTitle" :visible.sync="planOpen" width="560px" append-to-body @close="planForm = {}">
|
||||
<el-form ref="planFormRef" :model="planForm" :rules="planRules" label-width="100px">
|
||||
<el-form-item label="段类型" prop="segmentType">
|
||||
<el-select v-model="planForm.segmentType" placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="s in segmentOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="段名称" prop="segmentName">
|
||||
<el-input v-model="planForm.segmentName" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点位名称" prop="pointName">
|
||||
<el-input v-model="planForm.pointName" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="点位编码" prop="pointCode">
|
||||
<el-input v-model="planForm.pointCode" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sortOrder">
|
||||
<el-input-number v-model="planForm.sortOrder" :min="0" :max="999999" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="planForm.remark" type="textarea" rows="2" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="planSubmitLoading" type="primary" @click="submitPlan">确 定</el-button>
|
||||
<el-button @click="planOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 方案参数 -->
|
||||
<el-dialog :title="paramTitle" :visible.sync="paramOpen" width="560px" append-to-body @close="paramForm = {}">
|
||||
<el-form ref="paramFormRef" :model="paramForm" :rules="paramRules" label-width="100px">
|
||||
<el-form-item label="参数编码" prop="paramCode">
|
||||
<el-input v-model="paramForm.paramCode" maxlength="64" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参数名称" prop="paramName">
|
||||
<el-input v-model="paramForm.paramName" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设定值" prop="targetValue">
|
||||
<el-input v-model="paramForm.targetValue" placeholder="数值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下限" prop="lowerLimit">
|
||||
<el-input v-model="paramForm.lowerLimit" placeholder="数值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上限" prop="upperLimit">
|
||||
<el-input v-model="paramForm.upperLimit" placeholder="数值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位" prop="unit">
|
||||
<el-input v-model="paramForm.unit" maxlength="32" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="paramForm.remark" type="textarea" rows="2" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button :loading="paramSubmitLoading" type="primary" @click="submitParam">确 定</el-button>
|
||||
<el-button @click="paramOpen = false">取 消</el-button>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="versionOpen = false">取消</el-button>
|
||||
<el-button size="small" type="primary" :loading="versionSubmitLoading" @click="submitVersion">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -201,8 +106,6 @@ import {
|
||||
delProcessSpecVersion,
|
||||
activateProcessSpecVersion
|
||||
} from '@/api/wms/processSpecVersion'
|
||||
import { listProcessPlan, addProcessPlan, updateProcessPlan, delProcessPlan } from '@/api/wms/processPlan'
|
||||
import { listProcessPlanParam, addProcessPlanParam, updateProcessPlanParam, delProcessPlanParam } from '@/api/wms/processPlanParam'
|
||||
|
||||
export default {
|
||||
name: 'ProcessSpecVersionManage',
|
||||
@@ -212,18 +115,7 @@ export default {
|
||||
specId: undefined,
|
||||
specInfo: {},
|
||||
versionList: [],
|
||||
selectedVersion: null,
|
||||
planList: [],
|
||||
planLoading: false,
|
||||
selectedPlan: null,
|
||||
paramList: [],
|
||||
paramLoading: false,
|
||||
statusOptions: ['DRAFT', 'PUBLISHED', 'OBSOLETE'],
|
||||
segmentOptions: [
|
||||
{ label: '入口', value: 'INLET' },
|
||||
{ label: '过程', value: 'PROCESS' },
|
||||
{ label: '出口', value: 'OUTLET' }
|
||||
],
|
||||
versionOpen: false,
|
||||
versionTitle: '',
|
||||
versionSubmitLoading: false,
|
||||
@@ -232,180 +124,62 @@ export default {
|
||||
versionCode: [{ required: true, message: '版本号不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
},
|
||||
planOpen: false,
|
||||
planTitle: '',
|
||||
planSubmitLoading: false,
|
||||
planForm: {},
|
||||
planRules: {
|
||||
segmentType: [{ required: true, message: '段类型不能为空', trigger: 'change' }],
|
||||
pointName: [{ required: true, message: '点位名称不能为空', trigger: 'blur' }],
|
||||
pointCode: [{ required: true, message: '点位编码不能为空', trigger: 'blur' }],
|
||||
sortOrder: [{ required: true, message: '排序不能为空', trigger: 'blur' }]
|
||||
},
|
||||
specPickerId: '',
|
||||
specPickerOptions: [],
|
||||
specPickerLoading: false,
|
||||
paramOpen: false,
|
||||
paramTitle: '',
|
||||
paramSubmitLoading: false,
|
||||
paramForm: {},
|
||||
paramRules: {
|
||||
paramCode: [{ required: true, message: '参数编码不能为空', trigger: 'blur' }],
|
||||
paramName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }]
|
||||
}
|
||||
specPickerLoading: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route': {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.syncSpecIdFromRoute()
|
||||
}
|
||||
}
|
||||
$route: { immediate: true, handler() { this.syncFromRoute() } }
|
||||
},
|
||||
methods: {
|
||||
syncSpecIdFromRoute() {
|
||||
syncFromRoute() {
|
||||
const raw = this.$route.query.specId
|
||||
if (raw != null && raw !== '') {
|
||||
this.specId = String(raw)
|
||||
} else {
|
||||
this.specId = undefined
|
||||
}
|
||||
this.specId = raw != null && raw !== '' ? String(raw) : undefined
|
||||
this.initPage()
|
||||
},
|
||||
loadSpecPickerOptions() {
|
||||
this.specPickerLoading = true
|
||||
listProcessSpec({ pageNum: 1, pageSize: 500 })
|
||||
.then((res) => {
|
||||
this.specPickerOptions = res.rows || []
|
||||
})
|
||||
.catch((e) => console.error('加载规程列表失败', e))
|
||||
.finally(() => {
|
||||
this.specPickerLoading = false
|
||||
})
|
||||
},
|
||||
applySpecPicker() {
|
||||
if (!this.specPickerId) {
|
||||
return
|
||||
}
|
||||
this.$router.replace({
|
||||
path: this.$route.path,
|
||||
query: { ...this.$route.query, specId: this.specPickerId }
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
goBack() { this.$router.go(-1) },
|
||||
initPage() {
|
||||
if (!this.specId) {
|
||||
this.specInfo = {}
|
||||
if (this.specPickerOptions.length === 0) {
|
||||
this.loadSpecPickerOptions()
|
||||
}
|
||||
if (!this.specPickerOptions.length) this.loadSpecPicker()
|
||||
return
|
||||
}
|
||||
this.pageLoading = true
|
||||
getProcessSpec(this.specId)
|
||||
.then((res) => {
|
||||
this.specInfo = res.data || {}
|
||||
return listProcessSpecVersion({ specId: this.specId, pageNum: 1, pageSize: 200 })
|
||||
})
|
||||
.then((res) => {
|
||||
this.versionList = res.rows || []
|
||||
this.selectedVersion = null
|
||||
this.planList = []
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('加载规程版本失败', e)
|
||||
})
|
||||
.finally(() => {
|
||||
this.pageLoading = false
|
||||
})
|
||||
getProcessSpec(this.specId).then(res => {
|
||||
this.specInfo = res.data || {}
|
||||
return listProcessSpecVersion({ specId: this.specId, pageNum: 1, pageSize: 200 })
|
||||
}).then(res => {
|
||||
this.versionList = res.rows || []
|
||||
}).catch(e => console.error(e)).finally(() => { this.pageLoading = false })
|
||||
},
|
||||
loadSpecPicker() {
|
||||
this.specPickerLoading = true
|
||||
listProcessSpec({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.specPickerOptions = res.rows || []
|
||||
}).finally(() => { this.specPickerLoading = false })
|
||||
},
|
||||
applySpecPicker() {
|
||||
if (!this.specPickerId) return
|
||||
this.$router.replace({ path: this.$route.path, query: { ...this.$route.query, specId: this.specPickerId } })
|
||||
},
|
||||
onVersionRowClick(row) {
|
||||
this.selectedVersion = row
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
this.loadPlans(row.versionId)
|
||||
this.goPlanSpec(row)
|
||||
},
|
||||
onPlanRowClick(row) {
|
||||
this.selectedPlan = row
|
||||
this.loadParams(row.planId)
|
||||
},
|
||||
loadPlans(versionId) {
|
||||
if (!versionId) {
|
||||
this.planList = []
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
return
|
||||
}
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
this.planLoading = true
|
||||
listProcessPlan({ versionId, pageNum: 1, pageSize: 500 })
|
||||
.then((res) => {
|
||||
this.planList = res.rows || []
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('加载方案点位失败', e)
|
||||
})
|
||||
.finally(() => {
|
||||
this.planLoading = false
|
||||
})
|
||||
},
|
||||
loadParams(planId) {
|
||||
if (!planId) {
|
||||
this.paramList = []
|
||||
return
|
||||
}
|
||||
this.paramLoading = true
|
||||
listProcessPlanParam({ planId, pageNum: 1, pageSize: 500 })
|
||||
.then((res) => {
|
||||
this.paramList = res.rows || []
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('加载方案参数失败', e)
|
||||
})
|
||||
.finally(() => {
|
||||
this.paramLoading = false
|
||||
})
|
||||
},
|
||||
openVersionDialog(row) {
|
||||
this.versionForm = row
|
||||
? { ...row }
|
||||
: {
|
||||
specId: this.specId,
|
||||
versionCode: undefined,
|
||||
status: 'DRAFT',
|
||||
isActive: 0,
|
||||
remark: undefined
|
||||
}
|
||||
this.versionTitle = row ? '编辑版本' : '新建版本'
|
||||
this.versionOpen = true
|
||||
this.$nextTick(() => this.$refs.versionFormRef && this.$refs.versionFormRef.clearValidate())
|
||||
},
|
||||
submitVersion() {
|
||||
this.$refs.versionFormRef.validate((ok) => {
|
||||
if (!ok) return
|
||||
this.versionSubmitLoading = true
|
||||
const req = this.versionForm.versionId
|
||||
? updateProcessSpecVersion({ ...this.versionForm, specId: this.specId })
|
||||
: addProcessSpecVersion({ ...this.versionForm, specId: this.specId })
|
||||
req
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.versionOpen = false
|
||||
this.initPage()
|
||||
})
|
||||
.catch((e) => console.error('保存版本失败', e))
|
||||
.finally(() => {
|
||||
this.versionSubmitLoading = false
|
||||
})
|
||||
goPlanSpec(row) {
|
||||
const pathCurrent = this.$route.path.replace(/\/$/, '')
|
||||
const m = pathCurrent.match(/^(.*\/processSpec)(?:\/.*)?$/)
|
||||
const base = m ? m[1] : pathCurrent
|
||||
this.$router.push({
|
||||
path: `${base}/planSpec`,
|
||||
query: { specId: this.specId, versionId: String(row.versionId), versionCode: row.versionCode }
|
||||
})
|
||||
},
|
||||
activateVersion(row) {
|
||||
handleActiveChange(row, val) {
|
||||
if (!val) {
|
||||
this.$message.info('请激活其他版本来替换当前生效版本')
|
||||
return
|
||||
}
|
||||
this.$modal.confirm('确认将版本「' + row.versionCode + '」设为当前生效版本?').then(() => {
|
||||
return activateProcessSpecVersion(row.versionId)
|
||||
}).then(() => {
|
||||
@@ -413,129 +187,116 @@ export default {
|
||||
this.initPage()
|
||||
}).catch(() => {})
|
||||
},
|
||||
openVersionDialog(row) {
|
||||
this.versionForm = row
|
||||
? { ...row }
|
||||
: { specId: this.specId, versionCode: undefined, status: 'DRAFT', isActive: 0, remark: undefined }
|
||||
this.versionTitle = row ? '编辑版本' : '新建版本'
|
||||
this.versionOpen = true
|
||||
this.$nextTick(() => this.$refs.versionFormRef && this.$refs.versionFormRef.clearValidate())
|
||||
},
|
||||
submitVersion() {
|
||||
this.$refs.versionFormRef.validate(ok => {
|
||||
if (!ok) return
|
||||
this.versionSubmitLoading = true
|
||||
const req = this.versionForm.versionId
|
||||
? updateProcessSpecVersion({ ...this.versionForm, specId: this.specId })
|
||||
: addProcessSpecVersion({ ...this.versionForm, specId: this.specId })
|
||||
req.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.versionOpen = false
|
||||
this.initPage()
|
||||
}).catch(e => console.error(e)).finally(() => { this.versionSubmitLoading = false })
|
||||
})
|
||||
},
|
||||
removeVersion(row) {
|
||||
this.$modal.confirm('确认删除版本「' + row.versionCode + '」及其下方案点位?').then(() => {
|
||||
this.$modal.confirm('确认删除版本「' + row.versionCode + '」?').then(() => {
|
||||
return delProcessSpecVersion(row.versionId)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.selectedVersion = null
|
||||
this.planList = []
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
this.initPage()
|
||||
}).catch(() => {})
|
||||
},
|
||||
openPlanDialog(row) {
|
||||
if (!this.selectedVersion) {
|
||||
this.$modal.msgWarning('请先选择一个规程版本')
|
||||
return
|
||||
}
|
||||
this.planForm = row
|
||||
? { ...row }
|
||||
: {
|
||||
versionId: this.selectedVersion.versionId,
|
||||
segmentType: 'PROCESS',
|
||||
segmentName: undefined,
|
||||
pointName: undefined,
|
||||
pointCode: undefined,
|
||||
sortOrder: 0,
|
||||
remark: undefined
|
||||
}
|
||||
this.planTitle = row ? '编辑方案点位' : '新建方案点位'
|
||||
this.planOpen = true
|
||||
this.$nextTick(() => this.$refs.planFormRef && this.$refs.planFormRef.clearValidate())
|
||||
},
|
||||
submitPlan() {
|
||||
this.$refs.planFormRef.validate((ok) => {
|
||||
if (!ok) return
|
||||
this.planSubmitLoading = true
|
||||
const req = this.planForm.planId ? updateProcessPlan(this.planForm) : addProcessPlan(this.planForm)
|
||||
req
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.planOpen = false
|
||||
this.loadPlans(this.selectedVersion.versionId)
|
||||
})
|
||||
.catch((e) => console.error('保存方案点位失败', e))
|
||||
.finally(() => {
|
||||
this.planSubmitLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
removePlan(row) {
|
||||
this.$modal.confirm('确认删除该方案点位?').then(() => {
|
||||
return delProcessPlan(row.planId)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
if (this.selectedPlan && this.selectedPlan.planId === row.planId) {
|
||||
this.selectedPlan = null
|
||||
this.paramList = []
|
||||
}
|
||||
this.loadPlans(this.selectedVersion.versionId)
|
||||
}).catch(() => {})
|
||||
},
|
||||
openParamDialog(row) {
|
||||
if (!this.selectedPlan) {
|
||||
this.$modal.msgWarning('请先在上方方案点位表格中选中一行')
|
||||
return
|
||||
}
|
||||
this.paramForm = row
|
||||
? { ...row }
|
||||
: {
|
||||
planId: this.selectedPlan.planId,
|
||||
paramCode: undefined,
|
||||
paramName: undefined,
|
||||
targetValue: undefined,
|
||||
lowerLimit: undefined,
|
||||
upperLimit: undefined,
|
||||
unit: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
this.paramTitle = row ? '编辑方案参数' : '新建方案参数'
|
||||
this.paramOpen = true
|
||||
this.$nextTick(() => this.$refs.paramFormRef && this.$refs.paramFormRef.clearValidate())
|
||||
},
|
||||
parseDecimal(val) {
|
||||
if (val === undefined || val === null || val === '') {
|
||||
return undefined
|
||||
}
|
||||
const n = Number(val)
|
||||
return Number.isFinite(n) ? n : undefined
|
||||
},
|
||||
buildParamPayload() {
|
||||
return {
|
||||
...this.paramForm,
|
||||
targetValue: this.parseDecimal(this.paramForm.targetValue),
|
||||
lowerLimit: this.parseDecimal(this.paramForm.lowerLimit),
|
||||
upperLimit: this.parseDecimal(this.paramForm.upperLimit)
|
||||
}
|
||||
},
|
||||
submitParam() {
|
||||
this.$refs.paramFormRef.validate((ok) => {
|
||||
if (!ok) return
|
||||
this.paramSubmitLoading = true
|
||||
const payload = this.buildParamPayload()
|
||||
const req = payload.paramId ? updateProcessPlanParam(payload) : addProcessPlanParam(payload)
|
||||
req
|
||||
.then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.paramOpen = false
|
||||
this.loadParams(this.selectedPlan.planId)
|
||||
})
|
||||
.catch((e) => console.error('保存方案参数失败', e))
|
||||
.finally(() => {
|
||||
this.paramSubmitLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
removeParam(row) {
|
||||
this.$modal.confirm('确认删除该方案参数?').then(() => {
|
||||
return delProcessPlanParam(row.paramId)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.loadParams(this.selectedPlan.planId)
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ver-page {
|
||||
padding: 16px 20px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.spec-code {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
color: #909399;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.pick-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 24px;
|
||||
border: 1px solid #ebeef5;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pick-hint {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::v-deep .el-button--primary {
|
||||
color: #fff !important;
|
||||
background: #5F7BA0 !important;
|
||||
border-color: #5F7BA0 !important;
|
||||
}
|
||||
::v-deep .el-button--primary:hover,
|
||||
::v-deep .el-button--primary:focus { background: #4d6a8e !important; border-color: #4d6a8e !important; }
|
||||
::v-deep .el-button--primary:active { background: #4a6585 !important; border-color: #4a6585 !important; }
|
||||
::v-deep .el-button--primary.is-disabled { opacity: .5; }
|
||||
|
||||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger) {
|
||||
color: #606266 !important;
|
||||
background: #fff !important;
|
||||
border-color: #dcdfe6 !important;
|
||||
}
|
||||
::v-deep .el-button:not(.el-button--primary):not(.el-button--text):not(.el-button--danger):hover {
|
||||
color: #5F7BA0 !important;
|
||||
border-color: #5F7BA0 !important;
|
||||
}
|
||||
|
||||
::v-deep .el-button--text { background: transparent !important; border-color: transparent !important; }
|
||||
::v-deep .el-button--text.btn-danger { color: #f56c6c !important; }
|
||||
|
||||
.btn-danger { color: #f56c6c; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user