Merge pull request 'feat(orders): order detail modal, list actions, use configured prefix' (#309) from worktree-order-center-list into main
Reviewed-on: CYL/wordpress-theme-aksrt#309
This commit is contained in:
@@ -521,6 +521,159 @@
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Order detail modal content */
|
||||
.aksrt-order-detail-dialog {
|
||||
width: min(560px, 100%);
|
||||
}
|
||||
|
||||
.aksrt-order-detail__list {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.aksrt-order-detail__row {
|
||||
display: grid;
|
||||
grid-template-columns: 96px minmax(0, 1fr);
|
||||
gap: 10px 14px;
|
||||
align-items: start;
|
||||
font-size: 13.5px;
|
||||
}
|
||||
|
||||
.aksrt-order-detail__row dt {
|
||||
margin: 0;
|
||||
color: var(--color-ink-3, #8b8f97);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.aksrt-order-detail__row dd {
|
||||
margin: 0;
|
||||
color: var(--color-ink, #1f2329);
|
||||
font-weight: 600;
|
||||
word-break: break-all;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.aksrt-order-detail__items,
|
||||
.aksrt-order-detail__timeline {
|
||||
margin-top: 16px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px dashed color-mix(in srgb, var(--color-line, #e5e7eb) 85%, transparent);
|
||||
}
|
||||
|
||||
.aksrt-order-detail__items h4,
|
||||
.aksrt-order-detail__timeline h4 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 13.5px;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink, #1f2329);
|
||||
}
|
||||
|
||||
.aksrt-order-detail__items ul,
|
||||
.aksrt-order-detail__timeline ol {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-order-detail__items li {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-2, #4b5563);
|
||||
}
|
||||
|
||||
.aksrt-order-detail__items li em {
|
||||
font-style: normal;
|
||||
color: var(--color-ink-3, #8b8f97);
|
||||
}
|
||||
|
||||
.aksrt-order-detail__items li strong {
|
||||
font-weight: 700;
|
||||
color: var(--color-ink, #1f2329);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.aksrt-order-detail__timeline li {
|
||||
display: grid;
|
||||
grid-template-columns: 140px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
font-size: 12.5px;
|
||||
color: var(--color-ink-2, #4b5563);
|
||||
}
|
||||
|
||||
.aksrt-order-detail__timeline time {
|
||||
color: var(--color-ink-3, #8b8f97);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.aksrt-modal__footer .btn-primary,
|
||||
.aksrt-modal__footer .btn-outline,
|
||||
.aksrt-modal__footer .btn-ghost,
|
||||
.aksrt-modal__footer a.btn-ghost,
|
||||
.aksrt-modal__footer a.btn-outline {
|
||||
min-height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-modal__footer .btn-primary {
|
||||
border: 1px solid transparent;
|
||||
background: var(--color-primary, #3b82f6);
|
||||
color: var(--color-on-primary, #fff);
|
||||
}
|
||||
|
||||
.aksrt-modal__footer .btn-primary.c-green {
|
||||
background: var(--color-success, #16a34a);
|
||||
}
|
||||
|
||||
.aksrt-modal__footer .btn-outline {
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
background: var(--surface-1, #fff);
|
||||
color: var(--color-ink-2, #4b5563);
|
||||
}
|
||||
|
||||
.aksrt-modal__footer .btn-ghost {
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
color: var(--color-ink-3, #8b8f97);
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.aksrt-order-detail__row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.aksrt-order-detail__timeline li {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.aksrt-modal__footer {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.aksrt-modal__footer .btn-primary,
|
||||
.aksrt-modal__footer .btn-outline,
|
||||
.aksrt-modal__footer .btn-ghost,
|
||||
.aksrt-modal__footer a {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* legacy item classes (if any remain) */
|
||||
.aksrt-orders-page__item {
|
||||
background: var(--surface-1);
|
||||
|
||||
+221
-4
@@ -396,8 +396,39 @@
|
||||
return page.querySelector('[data-aksrt-order-card="' + orderNumber + '"]');
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value == null ? "" : value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function formatMoney(order) {
|
||||
var amount = Number(order && order.amount != null ? order.amount : 0);
|
||||
if (order && order.is_points) {
|
||||
var symbol = order.currency_symbol || "积分";
|
||||
return escapeHtml(symbol) + " " + Math.round(amount);
|
||||
}
|
||||
return "¥" + amount.toFixed(2);
|
||||
}
|
||||
|
||||
/** Plain money for table cells (formatMoney already escapes). */
|
||||
function formatMoneyText(order) {
|
||||
var amount = Number(order && order.amount != null ? order.amount : 0);
|
||||
if (order && order.is_points) {
|
||||
var symbol = order.currency_symbol || "积分";
|
||||
return String(symbol) + " " + Math.round(amount);
|
||||
}
|
||||
return "¥" + amount.toFixed(2);
|
||||
}
|
||||
|
||||
function updateCardStatus(card, newStatus, statusLabel) {
|
||||
if (!card) return;
|
||||
if (newStatus) {
|
||||
card.setAttribute("data-order-status", newStatus);
|
||||
}
|
||||
var badge = card.querySelector(".aksrt-orders-page__card-status, .aksrt-orders-page__item-badge");
|
||||
if (badge) {
|
||||
badge.textContent = statusLabel || newStatus;
|
||||
@@ -414,6 +445,162 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Order detail modal ── */
|
||||
var detailModal = page.querySelector("[data-aksrt-order-detail-modal]");
|
||||
var detailBody = page.querySelector("[data-aksrt-order-detail-body]");
|
||||
var detailFooter = page.querySelector("[data-aksrt-order-detail-footer]");
|
||||
var detailOrderNumber = "";
|
||||
|
||||
function closeOrderDetailModal() {
|
||||
if (detailModal) detailModal.hidden = true;
|
||||
detailOrderNumber = "";
|
||||
document.documentElement.classList.remove("aksrt-modal-open");
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === "Escape" && detailModal && !detailModal.hidden) {
|
||||
closeOrderDetailModal();
|
||||
}
|
||||
});
|
||||
|
||||
function openOrderDetailModal(orderNumber) {
|
||||
if (!detailModal || !detailBody || !detailFooter) {
|
||||
showToast("订单详情组件未加载");
|
||||
return;
|
||||
}
|
||||
detailOrderNumber = orderNumber || "";
|
||||
detailBody.innerHTML = '<p class="muted-2-color">加载中…</p>';
|
||||
detailFooter.innerHTML = "";
|
||||
detailModal.hidden = false;
|
||||
document.documentElement.classList.add("aksrt-modal-open");
|
||||
|
||||
request(orderPath(orderNumber), undefined, "GET").then(function (result) {
|
||||
var order = null;
|
||||
if (result && result.data && result.data.order_number) {
|
||||
order = result.data;
|
||||
} else if (result && result.data && result.data.data && result.data.data.order_number) {
|
||||
order = result.data.data;
|
||||
} else if (result && result.order_number) {
|
||||
order = result;
|
||||
}
|
||||
if (result && result.success === false) {
|
||||
detailBody.innerHTML = '<p class="c-red">' + escapeHtml((result && result.message) || "加载失败") + "</p>";
|
||||
return;
|
||||
}
|
||||
if (!order || !order.order_number) {
|
||||
detailBody.innerHTML = '<p class="c-red">订单不存在</p>';
|
||||
return;
|
||||
}
|
||||
renderOrderDetail(order);
|
||||
}).catch(function () {
|
||||
detailBody.innerHTML = '<p class="c-red">网络错误,请稍后重试</p>';
|
||||
});
|
||||
}
|
||||
|
||||
function renderOrderDetail(order) {
|
||||
if (!detailBody || !detailFooter) return;
|
||||
var actions = (order && order.actions) || {};
|
||||
var payment = order.payment_method || {};
|
||||
var paymentLabel = payment.label || payment.value || "—";
|
||||
var qty = order.quantity || 1;
|
||||
if ((!qty || qty < 1) && order.items && order.items.length) {
|
||||
qty = 0;
|
||||
order.items.forEach(function (item) {
|
||||
qty += Math.max(1, parseInt(item.quantity || item.qty || 1, 10) || 1);
|
||||
});
|
||||
}
|
||||
if (!qty || qty < 1) qty = 1;
|
||||
|
||||
var rows = [
|
||||
["订单状态", order.status_label || order.status || "—"],
|
||||
["订单名称", order.product_name || (order.product && order.product.title) || "—"],
|
||||
["订单类型", order.product_type_label || order.product_type || "—"],
|
||||
["订单数量", String(qty)],
|
||||
["应付金额", formatMoneyText(order)],
|
||||
["订单编号", order.order_number || "—"],
|
||||
["创建时间", order.created_at || "—"],
|
||||
["支付时间", order.paid_at || "—"],
|
||||
["支付方式", paymentLabel]
|
||||
];
|
||||
|
||||
if (Number(order.shipping_fee || 0) > 0) {
|
||||
rows.push(["运费", "¥" + Number(order.shipping_fee || 0).toFixed(2)]);
|
||||
}
|
||||
if (Number(order.discount || 0) > 0) {
|
||||
rows.push(["优惠", "¥" + Number(order.discount || 0).toFixed(2)]);
|
||||
}
|
||||
if (order.customer_note) {
|
||||
rows.push(["备注", order.customer_note]);
|
||||
}
|
||||
|
||||
var html = '<dl class="aksrt-order-detail__list">';
|
||||
rows.forEach(function (row) {
|
||||
html += '<div class="aksrt-order-detail__row">';
|
||||
html += '<dt>' + escapeHtml(row[0]) + '</dt>';
|
||||
// Money strings already composed; still escape all cells for safety.
|
||||
html += '<dd>' + escapeHtml(row[1]) + '</dd>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += "</dl>";
|
||||
|
||||
if (order.items && order.items.length) {
|
||||
html += '<div class="aksrt-order-detail__items"><h4>商品明细</h4><ul>';
|
||||
order.items.forEach(function (item) {
|
||||
var title = item.product_title || item.title || order.product_name || "商品";
|
||||
var itemQty = item.quantity || item.qty || 1;
|
||||
var price = item.pay_price != null ? item.pay_price : (item.unit_price || 0);
|
||||
html += '<li><span>' + escapeHtml(title) + '</span>';
|
||||
html += '<em>×' + escapeHtml(String(itemQty)) + '</em>';
|
||||
if (order.is_points) {
|
||||
html += '<strong>' + escapeHtml(order.currency_symbol || "积分") + " " + Math.round(Number(price || 0)) + "</strong>";
|
||||
} else {
|
||||
html += '<strong>¥' + Number(price || 0).toFixed(2) + "</strong>";
|
||||
}
|
||||
html += "</li>";
|
||||
});
|
||||
html += "</ul></div>";
|
||||
}
|
||||
|
||||
if (order.timeline && order.timeline.length) {
|
||||
html += '<div class="aksrt-order-detail__timeline"><h4>订单进度</h4><ol>';
|
||||
order.timeline.forEach(function (tl) {
|
||||
html += '<li><time>' + escapeHtml(tl.time || "") + "</time><span>" + escapeHtml(tl.label || "") + "</span></li>";
|
||||
});
|
||||
html += "</ol></div>";
|
||||
}
|
||||
|
||||
detailBody.innerHTML = html;
|
||||
|
||||
var footer = "";
|
||||
// Status-aware footer actions.
|
||||
if (actions.can_cancel) {
|
||||
footer += '<button type="button" class="btn-ghost" data-aksrt-order-cancel="' + escapeHtml(order.order_number) + '">关闭订单</button>';
|
||||
}
|
||||
if (actions.can_pay) {
|
||||
footer += '<button type="button" class="btn-primary" data-aksrt-order-pay="' + escapeHtml(order.order_number) + '" data-aksrt-order-method="' + escapeHtml((payment && payment.value) || "") + '">立即支付</button>';
|
||||
}
|
||||
if (actions.can_receipt) {
|
||||
footer += '<button type="button" class="btn-primary" data-aksrt-order-receipt="' + escapeHtml(order.order_number) + '">确认收货</button>';
|
||||
}
|
||||
if (actions.can_review) {
|
||||
footer += '<button type="button" class="btn-primary c-green" data-aksrt-order-review="' + escapeHtml(order.order_number) + '">评价</button>';
|
||||
}
|
||||
if (actions.can_after_sale) {
|
||||
footer += '<button type="button" class="btn-outline" data-aksrt-order-after-sale="' + escapeHtml(order.order_number) + '">申请售后</button>';
|
||||
}
|
||||
if (actions.can_rebuy) {
|
||||
footer += '<button type="button" class="btn-outline" data-aksrt-order-rebuy="' + escapeHtml(order.order_number) + '">再次购买</button>';
|
||||
}
|
||||
if (actions.can_view_product && order.permalink) {
|
||||
footer += '<a class="btn-ghost" href="' + escapeHtml(order.permalink) + '">查看商品</a>';
|
||||
}
|
||||
if (order.secondary_url && order.secondary_label) {
|
||||
footer += '<a class="btn-ghost" href="' + escapeHtml(order.secondary_url) + '">' + escapeHtml(order.secondary_label) + "</a>";
|
||||
}
|
||||
footer += '<button type="button" class="btn-outline" data-aksrt-order-detail-close>关闭</button>';
|
||||
detailFooter.innerHTML = footer;
|
||||
}
|
||||
|
||||
/* ── After-Sale Modal ── */
|
||||
var afterSaleModal = page.querySelector("[data-aksrt-aftersale-modal]");
|
||||
var afterSaleForm = page.querySelector("[data-aksrt-aftersale-form]");
|
||||
@@ -496,6 +683,20 @@
|
||||
|
||||
/* ── Event delegation ── */
|
||||
page.addEventListener("click", function (event) {
|
||||
// Order detail open
|
||||
var detailBtn = event.target.closest("[data-aksrt-order-detail]");
|
||||
if (detailBtn && !event.target.closest("[data-aksrt-order-detail-modal]")) {
|
||||
var detailOrder = detailBtn.getAttribute("data-aksrt-order-detail");
|
||||
if (detailOrder) openOrderDetailModal(detailOrder);
|
||||
return;
|
||||
}
|
||||
|
||||
// Order detail close
|
||||
if (event.target.closest("[data-aksrt-order-detail-close]")) {
|
||||
closeOrderDetailModal();
|
||||
return;
|
||||
}
|
||||
|
||||
// Timeline toggle
|
||||
var timelineToggle = event.target.closest("[data-aksrt-toggle-timeline]");
|
||||
if (timelineToggle) {
|
||||
@@ -521,6 +722,9 @@
|
||||
}
|
||||
showToast("已确认收货");
|
||||
updateCardStatus(findOrderCard(receiptOrder), "completed", "已完成");
|
||||
if (detailOrderNumber === receiptOrder) {
|
||||
closeOrderDetailModal();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
receiptBtn.disabled = false;
|
||||
@@ -557,6 +761,7 @@
|
||||
if (reviewBtn) {
|
||||
var reviewOrder = reviewBtn.getAttribute("data-aksrt-order-review");
|
||||
if (!reviewOrder) return;
|
||||
closeOrderDetailModal();
|
||||
openOrderReviewModal(reviewOrder, reviewBtn);
|
||||
return;
|
||||
}
|
||||
@@ -568,13 +773,14 @@
|
||||
// 支付方式取订单创建时选定的方式(按钮 data 属性可覆盖);
|
||||
// 留空则由服务端使用订单存储的 payment_method。
|
||||
var payMethod = payButton.getAttribute("data-aksrt-order-method") || "";
|
||||
var payLabel = payButton.textContent || "去支付";
|
||||
payButton.disabled = true;
|
||||
payButton.textContent = "处理中...";
|
||||
request(orderPath(payOrderNumber, "/pay"), payMethod ? { payment_method: payMethod } : {})
|
||||
.then(function (result) {
|
||||
if (!result.success) {
|
||||
payButton.disabled = false;
|
||||
payButton.textContent = "立即支付";
|
||||
payButton.textContent = payLabel;
|
||||
showToast(result.message || "支付失败。");
|
||||
return;
|
||||
}
|
||||
@@ -600,10 +806,13 @@
|
||||
showToast("支付成功!");
|
||||
var card = findOrderCard(payOrderNumber);
|
||||
updateCardStatus(card, "paid", "已支付");
|
||||
if (detailOrderNumber === payOrderNumber) {
|
||||
closeOrderDetailModal();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
payButton.disabled = false;
|
||||
payButton.textContent = "立即支付";
|
||||
payButton.textContent = payLabel;
|
||||
showToast("网络错误,请稍后重试。");
|
||||
});
|
||||
return;
|
||||
@@ -613,23 +822,30 @@
|
||||
var cancelButton = event.target.closest("[data-aksrt-order-cancel]");
|
||||
if (cancelButton) {
|
||||
var cancelOrderNumber = cancelButton.getAttribute("data-aksrt-order-cancel");
|
||||
if (!window.confirm("确认关闭该订单?关闭后不可再支付。")) {
|
||||
return;
|
||||
}
|
||||
var cancelLabel = cancelButton.textContent || "取消";
|
||||
cancelButton.disabled = true;
|
||||
cancelButton.textContent = "取消中...";
|
||||
request(orderPath(cancelOrderNumber, "/cancel"))
|
||||
.then(function (result) {
|
||||
if (!result.success) {
|
||||
cancelButton.disabled = false;
|
||||
cancelButton.textContent = "取消订单";
|
||||
cancelButton.textContent = cancelLabel;
|
||||
showToast(result.message || "取消失败。");
|
||||
return;
|
||||
}
|
||||
showToast("订单已取消。");
|
||||
var card = findOrderCard(cancelOrderNumber);
|
||||
updateCardStatus(card, "cancelled", "已取消");
|
||||
if (detailOrderNumber === cancelOrderNumber) {
|
||||
closeOrderDetailModal();
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
cancelButton.disabled = false;
|
||||
cancelButton.textContent = "取消订单";
|
||||
cancelButton.textContent = cancelLabel;
|
||||
showToast("网络错误,请稍后重试。");
|
||||
});
|
||||
return;
|
||||
@@ -639,6 +855,7 @@
|
||||
var afterSaleButton = event.target.closest("[data-aksrt-order-after-sale]");
|
||||
if (afterSaleButton) {
|
||||
var afterSaleOrderNumber = afterSaleButton.getAttribute("data-aksrt-order-after-sale");
|
||||
closeOrderDetailModal();
|
||||
openAfterSaleModal(afterSaleOrderNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -209,9 +209,11 @@ $pending_count = (int) ($stats['pending'] ?? 0);
|
||||
? esc_html($symbol) . ' ' . esc_html((string) (int) round((float) ($order['amount'] ?? 0)))
|
||||
: '¥' . esc_html(number_format((float) ($order['amount'] ?? 0), 2));
|
||||
?>
|
||||
<article class="aksrt-orders-page__card" data-aksrt-order-card="<?php echo esc_attr($on); ?>" data-product-type="<?php echo esc_attr($ptype); ?>">
|
||||
<article class="aksrt-orders-page__card" data-aksrt-order-card="<?php echo esc_attr($on); ?>" data-product-type="<?php echo esc_attr($ptype); ?>" data-order-status="<?php echo esc_attr($os); ?>">
|
||||
<div class="aksrt-orders-page__card-head">
|
||||
<span class="aksrt-orders-page__card-type"><?php echo esc_html((string) ($order['product_type_label'] ?? '')); ?></span>
|
||||
<?php if ((string) ($order['product_type_label'] ?? '') !== '') : ?>
|
||||
<span class="aksrt-orders-page__card-type"><?php echo esc_html((string) $order['product_type_label']); ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="aksrt-orders-page__card-number"><?php echo esc_html($on); ?></span>
|
||||
<time class="aksrt-orders-page__card-time"><?php echo esc_html((string) ($order['created_at'] ?? '')); ?></time>
|
||||
<span class="aksrt-orders-page__card-status"><?php echo esc_html((string) ($order['status_label'] ?? '')); ?></span>
|
||||
@@ -290,34 +292,6 @@ $pending_count = (int) ($stats['pending'] ?? 0);
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-orders-page__card-actions">
|
||||
<?php if (!empty($actions['can_pay'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-pay="<?php echo esc_attr($on); ?>" data-aksrt-order-method="<?php echo esc_attr((string) ($order['payment_method']['value'] ?? '')); ?>"><?php esc_html_e('去支付', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_receipt'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-receipt="<?php echo esc_attr($on); ?>"><?php esc_html_e('确认收货', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_review']) || (class_exists('\AKSRT\Payment\Order') && $os === \AKSRT\Payment\Order::STATUS_PENDING_REVIEW)) : ?>
|
||||
<button type="button" class="btn-primary btn-sm c-green" data-aksrt-order-review="<?php echo esc_attr($on); ?>"><?php esc_html_e('评价', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_cancel'])) : ?>
|
||||
<button type="button" class="btn-ghost btn-sm" data-aksrt-order-cancel="<?php echo esc_attr($on); ?>"><?php esc_html_e('取消', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_rebuy'])) : ?>
|
||||
<button type="button" class="btn-outline btn-sm" data-aksrt-order-rebuy="<?php echo esc_attr($on); ?>"><?php esc_html_e('再次购买', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_after_sale'])) : ?>
|
||||
<button type="button" class="btn-outline btn-sm" data-aksrt-order-after-sale="<?php echo esc_attr($on); ?>"><?php esc_html_e('售后', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_view']) && !empty($order['permalink'])) : ?>
|
||||
<a class="btn-outline btn-sm" href="<?php echo esc_url((string) $order['permalink']); ?>"><?php
|
||||
echo (class_exists('\AKSRT\Core\DomainModel') && $ptype === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE)
|
||||
? esc_html__('去查看', 'aksrt')
|
||||
: esc_html__('查看', 'aksrt');
|
||||
?></a>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['secondary_url']) && !empty($order['secondary_label'])) : ?>
|
||||
<a class="btn-ghost btn-sm" href="<?php echo esc_url((string) $order['secondary_url']); ?>"><?php echo esc_html((string) $order['secondary_label']); ?></a>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['time_left'])) : ?>
|
||||
<span class="aksrt-orders-page__countdown" data-aksrt-order-countdown="<?php echo esc_attr((string) $order['time_left']); ?>">
|
||||
<?php
|
||||
@@ -329,6 +303,32 @@ $pending_count = (int) ($stats['pending'] ?? 0);
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="btn-outline btn-sm" data-aksrt-order-detail="<?php echo esc_attr($on); ?>"><?php esc_html_e('查看', 'aksrt'); ?></button>
|
||||
<?php if (!empty($actions['can_cancel'])) : ?>
|
||||
<button type="button" class="btn-ghost btn-sm" data-aksrt-order-cancel="<?php echo esc_attr($on); ?>"><?php esc_html_e('取消', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_pay'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-pay="<?php echo esc_attr($on); ?>" data-aksrt-order-method="<?php echo esc_attr((string) ($order['payment_method']['value'] ?? '')); ?>"><?php esc_html_e('去支付', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_receipt'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-receipt="<?php echo esc_attr($on); ?>"><?php esc_html_e('确认收货', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_review']) || (class_exists('\AKSRT\Payment\Order') && $os === \AKSRT\Payment\Order::STATUS_PENDING_REVIEW)) : ?>
|
||||
<button type="button" class="btn-primary btn-sm c-green" data-aksrt-order-review="<?php echo esc_attr($on); ?>"><?php esc_html_e('评价', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_rebuy'])) : ?>
|
||||
<button type="button" class="btn-outline btn-sm" data-aksrt-order-rebuy="<?php echo esc_attr($on); ?>"><?php esc_html_e('再次购买', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_after_sale'])) : ?>
|
||||
<button type="button" class="btn-outline btn-sm" data-aksrt-order-after-sale="<?php echo esc_attr($on); ?>"><?php esc_html_e('售后', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_view_product']) && !empty($order['permalink'])) : ?>
|
||||
<a class="btn-ghost btn-sm" href="<?php echo esc_url((string) $order['permalink']); ?>"><?php
|
||||
echo (class_exists('\AKSRT\Core\DomainModel') && $ptype === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE)
|
||||
? esc_html__('去内容', 'aksrt')
|
||||
: esc_html__('商品页', 'aksrt');
|
||||
?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
@@ -358,6 +358,21 @@ $pending_count = (int) ($stats['pending'] ?? 0);
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Order detail modal -->
|
||||
<div class="aksrt-modal" data-aksrt-order-detail-modal hidden>
|
||||
<div class="aksrt-modal__overlay" data-aksrt-order-detail-close></div>
|
||||
<div class="aksrt-modal__dialog aksrt-order-detail-dialog" role="dialog" aria-modal="true" aria-labelledby="aksrt-order-detail-title">
|
||||
<div class="aksrt-modal__header">
|
||||
<h3 id="aksrt-order-detail-title"><?php esc_html_e('订单详情', 'aksrt'); ?></h3>
|
||||
<button type="button" class="aksrt-modal__close" data-aksrt-order-detail-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>">×</button>
|
||||
</div>
|
||||
<div class="aksrt-modal__body" data-aksrt-order-detail-body>
|
||||
<p class="muted-2-color"><?php esc_html_e('加载中…', 'aksrt'); ?></p>
|
||||
</div>
|
||||
<div class="aksrt-modal__footer" data-aksrt-order-detail-footer></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- After-sale modal -->
|
||||
<div class="aksrt-modal" data-aksrt-aftersale-modal hidden>
|
||||
<div class="aksrt-modal__overlay"></div>
|
||||
|
||||
@@ -41,7 +41,7 @@ trait OrderRepositoryCore {
|
||||
$note = sanitize_textarea_field($note_raw);
|
||||
}
|
||||
|
||||
$wpdb->insert($this->table(), [
|
||||
$row = [
|
||||
'order_number' => $order_number,
|
||||
'user_id' => (int) ($data['user_id'] ?? 0),
|
||||
'product_type' => sanitize_key((string) ($data['product_type'] ?? '')),
|
||||
@@ -53,9 +53,17 @@ trait OrderRepositoryCore {
|
||||
'payment_transaction_id' => sanitize_text_field((string) ($data['payment_transaction_id'] ?? '')),
|
||||
'created_at' => $data['created_at'] ?? current_time('mysql'),
|
||||
'paid_at' => $data['paid_at'] ?? null,
|
||||
'expires_at' => $data['expires_at'] ?? null,
|
||||
'note' => $note,
|
||||
], ['%s', '%d', '%s', '%d', '%f', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']);
|
||||
];
|
||||
$formats = ['%s', '%d', '%s', '%d', '%f', '%s', '%s', '%s', '%s', '%s', '%s', '%s'];
|
||||
|
||||
// Honour configured expiry when callers pass expires_at (shop checkout etc.).
|
||||
if (array_key_exists('expires_at', $data) && $data['expires_at'] !== null && $data['expires_at'] !== '') {
|
||||
$row['expires_at'] = (string) $data['expires_at'];
|
||||
$formats[] = '%s';
|
||||
}
|
||||
|
||||
$wpdb->insert($this->table(), $row, $formats);
|
||||
|
||||
if ($wpdb->insert_id) {
|
||||
return $order_number;
|
||||
@@ -506,8 +514,36 @@ trait OrderRepositoryCore {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Build a unique order number using the admin-configured prefix.
|
||||
*
|
||||
* Column + major gateways (WeChat out_trade_no) cap at 32 chars, so the
|
||||
* prefix is trimmed to leave room for {YmdHis}{4 digits} (=18).
|
||||
*/
|
||||
private function generate_order_number(): string {
|
||||
return 'AKS' . date('YmdHis') . str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
||||
$prefix = 'AKSRT';
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_order_settings')) {
|
||||
$settings = \AKSRT\Settings\Settings::get_order_settings();
|
||||
$configured = sanitize_text_field((string) ($settings['prefix'] ?? ''));
|
||||
if ($configured !== '') {
|
||||
$prefix = $configured;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep only gateway-safe chars (REST + WeChat/Alipay out_trade_no).
|
||||
$prefix = preg_replace('/[^A-Za-z0-9_-]/', '', $prefix);
|
||||
if (!is_string($prefix) || $prefix === '') {
|
||||
$prefix = 'AKSRT';
|
||||
}
|
||||
|
||||
$body = date('YmdHis') . str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
||||
// Reserve 18 for body; leave ≥1 char of prefix so numbers stay human-readable.
|
||||
$max_prefix = max(1, 32 - strlen($body));
|
||||
if (strlen($prefix) > $max_prefix) {
|
||||
$prefix = substr($prefix, 0, $max_prefix);
|
||||
}
|
||||
|
||||
return $prefix . $body;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -185,7 +185,9 @@ class UserOrders {
|
||||
$can_receipt = $is_shop && $status === Order::STATUS_DELIVERED;
|
||||
$can_review = $is_shop && $status === Order::STATUS_PENDING_REVIEW;
|
||||
$can_rebuy = $is_shop && in_array($status, [Order::STATUS_COMPLETED, Order::STATUS_PENDING_REVIEW], true);
|
||||
$can_view = $permalink !== '' && in_array($status, [
|
||||
// Order detail modal is always available; product permalink is optional.
|
||||
$can_view = true;
|
||||
$can_view_product = $permalink !== '' && in_array($status, [
|
||||
Order::STATUS_PAID,
|
||||
Order::STATUS_DELIVERED,
|
||||
Order::STATUS_COMPLETED,
|
||||
@@ -232,14 +234,16 @@ class UserOrders {
|
||||
'shipping_records' => $shipping_records,
|
||||
'pay_url' => '#',
|
||||
'actions' => [
|
||||
'can_pay' => $can_pay,
|
||||
'can_cancel' => $can_cancel,
|
||||
'can_after_sale' => $can_after_sale,
|
||||
'can_receipt' => $can_receipt,
|
||||
'can_review' => $can_review,
|
||||
'can_rebuy' => $can_rebuy,
|
||||
'can_view' => $can_view,
|
||||
'can_pay' => $can_pay,
|
||||
'can_cancel' => $can_cancel,
|
||||
'can_after_sale' => $can_after_sale,
|
||||
'can_receipt' => $can_receipt,
|
||||
'can_review' => $can_review,
|
||||
'can_rebuy' => $can_rebuy,
|
||||
'can_view' => $can_view,
|
||||
'can_view_product' => $can_view_product,
|
||||
],
|
||||
'quantity' => $this->resolve_order_quantity($order_items, $meta),
|
||||
'time_left' => $this->get_order_countdown($expires_at, $status),
|
||||
'secondary_url' => $this->resolve_secondary_url($product_type),
|
||||
'secondary_label' => $this->resolve_secondary_label($product_type),
|
||||
@@ -534,4 +538,25 @@ class UserOrders {
|
||||
$left = strtotime($expires_at) - time();
|
||||
return max(0, (int) $left);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum line quantities for display (shop multi-item or single meta item).
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $order_items
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function resolve_order_quantity(array $order_items, array $meta): int {
|
||||
$qty = 0;
|
||||
$source = !empty($order_items) ? $order_items : (is_array($meta['items'] ?? null) ? $meta['items'] : []);
|
||||
foreach ($source as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$qty += max(1, (int) ($row['quantity'] ?? $row['qty'] ?? 1));
|
||||
}
|
||||
if ($qty <= 0) {
|
||||
$qty = max(1, (int) ($meta['quantity'] ?? $meta['qty'] ?? 1));
|
||||
}
|
||||
return $qty;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user