胶料快检添加离线模式

This commit is contained in:
2026-06-30 14:43:48 +08:00
parent 672259c94f
commit d55223a1c0
6 changed files with 312 additions and 92 deletions

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable; import java.io.Serializable;
@@ -153,6 +154,9 @@ public class MesXslRubberQuickTestRecord implements Serializable {
@TableField(exist = false) @TableField(exist = false)
@Schema(description = "曲线图数据点") @Schema(description = "曲线图数据点")
//update-begin---author:jiangxh ---date:2026-06-29 for【快检记录】主表曲线图列表JSON别名-----------
@JsonAlias("chart_point_list")
//update-end---author:jiangxh ---date:2026-06-29 for【快检记录】主表曲线图列表JSON别名-----------
private List<MesXslRubberQuickTestRecordChartPoint> chartPointList; private List<MesXslRubberQuickTestRecordChartPoint> chartPointList;
//update-end---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图----------- //update-end---author:jiangxh ---date:2026-06-22 for【快检记录】数据标准明细与曲线图-----------
} }

View File

@@ -3,6 +3,7 @@ package org.jeecg.modules.xslmes.entity;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable; import java.io.Serializable;
@@ -32,19 +33,26 @@ public class MesXslRubberQuickTestRecordChartPoint implements Serializable {
@Schema(description = "主表ID mes_xsl_rubber_quick_test_record.id") @Schema(description = "主表ID mes_xsl_rubber_quick_test_record.id")
private String recordId; private String recordId;
//update-begin---author:jiangxh ---date:2026-06-29 for【快检记录】曲线图字段兼容桌面端与下划线JSON-----------
@Excel(name = "时间(min)", width = 12, type = 10) @Excel(name = "时间(min)", width = 12, type = 10)
@JsonAlias("time_min")
private BigDecimal timeMin; private BigDecimal timeMin;
@Excel(name = "上模温度", width = 12, type = 10) @Excel(name = "上模温度", width = 12, type = 10)
@JsonAlias("upper_temp")
private BigDecimal upperTemp; private BigDecimal upperTemp;
@Excel(name = "下模温度", width = 12, type = 10) @Excel(name = "下模温度", width = 12, type = 10)
@JsonAlias("lower_temp")
private BigDecimal lowerTemp; private BigDecimal lowerTemp;
@Excel(name = "S'(dNm)", width = 12, type = 10) @Excel(name = "S'(dNm)", width = 12, type = 10)
@JsonAlias("torque_s")
private BigDecimal torqueS; private BigDecimal torqueS;
@JsonAlias("sort_no")
private Integer sortNo; private Integer sortNo;
//update-end---author:jiangxh ---date:2026-06-29 for【快检记录】曲线图字段兼容桌面端与下划线JSON-----------
private String createBy; private String createBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")

View File

@@ -223,10 +223,10 @@ export const rawLineJVxeColumns: JVxeColumn[] = [
componentProps: numProps, componentProps: numProps,
}, },
{ {
title: '检验结果', title: '单次检验结果',
key: 'rowInspectResult', key: 'rowInspectResult',
type: JVxeTypes.select, type: JVxeTypes.select,
width: 110, width: 130,
disabled: true, disabled: true,
dictCode: 'xslmes_rubber_quick_test_record_result', dictCode: 'xslmes_rubber_quick_test_record_result',
}, },

View File

