Merge remote-tracking branch 'origin/master'

master
wanghao 1 day ago
commit 2f8629d9fd

@ -1,6 +1,7 @@
package com.ruoyi.web.controller.tyre; package com.ruoyi.web.controller.tyre;
import java.util.List; import java.util.List;
import java.util.Map;
import com.ruoyi.common.utils.ShiroUtils; import com.ruoyi.common.utils.ShiroUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
@ -13,7 +14,6 @@ import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.BaseCar; import com.ruoyi.system.domain.BaseCar;
import com.ruoyi.system.domain.CarCheckLifecycleDTO; import com.ruoyi.system.domain.CarCheckLifecycleDTO;
import com.ruoyi.system.domain.CarLatestMaintenanceDTO;
import com.ruoyi.system.domain.CarLifecycleDTO; import com.ruoyi.system.domain.CarLifecycleDTO;
import com.ruoyi.system.domain.CarLifecycleQuery; import com.ruoyi.system.domain.CarLifecycleQuery;
import com.ruoyi.system.domain.CarMileageLifecycleDTO; import com.ruoyi.system.domain.CarMileageLifecycleDTO;
@ -206,14 +206,14 @@ public class BaseCarController extends BaseController
} }
/** /**
* *
*/ */
@RequiresPermissions("system:car:view") @RequiresPermissions("system:car:view")
@GetMapping("/lifecycle/{carNo}/latest-maintenance") @GetMapping("/lifecycle/{carNo}/latest-maintenance")
@ResponseBody @ResponseBody
public AjaxResult latestMaintenance(@PathVariable("carNo") String carNo) public AjaxResult latestMaintenance(@PathVariable("carNo") String carNo)
{ {
CarLatestMaintenanceDTO latestMaintenance = baseCarLifecycleService.selectLatestMaintenance(buildLifecycleQuery(carNo)); List<Map> latestMaintenance = baseCarLifecycleService.selectLatestWheelPositionMaintenance(buildLifecycleQuery(carNo));
return AjaxResult.success(latestMaintenance); return AjaxResult.success(latestMaintenance);
} }

@ -406,8 +406,8 @@ public class BaseTyreController extends BaseController
return "error/404"; return "error/404";
} }
// 与 PDA 时间线接口保持同一个 service 返回结构;列表页传 tyreIdPDA 仍可传 keyParam // Web 详情抽屉在 PDA 原时间线外补充里程/花纹统计,避免改动老 PDA 接口返回契约
Map resultMap = baseTyreService.pdaQueryTyreTimeLine(baseTyre); Map resultMap = baseTyreService.selectTyreDetailForWeb(baseTyre);
if (resultMap == null || resultMap.isEmpty()) { if (resultMap == null || resultMap.isEmpty()) {
return "error/404"; return "error/404";
} }

