Files
wb-java-nc-shop/target/classes/templates/user/order-detail.html
2026-01-11 18:14:42 +08:00

356 lines
15 KiB
HTML

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>订单详情</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<style>
:root {
--bg-body: #f8fafc;
--text-primary: #0f172a;
--primary-color: #3b82f6;
}
body {
background: var(--bg-body);
padding-bottom: 90px;
font-family: -apple-system, sans-serif;
color: #334155;
}
/* 顶部导航 Header */
.nav-header {
position: fixed; top: 0; left: 0; right: 0; height: 50px;
background: #fff;
display: flex; align-items: center; justify-content: space-between;
padding: 0 16px; z-index: 1000; box-shadow: 0 1px 0 rgba(0,0,0,0.05);
}
.nav-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
.btn-back { border: none; background: transparent; font-size: 20px; color: var(--text-primary); padding: 0; }
/* 状态头 */
.status-header {
background: #fff; padding: 20px; margin-bottom: 12px;
margin-top: 50px; /* 避开Header */
}
.status-title { font-size: 20px; font-weight: 800; color: var(--text-primary); margin-bottom: 4px; }
.status-desc { font-size: 13px; color: #64748b; }
/* 进度追踪 - 极简风 */
.track-list { margin-top: 20px; position: relative; }
.track-item { display: flex; margin-bottom: 24px; position: relative; }
.track-item:last-child { margin-bottom: 0; }
/* 连接线 */
.track-item:not(:last-child)::before {
content: ''; position: absolute; left: 7px; top: 20px; bottom: -20px;
width: 2px; background: #e2e8f0;
}
.track-dot {
width: 16px; height: 16px; border-radius: 50%; background: #cbd5e1;
margin-right: 16px; flex-shrink: 0; margin-top: 4px; border: 3px solid #fff; box-shadow: 0 0 0 1px #cbd5e1;
}
.track-item.active .track-dot {
background: var(--primary-color); box-shadow: 0 0 0 1px var(--primary-color);
}
.track-content { flex: 1; }
.track-status { font-size: 14px; font-weight: 600; color: var(--text-primary); }
.track-time { font-size: 12px; color: #94a3b8; margin-top: 2px; }
/* 通用卡片 */
.info-card { background: #fff; padding: 16px; margin-bottom: 12px; }
.card-title { font-size: 14px; font-weight: 700; color: var(--text-primary); margin-bottom: 12px; display: flex; align-items: center; }
.card-title i { margin-right: 6px; font-size: 16px; color: #64748b; }
/* 商品列表 */
.order-item { display: flex; margin-bottom: 16px; align-items: flex-start; }
.order-item:last-child { margin-bottom: 0; }
.item-img {
width: 60px; height: 60px; border-radius: 8px; object-fit: cover;
background: #f1f5f9; margin-right: 12px; cursor: pointer; flex-shrink: 0;
transition: transform 0.2s;
}
.item-img:hover { transform: scale(1.05); }
.item-info { flex: 1; }
.item-name { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
.item-spec { font-size: 12px; color: #64748b; margin-bottom: 4px; }
.item-price-row { display: flex; justify-content: space-between; align-items: baseline; }
.item-price { font-size: 14px; font-weight: 600; color: var(--text-primary); }
.item-qty { font-size: 12px; color: #94a3b8; margin-left: 8px; }
.item-subtotal { font-size: 13px; color: #64748b; text-align: right; }
/* 详情行 */
.detail-row { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 8px; }
.detail-label { color: #64748b; }
.detail-val { color: var(--text-primary); font-weight: 500; text-align: right; max-width: 70%; }
.total-row {
border-top: 1px solid #f1f5f9; margin-top: 12px; padding-top: 12px;
display: flex; justify-content: space-between; align-items: center;
}
.total-price { font-size: 18px; font-weight: 800; color: var(--text-primary); }
/* 底部操作 */
.bottom-bar {
position: fixed; bottom: 0; left: 0; right: 0;
background: #fff; padding: 12px 16px 20px;
box-shadow: 0 -4px 12px rgba(0,0,0,0.05);
display: flex; justify-content: flex-end; gap: 12px;
z-index: 100;
}
.btn-outline {
padding: 8px 20px; border-radius: 20px; font-size: 13px; font-weight: 600;
background: #fff; border: 1px solid #cbd5e1; color: #475569;
}
.btn-primary-action {
padding: 8px 20px; border-radius: 20px; font-size: 13px; font-weight: 600;
background: #0f172a; border: 1px solid #0f172a; color: #fff;
}
/* 商品推荐模块 */
.recommend-section {
margin: 24px 16px;
padding: 20px 16px;
background: #fff;
border-radius: 16px;
}
.recommend-title {
font-size: 16px; font-weight: 700; color: var(--text-primary);
margin-bottom: 16px; display: flex; align-items: center;
}
.recommend-title i { margin-right: 8px; color: var(--primary-color); }
.recommend-grid {
display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;
}
.recommend-item {
background: #f8fafc; border-radius: 12px; overflow: hidden;
cursor: pointer; transition: transform 0.2s;
}
.recommend-item:active { transform: scale(0.98); }
.recommend-img {
width: 100%; height: 120px; object-fit: cover; background: #f1f5f9;
}
.recommend-info {
padding: 10px;
}
.recommend-name {
font-size: 13px; font-weight: 600; color: var(--text-primary);
margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.recommend-price {
font-size: 14px; font-weight: 700; color: var(--text-primary);
}
</style>
</head>
<body>
<!-- 顶部导航 -->
<div class="nav-header">
<button class="btn-back" onclick="history.back()"><i class="bi bi-arrow-left"></i></button>
<span class="nav-title">订单详情</span>
<div style="width: 24px;"></div>
</div>
<div id="loading" class="text-center p-5 text-muted" style="margin-top: 50px;">加载中...</div>
<div id="content" style="display: none;">
<!-- 头部状态 -->
<div class="status-header">
<div class="status-title" id="orderStatusText">--</div>
<div class="status-desc">感谢您的光临</div>
<div class="track-list" id="trackList"></div>
</div>
<!-- 商品明细 -->
<div class="info-card">
<div class="card-title"><i class="bi bi-bag"></i> 商品明细</div>
<div id="itemList"></div>
<div class="total-row">
<span>合计</span>
<span class="total-price" id="totalAmount">¥0.00</span>
</div>
</div>
<!-- 配送信息 -->
<div class="info-card">
<div class="card-title"><i class="bi bi-geo-alt"></i> 配送信息</div>
<div class="detail-row">
<span class="detail-label">收货人</span>
<span class="detail-val" id="contactInfo">--</span>
</div>
<div class="detail-row">
<span class="detail-label">收货地址</span>
<span class="detail-val" id="addressInfo">--</span>
</div>
</div>
<!-- 订单信息 -->
<div class="info-card">
<div class="card-title"><i class="bi bi-file-text"></i> 订单信息</div>
<div class="detail-row">
<span class="detail-label">订单编号</span>
<span class="detail-val" id="orderNo">--</span>
</div>
<div class="detail-row">
<span class="detail-label">下单时间</span>
<span class="detail-val" id="createTime">--</span>
</div>
<div class="detail-row">
<span class="detail-label">支付方式</span>
<span class="detail-val" id="payMethod">--</span>
</div>
</div>
<div style="height: 20px;"></div>
<!-- 商品推荐模块 -->
<div class="recommend-section" id="recommendSection" style="display: none;">
<div class="recommend-title">
<i class="bi bi-fire"></i>
<span>为您推荐</span>
</div>
<div class="recommend-grid" id="recommendGrid"></div>
</div>
</div>
<!-- 底部操作栏 -->
<div class="bottom-bar" id="actionBar">
<button class="btn-outline" onclick="history.back()">返回列表</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/ui-enhancements.js"></script>
<script src="/static/js/custom-modal.js"></script>
<script>
const oid = new URLSearchParams(location.search).get('id');
let currentOrder = null;
async function init() {
if(!oid) return alert('参数错误');
try {
const { data } = await UIEnhancements.enhancedFetch(`/order/orders/${oid}`, { credentials: 'include' });
if(data.code === 200) {
currentOrder = data.data.order;
render(data.data);
document.getElementById('loading').style.display = 'none';
document.getElementById('content').style.display = 'block';
// 加载推荐商品
loadRecommendations();
} else {
alert(data.message);
}
} catch(e) {
console.error(e);
alert('加载失败');
}
}
// 加载推荐商品
async function loadRecommendations() {
try {
const res = await fetch('/recommend?limit=4', { credentials: 'include' });
const data = await res.json();
if (data.code === 200 && data.data && data.data.length > 0) {
renderRecommendations(data.data);
document.getElementById('recommendSection').style.display = 'block';
}
} catch(e) { console.error(e); }
}
function renderRecommendations(products) {
const container = document.getElementById('recommendGrid');
container.innerHTML = products.map(p => `
<div class="recommend-item" onclick="location.href='/product-detail.html?id=${p.id}'">
<img src="${p.image || '/static/images/default.jpg'}" class="recommend-img" alt="${p.name}">
<div class="recommend-info">
<div class="recommend-name">${p.name}</div>
<div class="recommend-price">¥${p.basePrice}</div>
</div>
</div>
`).join('');
}
function render(data) {
const o = data.order;
const items = data.items || [];
const tracks = data.tracks || [];
// 基础信息
document.getElementById('orderStatusText').textContent = getStatusText(o.orderStatus);
document.getElementById('totalAmount').textContent = `¥${o.totalAmount}`;
document.getElementById('contactInfo').textContent = `${o.contactPhone}`;
document.getElementById('addressInfo').textContent = o.address;
document.getElementById('orderNo').textContent = o.orderNo;
document.getElementById('createTime').textContent = new Date(o.createTime).toLocaleString();
document.getElementById('payMethod').textContent = o.paymentMethod === 'wechat' ? '微信支付' : (o.paymentMethod === 'alipay' ? '支付宝' : '未支付');
// 渲染商品
const productImages = data.productImages || {};
document.getElementById('itemList').innerHTML = items.map(i => {
let extra = '';
if(i.specName) extra += i.specName + ' ';
if(i.customSweetness) extra += `甜度: ${i.customSweetness} `;
if(i.customIce) extra += `冰度: ${i.customIce} `;
// 可以增加 Topping 展示
const productImage = productImages[i.productId] || '/static/images/default.jpg';
const subtotal = (parseFloat(i.price) * i.quantity).toFixed(2);
return `
<div class="order-item">
<img src="${productImage}" class="item-img"
onclick="location.href='/product-detail.html?id=${i.productId}'"
alt="${i.productName}">
<div class="item-info">
<div class="item-name">${i.productName}</div>
<div class="item-spec">${extra}</div>
<div class="item-price-row">
<div>
<span class="item-price">¥${i.price}</span>
<span class="item-qty">x${i.quantity}</span>
</div>
<div class="item-subtotal">小计: ¥${subtotal}</div>
</div>
</div>
</div>
`;
}).join('');
// 渲染进度 (Tracks)
if(tracks.length > 0) {
document.getElementById('trackList').innerHTML = tracks.map((t, idx) => `
<div class="track-item ${idx === 0 ? 'active' : ''}">
<div class="track-dot"></div>
<div class="track-content">
<div class="track-status">${getStatusText(t.status)}</div>
<div class="track-time">${new Date(t.createTime).toLocaleString()}</div>
</div>
</div>
`).join('');
} else {
document.getElementById('trackList').innerHTML = '<div class="text-muted small">暂无物流信息</div>';
}
// 底部按钮
const bar = document.getElementById('actionBar');
if(o.orderStatus === 'PENDING_PAY') {
const btn = document.createElement('button');
btn.className = 'btn-primary-action';
btn.textContent = '立即支付';
btn.onclick = () => goToPayment(o.id);
bar.appendChild(btn);
}
}
function getStatusText(s) {
const map = { 'PENDING_PAY': '待支付', 'MAKING': '制作中', 'READY': '待取餐', 'COMPLETED': '已完成', 'CANCELLED': '已取消' };
return map[s] || s;
}
// 跳转到支付页面
function goToPayment(orderId) {
window.location.href = `/payment.html?orderId=${orderId}`;
}
init();
</script>
</body>
</html>