@@ -6,6 +6,7 @@
width="1200px" width="1200px"
@register="registerModal" @register="registerModal"
@ok="handleSubmit" @ok="handleSubmit"
@visible-change="onModalVisibleChange"
> >
<BasicForm @register="registerForm"> <BasicForm @register="registerForm">
<template #prodEquipmentPicker="{ model, field }"> <template #prodEquipmentPicker="{ model, field }">
@@ -25,64 +26,108 @@
</BasicForm> </BasicForm>
<template v-if="useNewDetail"> <template v-if="useNewDetail">
<a-divider orientation="left">数据标准明细</a-divider> <div class="detail-section">
<JVxeTable <div class="detail-section__header">
v-if="tableReady" <span class="detail-section__accent" />
row-number <div class="detail-section__titles">
keep-source <span class="detail-section__title">数据标准明细</span>
:toolbar="false" <span class="detail-section__desc">实验标准数据点及上下限快照</span>
:insert-row="false" </div>
:remove-btn="false" </div>
:max-height="260" <div class="detail-section__body">
:loading="detailLoading" <JVxeTable
:columns="stdLineJVxeColumns" v-if="tableReady"
:dataSource="stdLineDataSource" row-number
disabled keep-source
/> :toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="260"
:loading="detailLoading"
:columns="stdLineJVxeColumns"
:dataSource="stdLineDataSource"
disabled
/>
</div>
</div>
<a-divider orientation="left">试验结果明细</a-divider> <div class="detail-section">
<JVxeTable <div class="detail-section__header">
v-if="tableReady" <span class="detail-section__accent detail-section__accent--green" />
row-number <div class="detail-section__titles">
keep-source <span class="detail-section__title">试验结果明细</span>
:toolbar="false" <span class="detail-section__desc">各次试验检测值与单次检验结果</span>
:insert-row="false" </div>
:remove-btn="false" </div>
:max-height="320" <div class="detail-section__body">
:loading="detailLoading" <JVxeTable
:columns="rawLineJVxeColumns" v-if="tableReady"
:dataSource="rawLineDataSource" row-number
:disabled="isDetail" keep-source
/> :toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="320"
:loading="detailLoading"
:columns="rawLineJVxeColumns"
:dataSource="rawLineDataSource"
:disabled="isDetail"
/>
</div>
</div>
<a-divider orientation="left">曲线图</a-divider> <div class="detail-section detail-section--chart">
<a-row :gutter="16"> <div class="detail-section__header">
<a-col :span="12"> <span class="detail-section__accent detail-section__accent--orange" />
<div class="chart-title">温度曲线上模/下模</div> <div class="detail-section__titles">
<div ref="tempChartRef" class="chart-box"></div> <span class="detail-section__title">曲线图明细</span>
</a-col> <span class="detail-section__desc">密炼过程温度与扭矩曲线</span>
<a-col :span="12"> </div>
<div class="chart-title">S'(dNm) 曲线</div> </div>
<div ref="torqueChartRef" class="chart-box"></div> <div class="detail-section__body detail-section__body--chart">
</a-col> <a-row :gutter="16">
</a-row> <a-col :span="12">
<div class="chart-card">
<div class="chart-card__title">温度曲线上模 / 下模</div>
<div ref="tempChartRef" class="chart-box"></div>
</div>
</a-col>
<a-col :span="12">
<div class="chart-card">
<div class="chart-card__title">S'(dNm) 曲线</div>
<div ref="torqueChartRef" class="chart-box"></div>
</div>
</a-col>
</a-row>
</div>
</div>
</template> </template>
<template v-else> <template v-else>
<a-divider orientation="left">检验明细(历史数据)</a-divider> <div class="detail-section">
<JVxeTable <div class="detail-section__header">
v-if="tableReady" <span class="detail-section__accent detail-section__accent--gray" />
row-number <div class="detail-section__titles">
keep-source <span class="detail-section__title">检验明细(历史数据)</span>
:toolbar="false" <span class="detail-section__desc">旧版汇总检验数据</span>
:insert-row="false" </div>
:remove-btn="false" </div>
:max-height="380" <div class="detail-section__body">
:loading="detailLoading" <JVxeTable
:columns="lineJVxeColumns" v-if="tableReady"
:dataSource="lineDataSource" row-number
:disabled="isDetail" keep-source
/> :toolbar="false"
:insert-row="false"
:remove-btn="false"
:max-height="380"
:loading="detailLoading"
:columns="lineJVxeColumns"
:dataSource="lineDataSource"
:disabled="isDetail"
/>
</div>
</div>
</template> </template>
<MesXslEquipmentLedgerSelectModal @register="registerLedgerModal" @select="onLedgerSelect" /> <MesXslEquipmentLedgerSelectModal @register="registerLedgerModal" @select="onLedgerSelect" />
@@ -102,7 +147,13 @@
stdLineJVxeColumns, stdLineJVxeColumns,
rawLineJVxeColumns, rawLineJVxeColumns,
} from '../MesXslRubberQuickTestRecord.data'; } from '../MesXslRubberQuickTestRecord.data';
import { saveOrUpdate, queryById, queryChartPointListByRecordId } from '../MesXslRubberQuickTestRecord.api'; import {
saveOrUpdate,
queryById,
queryChartPointListByRecordId,
queryStdLineListByRecordId,
queryRawLineListByRecordId,
} from '../MesXslRubberQuickTestRecord.api';
import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue'; import MesXslEquipmentLedgerSelectModal from '/@/views/xslmes/mesXslEquipInspectConfig/components/MesXslEquipmentLedgerSelectModal.vue';
const emit = defineEmits(['register', 'success']); const emit = defineEmits(['register', 'success']);
@@ -135,6 +186,35 @@
const [registerLedgerModal, { openModal: openLedgerModal }] = useModal(); const [registerLedgerModal, { openModal: openLedgerModal }] = useModal();
function unwrapApiList(payload: unknown): Recordable[] {
if (Array.isArray(payload)) return payload;
if (payload && typeof payload === 'object') {
const result = (payload as Recordable).result;
if (Array.isArray(result)) return result;
}
return [];
}
function unwrapApiRecord(payload: unknown): Recordable {
if (payload && typeof payload === 'object' && (payload as Recordable).id != null) {
return payload as Recordable;
}
if (payload && typeof payload === 'object' && (payload as Recordable).result) {
return ((payload as Recordable).result ?? {}) as Recordable;
}
return (payload ?? {}) as Recordable;
}
function normalizeChartPoint(point: Recordable): Recordable {
return {
sortNo: point.sortNo ?? point.sort_no ?? 0,
timeMin: point.timeMin ?? point.time_min,
upperTemp: point.upperTemp ?? point.upper_temp,
lowerTemp: point.lowerTemp ?? point.lower_temp,
torqueS: point.torqueS ?? point.torque_s,
};
}
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => { const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
tableReady.value = false; tableReady.value = false;
stdLineDataSource.value = []; stdLineDataSource.value = [];
@@ -149,9 +229,9 @@
if (data?.record?.id) { if (data?.record?.id) {
detailLoading.value = true; detailLoading.value = true;
const recordId = data.record.id as string;
try { try {
const mainRaw = await queryById({ id: data.record.id }); const m = unwrapApiRecord(await queryById({ id: recordId }));
const m = (mainRaw as any)?.id != null ? mainRaw : (mainRaw as any)?.result ?? mainRaw;
const patch: Recordable = { ...m }; const patch: Recordable = { ...m };
if (data?.showFooter && !patch.inspectorRealname && !patch.inspectorUserId) { if (data?.showFooter && !patch.inspectorRealname && !patch.inspectorUserId) {
const user = userStore.getUserInfo || {}; const user = userStore.getUserInfo || {};
@@ -161,22 +241,35 @@
} }
await setFieldsValue(patch); await setFieldsValue(patch);
const stdLines = m?.stdLineList ?? []; let stdLines = unwrapApiList(m?.stdLineList);
const rawLines = m?.rawLineList ?? []; let rawLines = unwrapApiList(m?.rawLineList);
let chartPoints = m?.chartPointList ?? []; const legacyLines = unwrapApiList(m?.lineList);
const legacyLines = m?.lineList ?? [];
if (stdLines.length > 0 || rawLines.length > 0 || chartPoints.length > 0) { if (!stdLines.length) {
try {
stdLines = unwrapApiList(await queryStdLineListByRecordId({ id: recordId }));
} catch (_) {
stdLines = [];
}
}
if (!rawLines.length) {
try {
rawLines = unwrapApiList(await queryRawLineListByRecordId({ id: recordId }));
} catch (_) {
rawLines = [];
}
}
if (stdLines.length > 0 || rawLines.length > 0) {
useNewDetail.value = true; useNewDetail.value = true;
stdLineDataSource.value = stdLines; stdLineDataSource.value = stdLines;
rawLineDataSource.value = rawLines; rawLineDataSource.value = rawLines;
if (!chartPoints.length && data?.record?.id) {
try { let chartPoints: Recordable[] = [];
const chartRes = await queryChartPointListByRecordId({ id: data.record.id }); try {
chartPoints = Array.isArray(chartRes) ? chartRes : (chartRes as any)?.result ?? []; chartPoints = unwrapApiList(await queryChartPointListByRecordId({ id: recordId })).map(normalizeChartPoint);
} catch (_) { } catch (_) {
chartPoints = []; chartPoints = unwrapApiList(m?.chartPointList).map(normalizeChartPoint);
}
} }
chartPointDataSource.value = chartPoints; chartPointDataSource.value = chartPoints;
} else { } else {
@@ -195,19 +288,48 @@
async function scheduleRenderCharts(points: Recordable[]) { async function scheduleRenderCharts(points: Recordable[]) {
await nextTick(); await nextTick();
await nextTick(); await nextTick();
window.setTimeout(() => { await new Promise<void>((resolve) => {
renderCharts(points); window.setTimeout(() => {
resizeTempChart(); renderCharts(points);
resizeTorqueChart(); resizeTempChart();
}, 120); resizeTorqueChart();
resolve();
}, 280);
});
}
function onModalVisibleChange(visible: boolean) {
if (visible && useNewDetail.value) {
scheduleRenderCharts(chartPointDataSource.value);
}
} }
watch(useNewDetail, async (visible) => { watch(useNewDetail, async (visible) => {
if (visible && chartPointDataSource.value.length) { if (visible) {
await scheduleRenderCharts(chartPointDataSource.value); await scheduleRenderCharts(chartPointDataSource.value);
} }
}); });
watch(
() => [useNewDetail.value, tempChartRef.value, torqueChartRef.value] as const,
([visible, tempEl, torqueEl]) => {
if (visible && tempEl && torqueEl) {
scheduleRenderCharts(chartPointDataSource.value);
}
},
{ flush: 'post' },
);
watch(
chartPointDataSource,
async (points) => {
if (useNewDetail.value && points?.length) {
await scheduleRenderCharts(points);
}
},
{ deep: true },
);
const title = computed(() => (unref(isDetail) ? '快检记录详情' : '编辑胶料快检记录')); const title = computed(() => (unref(isDetail) ? '快检记录详情' : '编辑胶料快检记录'));
function toChartNumber(value: unknown) { function toChartNumber(value: unknown) {
@@ -217,24 +339,27 @@
} }
function renderCharts(points: Recordable[]) { function renderCharts(points: Recordable[]) {
if (!points?.length) { const normalized = (points ?? []).map((p) => normalizeChartPoint(p));
if (!normalized.length) {
setTempChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } }); setTempChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } });
setTorqueChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } }); setTorqueChartOptions({ title: { text: '暂无曲线数据', left: 'center', top: 'center', textStyle: { fontSize: 14 } } });
return; return;
} }
const sorted = [...points].sort((a, b) => (a.sortNo ?? 0) - (b.sortNo ?? 0)); const sorted = [...normalized].sort((a, b) => (Number(a.sortNo) || 0) - (Number(b.sortNo) || 0));
const times = sorted.map((p) => toChartNumber(p.timeMin) ?? 0); const tempUpper = sorted.map((p) => [toChartNumber(p.timeMin) ?? 0, toChartNumber(p.upperTemp)]);
const tempLower = sorted.map((p) => [toChartNumber(p.timeMin) ?? 0, toChartNumber(p.lowerTemp)]);
const torque = sorted.map((p) => [toChartNumber(p.timeMin) ?? 0, toChartNumber(p.torqueS)]);
setTempChartOptions({ setTempChartOptions({
title: undefined, title: undefined,
tooltip: { trigger: 'axis' }, tooltip: { trigger: 'axis' },
legend: { data: ['上模温度', '下模温度'] }, legend: { data: ['上模温度', '下模温度'] },
grid: { left: 48, right: 24, top: 40, bottom: 32 }, grid: { left: 48, right: 24, top: 40, bottom: 32 },
xAxis: { type: 'category', name: '时间(min)', data: times }, xAxis: { type: 'value', name: '时间(min)', min: 0, max: 2, interval: 0.5 },
yAxis: { type: 'value', name: '温度()', min: 189, max: 201, interval: 3 }, yAxis: { type: 'value', name: '温度()', scale: true },
series: [ series: [
{ name: '上模温度', type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.upperTemp)) }, { name: '上模温度', type: 'line', smooth: true, showSymbol: true, data: tempUpper },
{ name: '下模温度', type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.lowerTemp)) }, { name: '下模温度', type: 'line', smooth: true, showSymbol: true, data: tempLower },
], ],
}); });
@@ -243,9 +368,9 @@
tooltip: { trigger: 'axis' }, tooltip: { trigger: 'axis' },
legend: { data: ["S'(dNm)"] }, legend: { data: ["S'(dNm)"] },
grid: { left: 48, right: 24, top: 40, bottom: 32 }, grid: { left: 48, right: 24, top: 40, bottom: 32 },
xAxis: { type: 'category', name: '时间(min)', data: times }, xAxis: { type: 'value', name: '时间(min)', min: 0, max: 2, interval: 0.5 },
yAxis: { type: 'value', name: "S'(dNm)", min: 0, max: 14.8 }, yAxis: { type: 'value', name: "S'(dNm)", scale: true },
series: [{ name: "S'(dNm)", type: 'line', smooth: true, data: sorted.map((p) => toChartNumber(p.torqueS)) }], series: [{ name: "S'(dNm)", type: 'line', smooth: true, showSymbol: true, data: torque }],
}); });
} }
@@ -331,13 +456,96 @@
</script> </script>
<style scoped> <style scoped>
.chart-title { .detail-section {
font-size: 13px; margin-top: 20px;
color: rgba(0, 0, 0, 0.65);
margin-bottom: 8px;
} }
.detail-section:first-of-type {
margin-top: 8px;
}
.detail-section__header {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: linear-gradient(90deg, #f7f9fc 0%, #fafbfc 100%);
border: 1px solid #eef1f5;
border-bottom: none;
border-radius: 8px 8px 0 0;
}
.detail-section__accent {
flex-shrink: 0;
width: 4px;
height: 18px;
border-radius: 2px;
background: var(--j-global-primary-color, #1677ff);
}
.detail-section__accent--green {
background: #52c41a;
}
.detail-section__accent--orange {
background: #fa8c16;
}
.detail-section__accent--gray {
background: #8c8c8c;
}
.detail-section__titles {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.detail-section__title {
font-size: 15px;
font-weight: 600;
line-height: 1.35;
color: rgba(0, 0, 0, 0.88);
letter-spacing: 0.2px;
}
.detail-section__desc {
font-size: 12px;
line-height: 1.4;
color: rgba(0, 0, 0, 0.45);
}
.detail-section__body {
padding: 12px;
background: #fff;
border: 1px solid #eef1f5;
border-radius: 0 0 8px 8px;
}
.detail-section__body--chart {
padding: 12px 12px 4px;
}
.chart-card {
padding: 10px 12px 8px;
background: #fafbfc;
border: 1px solid #f0f2f5;
border-radius: 6px;
}
.chart-card__title {
margin-bottom: 8px;
padding-left: 8px;
font-size: 13px;
font-weight: 500;
color: rgba(0, 0, 0, 0.72);
border-left: 2px solid rgba(0, 0, 0, 0.12);
}
.chart-box { .chart-box {
width: 100%; width: 100%;
height: 220px; height: 220px;
min-height: 220px;
} }
</style> </style>

View File

@@ -109,7 +109,7 @@ public partial class RubberQuickTestOperationView : UserControl
InspectResultGrid.Columns.Add(new DataGridTextColumn InspectResultGrid.Columns.Add(new DataGridTextColumn
{ {
Header = "验结果", Header = "单次检验结果",
Binding = new Binding(nameof(QuickTestInspectRowViewModel.InspectResultText)) Binding = new Binding(nameof(QuickTestInspectRowViewModel.InspectResultText))
{ {
Mode = BindingMode.OneWay Mode = BindingMode.OneWay

View File

@@ -117,7 +117,7 @@
<DataGridTextColumn Header="编号" Binding="{Binding RowNo}" Width="80"/> <DataGridTextColumn Header="编号" Binding="{Binding RowNo}" Width="80"/>
<DataGridTextColumn Header="数据点" Binding="{Binding InspectItem}" Width="*"/> <DataGridTextColumn Header="数据点" Binding="{Binding InspectItem}" Width="*"/>
<DataGridTextColumn Header="检测值" Binding="{Binding InspectValue}" Width="100"/> <DataGridTextColumn Header="检测值" Binding="{Binding InspectValue}" Width="100"/>
<DataGridTextColumn Header="结果" Binding="{Binding RowInspectResult}" Width="80"/> <DataGridTextColumn Header="单次检验结果" Binding="{Binding RowInspectResult}" Width="100"/>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</StackPanel> </StackPanel>