@ -152,7 +152,7 @@
<div class="lifecycle-drawer-mask" id="lifecycleDrawerMask" onclick="closeLifecycleDrawer()"></div> <div class="lifecycle-drawer-mask" id="lifecycleDrawerMask" onclick="closeLifecycleDrawer()"></div>
<div class="lifecycle-drawer" id="lifecycleDrawer"> <div class="lifecycle-drawer" id="lifecycleDrawer">
<div class="lifecycle-drawer-header"> <div class="lifecycle-drawer-header">
<div class="lifecycle-drawer-title" id="lifecycleDrawerTitle">车辆全生命周期</div> <div class="lifecycle-drawer-title" id="lifecycleDrawerTitle">车辆详情</div>
<button type="button" class="lifecycle-drawer-close" onclick="closeLifecycleDrawer()" title="关闭">×</button> <button type="button" class="lifecycle-drawer-close" onclick="closeLifecycleDrawer()" title="关闭">×</button>
</div> </div>
<iframe id="lifecycleFrame" src="about:blank"></iframe> <iframe id="lifecycleFrame" src="about:blank"></iframe>
@ -234,7 +234,7 @@
$.modal.alertWarning("车牌号为空,无法查看生命周期报表"); $.modal.alertWarning("车牌号为空,无法查看生命周期报表");
return; return;
} }
$("#lifecycleDrawerTitle").text("车辆全生命周期 - " + carNo); $("#lifecycleDrawerTitle").text("车辆详情 - " + carNo);
$("#lifecycleFrame").attr("src", prefix + "/lifecycle/view/" + encodeURIComponent(carNo)); $("#lifecycleFrame").attr("src", prefix + "/lifecycle/view/" + encodeURIComponent(carNo));
$("#lifecycleDrawerMask").show(); $("#lifecycleDrawerMask").show();
$("#lifecycleDrawer").addClass("open"); $("#lifecycleDrawer").addClass("open");
@ -247,6 +247,28 @@
$("#lifecycleFrame").attr("src", "about:blank"); $("#lifecycleFrame").attr("src", "about:blank");
} }
function openMaintenanceOrderDetailTabFromLifecycle(orderId, orderNo) {
var normalizedOrderId = $.common.nullToStr(orderId);
if (!/^\d+$/.test(normalizedOrderId)) {
// 子 iframe 传来的主键只作为导航参数使用,父页再校验一次避免拼接异常详情地址。
$.modal.alertWarning("工单ID无效无法打开维保工单详情");
return;
}
var tabTitle = buildMaintenanceOrderDetailTitle(orderNo);
// 页签必须由车辆列表所在主 iframe 发起RuoYi 才能把详情页挂到后台主框架,而不是抽屉 iframe 内部。
$.modal.openTab(tabTitle, ctx + "tyre/order/edit/" + encodeURIComponent(normalizedOrderId));
closeLifecycleDrawer();
}
function buildMaintenanceOrderDetailTitle(orderNo) {
var text = $.common.nullToStr(orderNo);
if (!text || text === "-") {
return "维保工单详情";
}
// 页签标题不承载 HTML去掉尖括号可避免异常工单号影响主框架页签 DOM。
return "维保工单详情 - " + text.replace(/[<>]/g, "");
}
function escapeHtml(value) { function escapeHtml(value) {
return String(value) return String(value)
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org"> <html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head> <head>
<th:block th:include="include :: header('车辆全生命周期')" /> <th:block th:include="include :: header('车辆详情')" />
<style> <style>
.detail-shell { .detail-shell {
padding: 15px; padding: 15px;
@ -187,8 +187,8 @@
.compare-tyre-card { .compare-tyre-card {
position: relative; position: relative;
width: 66px; width: 90px;
min-height: 126px; min-height: 174px;
padding: 24px 6px 8px; padding: 24px 6px 8px;
border-radius: 8px 8px 16px 16px; border-radius: 8px 8px 16px 16px;
background: #2f4050; background: #2f4050;
@ -432,7 +432,8 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-lg-4 col-md-5 col-sm-12"> <!-- 暂时隐藏基础信息与当前装车轮胎,避免车辆详情对话框出现左右分栏导致主视图宽度不足。 -->
<!--<div class="col-lg-4 col-md-5 col-sm-12">
<div class="info-card"> <div class="info-card">
<div class="section-title"><i class="fa fa-bookmark"></i>基本信息</div> <div class="section-title"><i class="fa fa-bookmark"></i>基本信息</div>
<table class="info-table" id="vehicleInfo"></table> <table class="info-table" id="vehicleInfo"></table>
@ -444,11 +445,11 @@
<div class="text-center text-muted-dash" style="padding: 40px 0;">正在加载...</div> <div class="text-center text-muted-dash" style="padding: 40px 0;">正在加载...</div>
</div> </div>
</div> </div>
</div> </div>-->
<div class="col-lg-8 col-md-7 col-sm-12"> <div class="col-lg-12 col-md-12 col-sm-12">
<div class="info-card"> <div class="info-card">
<div class="section-title"><i class="fa fa-wrench"></i>最近一次维保</div> <div class="section-title"><i class="fa fa-wrench"></i>轮位视图</div>
<div id="latestMaintenance"> <div id="latestMaintenance">
<div class="text-center text-muted-dash" style="padding: 48px 0;">正在加载...</div> <div class="text-center text-muted-dash" style="padding: 48px 0;">正在加载...</div>
</div> </div>
@ -456,12 +457,12 @@
<div class="timeline-card"> <div class="timeline-card">
<div class="timeline-header"> <div class="timeline-header">
<span class="title clickable-title" onclick="goMaintenanceList()">保养信息</span> <span class="title">生命周期</span>
<span class="mini-tip">按时间倒序展示维保工单</span> <span class="mini-tip">按时间倒序展示维保工单,点击记录打开维保工单详情页签</span>
</div> </div>
<ul class="life-list" id="timelineList"></ul> <ul class="life-list" id="timelineList"></ul>
<div class="text-center text-muted-dash" id="emptyTimeline" style="display: none; padding: 80px 0;"> <div class="text-center text-muted-dash" id="emptyTimeline" style="display: none; padding: 80px 0;">
暂无保养信息 暂无生命周期
</div> </div>
</div> </div>
</div> </div>
@ -472,11 +473,11 @@
<script th:inline="javascript"> <script th:inline="javascript">
var prefix = ctx + "tyre/car"; var prefix = ctx + "tyre/car";
var carNo = $("#carNo").val(); var carNo = $("#carNo").val();
var POSITION_ORDER = ["左前轮", "右前轮", "左外轮", "左内轮", "右内轮", "右外轮"]; var POSITION_ORDER = ["左前轮", "右前轮", "右内轮", "右外轮", "左内轮", "左外轮"];
$(function () { $(function () {
loadLifecycle(); loadLifecycle();
loadLatestMaintenance(); loadWheelPositionView();
// 兜底关闭父窗口 loading避免 iframe 加载时序导致遮罩残留。 // 兜底关闭父窗口 loading避免 iframe 加载时序导致遮罩残留。
if (window.parent && window.parent !== window && window.parent.$ && window.parent.$.modal) { if (window.parent && window.parent !== window && window.parent.$ && window.parent.$.modal) {
window.parent.$.modal.closeLoading(); window.parent.$.modal.closeLoading();
@ -491,20 +492,21 @@
} }
var data = result.data || {}; var data = result.data || {};
renderSummary(data); renderSummary(data);
renderVehicleInfo(data.car || {}); // 基本信息与当前装车轮胎已从页面隐藏,保留汇总和生命周期即可让主内容与对话框等宽。
renderMountedTyres(data.mountedTyres || []); // renderVehicleInfo(data.car || {});
// renderMountedTyres(data.mountedTyres || []);
// 维保历史已合并到生命周期聚合接口,页面不再额外调用独立列表接口,避免同一车牌重复查数。 // 维保历史已合并到生命周期聚合接口,页面不再额外调用独立列表接口,避免同一车牌重复查数。
renderTimeline(data.maintenanceList || []); renderTimeline(data.maintenanceList || []);
}); });
} }
function loadLatestMaintenance() { function loadWheelPositionView() {
$.get(prefix + "/lifecycle/" + encodeURIComponent(carNo) + "/latest-maintenance", function (result) { $.get(prefix + "/lifecycle/" + encodeURIComponent(carNo) + "/latest-maintenance", function (result) {
if (result.code != web_status.SUCCESS) { if (result.code != web_status.SUCCESS) {
$("#latestMaintenance").html('<div class="text-center text-muted-dash" style="padding: 48px 0;">维保对比加载失败</div>'); $("#latestMaintenance").html('<div class="text-center text-muted-dash" style="padding: 48px 0;">轮位视图加载失败</div>');
return; return;
} }
renderLatestMaintenance(result.data || {}); renderWheelPositionView(result.data || []);
}); });
} }
@ -565,7 +567,12 @@
$("#emptyTimeline").hide(); $("#emptyTimeline").hide();
var html = []; var html = [];
$.each(maintenanceRows, function (index, item) { $.each(maintenanceRows, function (index, item) {
html.push('<li class="life-item">'); var orderId = item.orderId == null ? item.id : item.orderId;
// 生命周期页嵌在车辆抽屉 iframe 中,只把业务主键交给父页,由父页接管主框架页签与抽屉关闭。
html.push('<li class="life-item clickable-title" data-order-id="'
+ encodeURIComponent(displayValue(orderId)) + '" data-order-no="'
+ encodeURIComponent(displayValue(item.orderNo))
+ '" onclick="openMaintenanceOrderDetail(this)" title="打开维保工单详情页签">');
html.push('<span class="life-dot maintenance"></span>'); html.push('<span class="life-dot maintenance"></span>');
html.push('<div class="life-time">' + safeText(item.maintainDate) + '</div>'); html.push('<div class="life-time">' + safeText(item.maintainDate) + '</div>');
html.push('<div class="life-title">' + safeText(eventTitle(item)) + '</div>'); html.push('<div class="life-title">' + safeText(eventTitle(item)) + '</div>');
@ -575,35 +582,13 @@
$("#timelineList").html(html.join("")); $("#timelineList").html(html.join(""));
} }
function renderLatestMaintenance(data) { function renderWheelPositionView(rows) {
var order = data.order; if (!rows || !rows.length) {
if (!order) { $("#latestMaintenance").html('<div class="text-center text-muted-dash" style="padding: 48px 0;">暂无轮位维护数据</div>');
$("#latestMaintenance").html('<div class="text-center text-muted-dash" style="padding: 48px 0;">暂无维保记录</div>');
return; return;
} }
var html = []; var html = [];
html.push('<div class="maintenance-summary">'); html.push(renderCompareTyres(rows));
html.push(metaCell("工单号", order.orderNo));
html.push(metaCell("维保类型", orderTypeFormatter(order.typeCode)));
html.push(metaCell("状态", statusFormatter(order.status)));
html.push(metaCell("维修站点", order.factoryName));
html.push(metaCell("保养日期", order.maintainDate));
html.push(metaCell("录入里程", order.inputMileage == null ? "-" : order.inputMileage + " km"));
html.push(metaCell("上次里程", order.lastMileage == null ? "-" : order.lastMileage + " km"));
if (order.description) {
html.push('<div>补充说明:' + safeText(order.description) + '</div>');
}
html.push('</div>');
html.push('<div class="maintenance-compare">');
html.push('<div class="maintenance-side"><div class="maintenance-side-title">维保前</div>');
html.push(renderCompareTyres(data.tireDetailsBefore || []));
html.push('</div>');
html.push('<div class="maintenance-side"><div class="maintenance-side-title">维保后</div>');
html.push(renderCompareTyres(data.tireDetailsAfter || []));
html.push('</div>');
html.push('</div>');
$("#latestMaintenance").html(html.join("")); $("#latestMaintenance").html(html.join(""));
} }
@ -615,10 +600,10 @@
html.push(createTyreCard(data[1], "右前轮")); html.push(createTyreCard(data[1], "右前轮"));
html.push('</div>'); html.push('</div>');
html.push('<div class="axle-row"><div class="axle-label">2</div>'); html.push('<div class="axle-row"><div class="axle-label">2</div>');
html.push(createTyreCard(data[2], "左外轮")); html.push(createTyreCard(data[2], "右内轮"));
html.push(createTyreCard(data[3], "左内轮")); html.push(createTyreCard(data[3], "右外轮"));
html.push(createTyreCard(data[4], "内轮")); html.push(createTyreCard(data[4], "内轮"));
html.push(createTyreCard(data[5], "外轮")); html.push(createTyreCard(data[5], "外轮"));
html.push('</div>'); html.push('</div>');
return html.join(""); return html.join("");
} }
@ -637,7 +622,10 @@
spec: displayValue(item.spec), spec: displayValue(item.spec),
dot: displayValue(item.dot), dot: displayValue(item.dot),
depth: item.depth == null || item.depth === "" ? "-" : item.depth + " mm", depth: item.depth == null || item.depth === "" ? "-" : item.depth + " mm",
status: displayValue(item.status) status: displayValue(item.status),
orderNo: displayValue(item.orderNo),
maintainDate: displayValue(item.maintainDate),
inputMileage: item.inputMileage == null ? "-" : item.inputMileage + " km"
}; };
}); });
return result; return result;
@ -656,6 +644,9 @@
'<div>' + safeText(tyre.spec) + '</div>', '<div>' + safeText(tyre.spec) + '</div>',
'<div>' + safeText(tyre.dot) + '</div>', '<div>' + safeText(tyre.dot) + '</div>',
'<div class="compare-depth">' + safeText(tyre.depth) + '</div>', '<div class="compare-depth">' + safeText(tyre.depth) + '</div>',
'<div>工单:' + safeText(tyre.orderNo) + '</div>',
'<div>日期:' + safeText(tyre.maintainDate) + '</div>',
'<div>里程:' + safeText(tyre.inputMileage) + '</div>',
'</div>', '</div>',
'</div>' '</div>'
].join(""); ].join("");
@ -832,9 +823,26 @@
return displayValue(value); return displayValue(value);
} }
function goMaintenanceList() { function openMaintenanceOrderDetail(target) {
// ctx 由 include.html 注入且自带尾斜杠,按项目既有写法直接拼接相对路径。 var orderId = decodeURIComponent($(target).attr("data-order-id") || "");
window.open(ctx + "tyre/order", "_blank"); var orderNo = decodeURIComponent($(target).attr("data-order-no") || "");
if (!/^\d+$/.test(orderId)) {
// 没有有效工单主键时禁止拼接详情 URL避免无意义请求或路径污染。
$.modal.alertWarning("该维保记录缺少有效工单ID无法打开详情");
return;
}
// 生命周期页嵌在车辆列表抽屉 iframe 中,必须交给父页打开主框架页签并关闭抽屉。
if (window.parent && typeof window.parent.openMaintenanceOrderDetailTabFromLifecycle === "function") {
window.parent.openMaintenanceOrderDetailTabFromLifecycle(orderId, orderNo);
return;
}
// 兜底:若未来生命周期页被独立打开,仍按 RuoYi 页签方式进入对应工单详情。
$.modal.openTab(buildMaintenanceOrderDetailTitle(orderNo), ctx + "tyre/order/edit/" + encodeURIComponent(orderId));
}
function buildMaintenanceOrderDetailTitle(orderNo) {
var text = displayValue(orderNo);
return text == "-" ? "维保工单详情" : "维保工单详情 - " + text;
} }
</script> </script>
</body> </body>

