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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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>