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.

842 lines
27 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: 66px;
min-height: 126px;
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-8 col-md-7 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 clickable-title" onclick="goMaintenanceList()">保养信息</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();
loadLatestMaintenance();
// 兜底关闭父窗口 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 loadLatestMaintenance() {
$.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;
}
renderLatestMaintenance(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) {
html.push('<li class="life-item">');
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 renderLatestMaintenance(data) {
var order = data.order;
if (!order) {
$("#latestMaintenance").html('<div class="text-center text-muted-dash" style="padding: 48px 0;">暂无维保记录</div>');
return;
}
var html = [];
html.push('<div class="maintenance-summary">');
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(""));
}
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)
};
});
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>',
'</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 goMaintenanceList() {
// ctx 由 include.html 注入且自带尾斜杠,按项目既有写法直接拼接相对路径。
window.open(ctx + "tyre/order", "_blank");
}
</script>
</body>
</html>