@ -277,10 +277,13 @@
<body> <body>
<div class="detail-shell" <div class="detail-shell"
th:with="resultBase=${resultMap['resultBase']}, th:with="resultBase=${resultMap['resultBase']},
recordWarehousingList=${resultMap['recordWarehousingList']}, recordWarehousingList=${resultMap['recordWarehousingList']},
recordTyreInstallList=${resultMap['recordTyreInstallList']}, recordTyreInstallList=${resultMap['recordTyreInstallList']},
bizMaintenanceOrder=${resultMap['bizMaintenanceOrder']}, recordTyreMileageList=${resultMap['recordTyreMileageList']},
bizOrderTireDetailList=${resultMap['bizMaintenanceOrder'] == null ? null : resultMap['bizMaintenanceOrder'].bizOrderTireDetailList}"> totalMileage=${resultMap['totalMileage']},
currentPatternDepth=${resultMap['currentPatternDepth']},
bizMaintenanceOrder=${resultMap['bizMaintenanceOrder']},
bizOrderTireDetailList=${resultMap['bizMaintenanceOrder'] == null ? null : resultMap['bizMaintenanceOrder'].bizOrderTireDetailList}">
<div class="summary-panel"> <div class="summary-panel">
<div class="summary-main"> <div class="summary-main">
<div> <div>
@ -316,14 +319,24 @@
<div class="info-label">轮胎花纹</div> <div class="info-label">轮胎花纹</div>
<div class="info-value" th:text="${#strings.isEmpty(resultBase.tyrePattern) ? '-' : resultBase.tyrePattern}">-</div> <div class="info-value" th:text="${#strings.isEmpty(resultBase.tyrePattern) ? '-' : resultBase.tyrePattern}">-</div>
</div> </div>
<div class="info-cell"> <!-- <div class="info-cell">-->
<div class="info-label">轮胎类别</div> <!-- <div class="info-label">轮胎类别</div>-->
<div class="info-value" th:text="${#strings.isEmpty(resultBase.tyreType) ? '-' : @dict.getLabel('tyre_type', resultBase.tyreType)}">-</div> <!-- <div class="info-value" th:text="${#strings.isEmpty(resultBase.tyreType) ? '-' : @dict.getLabel('tyre_type', resultBase.tyreType)}">-</div>-->
</div> <!-- </div>-->
<div class="info-cell"> <div class="info-cell">
<div class="info-label">初始花纹深度</div> <div class="info-label">初始花纹深度</div>
<div class="info-value" th:text="${#strings.isEmpty(resultBase.patternDepth) ? '-' : resultBase.patternDepth + ' mm'}">-</div> <div class="info-value" th:text="${#strings.isEmpty(resultBase.patternDepth) ? '-' : resultBase.patternDepth + ' mm'}">-</div>
</div> </div>
<div class="info-cell">
<div class="info-label">轮胎总行驶里程</div>
<!-- 后端按 record_tyre_mileage.mileage 累加;空值说明无有效里程段,不在前端猜测。 -->
<div class="info-value" th:text="${#strings.isEmpty(totalMileage) ? '-' : totalMileage + ' km'}">-</div>
</div>
<div class="info-cell">
<div class="info-label">当前剩余花纹</div>
<!-- 当前花纹取装卸记录最新一条非空 pattern_depth保持与装卸业务录入口径一致。 -->
<div class="info-value" th:text="${#strings.isEmpty(currentPatternDepth) ? '-' : currentPatternDepth + ' mm'}">-</div>
</div>
<div class="info-cell"> <div class="info-cell">
<div class="info-label">标准气压</div> <div class="info-label">标准气压</div>
<div class="info-value" th:text="${#strings.isEmpty(resultBase.pressure) ? '-' : resultBase.pressure}">-</div> <div class="info-value" th:text="${#strings.isEmpty(resultBase.pressure) ? '-' : resultBase.pressure}">-</div>
@ -374,6 +387,7 @@
<div class="life-summary"> <div class="life-summary">
<span class="life-count" th:text="${'出入库记录:' + (#lists.isEmpty(recordWarehousingList) ? 0 : #lists.size(recordWarehousingList))}">出入库记录0</span> <span class="life-count" th:text="${'出入库记录:' + (#lists.isEmpty(recordWarehousingList) ? 0 : #lists.size(recordWarehousingList))}">出入库记录0</span>
<span class="life-count" th:text="${'装卸车记录:' + (#lists.isEmpty(recordTyreInstallList) ? 0 : #lists.size(recordTyreInstallList))}">装卸车记录0</span> <span class="life-count" th:text="${'装卸车记录:' + (#lists.isEmpty(recordTyreInstallList) ? 0 : #lists.size(recordTyreInstallList))}">装卸车记录0</span>
<span class="life-count" th:text="${'里程使用记录:' + (#lists.isEmpty(recordTyreMileageList) ? 0 : #lists.size(recordTyreMileageList))}">里程使用记录0</span>
<span class="life-count" th:text="${'维保工单:' + (bizMaintenanceOrder == null ? 0 : 1)}">维保工单0</span> <span class="life-count" th:text="${'维保工单:' + (bizMaintenanceOrder == null ? 0 : 1)}">维保工单0</span>
<span class="life-count" th:if="${bizMaintenanceOrder != null}" th:text="${'维保轮胎明细:' + (#lists.isEmpty(bizOrderTireDetailList) ? 0 : #lists.size(bizOrderTireDetailList))}">维保轮胎明细0</span> <span class="life-count" th:if="${bizMaintenanceOrder != null}" th:text="${'维保轮胎明细:' + (#lists.isEmpty(bizOrderTireDetailList) ? 0 : #lists.size(bizOrderTireDetailList))}">维保轮胎明细0</span>
</div> </div>
@ -392,6 +406,7 @@
<div th:text="${#strings.isEmpty(item.tyreRfid) ? 'RFID-' : 'RFID' + item.tyreRfid}">RFID-</div> <div th:text="${#strings.isEmpty(item.tyreRfid) ? 'RFID-' : 'RFID' + item.tyreRfid}">RFID-</div>
<div th:text="${#strings.isEmpty(item.tyreBrand) ? '品牌:-' : '品牌:' + item.tyreBrand}">品牌:-</div> <div th:text="${#strings.isEmpty(item.tyreBrand) ? '品牌:-' : '品牌:' + item.tyreBrand}">品牌:-</div>
<div th:text="${#strings.isEmpty(item.tyreModel) ? '规格:-' : '规格:' + item.tyreModel}">规格:-</div> <div th:text="${#strings.isEmpty(item.tyreModel) ? '规格:-' : '规格:' + item.tyreModel}">规格:-</div>
<div th:text="${#strings.isEmpty(item.deptName) ? '入库场站:-' : '入库场站:' + item.deptName}">入库场站:-</div>
<div class="life-remark" th:if="${!#strings.isEmpty(item.remark)}" th:text="${'备注:' + item.remark}">备注:-</div> <div class="life-remark" th:if="${!#strings.isEmpty(item.remark)}" th:text="${'备注:' + item.remark}">备注:-</div>
</div> </div>
</li> </li>
@ -413,15 +428,42 @@
<div th:text="${#strings.isEmpty(item.createBy) ? '操作人:-' : '操作人:' + item.createBy}">操作人:-</div> <div th:text="${#strings.isEmpty(item.createBy) ? '操作人:-' : '操作人:' + item.createBy}">操作人:-</div>
<div th:text="${#strings.isEmpty(item.carNo) ? '车辆:-' : '车辆:' + item.carNo}">车辆:-</div> <div th:text="${#strings.isEmpty(item.carNo) ? '车辆:-' : '车辆:' + item.carNo}">车辆:-</div>
<div th:text="${#strings.isEmpty(item.wheelPostion) ? '轮位:-' : '轮位:' + item.wheelPostion}">轮位:-</div> <div th:text="${#strings.isEmpty(item.wheelPostion) ? '轮位:-' : '轮位:' + item.wheelPostion}">轮位:-</div>
<div th:text="${item.mileage == null ? '里程:-' : '里程:' + item.mileage + ' km'}">里程:-</div> <div th:text="${item.mileage == null ? '车辆里程:-' : '车辆里程:' + item.mileage + ' km'}">车辆里程:-</div>
<div th:text="${#strings.isEmpty(item.patternDepth) ? '花纹深度:-' : '花纹深度:' + item.patternDepth + ' mm'}">花纹深度:-</div> <div th:text="${#strings.isEmpty(item.patternDepth) ? '花纹深度:-' : '花纹深度:' + item.patternDepth + ' mm'}">花纹深度:-</div>
<div th:text="${#strings.isEmpty(item.carTeam) ? '车队:-' : '车队:' + item.carTeam}">车队:-</div> <!-- 三项归属已由装卸记录查询返回,缺哪项省略哪项,避免历史脏数据展示整行占位。 -->
<div th:if="${!#strings.isEmpty(item.team) or !#strings.isEmpty(item.company) or !#strings.isEmpty(item.carTeam)}">
<span th:if="${!#strings.isEmpty(item.team)}" th:text="${'修理厂:' + item.team}">修理厂:-</span>
<span th:if="${!#strings.isEmpty(item.company)}" th:text="${' / 分公司:' + item.company}"> / 分公司:-</span>
<span th:if="${!#strings.isEmpty(item.carTeam)}" th:text="${' / 车队:' + item.carTeam}"> / 车队:-</span>
</div>
<div class="life-remark" th:if="${!#strings.isEmpty(item.remark)}" th:text="${'备注:' + item.remark}">备注:-</div> <div class="life-remark" th:if="${!#strings.isEmpty(item.remark)}" th:text="${'备注:' + item.remark}">备注:-</div>
</div> </div>
</li> </li>
</ul> </ul>
</div> </div>
<div class="life-section" th:if="${!#lists.isEmpty(recordTyreMileageList)}">
<div class="life-section-title">里程使用记录</div>
<ul class="life-list">
<li class="life-item" th:each="seg : ${recordTyreMileageList}">
<span class="life-dot"></span>
<div class="life-title-row">
<div class="life-title" th:text="${seg.mileage == null ? '单程行驶里程:-' : '单程行驶里程:' + seg.mileage + ' km'}">单程行驶里程:-</div>
<div class="life-time" th:text="${seg.startTime == null ? '-' : #dates.format(seg.startTime, 'yyyy-MM-dd HH:mm:ss')}">-</div>
</div>
<div class="life-meta">
<div th:text="${seg.endTime == null ? '卸下时间:-' : '卸下时间:' + #dates.format(seg.endTime, 'yyyy-MM-dd HH:mm:ss')}">卸下时间:-</div>
<div th:text="${seg.mileage == null ? '该程行驶里程:-' : '该程行驶里程:' + seg.mileage + ' km'}">该程行驶里程:-</div>
<div th:text="${#strings.isEmpty(seg.patternDepth) ? '本程剩余花纹:-' : '本程剩余花纹:' + seg.patternDepth + ' mm'}">本程剩余花纹:-</div>
<div th:text="${#strings.isEmpty(seg.wearDepth) ? '该程花纹磨损:-' : '该程花纹磨损:' + seg.wearDepth + ' mm'}">该程花纹磨损:-</div>
<div th:text="${#strings.isEmpty(seg.createBy) ? '记录人:-' : '记录人:' + seg.createBy}">记录人:-</div>
<div th:text="${seg.createTime == null ? '记录时间:-' : '记录时间:' + #dates.format(seg.createTime, 'yyyy-MM-dd HH:mm:ss')}">记录时间:-</div>
<div class="life-remark" th:if="${!#strings.isEmpty(seg.remark)}" th:text="${'备注:' + seg.remark}">备注:-</div>
</div>
</li>
</ul>
</div>
<div class="life-section" th:if="${bizMaintenanceOrder != null}"> <div class="life-section" th:if="${bizMaintenanceOrder != null}">
<div class="life-section-title">维保工单</div> <div class="life-section-title">维保工单</div>
<ul class="life-list"> <ul class="life-list">
@ -462,6 +504,7 @@
<div th:text="${#strings.isEmpty(bizMaintenanceOrder.updateBy) ? '完成操作人:-' : '完成操作人:' + bizMaintenanceOrder.updateBy}">完成操作人:-</div> <div th:text="${#strings.isEmpty(bizMaintenanceOrder.updateBy) ? '完成操作人:-' : '完成操作人:' + bizMaintenanceOrder.updateBy}">完成操作人:-</div>
<div class="life-remark" th:if="${!#strings.isEmpty(bizMaintenanceOrder.description)}" th:text="${'补充说明:' + bizMaintenanceOrder.description}">补充说明:-</div> <div class="life-remark" th:if="${!#strings.isEmpty(bizMaintenanceOrder.description)}" th:text="${'补充说明:' + bizMaintenanceOrder.description}">补充说明:-</div>
<div class="life-remark" th:if="${!#strings.isEmpty(bizMaintenanceOrder.remark)}" th:text="${'备注:' + bizMaintenanceOrder.remark}">备注:-</div> <div class="life-remark" th:if="${!#strings.isEmpty(bizMaintenanceOrder.remark)}" th:text="${'备注:' + bizMaintenanceOrder.remark}">备注:-</div>
<div class="life-remark">如该工单涉及多条轮胎明细,请以维保工单详情页为完整明细来源。</div>
<div class="tire-detail-list" th:if="${!#lists.isEmpty(bizOrderTireDetailList)}"> <div class="tire-detail-list" th:if="${!#lists.isEmpty(bizOrderTireDetailList)}">
<div class="tire-detail-title">轮胎执行明细</div> <div class="tire-detail-title">轮胎执行明细</div>
@ -481,7 +524,7 @@
</ul> </ul>
</div> </div>
<div class="empty-life" th:if="${#lists.isEmpty(recordWarehousingList) and #lists.isEmpty(recordTyreInstallList) and bizMaintenanceOrder == null}"> <div class="empty-life" th:if="${#lists.isEmpty(recordWarehousingList) and #lists.isEmpty(recordTyreInstallList) and #lists.isEmpty(recordTyreMileageList) and bizMaintenanceOrder == null}">
暂无生命周期记录 暂无生命周期记录
</div> </div>
</div> </div>

