441 lines
18 KiB
HTML
441 lines
18 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; --primary: #0f172a; --accent: #3b82f6; }
|
||
body { background: var(--bg-body); padding-bottom: 80px; font-family: -apple-system, sans-serif; }
|
||
|
||
/* 筛选条 */
|
||
.filter-bar {
|
||
position: sticky; top: 0; background: var(--bg-body); z-index: 10;
|
||
padding: 16px 16px 8px; overflow-x: auto; white-space: nowrap;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.filter-chip {
|
||
display: inline-block; padding: 6px 16px; margin-right: 8px;
|
||
background: #fff; border: 1px solid #e2e8f0; border-radius: 20px;
|
||
font-size: 13px; color: #64748b; transition: all 0.2s; cursor: pointer;
|
||
}
|
||
.filter-chip.active {
|
||
background: var(--primary); color: #fff; border-color: var(--primary);
|
||
}
|
||
|
||
/* 订单卡片 */
|
||
.order-card {
|
||
background: #fff; margin: 0 16px 16px; padding: 16px;
|
||
border-radius: 16px; border: none; box-shadow: 0 1px 2px rgba(0,0,0,0.02);
|
||
}
|
||
.order-top {
|
||
display: flex; justify-content: space-between; margin-bottom: 12px; font-size: 12px;
|
||
}
|
||
.order-no { color: #94a3b8; }
|
||
.order-status { font-weight: 600; }
|
||
.st-PENDING_PAY { color: #f59e0b; }
|
||
.st-MAKING { color: var(--accent); }
|
||
.st-COMPLETED { color: #10b981; }
|
||
|
||
.order-info { margin-bottom: 12px; }
|
||
.order-price { font-size: 16px; font-weight: 700; color: var(--primary); }
|
||
.order-date { font-size: 12px; color: #94a3b8; margin-top: 4px; }
|
||
|
||
.order-actions {
|
||
border-top: 1px solid #f8fafc; padding-top: 12px; display: flex; justify-content: flex-end; gap: 8px;
|
||
}
|
||
.btn-act {
|
||
padding: 6px 14px; border-radius: 18px; font-size: 12px; font-weight: 500;
|
||
background: #fff; border: 1px solid #cbd5e1; color: #475569;
|
||
}
|
||
.btn-act.primary { background: var(--primary); border-color: var(--primary); color: #fff; }
|
||
|
||
/* 评价展示样式 */
|
||
.review-display-stars { color: #f59e0b; letter-spacing: 2px; }
|
||
.review-reply-box { background: #f8fafc; padding: 12px; border-radius: 8px; margin-top: 12px; font-size: 13px; color: #475569; }
|
||
|
||
/* 评价星星 */
|
||
.rating-star { font-size: 24px; color: #e2e8f0; cursor: pointer; margin-right: 4px; }
|
||
.rating-star.active { color: #f59e0b; }
|
||
|
||
/* 商品推荐模块 */
|
||
.recommend-section {
|
||
margin: 24px 16px;
|
||
padding: 20px 16px;
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
}
|
||
.recommend-title {
|
||
font-size: 16px; font-weight: 700; color: var(--primary);
|
||
margin-bottom: 16px; display: flex; align-items: center;
|
||
}
|
||
.recommend-title i { margin-right: 8px; color: var(--accent); }
|
||
.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(--primary);
|
||
margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.recommend-price {
|
||
font-size: 14px; font-weight: 700; color: var(--primary);
|
||
}
|
||
|
||
/* 底部导航 */
|
||
.mobile-tabbar {
|
||
position: fixed; bottom: 0; left: 0; right: 0; background: #fff;
|
||
padding: 8px 0 20px; display: flex; border-top: 1px solid #f1f5f9; z-index: 99;
|
||
}
|
||
.tab-item {
|
||
flex: 1; text-align: center; color: #94a3b8; text-decoration: none;
|
||
font-size: 10px; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||
}
|
||
.tab-item.active { color: var(--accent); }
|
||
.tab-item i { font-size: 22px; margin-bottom: 4px; line-height: 1; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="filter-bar">
|
||
<div class="filter-chip active" onclick="filter('')">全部</div>
|
||
<div class="filter-chip" onclick="filter('PENDING_PAY')">待支付</div>
|
||
<div class="filter-chip" onclick="filter('MAKING')">制作中</div>
|
||
<div class="filter-chip" onclick="filter('READY')">待取餐</div>
|
||
<div class="filter-chip" onclick="filter('COMPLETED')">已完成</div>
|
||
</div>
|
||
|
||
<div id="orderList" class="pt-2"></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 class="modal fade" id="reviewModal" tabindex="-1">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<div class="modal-content" style="border-radius: 16px; border: none;">
|
||
<div class="modal-header border-0 pb-0">
|
||
<h5 class="modal-title fw-bold">评价订单</h5>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="reviewOrderId">
|
||
<input type="hidden" id="reviewRating" value="0">
|
||
|
||
<div class="mb-3">
|
||
<label class="small text-muted mb-2 d-block">选择商品</label>
|
||
<select class="form-select border-0 bg-light" id="reviewProductSelect"></select>
|
||
</div>
|
||
|
||
<div class="mb-3 text-center">
|
||
<div id="starContainer">
|
||
<i class="bi bi-star-fill rating-star" data-val="1"></i>
|
||
<i class="bi bi-star-fill rating-star" data-val="2"></i>
|
||
<i class="bi bi-star-fill rating-star" data-val="3"></i>
|
||
<i class="bi bi-star-fill rating-star" data-val="4"></i>
|
||
<i class="bi bi-star-fill rating-star" data-val="5"></i>
|
||
</div>
|
||
<div class="small text-muted mt-1" id="ratingText">点击星星打分</div>
|
||
</div>
|
||
|
||
<textarea class="form-control bg-light border-0" id="reviewContent" rows="3" placeholder="口味如何?服务周到吗?"></textarea>
|
||
</div>
|
||
<div class="modal-footer border-0">
|
||
<button type="button" class="btn btn-light rounded-pill" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-dark rounded-pill px-4" onclick="submitReview()">提交评价</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 查看评价模态框 -->
|
||
<div class="modal fade" id="viewReviewModal" tabindex="-1">
|
||
<div class="modal-dialog modal-dialog-centered">
|
||
<div class="modal-content" style="border-radius: 16px; border: none;">
|
||
<div class="modal-header border-0 pb-0">
|
||
<h5 class="modal-title fw-bold">我的评价</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body" id="viewReviewContent">
|
||
<div class="text-center py-3">加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 底部导航 -->
|
||
<div class="mobile-tabbar">
|
||
<a href="/index.html" class="tab-item">
|
||
<i class="bi bi-cup-hot"></i>
|
||
<span>点餐</span>
|
||
</a>
|
||
<a href="/cart.html" class="tab-item">
|
||
<i class="bi bi-bag"></i>
|
||
<span>购物袋</span>
|
||
</a>
|
||
<a href="/orders.html" class="tab-item active">
|
||
<i class="bi bi-receipt-cutoff"></i>
|
||
<span>订单</span>
|
||
</a>
|
||
<a href="/profile.html" class="tab-item">
|
||
<i class="bi bi-person"></i>
|
||
<span>我的</span>
|
||
</a>
|
||
</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>
|
||
let currentStatus = '';
|
||
|
||
// 初始化
|
||
filter('');
|
||
loadRecommendations();
|
||
|
||
async function filter(status) {
|
||
currentStatus = status;
|
||
document.querySelectorAll('.filter-chip').forEach(el => {
|
||
const isActive = (status === '' && el.innerText === '全部') ||
|
||
(status !== '' && el.onclick.toString().includes(status));
|
||
el.classList.toggle('active', isActive);
|
||
});
|
||
await loadOrders();
|
||
}
|
||
|
||
async function loadOrders() {
|
||
const box = document.getElementById('orderList');
|
||
box.innerHTML = '<div class="text-center py-5 text-muted small">加载中...</div>';
|
||
|
||
try {
|
||
const url = currentStatus ? `/order/orders?status=${currentStatus}` : '/order/orders';
|
||
const res = await fetch(url, { credentials: 'include' });
|
||
const data = await res.json();
|
||
|
||
if (data.code === 200) {
|
||
const orders = data.data || [];
|
||
if (!orders.length) {
|
||
box.innerHTML = '<div class="text-center py-5 text-muted small">暂无订单</div>';
|
||
return;
|
||
}
|
||
|
||
// 批量查询评论状态
|
||
const orderIds = orders.map(o => o.id);
|
||
const reviewsMap = await loadReviewsForOrders(orderIds);
|
||
|
||
box.innerHTML = orders.map(o => {
|
||
const statusMap = { 'PENDING_PAY': '待支付', 'MAKING': '制作中', 'READY': '待取餐', 'COMPLETED': '已完成', 'CANCELLED': '已取消' };
|
||
const statusTxt = statusMap[o.orderStatus] || o.orderStatus;
|
||
|
||
let btns = `<button class="btn-act" onclick="location.href='/order-detail.html?id=${o.id}'">详情</button>`;
|
||
|
||
if (o.orderStatus === 'PENDING_PAY') {
|
||
btns += `<button class="btn-act primary" onclick="goToPayment(${o.id})">去支付</button>`;
|
||
} else if (o.orderStatus === 'COMPLETED') {
|
||
// 根据评论状态显示不同按钮
|
||
if (reviewsMap.has(o.id)) {
|
||
btns += `<button class="btn-act" onclick="showViewReviewModal(${o.id})">查看评价</button>`;
|
||
} else {
|
||
btns += `<button class="btn-act" onclick="openReviewModal(${o.id})">评价</button>`;
|
||
}
|
||
}
|
||
|
||
return `
|
||
<div class="order-card">
|
||
<div class="order-top">
|
||
<span class="order-no">单号 ${o.orderNo.slice(-8)}</span>
|
||
<span class="order-status st-${o.orderStatus}">${statusTxt}</span>
|
||
</div>
|
||
<div class="order-info">
|
||
<div class="order-price">¥${o.totalAmount}</div>
|
||
<div class="order-date">${new Date(o.createTime).toLocaleString()}</div>
|
||
</div>
|
||
<div class="order-actions">
|
||
${btns}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
} else if (data.message?.includes('登录')) {
|
||
window.location.href = '/login.html';
|
||
}
|
||
} catch (e) { console.error(e); }
|
||
}
|
||
|
||
// 批量获取评论状态
|
||
async function loadReviewsForOrders(orderIds) {
|
||
if (!orderIds || orderIds.length === 0) return new Map();
|
||
try {
|
||
const res = await fetch(`/review/by-orders?orderIds=${orderIds.join(',')}`, { credentials: 'include' });
|
||
const data = await res.json();
|
||
if (data.code === 200 && data.data) {
|
||
const map = new Map();
|
||
data.data.forEach(r => map.set(r.orderId, r));
|
||
return map;
|
||
}
|
||
} catch (e) { console.error(e); }
|
||
return new Map();
|
||
}
|
||
|
||
// 跳转到支付页面
|
||
function goToPayment(orderId) {
|
||
window.location.href = `/payment.html?orderId=${orderId}`;
|
||
}
|
||
|
||
// 评价输入逻辑
|
||
let reviewModal;
|
||
async function openReviewModal(orderId) {
|
||
try {
|
||
const res = await fetch(`/order/orders/${orderId}`, { credentials: 'include' });
|
||
const d = await res.json();
|
||
if (d.code === 200) {
|
||
const items = d.data.items || [];
|
||
const select = document.getElementById('reviewProductSelect');
|
||
select.innerHTML = items.map(i =>
|
||
`<option value="${i.productId}">${i.productName}</option>`
|
||
).join('');
|
||
|
||
document.getElementById('reviewOrderId').value = orderId;
|
||
document.getElementById('reviewRating').value = 0;
|
||
document.getElementById('reviewContent').value = '';
|
||
resetStars();
|
||
|
||
reviewModal = new bootstrap.Modal(document.getElementById('reviewModal'));
|
||
reviewModal.show();
|
||
}
|
||
} catch(e) { alert('无法加载订单信息'); }
|
||
}
|
||
|
||
// 查看评价逻辑
|
||
async function showViewReviewModal(orderId) {
|
||
const modal = new bootstrap.Modal(document.getElementById('viewReviewModal'));
|
||
document.getElementById('viewReviewContent').innerHTML = '<div class="text-center py-3">加载中...</div>';
|
||
modal.show();
|
||
|
||
try {
|
||
const res = await fetch(`/review/by-order?orderId=${orderId}`, { credentials: 'include' });
|
||
const d = await res.json();
|
||
if (d.code === 200 && d.data) {
|
||
const r = d.data;
|
||
const stars = '★'.repeat(r.rating) + '☆'.repeat(5 - r.rating);
|
||
|
||
let html = `
|
||
<div class="mb-3">
|
||
<div class="review-display-stars fs-4 mb-2">${stars}</div>
|
||
<div class="text-dark">${r.content || '未填写评价内容'}</div>
|
||
<div class="text-muted small mt-2">${new Date(r.createTime).toLocaleString()}</div>
|
||
</div>
|
||
`;
|
||
|
||
if (r.reply) {
|
||
html += `
|
||
<div class="review-reply-box">
|
||
<div class="fw-bold mb-1">商家回复:</div>
|
||
<div>${r.reply}</div>
|
||
</div>
|
||
`;
|
||
}
|
||
document.getElementById('viewReviewContent').innerHTML = html;
|
||
} else {
|
||
document.getElementById('viewReviewContent').innerHTML = '<div class="text-center">未找到评价信息</div>';
|
||
}
|
||
} catch (e) {
|
||
document.getElementById('viewReviewContent').innerHTML = '<div class="text-center">加载失败</div>';
|
||
}
|
||
}
|
||
|
||
// 星星交互
|
||
document.querySelectorAll('#starContainer .rating-star').forEach(star => {
|
||
star.onclick = function() {
|
||
const val = this.dataset.val;
|
||
document.getElementById('reviewRating').value = val;
|
||
document.getElementById('ratingText').innerText = val + ' 分';
|
||
updateStars(val);
|
||
}
|
||
});
|
||
|
||
function updateStars(val) {
|
||
document.querySelectorAll('#starContainer .rating-star').forEach(s => {
|
||
if (s.dataset.val <= val) s.classList.add('active');
|
||
else s.classList.remove('active');
|
||
});
|
||
}
|
||
function resetStars() {
|
||
updateStars(0);
|
||
document.getElementById('ratingText').innerText = '点击星星打分';
|
||
}
|
||
|
||
async function submitReview() {
|
||
const oid = document.getElementById('reviewOrderId').value;
|
||
const pid = document.getElementById('reviewProductSelect').value;
|
||
const rating = document.getElementById('reviewRating').value;
|
||
const content = document.getElementById('reviewContent').value;
|
||
|
||
if (rating == 0) return alert('请打分');
|
||
|
||
try {
|
||
const params = new URLSearchParams();
|
||
params.append('orderId', oid);
|
||
params.append('productId', pid);
|
||
params.append('rating', rating);
|
||
params.append('content', content);
|
||
|
||
const res = await fetch('/review', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||
credentials: 'include',
|
||
body: params
|
||
});
|
||
const d = await res.json();
|
||
if (d.code === 200) {
|
||
alert('评价成功');
|
||
reviewModal.hide();
|
||
loadOrders(); // 刷新列表状态
|
||
} else { alert(d.message); }
|
||
} catch(e) { alert('提交失败'); }
|
||
}
|
||
|
||
// 加载推荐商品(限制3-5个)
|
||
async function loadRecommendations() {
|
||
try {
|
||
const limit = Math.floor(Math.random() * 3) + 3; // 3-5个随机
|
||
const res = await fetch(`/recommend?limit=${limit}`, { 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('');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |