订单展示支付流程
This commit is contained in:
@@ -181,6 +181,51 @@ public class MessageListenerService {
|
|||||||
log.debug("推荐替代商品: 用户ID={}, 秒杀ID={}", userId, flashSaleId);
|
log.debug("推荐替代商品: 用户ID={}, 秒杀ID={}", userId, flashSaleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取Long值
|
||||||
|
*/
|
||||||
|
private Long extractLongValue(Object value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Long) return (Long) value;
|
||||||
|
if (value instanceof Integer) return ((Integer) value).longValue();
|
||||||
|
if (value instanceof String) return Long.valueOf((String) value);
|
||||||
|
return Long.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取Integer值
|
||||||
|
*/
|
||||||
|
private Integer extractIntegerValue(Object value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value instanceof Integer) return (Integer) value;
|
||||||
|
if (value instanceof Long) return ((Long) value).intValue();
|
||||||
|
if (value instanceof String) return Integer.valueOf((String) value);
|
||||||
|
return Integer.valueOf(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析Redisson消息格式
|
||||||
|
*/
|
||||||
|
private Map<String, Object> parseRedissonMessage(String messageBody) throws Exception {
|
||||||
|
// 处理Redisson序列化的数据格式
|
||||||
|
if (messageBody.startsWith("[") && messageBody.contains("java.util.HashMap")) {
|
||||||
|
// 解析Redisson序列化格式: ["java.util.HashMap",{...}]
|
||||||
|
int startIndex = messageBody.indexOf('{');
|
||||||
|
int endIndex = messageBody.lastIndexOf('}') + 1;
|
||||||
|
if (startIndex > 0 && endIndex > startIndex) {
|
||||||
|
String jsonPart = messageBody.substring(startIndex, endIndex);
|
||||||
|
// 处理Redisson的类型信息格式: ["java.lang.Long",11] -> 11
|
||||||
|
jsonPart = jsonPart.replaceAll("\\[\"java\\.lang\\.(Long|Integer|String)\",([^\\]]+)\\]", "$2");
|
||||||
|
return objectMapper.readValue(jsonPart, Map.class);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("无法解析Redisson消息格式");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 标准JSON格式
|
||||||
|
return objectMapper.readValue(messageBody, Map.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单状态变更监听器
|
* 订单状态变更监听器
|
||||||
*/
|
*/
|
||||||
@@ -189,11 +234,13 @@ public class MessageListenerService {
|
|||||||
public void onMessage(Message message, byte[] pattern) {
|
public void onMessage(Message message, byte[] pattern) {
|
||||||
try {
|
try {
|
||||||
String messageBody = new String(message.getBody());
|
String messageBody = new String(message.getBody());
|
||||||
Map<String, Object> data = objectMapper.readValue(messageBody, Map.class);
|
log.debug("接收到订单状态变更消息: {}", messageBody);
|
||||||
|
|
||||||
Long orderId = Long.valueOf(data.get("orderId").toString());
|
Map<String, Object> data = parseRedissonMessage(messageBody);
|
||||||
Long userId = Long.valueOf(data.get("userId").toString());
|
|
||||||
Integer status = Integer.valueOf(data.get("status").toString());
|
Long orderId = extractLongValue(data.get("orderId"));
|
||||||
|
Long userId = extractLongValue(data.get("userId"));
|
||||||
|
Integer status = extractIntegerValue(data.get("status"));
|
||||||
String action = data.get("action").toString();
|
String action = data.get("action").toString();
|
||||||
|
|
||||||
log.info("订单状态变更: 订单ID={}, 用户ID={}, 状态={}, 操作={}",
|
log.info("订单状态变更: 订单ID={}, 用户ID={}, 状态={}, 操作={}",
|
||||||
@@ -216,10 +263,12 @@ public class MessageListenerService {
|
|||||||
public void onMessage(Message message, byte[] pattern) {
|
public void onMessage(Message message, byte[] pattern) {
|
||||||
try {
|
try {
|
||||||
String messageBody = new String(message.getBody());
|
String messageBody = new String(message.getBody());
|
||||||
Map<String, Object> data = objectMapper.readValue(messageBody, Map.class);
|
log.debug("接收到库存变更消息: {}", messageBody);
|
||||||
|
|
||||||
Long productId = Long.valueOf(data.get("productId").toString());
|
Map<String, Object> data = parseRedissonMessage(messageBody);
|
||||||
Integer quantity = Integer.valueOf(data.get("quantity").toString());
|
|
||||||
|
Long productId = extractLongValue(data.get("productId"));
|
||||||
|
Integer quantity = extractIntegerValue(data.get("quantity"));
|
||||||
String operation = data.get("operation").toString();
|
String operation = data.get("operation").toString();
|
||||||
|
|
||||||
log.info("库存变更: 商品ID={}, 数量={}, 操作={}", productId, quantity, operation);
|
log.info("库存变更: 商品ID={}, 数量={}, 操作={}", productId, quantity, operation);
|
||||||
@@ -241,10 +290,12 @@ public class MessageListenerService {
|
|||||||
public void onMessage(Message message, byte[] pattern) {
|
public void onMessage(Message message, byte[] pattern) {
|
||||||
try {
|
try {
|
||||||
String messageBody = new String(message.getBody());
|
String messageBody = new String(message.getBody());
|
||||||
Map<String, Object> data = objectMapper.readValue(messageBody, Map.class);
|
log.debug("接收到秒杀结果消息: {}", messageBody);
|
||||||
|
|
||||||
Long userId = Long.valueOf(data.get("userId").toString());
|
Map<String, Object> data = parseRedissonMessage(messageBody);
|
||||||
Long flashSaleId = Long.valueOf(data.get("flashSaleId").toString());
|
|
||||||
|
Long userId = extractLongValue(data.get("userId"));
|
||||||
|
Long flashSaleId = extractLongValue(data.get("flashSaleId"));
|
||||||
Boolean success = Boolean.valueOf(data.get("success").toString());
|
Boolean success = Boolean.valueOf(data.get("success").toString());
|
||||||
|
|
||||||
log.info("秒杀结果: 用户ID={}, 秒杀ID={}, 成功={}", userId, flashSaleId, success);
|
log.info("秒杀结果: 用户ID={}, 秒杀ID={}, 成功={}", userId, flashSaleId, success);
|
||||||
@@ -266,9 +317,11 @@ public class MessageListenerService {
|
|||||||
public void onMessage(Message message, byte[] pattern) {
|
public void onMessage(Message message, byte[] pattern) {
|
||||||
try {
|
try {
|
||||||
String messageBody = new String(message.getBody());
|
String messageBody = new String(message.getBody());
|
||||||
Map<String, Object> data = objectMapper.readValue(messageBody, Map.class);
|
log.debug("接收到用户行为消息: {}", messageBody);
|
||||||
|
|
||||||
Long userId = Long.valueOf(data.get("userId").toString());
|
Map<String, Object> data = parseRedissonMessage(messageBody);
|
||||||
|
|
||||||
|
Long userId = extractLongValue(data.get("userId"));
|
||||||
String action = data.get("action").toString();
|
String action = data.get("action").toString();
|
||||||
|
|
||||||
log.info("用户行为: 用户ID={}, 行为={}", userId, action);
|
log.info("用户行为: 用户ID={}, 行为={}", userId, action);
|
||||||
|
|||||||
571
src/main/webapp/WEB-INF/views/order-detail.jsp
Normal file
571
src/main/webapp/WEB-INF/views/order-detail.jsp
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||||
|
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
||||||
|
<%@ taglib prefix="fn" uri="http://flashsale.org/functions" %>
|
||||||
|
|
||||||
|
<c:set var="pageTitle" value="订单详情"/>
|
||||||
|
<%@ include file="common/header.jsp" %>
|
||||||
|
|
||||||
|
<div class="container my-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="${pageContext.request.contextPath}/">首页</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="${pageContext.request.contextPath}/orders">我的订单</a></li>
|
||||||
|
<li class="breadcrumb-item active">订单详情</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载中状态 -->
|
||||||
|
<div id="loadingOrder" class="text-center py-5">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">加载中...</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted mt-2">正在加载订单详情...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 订单详情内容 -->
|
||||||
|
<div id="orderDetail" style="display: none;">
|
||||||
|
<!-- 订单状态和基本信息 -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-receipt text-primary"></i> 订单信息
|
||||||
|
</h5>
|
||||||
|
<div id="orderActions">
|
||||||
|
<!-- 订单操作按钮将动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<table class="table table-borderless">
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="width: 120px;">订单号:</td>
|
||||||
|
<td id="orderNo">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">订单状态:</td>
|
||||||
|
<td><span id="orderStatus" class="badge">-</span></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">订单类型:</td>
|
||||||
|
<td id="orderType">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">下单时间:</td>
|
||||||
|
<td id="createdAt">-</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<table class="table table-borderless">
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted" style="width: 120px;">商品数量:</td>
|
||||||
|
<td id="quantity">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">订单金额:</td>
|
||||||
|
<td class="text-danger fw-bold fs-5" id="totalPrice">¥0.00</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-muted">更新时间:</td>
|
||||||
|
<td id="updatedAt">-</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 商品信息 -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-box text-success"></i> 商品信息
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" id="productInfo">
|
||||||
|
<!-- 商品信息将动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 订单操作历史 -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-history text-info"></i> 订单状态记录
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div id="statusHistory">
|
||||||
|
<!-- 状态历史将动态生成 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误状态 -->
|
||||||
|
<div id="errorState" class="text-center py-5" style="display: none;">
|
||||||
|
<i class="fas fa-exclamation-triangle fa-4x text-warning mb-3"></i>
|
||||||
|
<h4 class="text-muted">订单信息加载失败</h4>
|
||||||
|
<p class="text-muted" id="errorMessage">请稍后重试或联系客服</p>
|
||||||
|
<a href="${pageContext.request.contextPath}/orders" class="btn btn-primary">
|
||||||
|
<i class="fas fa-arrow-left"></i> 返回订单列表
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 支付确认模态框 -->
|
||||||
|
<div class="modal fade" id="paymentModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">确认支付</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center mb-3">
|
||||||
|
<i class="fas fa-credit-card fa-3x text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-center">确定要支付此订单吗?</p>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<small>
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
这是模拟支付,99%概率成功
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="processPayment()">
|
||||||
|
<i class="fas fa-credit-card"></i> 确认支付
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const orderId = ${orderId};
|
||||||
|
let currentOrder = null;
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
loadOrderDetail();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载订单详情
|
||||||
|
function loadOrderDetail() {
|
||||||
|
$('#loadingOrder').show();
|
||||||
|
$('#orderDetail').hide();
|
||||||
|
$('#errorState').hide();
|
||||||
|
|
||||||
|
console.log('Loading order detail for orderId:', orderId);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '${pageContext.request.contextPath}/api/order/' + orderId,
|
||||||
|
type: 'GET',
|
||||||
|
success: function (response) {
|
||||||
|
console.log('Order detail response:', response);
|
||||||
|
if (response.success) {
|
||||||
|
currentOrder = response.data;
|
||||||
|
console.log('Current order data:', currentOrder);
|
||||||
|
renderOrderDetail(currentOrder);
|
||||||
|
$('#orderDetail').show();
|
||||||
|
} else {
|
||||||
|
console.error('Failed to get order:', response.message);
|
||||||
|
showError(response.message || '订单信息获取失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error('Error loading order:', xhr.status, xhr.responseText);
|
||||||
|
let errorMessage = '网络错误,请稍后重试';
|
||||||
|
if (xhr.status === 404) {
|
||||||
|
errorMessage = '订单不存在或已被删除';
|
||||||
|
} else if (xhr.status === 401) {
|
||||||
|
errorMessage = '请先登录';
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '${pageContext.request.contextPath}/login?returnUrl=' + encodeURIComponent(window.location.pathname);
|
||||||
|
}, 1500);
|
||||||
|
} else if (xhr.status === 403) {
|
||||||
|
errorMessage = '无权限查看此订单';
|
||||||
|
}
|
||||||
|
showError(errorMessage);
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
$('#loadingOrder').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染订单详情
|
||||||
|
function renderOrderDetail(order) {
|
||||||
|
if (!order) {
|
||||||
|
console.error('Order data is null or undefined');
|
||||||
|
showError('订单数据为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Rendering order detail:', order);
|
||||||
|
|
||||||
|
// 基本信息
|
||||||
|
$('#orderNo').text('#' + (order.id || 'unknown'));
|
||||||
|
$('#quantity').text((order.quantity || 0) + ' 件');
|
||||||
|
$('#totalPrice').text('¥' + (order.totalPrice ? parseFloat(order.totalPrice).toFixed(2) : '0.00'));
|
||||||
|
$('#createdAt').text(formatDateTime(order.createdAt));
|
||||||
|
$('#updatedAt').text(formatDateTime(order.updatedAt));
|
||||||
|
|
||||||
|
// 订单状态
|
||||||
|
const statusInfo = getStatusInfo(order.status || 0);
|
||||||
|
$('#orderStatus').removeClass().addClass('badge ' + statusInfo.class).text(statusInfo.text);
|
||||||
|
|
||||||
|
// 订单类型
|
||||||
|
const typeText = (order.orderType === 2) ? '秒杀订单' : '普通订单';
|
||||||
|
$('#orderType').text(typeText);
|
||||||
|
|
||||||
|
// 商品信息
|
||||||
|
renderProductInfo(order);
|
||||||
|
|
||||||
|
// 订单操作按钮
|
||||||
|
renderOrderActions(order);
|
||||||
|
|
||||||
|
// 状态历史
|
||||||
|
renderStatusHistory(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染商品信息
|
||||||
|
function renderProductInfo(order) {
|
||||||
|
// 计算单价
|
||||||
|
const totalPrice = order.totalPrice ? parseFloat(order.totalPrice) : 0;
|
||||||
|
const quantity = order.quantity || 1;
|
||||||
|
const unitPrice = totalPrice / quantity;
|
||||||
|
|
||||||
|
const productName = order.productName || '未知商品';
|
||||||
|
const productId = order.productId || '-';
|
||||||
|
const productImageUrl = order.productImageUrl || '${pageContext.request.contextPath}/images/default-product.svg';
|
||||||
|
const isFlashSale = order.orderType === 2;
|
||||||
|
|
||||||
|
const productHtml = `
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<img src="` + productImageUrl + `"
|
||||||
|
class="img-fluid rounded" alt="` + productName + `"
|
||||||
|
onerror="this.src='${pageContext.request.contextPath}/images/default-product.svg'; this.onerror=null;"
|
||||||
|
style="max-height: 100px; object-fit: cover;">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6 class="mb-1">` + productName + `</h6>
|
||||||
|
<p class="text-muted small mb-0">商品ID: ` + productId + `</p>
|
||||||
|
` + (isFlashSale ? '<span class="badge bg-danger small">秒杀商品</span>' : '<span class="badge bg-primary small">普通商品</span>') + `
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-center">
|
||||||
|
<span class="text-primary fw-bold">¥` + unitPrice.toFixed(2) + `</span>
|
||||||
|
<br><small class="text-muted">单价</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-center">
|
||||||
|
<span class="badge bg-light text-dark fs-6">× ` + quantity + `</span>
|
||||||
|
<br><small class="text-muted">数量</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
$('#productInfo').html(productHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染订单操作按钮
|
||||||
|
function renderOrderActions(order) {
|
||||||
|
let actionsHtml = '';
|
||||||
|
|
||||||
|
switch (order.status) {
|
||||||
|
case 1: // 待支付
|
||||||
|
actionsHtml = `
|
||||||
|
<button class="btn btn-primary btn-sm me-2" onclick="showPaymentModal()">
|
||||||
|
<i class="fas fa-credit-card"></i> 立即支付
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-danger btn-sm" onclick="cancelOrder()">
|
||||||
|
<i class="fas fa-times"></i> 取消订单
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 2: // 已支付
|
||||||
|
actionsHtml = `
|
||||||
|
<button class="btn btn-info btn-sm" onclick="confirmReceipt()" disabled>
|
||||||
|
<i class="fas fa-truck"></i> 等待发货
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 3: // 已发货
|
||||||
|
actionsHtml = `
|
||||||
|
<button class="btn btn-success btn-sm" onclick="confirmReceipt()">
|
||||||
|
<i class="fas fa-check"></i> 确认收货
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 4: // 已完成
|
||||||
|
actionsHtml = `
|
||||||
|
<span class="text-success">
|
||||||
|
<i class="fas fa-check-circle"></i> 订单已完成
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 5: // 已取消
|
||||||
|
actionsHtml = `
|
||||||
|
<span class="text-muted">
|
||||||
|
<i class="fas fa-ban"></i> 订单已取消
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#orderActions').html(actionsHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染状态历史
|
||||||
|
function renderStatusHistory(order) {
|
||||||
|
const statusSteps = [
|
||||||
|
{status: 1, text: '订单创建', icon: 'fas fa-plus-circle', time: order.createdAt},
|
||||||
|
{status: 2, text: '支付完成', icon: 'fas fa-credit-card'},
|
||||||
|
{status: 3, text: '商品发货', icon: 'fas fa-truck'},
|
||||||
|
{status: 4, text: '订单完成', icon: 'fas fa-check-circle'}
|
||||||
|
];
|
||||||
|
|
||||||
|
let historyHtml = '<div class="timeline">';
|
||||||
|
statusSteps.forEach((step, index) => {
|
||||||
|
const isActive = order.status >= step.status;
|
||||||
|
const isCurrent = order.status === step.status;
|
||||||
|
const statusClass = isActive ? 'text-success' : 'text-muted';
|
||||||
|
|
||||||
|
historyHtml += `
|
||||||
|
<div class="timeline-item ` + (isActive ? 'active' : '') + `">
|
||||||
|
<div class="timeline-marker">
|
||||||
|
<i class="` + step.icon + ` ` + statusClass + `"></i>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<h6 class="` + statusClass + `">` + step.text + `</h6>
|
||||||
|
` + (step.time ? `<small class="text-muted">` + formatDateTime(step.time) + `</small>` : '') + `
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
historyHtml += '</div>';
|
||||||
|
|
||||||
|
$('#statusHistory').html(historyHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示支付模态框
|
||||||
|
function showPaymentModal() {
|
||||||
|
$('#paymentModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理支付
|
||||||
|
function processPayment() {
|
||||||
|
$('#paymentModal').modal('hide');
|
||||||
|
|
||||||
|
const payBtn = $('button[onclick="processPayment()"]');
|
||||||
|
const originalText = payBtn.html();
|
||||||
|
payBtn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 支付中...');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '${pageContext.request.contextPath}/api/order/' + orderId + '/pay',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({}),
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
showMessage('💳 支付成功!', 'success');
|
||||||
|
setTimeout(() => {
|
||||||
|
loadOrderDetail(); // 重新加载订单详情
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
showMessage('❌ 支付失败:' + response.message, 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
showMessage('❌ 支付失败,请重试', 'error');
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
payBtn.prop('disabled', false).html(originalText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消订单
|
||||||
|
function cancelOrder() {
|
||||||
|
if (!confirm('确定要取消这个订单吗?\n\n取消后无法恢复。')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '${pageContext.request.contextPath}/api/order/' + orderId + '/cancel',
|
||||||
|
type: 'POST',
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
showMessage('订单已取消', 'success');
|
||||||
|
setTimeout(() => {
|
||||||
|
loadOrderDetail(); // 重新加载订单详情
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
showMessage('取消失败:' + response.message, 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
showMessage('取消失败,请重试', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认收货
|
||||||
|
function confirmReceipt() {
|
||||||
|
if (!confirm('确定已收到商品吗?\n\n确认后订单将标记为完成。')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '${pageContext.request.contextPath}/api/order/' + orderId + '/confirm',
|
||||||
|
type: 'POST',
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
showMessage('✅ 确认收货成功!', 'success');
|
||||||
|
setTimeout(() => {
|
||||||
|
loadOrderDetail(); // 重新加载订单详情
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
showMessage('确认失败:' + response.message, 'error');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
showMessage('确认失败,请重试', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态信息
|
||||||
|
function getStatusInfo(status) {
|
||||||
|
switch (status) {
|
||||||
|
case 1:
|
||||||
|
return {text: '待支付', class: 'bg-warning'};
|
||||||
|
case 2:
|
||||||
|
return {text: '已支付', class: 'bg-info'};
|
||||||
|
case 3:
|
||||||
|
return {text: '已发货', class: 'bg-primary'};
|
||||||
|
case 4:
|
||||||
|
return {text: '已完成', class: 'bg-success'};
|
||||||
|
case 5:
|
||||||
|
return {text: '已取消', class: 'bg-secondary'};
|
||||||
|
default:
|
||||||
|
return {text: '未知状态', class: 'bg-secondary'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期时间
|
||||||
|
function formatDateTime(dateTimeStr) {
|
||||||
|
if (!dateTimeStr) return '-';
|
||||||
|
const date = new Date(dateTimeStr);
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示错误
|
||||||
|
function showError(message) {
|
||||||
|
$('#errorMessage').text(message);
|
||||||
|
$('#errorState').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示消息
|
||||||
|
function showMessage(message, type = 'info') {
|
||||||
|
const alertDiv = document.createElement('div');
|
||||||
|
alertDiv.className = 'alert alert-' + (type == 'error' ? 'danger' : type) + ' alert-dismissible fade show position-fixed';
|
||||||
|
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
|
||||||
|
|
||||||
|
alertDiv.innerHTML = message + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button>';
|
||||||
|
|
||||||
|
document.body.appendChild(alertDiv);
|
||||||
|
|
||||||
|
// 3秒后自动消失
|
||||||
|
setTimeout(() => {
|
||||||
|
if (alertDiv.parentNode) {
|
||||||
|
alertDiv.remove();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.timeline {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item:not(:last-child)::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -22px;
|
||||||
|
top: 20px;
|
||||||
|
bottom: -20px;
|
||||||
|
width: 2px;
|
||||||
|
background: #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item.active:not(:last-child)::before {
|
||||||
|
background: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-marker {
|
||||||
|
position: absolute;
|
||||||
|
left: -30px;
|
||||||
|
top: 0;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #dee2e6;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item.active .timeline-marker {
|
||||||
|
border-color: #28a745;
|
||||||
|
background: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<%@ include file="common/footer.jsp" %>
|
||||||
@@ -494,11 +494,142 @@
|
|||||||
// 支付订单
|
// 支付订单
|
||||||
function payOrder(orderId) {
|
function payOrder(orderId) {
|
||||||
if (confirm('确定要支付这个订单吗?')) {
|
if (confirm('确定要支付这个订单吗?')) {
|
||||||
// 这里可以集成真实的支付接口
|
// 显示支付方式选择模态框
|
||||||
showMessage('支付功能开发中...', 'info');
|
showPaymentModal(orderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示支付方式选择模态框
|
||||||
|
function showPaymentModal(orderId) {
|
||||||
|
const modalHtml = `
|
||||||
|
<div class="modal fade" id="paymentModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">选择支付方式</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="payment-methods">
|
||||||
|
<div class="form-check mb-3" data-payment-method="alipay">
|
||||||
|
<input class="form-check-input" type="radio" name="paymentMethod" id="alipay" value="alipay">
|
||||||
|
<label class="form-check-label" for="alipay">
|
||||||
|
<i class="fab fa-alipay text-primary"></i> 支付宝
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-3" data-payment-method="wechat">
|
||||||
|
<input class="form-check-input" type="radio" name="paymentMethod" id="wechat" value="wechat">
|
||||||
|
<label class="form-check-label" for="wechat">
|
||||||
|
<i class="fab fa-weixin text-success"></i> 微信支付
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-3" data-payment-method="unionpay">
|
||||||
|
<input class="form-check-input" type="radio" name="paymentMethod" id="unionpay" value="unionpay">
|
||||||
|
<label class="form-check-label" for="unionpay">
|
||||||
|
<i class="fas fa-credit-card text-info"></i> 银联支付
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||||
|
<div id="paymentStatus" class="text-muted me-auto">
|
||||||
|
<small>选择支付方式后将自动处理支付</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 移除现有的支付模态框
|
||||||
|
$('#paymentModal').remove();
|
||||||
|
|
||||||
|
// 添加新的支付模态框
|
||||||
|
$('body').append(modalHtml);
|
||||||
|
|
||||||
|
// 设置订单ID到模态框数据属性中
|
||||||
|
$('#paymentModal').attr('data-order-id', orderId);
|
||||||
|
|
||||||
|
// 添加支付方式点击事件
|
||||||
|
$('#paymentModal').on('click', '.form-check[data-payment-method]', function () {
|
||||||
|
const paymentMethod = $(this).attr('data-payment-method');
|
||||||
|
const currentOrderId = $('#paymentModal').attr('data-order-id');
|
||||||
|
selectPaymentMethod(paymentMethod, currentOrderId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在模态框关闭时移除事件监听器
|
||||||
|
$('#paymentModal').on('hidden.bs.modal', function () {
|
||||||
|
$(this).off('click');
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示模态框
|
||||||
|
$('#paymentModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择支付方式并直接处理支付
|
||||||
|
function selectPaymentMethod(paymentMethod, orderId) {
|
||||||
|
// 如果orderId未定义,从模态框数据属性中获取
|
||||||
|
if (!orderId || orderId === 'undefined') {
|
||||||
|
orderId = $('#paymentModal').attr('data-order-id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证orderId
|
||||||
|
if (!orderId || orderId === 'undefined') {
|
||||||
|
showMessage('订单ID错误,请重新尝试', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Processing payment for order:', orderId, 'with method:', paymentMethod);
|
||||||
|
|
||||||
|
// 选中对应的单选框
|
||||||
|
$('#paymentModal input[value="' + paymentMethod + '"]').prop('checked', true);
|
||||||
|
|
||||||
|
// 显示支付处理中状态
|
||||||
|
$('#paymentStatus').html('<i class="fas fa-spinner fa-spin text-primary"></i> <small class="text-primary">正在处理支付...</small>');
|
||||||
|
|
||||||
|
// 禁用所有支付方式选项
|
||||||
|
$('.payment-methods .form-check').css('pointer-events', 'none').css('opacity', '0.6');
|
||||||
|
|
||||||
|
// 模拟短暂延迟后调用后端支付接口
|
||||||
|
setTimeout(function () {
|
||||||
|
$.ajax({
|
||||||
|
url: '${pageContext.request.contextPath}/api/order/' + orderId + '/pay',
|
||||||
|
type: 'POST',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
paymentMethod: paymentMethod
|
||||||
|
}),
|
||||||
|
success: function (response) {
|
||||||
|
if (response.success) {
|
||||||
|
$('#paymentStatus').html('<i class="fas fa-check-circle text-success"></i> <small class="text-success">支付成功!</small>');
|
||||||
|
|
||||||
|
// 2秒后关闭模态框并刷新订单列表
|
||||||
|
setTimeout(function () {
|
||||||
|
$('#paymentModal').modal('hide');
|
||||||
|
showMessage('支付成功!订单状态已更新为已支付', 'success');
|
||||||
|
refreshOrders();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
$('#paymentStatus').html('<i class="fas fa-times-circle text-danger"></i> <small class="text-danger">支付失败: ' + response.message + '</small>');
|
||||||
|
// 恢复支付方式选项
|
||||||
|
$('.payment-methods .form-check').css('pointer-events', 'auto').css('opacity', '1');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
let errorMessage = '支付失败,请稍后重试';
|
||||||
|
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||||
|
errorMessage = xhr.responseJSON.message;
|
||||||
|
}
|
||||||
|
$('#paymentStatus').html('<i class="fas fa-times-circle text-danger"></i> <small class="text-danger">' + errorMessage + '</small>');
|
||||||
|
// 恢复支付方式选项
|
||||||
|
$('.payment-methods .form-check').css('pointer-events', 'auto').css('opacity', '1');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1000); // 1秒延迟模拟支付处理
|
||||||
|
}
|
||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
function cancelOrder(orderId) {
|
function cancelOrder(orderId) {
|
||||||
if (confirm('确定要取消这个订单吗?')) {
|
if (confirm('确定要取消这个订单吗?')) {
|
||||||
@@ -626,6 +757,65 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 支付方式样式 */
|
||||||
|
.payment-methods .form-check {
|
||||||
|
border: 2px solid #dee2e6;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods .form-check:hover {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 4px 12px rgba(13, 110, 253, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods .form-check-input {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods .form-check-input:checked + .form-check-label {
|
||||||
|
color: #0d6efd;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods .form-check-input:checked {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods .form-check:has(.form-check-input:checked) {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
background-color: rgba(13, 110, 253, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods .form-check-label {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-right: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods .form-check-label i {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#paymentStatus {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<%@ include file="common/footer.jsp" %>
|
<%@ include file="common/footer.jsp" %>
|
||||||
Reference in New Issue
Block a user