@ -1,57 +0,0 @@
package com.ruoyi.system.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* DTO
* <p>
* /
* Map
* </p>
*/
public class CarLatestMaintenanceDTO implements Serializable
{
private static final long serialVersionUID = 1L;
/** 最近一次维保工单概要;车辆无维保记录时为空。 */
private CarMaintenanceLifecycleDTO order;
/** 维保前轮胎布局,字段同 BizOrderTireDetailMapper.selectBaseTrieInstall / selectBizOrderTireDetail。 */
private List<Map> tireDetailsBefore = new ArrayList<>();
/** 维保后轮胎布局,字段同 BizOrderTireDetailMapper.selectBizOrderTireDetail。 */
private List<Map> tireDetailsAfter = new ArrayList<>();
public CarMaintenanceLifecycleDTO getOrder()
{
return order;
}
public void setOrder(CarMaintenanceLifecycleDTO order)
{
this.order = order;
}
public List<Map> getTireDetailsBefore()
{
return tireDetailsBefore;
}
public void setTireDetailsBefore(List<Map> tireDetailsBefore)
{
this.tireDetailsBefore = tireDetailsBefore;
}
public List<Map> getTireDetailsAfter()
{
return tireDetailsAfter;
}
public void setTireDetailsAfter(List<Map> tireDetailsAfter)
{
this.tireDetailsAfter = tireDetailsAfter;
}
}

