You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

850 lines
28 KiB
HTML

<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:include="include :: header('车辆详情')" />
<style>
.detail-shell {
padding: 15px;
}
.summary-card,
.info-card,
.timeline-card {
background: #fff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
margin-bottom: 16px;
}
.summary-card {
padding: 20px 24px;
}
.summary-title {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
}
.summary-name {
font-size: 34px;
font-weight: 700;
color: #2f4050;
line-height: 1.2;
margin-bottom: 10px;
word-break: break-all;
}
.summary-meta {
color: #666;
font-size: 14px;
line-height: 1.9;
}
.summary-counts {
min-width: 180px;
display: grid;
grid-template-columns: minmax(130px, 1fr);
gap: 10px;
}
.count-item {
border: 1px solid #edf1f2;
border-radius: 8px;
padding: 10px 12px;
background: #fafbfc;
}
.count-value {
font-size: 22px;
font-weight: 700;
color: #1c84c6;
line-height: 1.2;
}
.count-label {
color: #7a8590;
font-size: 12px;
margin-top: 4px;
}
.info-card {
padding: 18px 20px 10px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #2f4050;
margin-bottom: 14px;
}
.section-title i {
margin-right: 6px;
color: #666;
}
.info-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
margin-bottom: 16px;
}
.info-table th,
.info-table td {
border: 1px solid #edf1f2;
padding: 12px 14px;
font-size: 14px;
word-break: break-all;
}
.info-table th {
width: 28%;
color: #7a8590;
font-weight: 500;
background: #fafbfc;
}
.info-table td {
color: #2f4050;
background: #fff;
}
.tyre-block {
border: 1px solid #edf1f2;
border-radius: 8px;
padding: 12px 14px;
margin-bottom: 12px;
background: #fff;
}
.tyre-title {
display: flex;
justify-content: space-between;
gap: 10px;
color: #2f4050;
font-weight: 600;
margin-bottom: 8px;
}
.tyre-meta {
color: #666;
font-size: 13px;
line-height: 1.8;
word-break: break-all;
}
.maintenance-summary {
margin-bottom: 12px;
color: #666;
font-size: 13px;
line-height: 1.8;
}
.maintenance-compare {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.maintenance-side {
border: 1px solid #edf1f2;
border-radius: 8px;
padding: 12px;
background: #fff;
}
.maintenance-side-title {
margin-bottom: 10px;
color: #2f4050;
font-weight: 600;
}
.axle-row {
position: relative;
display: flex;
justify-content: center;
gap: 6px;
margin-bottom: 12px;
min-width: 0;
}
.axle-label {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 2px 7px;
border: 1px solid #e7eaec;
border-radius: 4px;
background: #fff;
color: #666;
font-size: 12px;
z-index: 2;
}
.compare-tyre-card {
position: relative;
width: 90px;
min-height: 174px;
padding: 24px 6px 8px;
border-radius: 8px 8px 16px 16px;
background: #2f4050;
color: #fff;
overflow: hidden;
box-sizing: border-box;
}
.compare-tyre-card.empty {
border: 1px dashed #c9cfd6;
background: #eef1f4;
color: #8a929a;
}
.compare-tyre-card.installed {
border: 2px solid #f8ac59;
}
.compare-tyre-card.removed {
border: 2px solid #1ab394;
}
.compare-tyre-card:before {
content: "";
position: absolute;
inset: 0;
opacity: 0.16;
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, #fff 8px, #fff 14px);
}
.compare-tyre-card.empty:before {
display: none;
}
.compare-pos {
position: absolute;
top: 0;
left: 0;
padding: 2px 6px;
border-radius: 0 0 8px 0;
background: #8a929a;
color: #fff;
font-size: 11px;
z-index: 2;
}
.compare-tyre-card.installed .compare-pos {
background: #f8ac59;
}
.compare-tyre-card.removed .compare-pos {
background: #1ab394;
}
.compare-tyre-info {
position: relative;
z-index: 1;
font-size: 11px;
line-height: 1.45;
word-break: break-all;
}
.compare-depth {
margin-top: 3px;
color: #1ab394;
font-weight: 600;
}
.clickable-title {
cursor: pointer;
}
.clickable-title:hover {
color: #1c84c6;
}
.timeline-card {
padding: 18px 18px 12px;
min-height: 520px;
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
}
.timeline-header .title {
font-size: 18px;
font-weight: 600;
color: #2f4050;
}
.mini-tip {
font-size: 12px;
color: #999;
}
.life-list {
list-style: none;
padding: 0;
margin: 0;
position: relative;
}
.life-list:before {
content: "";
position: absolute;
left: 22px;
top: 8px;
bottom: 8px;
width: 2px;
background: #e4e7ea;
}
.life-item {
position: relative;
padding-left: 58px;
margin-bottom: 22px;
}
.life-dot {
position: absolute;
left: 12px;
top: 4px;
width: 22px;
height: 22px;
border-radius: 50%;
border: 3px solid #d7ebf8;
background: #1c84c6;
text-align: center;
color: #fff;
line-height: 16px;
font-size: 11px;
}
.life-dot.install {
background: #1c84c6;
border-color: #d7ebf8;
}
.life-dot.maintenance {
background: #f8ac59;
border-color: #fde6c8;
}
.life-dot.check {
background: #1ab394;
border-color: #d7f0eb;
}
.life-dot.mileage {
background: #23c6c8;
border-color: #d7f4f4;
}
.life-time {
color: #909399;
font-size: 12px;
margin-bottom: 4px;
}
.life-title {
color: #2f4050;
font-weight: 600;
margin-bottom: 8px;
word-break: break-all;
}
.life-body {
color: #666;
line-height: 1.7;
font-size: 13px;
}
.life-detail-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px 14px;
}
.life-detail {
min-width: 0;
word-break: break-all;
}
.life-detail .label-title {
color: #8a929a;
}
.life-description {
margin-top: 8px;
padding: 8px 10px;
border-left: 3px solid #e4e7ea;
background: #fafbfc;
color: #666;
word-break: break-all;
}
.text-muted-dash {
color: #999;
}
@media (max-width: 991px) {
.summary-title {
flex-direction: column;
}
.summary-counts {
min-width: 0;
width: 100%;
}
}
@media (max-width: 640px) {
.summary-counts,
.life-detail-grid,
.maintenance-compare {
grid-template-columns: 1fr;
}
.summary-name {
font-size: 26px;
}
}
</style>
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content detail-shell">
<input id="carNo" type="hidden" th:value="${carNo}"/>
<div class="summary-card">
<div class="summary-title">
<div>
<div class="summary-name" id="summaryName">车辆:-</div>
<div class="summary-meta" id="summaryMeta">正在加载...</div>
</div>
<div class="summary-counts" id="summaryCounts"></div>
</div>
</div>
<div class="row">
<!-- 暂时隐藏基础信息与当前装车轮胎,避免车辆详情对话框出现左右分栏导致主视图宽度不足。 -->
<!--<div class="col-lg-4 col-md-5 col-sm-12">
<div class="info-card">
<div class="section-title"><i class="fa fa-bookmark"></i>基本信息</div>
<table class="info-table" id="vehicleInfo"></table>
</div>
<div class="info-card">
<div class="section-title"><i class="fa fa-truck"></i>当前装车轮胎</div>
<div id="mountedTyres">
<div class="text-center text-muted-dash" style="padding: 40px 0;">正在加载...</div>
</div>
</div>
</div>-->
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="info-card">
<div class="section-title"><i class="fa fa-wrench"></i>轮位视图</div>
<div id="latestMaintenance">
<div class="text-center text-muted-dash" style="padding: 48px 0;">正在加载...</div>
</div>
</div>
<div class="timeline-card">
<div class="timeline-header">
<span class="title">生命周期</span>
<span class="mini-tip">按时间倒序展示维保工单,点击记录打开维保工单详情页签</span>
</div>
<ul class="life-list" id="timelineList"></ul>
<div class="text-center text-muted-dash" id="emptyTimeline" style="display: none; padding: 80px 0;">
暂无生命周期
</div>
</div>
</div>
</div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var prefix = ctx + "tyre/car";
var carNo = $("#carNo").val();
var POSITION_ORDER = ["左前轮", "右前轮", "右内轮", "右外轮", "左内轮", "左外轮"];
$(function () {
loadLifecycle();
loadWheelPositionView();
// 兜底关闭父窗口 loading避免 iframe 加载时序导致遮罩残留。
if (window.parent && window.parent !== window && window.parent.$ && window.parent.$.modal) {
window.parent.$.modal.closeLoading();
}
});
function loadLifecycle() {
$.get(prefix + "/lifecycle/" + encodeURIComponent(carNo), function (result) {
if (result.code != web_status.SUCCESS) {
$.modal.alertError(result.msg);
return;
}
var data = result.data || {};
renderSummary(data);
// 基本信息与当前装车轮胎已从页面隐藏,保留汇总和生命周期即可让主内容与对话框等宽。
// renderVehicleInfo(data.car || {});
// renderMountedTyres(data.mountedTyres || []);
// 维保历史已合并到生命周期聚合接口,页面不再额外调用独立列表接口,避免同一车牌重复查数。
renderTimeline(data.maintenanceList || []);
});
}
function loadWheelPositionView() {
$.get(prefix + "/lifecycle/" + encodeURIComponent(carNo) + "/latest-maintenance", function (result) {
if (result.code != web_status.SUCCESS) {
$("#latestMaintenance").html('<div class="text-center text-muted-dash" style="padding: 48px 0;">轮位视图加载失败</div>');
return;
}
renderWheelPositionView(result.data || []);
});
}
function renderSummary(data) {
var car = data.car || {};
var latestMileage = car.inputMileage == null ? "-" : safeText(car.inputMileage) + " km";
$("#summaryName").html("车辆:" + safeText(car.carNo));
$("#summaryMeta").html([
"所属车队:" + safeText(car.team),
"线路:" + safeText(car.line),
"车型:" + safeText(car.type),
"当前里程:" + latestMileage
].join("<br/>"));
$("#summaryCounts").html(countItem("维保工单", data.maintenanceCount));
}
function renderVehicleInfo(car) {
$("#vehicleInfo").html([
tableRow("车牌", car.carNo),
tableRow("车队", car.team),
tableRow("线路", car.line),
tableRow("车型", car.type),
tableRow("车辆最新里程", car.inputMileage == null ? "-" : safeText(car.inputMileage) + " km")
// tableRow("车辆主键", car.carId),
// tableRow("部门ID", car.deptId),
// tableRow("最近工单里程", car.inputMileage)
].join(""));
}
function renderMountedTyres(rows) {
if (!rows.length) {
$("#mountedTyres").html('<div class="text-center text-muted-dash" style="padding: 40px 0;">暂无当前装车轮胎</div>');
return;
}
var html = [];
$.each(rows, function (index, item) {
// 当前装车轮胎作为车辆当前状态展示,便于和时间轴历史装卸节点互相核对。
html.push('<div class="tyre-block">');
html.push('<div class="tyre-title"><span>' + safeText(item.tyreNo || item.selfNo || item.tyreEpc) + '</span><span>' + safeText(item.wheelPostion) + '</span></div>');
html.push('<div class="tyre-meta">');
html.push('自编号:' + safeText(item.selfNo) + '<br/>');
html.push('RFID' + safeText(item.tyreEpc) + '<br/>');
html.push('品牌/规格:' + safeText(joinText(item.tyreBrand, item.tyreModel)) + '<br/>');
html.push('花纹深度:' + safeText(item.patternDepth));
html.push('</div></div>');
});
$("#mountedTyres").html(html.join(""));
}
function renderTimeline(rows) {
var maintenanceRows = rows || [];
if (!maintenanceRows.length) {
$("#timelineList").empty();
$("#emptyTimeline").show();
return;
}
$("#emptyTimeline").hide();
var html = [];
$.each(maintenanceRows, function (index, 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('<div class="life-time">' + safeText(item.maintainDate) + '</div>');
html.push('<div class="life-title">' + safeText(eventTitle(item)) + '</div>');
html.push('<div class="life-body">' + eventDetails(item) + '</div>');
html.push('</li>');
});
$("#timelineList").html(html.join(""));
}
function renderWheelPositionView(rows) {
if (!rows || !rows.length) {
$("#latestMaintenance").html('<div class="text-center text-muted-dash" style="padding: 48px 0;">暂无轮位维护数据</div>');
return;
}
var html = [];
html.push(renderCompareTyres(rows));
$("#latestMaintenance").html(html.join(""));
}
function renderCompareTyres(rows) {
var data = transformTyreData(rows);
var html = [];
html.push('<div class="axle-row"><div class="axle-label">1</div>');
html.push(createTyreCard(data[0], "左前轮"));
html.push(createTyreCard(data[1], "右前轮"));
html.push('</div>');
html.push('<div class="axle-row"><div class="axle-label">2</div>');
html.push(createTyreCard(data[2], "右内轮"));
html.push(createTyreCard(data[3], "右外轮"));
html.push(createTyreCard(data[4], "左内轮"));
html.push(createTyreCard(data[5], "左外轮"));
html.push('</div>');
return html.join("");
}
function transformTyreData(rows) {
var result = new Array(6).fill(null);
$.each(rows || [], function (index, item) {
var position = displayValue(item.position || item.pos);
var posIndex = POSITION_ORDER.indexOf(position);
if (posIndex === -1) {
return true;
}
result[posIndex] = {
pos: position,
brand: displayValue(item.brand),
spec: displayValue(item.spec),
dot: displayValue(item.dot),
depth: item.depth == null || item.depth === "" ? "-" : item.depth + " mm",
status: displayValue(item.status),
orderNo: displayValue(item.orderNo),
maintainDate: displayValue(item.maintainDate),
inputMileage: item.inputMileage == null ? "-" : item.inputMileage + " km"
};
});
return result;
}
function createTyreCard(tyre, fallbackPos) {
if (!tyre) {
return '<div class="compare-tyre-card empty"><div class="compare-pos">' + safeText(fallbackPos) + '</div></div>';
}
var statusClass = tyre.status === "installed" ? " installed" : (tyre.status === "removed" ? " removed" : "");
return [
'<div class="compare-tyre-card' + statusClass + '">',
'<div class="compare-pos">' + safeText(tyre.pos) + '</div>',
'<div class="compare-tyre-info">',
'<div>' + safeText(tyre.brand) + '</div>',
'<div>' + safeText(tyre.spec) + '</div>',
'<div>' + safeText(tyre.dot) + '</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>'
].join("");
}
function eventDetails(item) {
var details = [];
if (item.eventType == "INSTALL") {
// 装卸节点保留轮胎、轮位、里程和花纹深度,方便追溯“何时装到哪个轮位、卸下时状态如何”。
details.push(detail("胎号", item.tyreNo));
details.push(detail("自编号", item.selfNo));
details.push(detail("RFID", item.tyreRfid));
details.push(detail("品牌/规格", joinText(item.tyreBrand, item.tyreModel)));
details.push(detail("轮位", item.wheelPostion));
details.push(detail("里程", item.mileage));
details.push(detail("花纹深度", item.patternDepth));
details.push(detail("动作", installTypeFormatter(item.status)));
} else if (item.eventType == "MAINTENANCE" || item.orderNo) {
// 工单节点展示工单号和站点,排查车辆维保历史时不用再跳转列表反查。
details.push(detail("工单号", item.orderNo));
details.push(detail("维保类型", orderTypeFormatter(item.typeCode)));
details.push(detail("状态", statusFormatter(item.status)));
details.push(detail("维修站点", item.factoryName));
details.push(detail("录入里程", item.inputMileage == null ? "-" : item.inputMileage + " km"));
details.push(detail("上次里程", item.lastMileage == null ? "-" : item.lastMileage + " km"));
details.push(detail("维保日期", item.maintainDate));
} else if (item.eventType == "CHECK") {
// 质检节点通过曾装车轮胎间接归属车辆,页面上明确展示轮胎身份,避免误以为质检表直接有车牌。
details.push(detail("胎号", item.tyreNo));
details.push(detail("自编号", item.selfNo));
details.push(detail("RFID", item.tyreRfid));
details.push(detail("品牌/规格", joinText(item.tyreBrand, item.tyreModel)));
details.push(detail("保养类型", item.maintenanceType));
details.push(detail("车辆里程", item.mileage));
details.push(detail("花纹深度", item.patternDepth));
details.push(detail("处理意见", item.result));
} else if (item.eventType == "MILEAGE") {
// 里程节点展示安装/卸下时间窗,提醒用户该记录是按轮胎 RFID 间接关联车辆。
details.push(detail("胎号", item.tyreNo));
details.push(detail("自编号", item.selfNo));
details.push(detail("RFID", item.tyreRfid));
details.push(detail("品牌/规格", joinText(item.tyreBrand, item.tyreModel)));
details.push(detail("安装时间", item.startTime));
details.push(detail("卸下时间", item.endTime));
details.push(detail("记录里程", item.mileage));
details.push(detail("花纹深度", item.patternDepth));
}
var html = '<div class="life-detail-grid">' + details.join("") + '</div>';
if (item.description) {
html += '<div class="life-description">补充说明:' + safeText(item.description) + '</div>';
}
return html;
}
function eventTitle(item) {
if (item.eventType == "INSTALL") {
return installTypeFormatter(item.status) + "轮胎:" + displayValue(item.tyreNo || item.selfNo || item.tyreRfid);
}
if (item.eventType == "MAINTENANCE") {
return orderTypeFormatter(item.typeCode) + "" + displayValue(item.orderNo);
}
if (item.orderNo) {
// 合并接口后时间线直接接收维保工单DTO通过工单号识别维保节点。
return orderTypeFormatter(item.typeCode) + "" + displayValue(item.orderNo);
}
if (item.eventType == "CHECK") {
return "轮胎质检:" + displayValue(item.tyreNo || item.selfNo || item.tyreRfid);
}
if (item.eventType == "MILEAGE") {
return "轮胎里程:" + displayValue(item.tyreNo || item.selfNo || item.tyreRfid);
}
return displayValue(item.eventType);
}
function eventClass(type) {
if (type == "MAINTENANCE") {
return "maintenance";
}
if (type == "CHECK") {
return "check";
}
if (type == "MILEAGE") {
return "mileage";
}
return "install";
}
function eventIcon(type) {
if (type == "MAINTENANCE") {
return "维";
}
if (type == "CHECK") {
return "检";
}
if (type == "MILEAGE") {
return "里";
}
return "装";
}
function detail(name, value) {
return '<div class="life-detail"><span class="label-title">' + safeText(name) + '</span>' + safeText(value) + '</div>';
}
function metaCell(name, value) {
return '<div>' + safeText(name) + '' + safeText(value) + '</div>';
}
function tableRow(name, value) {
return '<tr><th>' + safeText(name) + '</th><td>' + safeText(value) + '</td></tr>';
}
function countItem(name, value) {
return '<div class="count-item"><div class="count-value">' + safeText(value) + '</div><div class="count-label">' + safeText(name) + '</div></div>';
}
function joinText(left, right) {
var first = displayValue(left);
var second = displayValue(right);
if (first == "-" && second == "-") {
return "-";
}
return (first == "-" ? "" : first) + (second == "-" ? "" : " / " + second);
}
function displayValue(value) {
var text = $.common.nullToStr(value);
return text === "" ? "-" : text;
}
function safeText(value) {
return escapeHtml(displayValue(value));
}
function escapeHtml(value) {
return String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function installTypeFormatter(value) {
if (value == "0") {
return "安装";
}
if (value == "1") {
return "卸下";
}
return displayValue(value);
}
function orderTypeFormatter(value) {
if (value == "1") {
return "二级保养";
}
if (value == "4") {
return "月检";
}
return displayValue(value);
}
function statusFormatter(value) {
if (value == "UNSTARTED") {
return "未开始";
}
if (value == "PROCESSING") {
return "执行中";
}
if (value == "COMPLETED") {
return "已完成";
}
return displayValue(value);
}
function openMaintenanceOrderDetail(target) {
var orderId = decodeURIComponent($(target).attr("data-order-id") || "");
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>
</body>
</html>