@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.ruoyi.system.domain.BizOrderTireDetail; import com.ruoyi.system.domain.BizOrderTireDetail;
import org.apache.ibatis.annotations.Param;
/** /**
* Mapper * Mapper
@ -64,4 +65,12 @@ public interface BizOrderTireDetailMapper
List<Map> selectBizOrderTireDetail(BizOrderTireDetail bizOrderTireDetail); List<Map> selectBizOrderTireDetail(BizOrderTireDetail bizOrderTireDetail);
List<Map> selectBaseTrieInstall(String plateNumber); List<Map> selectBaseTrieInstall(String plateNumber);
/**
* Service
*
* @param plateNumber
* @return
*/
List<Map> selectCarMaintenanceTireDetails(@Param("plateNumber") String plateNumber);
} }

@ -1,8 +1,8 @@
package com.ruoyi.system.service; package com.ruoyi.system.service;
import java.util.List; import java.util.List;
import java.util.Map;
import com.ruoyi.system.domain.CarCheckLifecycleDTO; import com.ruoyi.system.domain.CarCheckLifecycleDTO;
import com.ruoyi.system.domain.CarLatestMaintenanceDTO;
import com.ruoyi.system.domain.CarLifecycleDTO; import com.ruoyi.system.domain.CarLifecycleDTO;
import com.ruoyi.system.domain.CarLifecycleQuery; import com.ruoyi.system.domain.CarLifecycleQuery;
import com.ruoyi.system.domain.CarMileageLifecycleDTO; import com.ruoyi.system.domain.CarMileageLifecycleDTO;
@ -46,10 +46,10 @@ public interface IBaseCarLifecycleService
public List<CarMileageLifecycleDTO> selectMileageList(CarLifecycleQuery query); public List<CarMileageLifecycleDTO> selectMileageList(CarLifecycleQuery query);
/** /**
* * 6
* *
* @param query * @param query
* @return DTO * @return
*/ */
public CarLatestMaintenanceDTO selectLatestMaintenance(CarLifecycleQuery query); public List<Map> selectLatestWheelPositionMaintenance(CarLifecycleQuery query);
} }

@ -86,4 +86,12 @@ public interface IBaseTyreService
Map pdaQueryTyreTimeLine(BaseTyre baseTyre); Map pdaQueryTyreTimeLine(BaseTyre baseTyre);
/**
* Web
* <p>
* PDA 线 Web / PDA
* </p>
*/
Map selectTyreDetailForWeb(BaseTyre baseTyre);
} }

@ -1,15 +1,16 @@
package com.ruoyi.system.service.impl; package com.ruoyi.system.service.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.BizMaintenanceOrder;
import com.ruoyi.system.domain.BizOrderTireDetail;
import com.ruoyi.system.domain.CarCheckLifecycleDTO; import com.ruoyi.system.domain.CarCheckLifecycleDTO;
import com.ruoyi.system.domain.CarLatestMaintenanceDTO;
import com.ruoyi.system.domain.CarLifecycleDTO; import com.ruoyi.system.domain.CarLifecycleDTO;
import com.ruoyi.system.domain.CarLifecycleQuery; import com.ruoyi.system.domain.CarLifecycleQuery;
import com.ruoyi.system.domain.CarLifecycleSummaryDTO; import com.ruoyi.system.domain.CarLifecycleSummaryDTO;
@ -17,7 +18,6 @@ import com.ruoyi.system.domain.CarMaintenanceLifecycleDTO;
import com.ruoyi.system.domain.CarMileageLifecycleDTO; import com.ruoyi.system.domain.CarMileageLifecycleDTO;
import com.ruoyi.system.domain.CarTyreInstallLifecycleDTO; import com.ruoyi.system.domain.CarTyreInstallLifecycleDTO;
import com.ruoyi.system.mapper.BaseCarLifecycleMapper; import com.ruoyi.system.mapper.BaseCarLifecycleMapper;
import com.ruoyi.system.mapper.BizMaintenanceOrderMapper;
import com.ruoyi.system.mapper.BizOrderTireDetailMapper; import com.ruoyi.system.mapper.BizOrderTireDetailMapper;
import com.ruoyi.system.service.IBaseCarLifecycleService; import com.ruoyi.system.service.IBaseCarLifecycleService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -34,11 +34,13 @@ import org.springframework.stereotype.Service;
@Service @Service
public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
{ {
@Autowired /**
private BaseCarLifecycleMapper baseCarLifecycleMapper; * WheelPosition 6
*/
private static final List<String> WHEEL_POSITION_ORDER = Arrays.asList("左前轮", "右前轮", "右内轮", "右外轮", "左内轮", "左外轮");
@Autowired @Autowired
private BizMaintenanceOrderMapper bizMaintenanceOrderMapper; private BaseCarLifecycleMapper baseCarLifecycleMapper;
@Autowired @Autowired
private BizOrderTireDetailMapper bizOrderTireDetailMapper; private BizOrderTireDetailMapper bizOrderTireDetailMapper;
@ -63,16 +65,27 @@ public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
@DataScope(deptAlias = "d", userAlias = "u") @DataScope(deptAlias = "d", userAlias = "u")
public CarLifecycleDTO selectLifecycle(CarLifecycleQuery query) public CarLifecycleDTO selectLifecycle(CarLifecycleQuery query)
{ {
// 1. 校验权限并回填 carId确保后续查询已授权且使用精确主键
CarLifecycleSummaryDTO car = buildAuthorizedCar(query); CarLifecycleSummaryDTO car = buildAuthorizedCar(query);
// 2. 初始化生命周期 DTO 容器
CarLifecycleDTO dto = new CarLifecycleDTO(); CarLifecycleDTO dto = new CarLifecycleDTO();
// 3. 写入车辆主档概要信息
dto.setCar(car); dto.setCar(car);
// 4. 查询当前装车的轮胎列表
dto.setMountedTyres(baseCarLifecycleMapper.selectMountedTyres(query)); dto.setMountedTyres(baseCarLifecycleMapper.selectMountedTyres(query));
// 5. 查询最近发生的事件时间线
dto.setRecentEvents(baseCarLifecycleMapper.selectRecentEvents(query)); dto.setRecentEvents(baseCarLifecycleMapper.selectRecentEvents(query));
// 6. 查询维保工单历史列表
dto.setMaintenanceList(baseCarLifecycleMapper.selectMaintenanceList(query)); dto.setMaintenanceList(baseCarLifecycleMapper.selectMaintenanceList(query));
// 7. 统计装卸记录总条数
dto.setInstallCount(baseCarLifecycleMapper.countInstallRecords(query)); dto.setInstallCount(baseCarLifecycleMapper.countInstallRecords(query));
// 8. 统计维保工单总条数
dto.setMaintenanceCount(baseCarLifecycleMapper.countMaintenanceOrders(query)); dto.setMaintenanceCount(baseCarLifecycleMapper.countMaintenanceOrders(query));
// 9. 统计质检记录总条数
dto.setCheckCount(baseCarLifecycleMapper.countCheckRecords(query)); dto.setCheckCount(baseCarLifecycleMapper.countCheckRecords(query));
// 10. 统计里程记录总条数
dto.setMileageCount(baseCarLifecycleMapper.countMileageRecords(query)); dto.setMileageCount(baseCarLifecycleMapper.countMileageRecords(query));
// 11. 返回聚合后的生命周期数据
return dto; return dto;
} }
@ -96,50 +109,51 @@ public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
} }
/** /**
* * 6
* <p> * <p>
* buildAuthorizedCar SQL * SQL
* Java SQL 使 TOP / LIMIT / ROW_NUMBER
* </p> * </p>
*/ */
@Override @Override
@DataScope(deptAlias = "d", userAlias = "u") @DataScope(deptAlias = "d", userAlias = "u")
public CarLatestMaintenanceDTO selectLatestMaintenance(CarLifecycleQuery query) public List<Map> selectLatestWheelPositionMaintenance(CarLifecycleQuery query)
{ {
buildAuthorizedCar(query); buildAuthorizedCar(query);
CarLatestMaintenanceDTO dto = new CarLatestMaintenanceDTO(); List<Map> detailRows = bizOrderTireDetailMapper.selectCarMaintenanceTireDetails(query.getCarNo());
List<CarMaintenanceLifecycleDTO> maintenanceList = baseCarLifecycleMapper.selectMaintenanceList(query); Map<String, Map> latestByPosition = new LinkedHashMap<>();
if (maintenanceList == null || maintenanceList.isEmpty()) Set<String> expectedPositions = new HashSet<>(WHEEL_POSITION_ORDER);
if (detailRows != null)
{ {
return dto; for (Map row : detailRows)
}
CarMaintenanceLifecycleDTO latest = maintenanceList.get(0);
dto.setOrder(latest);
if (latest.getOrderId() != null)
{
BizOrderTireDetail afterQuery = new BizOrderTireDetail();
afterQuery.setOrderId(latest.getOrderId());
dto.setTireDetailsAfter(emptyIfNull(bizOrderTireDetailMapper.selectBizOrderTireDetail(afterQuery)));
}
if (latest.getOrderId() != null && !StringUtils.isBlank(latest.getPlateNumber()))
{
BizMaintenanceOrder probe = new BizMaintenanceOrder();
probe.setOrderId(latest.getOrderId());
// 上一条工单SQL同时依赖 orderId 与 plateNumber缺车牌会导致无法命中上一条记录。
probe.setPlateNumber(latest.getPlateNumber());
BizMaintenanceOrder previousOrder = bizMaintenanceOrderMapper.selectBizMaintenanceOrderByOrderIdBefore(probe);
if (previousOrder != null && previousOrder.getOrderId() != null)
{ {
BizOrderTireDetail beforeQuery = new BizOrderTireDetail(); String position = row == null || row.get("position") == null ? null : String.valueOf(row.get("position"));
beforeQuery.setOrderId(previousOrder.getOrderId()); if (!expectedPositions.contains(position) || latestByPosition.containsKey(position))
dto.setTireDetailsBefore(emptyIfNull(bizOrderTireDetailMapper.selectBizOrderTireDetail(beforeQuery))); {
return dto; continue;
}
latestByPosition.put(position, row);
} }
} }
dto.setTireDetailsBefore(emptyIfNull(bizOrderTireDetailMapper.selectBaseTrieInstall(latest.getPlateNumber()))); List<Map> result = new ArrayList<>();
return dto; for (String position : WHEEL_POSITION_ORDER)
{
Map row = latestByPosition.get(position);
if (row == null)
{
row = new LinkedHashMap();
row.put("position", position);
row.put("status", "empty");
}
else if (row.get("status") == null)
{
// 维保后视图只关心该轮位最后一次维护快照;状态为空按普通维护数据展示。
row.put("status", "normal");
}
result.add(row);
}
return result;
} }
/** /**
@ -223,21 +237,21 @@ public class BaseCarLifecycleServiceImpl implements IBaseCarLifecycleService
*/ */
private void validateQuery(CarLifecycleQuery query) private void validateQuery(CarLifecycleQuery query)
{ {
// 1. 校验查询对象和车牌号不能为空
if (query == null || StringUtils.isBlank(query.getCarNo())) if (query == null || StringUtils.isBlank(query.getCarNo()))
{ {
throw new ServiceException("车牌号不能为空"); throw new ServiceException("车牌号不能为空");
} }
// 2. 去除车牌号前后空格,规范化输入
String carNo = query.getCarNo().trim(); String carNo = query.getCarNo().trim();
// 3. 校验车牌号长度不超过数据库字段定义varchar(50)
if (carNo.length() > 50) if (carNo.length() > 50)
{ {
// base_car.car_no 当前按 varchar(50) 设计,超长路径参数直接拒绝更利于定位异常调用。 // base_car.car_no 当前按 varchar(50) 设计,超长路径参数直接拒绝更利于定位异常调用。
throw new ServiceException("车牌号长度超过限制"); throw new ServiceException("车牌号长度超过限制");
} }
// 4. 将规范化后的车牌号写回查询对象供后续SQL使用
query.setCarNo(carNo); query.setCarNo(carNo);
} }
private List<Map> emptyIfNull(List<Map> list)
{
return list == null ? new ArrayList<>() : list;
}
} }

@ -18,18 +18,15 @@ import com.ruoyi.system.mapper.BaseTyreMapper;
import com.ruoyi.system.mapper.RecordTyreInstallMapper; import com.ruoyi.system.mapper.RecordTyreInstallMapper;
import com.ruoyi.system.mapper.RecordWarehousingMapper; import com.ruoyi.system.mapper.RecordWarehousingMapper;
import com.ruoyi.system.service.IBaseTyreService; import com.ruoyi.system.service.IBaseTyreService;
import com.sun.jna.platform.mac.SystemB; import com.ruoyi.system.util.TyreLifecycleCalc;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashMap; import java.math.BigDecimal;
import java.util.HashSet; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.ruoyi.common.utils.ShiroUtils.getLoginName; import static com.ruoyi.common.utils.ShiroUtils.getLoginName;
@ -57,6 +54,10 @@ public class BaseTyreServiceImpl implements IBaseTyreService
@Autowired @Autowired
private BizMaintenanceOrderMapper bizMaintenanceOrderMapper; private BizMaintenanceOrderMapper bizMaintenanceOrderMapper;
private static final Logger log = LoggerFactory.getLogger(BaseTyreServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(BaseTyreServiceImpl.class);
@Autowired
private RecordTyreMileageMapper recordTyreMileageMapper;
/** /**
* *
* *
@ -304,4 +305,122 @@ public class BaseTyreServiceImpl implements IBaseTyreService
return map; return map;
} }
} }
@Override
public Map selectTyreDetailForWeb(BaseTyre baseTyre)
{
// 1. 调用 PDA 查询方法获取轮胎时间线基础数据(包含入库、安装等流转记录)
Map map = pdaQueryTyreTimeLine(baseTyre);
// 2. 若基础数据为空,直接返回,避免后续空指针异常
if (map == null || map.isEmpty())
{
return map;
}
// 3. 初始化里程相关字段默认值,确保返回结构完整
map.put("recordTyreMileageList", new ArrayList<>());
map.put("totalMileage", null);
map.put("currentPatternDepth", null);
// 4. 获取轮胎基础信息对象,并校验类型正确性
Object resultBaseObj = map.get("resultBase");
if (!(resultBaseObj instanceof BaseTyre))
{
return map;
}
// 5. 提取轮胎实体和 RFIDEPC编码
BaseTyre resultBase = (BaseTyre) resultBaseObj;
String tyreRfid = resultBase.getTyreEpc();
// 6. RFID 为空时清空流转列表并返回,避免展示错误数据
if (StringUtils.isBlank(tyreRfid))
{
// 原 PDA 方法历史上会在 EPC 为空时按空条件查流转表Web 详情必须置空,避免误把全量历史展示给用户。
map.put("recordWarehousingList", new ArrayList<>());
map.put("recordTyreInstallList", new ArrayList<>());
return map;
}
// 7. 转换安装记录列表并计算当前花纹深度
List<RecordTyreInstall> installList = castInstallList(map.get("recordTyreInstallList"));
map.put("currentPatternDepth", TyreLifecycleCalc.currentPatternDepth(installList));
// 8. 构造里程查询对象并执行查询
RecordTyreMileage query = new RecordTyreMileage();
query.setTyreRfid(tyreRfid);
List<RecordTyreMileage> mileageList = recordTyreMileageMapper.selectRecordTyreMileageList(query);
// 9. 若无里程记录,直接返回当前已组装的数据
if (mileageList == null || mileageList.isEmpty())
{
return map;
}
// 10. 解析初始花纹深度,并计算各段磨损量
BigDecimal initialDepth = TyreLifecycleCalc.parseDepth(resultBase.getPatternDepth());
Map<Long, String> wearMap = TyreLifecycleCalc.computeSegmentWears(initialDepth, mileageList);
// 11. 初始化总里程累加器和标志位
BigDecimal totalMileage = BigDecimal.ZERO;
boolean hasMileage = false;
// 12. 按开始时间降序、ID 降序对里程记录排序,确保时间线正确
mileageList.sort(Comparator
.comparing(RecordTyreMileage::getStartTime, Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(RecordTyreMileage::getId, Comparator.nullsLast(Comparator.reverseOrder())));
// 13. 遍历里程记录,构造前端展示用的 VO 列表
List<Map<String, Object>> mileageVoList = new ArrayList<>(mileageList.size());
for (RecordTyreMileage item : mileageList)
{
Map<String, Object> vo = new HashMap<>();
vo.put("id", item.getId()); // 记录主键
vo.put("tyreRfid", item.getTyreRfid()); // 轮胎 RFID
vo.put("startTime", item.getStartTime()); // 里程开始时间
vo.put("endTime", item.getEndTime()); // 里程结束时间
vo.put("mileage", item.getMileage()); // 行驶里程数
vo.put("patternDepth", item.getPatternDepth()); // 当时花纹深度
vo.put("createBy", item.getCreateBy()); // 创建人
vo.put("createTime", item.getCreateTime()); // 创建时间
vo.put("updateBy", item.getUpdateBy()); // 更新人
vo.put("updateTime", item.getUpdateTime()); // 更新时间
vo.put("remark", item.getRemark()); // 备注信息
vo.put("tyreBrand", item.getTyreBrand()); // 轮胎品牌
vo.put("tyreNo", item.getTyreNo()); // 轮胎编号
vo.put("wearDepth", item.getId() == null ? null : wearMap.get(item.getId())); // 磨损深度(从计算结果取)
// 14. 累加有效里程值mileage 为 Long 类型,表示该段 km 差值)
if (item.getMileage() != null)
{
// mileage 实体类型是 Long表示该程 km 差值,直接累加可避免 varchar 花纹解析逻辑误入里程路径。
totalMileage = totalMileage.add(BigDecimal.valueOf(item.getMileage()));
hasMileage = true;
}
mileageVoList.add(vo);
}
// 15. 将组装好的里程列表和总里程写入返回 Map
map.put("recordTyreMileageList", mileageVoList);
map.put("totalMileage", hasMileage ? totalMileage.stripTrailingZeros().toPlainString() : null);
// 16. 返回完整的轮胎详情数据
return map;
}
private List<RecordTyreInstall> castInstallList(Object value)
{
// 1. 初始化空结果列表,用于存放转换后的安装记录
List<RecordTyreInstall> result = new ArrayList<>();
// 2. 校验入参是否为 List 类型,若不是则直接返回空列表
if (!(value instanceof List<?>))
{
return result;
}
// 3. 遍历列表,筛选出 RecordTyreInstall 类型的元素并加入结果
for (Object item : (List<?>) value)
{
if (item instanceof RecordTyreInstall)
{
result.add((RecordTyreInstall) item);
}
}
// 4. 返回类型安全的安装记录列表
return result;
}
} }

@ -24,6 +24,7 @@ public final class TyreLifecycleCalc
private TyreLifecycleCalc() private TyreLifecycleCalc()
{ {
// 私有构造器,禁止外部实例化,工具类保持静态方法调用
} }
/** /**
@ -34,22 +35,28 @@ public final class TyreLifecycleCalc
*/ */
public static String currentPatternDepth(List<RecordTyreInstall> installRecords) public static String currentPatternDepth(List<RecordTyreInstall> installRecords)
{ {
// 1. 空列表校验,无记录时返回 null
if (installRecords == null || installRecords.isEmpty()) if (installRecords == null || installRecords.isEmpty())
{ {
return null; return null;
} }
// 2. 初始化最新记录变量
RecordTyreInstall latest = null; RecordTyreInstall latest = null;
// 3. 遍历所有装卸记录,寻找最新一条有效花纹深度
for (RecordTyreInstall item : installRecords) for (RecordTyreInstall item : installRecords)
{ {
// 跳过空对象或无花纹深度的记录
if (item == null || isBlank(item.getPatternDepth())) if (item == null || isBlank(item.getPatternDepth()))
{ {
continue; continue;
} }
// 4. 比较时间先后,保留更晚的记录
if (latest == null || after(item, latest)) if (latest == null || after(item, latest))
{ {
latest = item; latest = item;
} }
} }
// 5. 返回最新记录的花纹深度,去除前后空格
return latest == null ? null : latest.getPatternDepth().trim(); return latest == null ? null : latest.getPatternDepth().trim();
} }
@ -63,12 +70,15 @@ public final class TyreLifecycleCalc
public static Map<Long, String> computeSegmentWears(BigDecimal initialDepth, public static Map<Long, String> computeSegmentWears(BigDecimal initialDepth,
List<RecordTyreMileage> mileageList) List<RecordTyreMileage> mileageList)
{ {
// 1. 初始化磨损结果 Map用于存储每条记录的磨损值
Map<Long, String> wearMap = new HashMap<>(); Map<Long, String> wearMap = new HashMap<>();
// 2. 空列表校验,直接返回空 Map
if (mileageList == null || mileageList.isEmpty()) if (mileageList == null || mileageList.isEmpty())
{ {
return wearMap; return wearMap;
} }
// 3. 过滤掉空对象,构建待排序列表
List<RecordTyreMileage> sortedList = new ArrayList<>(); List<RecordTyreMileage> sortedList = new ArrayList<>();
for (RecordTyreMileage item : mileageList) for (RecordTyreMileage item : mileageList)
{ {
@ -77,29 +87,37 @@ public final class TyreLifecycleCalc
sortedList.add(item); sortedList.add(item);
} }
} }
// 4. 按开始时间升序、ID 升序排序,确保时间线正确
sortedList.sort(Comparator sortedList.sort(Comparator
.comparing(RecordTyreMileage::getStartTime, Comparator.nullsLast(Comparator.naturalOrder())) .comparing(RecordTyreMileage::getStartTime, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(RecordTyreMileage::getId, Comparator.nullsLast(Comparator.naturalOrder()))); .thenComparing(RecordTyreMileage::getId, Comparator.nullsLast(Comparator.naturalOrder())));
// 5. 初始化前一段花纹深度为初始深度
BigDecimal previousDepth = initialDepth; BigDecimal previousDepth = initialDepth;
// 6. 遍历排序后的里程记录,逐段计算磨损
for (RecordTyreMileage item : sortedList) for (RecordTyreMileage item : sortedList)
{ {
// 解析当前记录的花纹深度
BigDecimal currentDepth = parseDepth(item.getPatternDepth()); BigDecimal currentDepth = parseDepth(item.getPatternDepth());
String wearDepth = null; String wearDepth = null;
// 7. 若前后深度均有效,计算差值作为本段磨损
if (previousDepth != null && currentDepth != null) if (previousDepth != null && currentDepth != null)
{ {
wearDepth = formatDecimal(previousDepth.subtract(currentDepth)); wearDepth = formatDecimal(previousDepth.subtract(currentDepth));
} }
// 8. 将磨损值存入 Map以记录 ID 为键
if (item.getId() != null) if (item.getId() != null)
{ {
wearMap.put(item.getId(), wearDepth); wearMap.put(item.getId(), wearDepth);
} }
// 9. 更新前一段深度为当前深度(脏数据不更新,避免污染后续计算)
if (currentDepth != null) if (currentDepth != null)
{ {
// 当前段脏数据不更新 prev避免一条坏花纹深度污染后续所有段。 // 当前段脏数据不更新 prev避免一条坏花纹深度污染后续所有段。
previousDepth = currentDepth; previousDepth = currentDepth;
} }
} }
// 10. 返回各段磨损值映射
return wearMap; return wearMap;
} }
@ -112,17 +130,21 @@ public final class TyreLifecycleCalc
*/ */
public static BigDecimal parseDepth(String value) public static BigDecimal parseDepth(String value)
{ {
// 1. 空值校验,空白字符串直接返回 null
if (isBlank(value)) if (isBlank(value))
{ {
return null; return null;
} }
// 2. 去除前后空格,规范化输入
String trimmed = value.trim(); String trimmed = value.trim();
try try
{ {
// 3. 尝试将字符串解析为 BigDecimal
return new BigDecimal(trimmed); return new BigDecimal(trimmed);
} }
catch (NumberFormatException ex) catch (NumberFormatException ex)
{ {
// 4. 解析失败时记录警告日志,返回 null 避免异常传播
log.warn("轮胎花纹深度无法解析,已按空值处理:{}", trimmed); log.warn("轮胎花纹深度无法解析,已按空值处理:{}", trimmed);
return null; return null;
} }
@ -136,56 +158,68 @@ public final class TyreLifecycleCalc
*/ */
public static BigDecimal sumMileage(List<RecordTyreMileage> mileageList) public static BigDecimal sumMileage(List<RecordTyreMileage> mileageList)
{ {
// 1. 空列表校验,直接返回 null
if (mileageList == null || mileageList.isEmpty()) if (mileageList == null || mileageList.isEmpty())
{ {
return null; return null;
} }
// 2. 初始化累加器和有效数据标志
BigDecimal total = BigDecimal.ZERO; BigDecimal total = BigDecimal.ZERO;
boolean hasMileage = false; boolean hasMileage = false;
// 3. 遍历里程记录,累加有效里程值
for (RecordTyreMileage item : mileageList) for (RecordTyreMileage item : mileageList)
{ {
// 跳过空对象和空里程值,仅累加有效数据
if (item != null && item.getMileage() != null) if (item != null && item.getMileage() != null)
{ {
total = total.add(BigDecimal.valueOf(item.getMileage())); total = total.add(BigDecimal.valueOf(item.getMileage()));
hasMileage = true; hasMileage = true;
} }
} }
// 4. 返回累加结果,无有效数据时返回 null
return hasMileage ? total : null; return hasMileage ? total : null;
} }
private static boolean after(RecordTyreInstall left, RecordTyreInstall right) private static boolean after(RecordTyreInstall left, RecordTyreInstall right)
{ {
// 1. 优先比较创建时间,两者均不为空时按时间先后判断
if (left.getCreateTime() != null && right.getCreateTime() != null) if (left.getCreateTime() != null && right.getCreateTime() != null)
{ {
int compare = left.getCreateTime().compareTo(right.getCreateTime()); int compare = left.getCreateTime().compareTo(right.getCreateTime());
if (compare != 0) if (compare != 0)
{ {
return compare > 0; return compare > 0; // 大于 0 表示 left 时间更晚
} }
} }
// 2. 仅 left 有创建时间,说明 left 更晚
else if (left.getCreateTime() != null) else if (left.getCreateTime() != null)
{ {
return true; return true;
} }
// 3. 仅 right 有创建时间,说明 right 更晚
else if (right.getCreateTime() != null) else if (right.getCreateTime() != null)
{ {
return false; return false;
} }
// 4. 创建时间相同或无创建时间时,比较 ID 作为兜底ID 大者视为更晚
if (left.getId() != null && right.getId() != null) if (left.getId() != null && right.getId() != null)
{ {
return left.getId().compareTo(right.getId()) > 0; return left.getId().compareTo(right.getId()) > 0;
} }
// 5. 仅 left 有 ID 时left 视为更晚
return left.getId() != null && right.getId() == null; return left.getId() != null && right.getId() == null;
} }
private static String formatDecimal(BigDecimal value) private static String formatDecimal(BigDecimal value)
{ {
// 空值直接返回 null非空时去除末尾零并转为纯文本字符串避免科学计数法
return value == null ? null : value.stripTrailingZeros().toPlainString(); return value == null ? null : value.stripTrailingZeros().toPlainString();
} }
private static boolean isBlank(String value) private static boolean isBlank(String value)
{ {
// 校验字符串是否为 null 或去除空格后为空
return value == null || value.trim().isEmpty(); return value == null || value.trim().isEmpty();
} }
} }

@ -140,4 +140,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</delete> </delete>
</mapper>
<select id="selectCarMaintenanceTireDetails" resultType="java.util.Map" parameterType="String">
SELECT
bt.tyre_brand as brand,
bt.tyre_model as spec,
botd.tire_code as dot,
botd.tread_depth as depth,
sdd.dict_label as position,
botd.tire_status as status,
bmo.order_id as orderId,
bmo.order_no as orderNo,
bmo.maintain_date as maintainDate,
bmo.input_mileage as inputMileage,
bmo.last_mileage as lastMileage,
bmo.description as description
FROM biz_maintenance_order bmo
INNER JOIN biz_order_tire_detail botd ON botd.order_id = bmo.order_id
LEFT JOIN base_tyre bt ON bt.tyre_id = botd.tire_id
LEFT JOIN sys_dict_data sdd ON sdd.dict_code = botd.position_id AND sdd.dict_type = 'WheelPosition'
WHERE bmo.plate_number = #{plateNumber}
ORDER BY bmo.maintain_date DESC, bmo.order_id DESC, botd.detail_id DESC
</select>
</mapper>

Loading…
Cancel
Save