Merge pull request 'feat(ux): after-sale multi-step, ban appeal, message types, coupon wallet' (#217) from worktree-commerce-user-ux-1to1 into main
Reviewed-on: CYL/wordpress-theme-aksrt#217
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Ban appeal page styles.
|
||||
* @package AKSRT
|
||||
*/
|
||||
.aksrt-ban-appeal .form-control {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--main-border-color, #ddd);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.aksrt-ban-appeal .mb15 { margin-bottom: 15px; }
|
||||
.aksrt-ban-appeal .but.c-blue {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--focus-color, #2f6df6);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.aksrt-ban-appeal .c-red { color: #e74c3c; }
|
||||
.aksrt-ban-appeal .muted-box {
|
||||
background: var(--surface-2, #f5f6f7);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -92,3 +92,89 @@
|
||||
.aksrt-orders-page__tabs {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
|
||||
/* After-sale multi-step + coupon wallet */
|
||||
.aksrt-orders-page__extra-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
.aksrt-orders-page__extra-link {
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
.aksrt-orders-page__extra-link.is-active {
|
||||
background: var(--focus-color, var(--color-primary));
|
||||
color: #fff;
|
||||
}
|
||||
.aksrt-as-list { display: grid; gap: 12px; }
|
||||
.aksrt-as-card {
|
||||
border: 1px solid var(--main-border-color, #eee);
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
background: var(--main-bg-color, #fff);
|
||||
}
|
||||
.aksrt-as-card__head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.aksrt-as-card__status {
|
||||
margin-left: auto;
|
||||
color: var(--focus-color, #2f6df6);
|
||||
font-weight: 600;
|
||||
}
|
||||
.aksrt-as-timeline {
|
||||
margin: 10px 0 0;
|
||||
padding-left: 18px;
|
||||
color: var(--muted-2-color, #888);
|
||||
font-size: 12px;
|
||||
}
|
||||
.aksrt-as-card__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.aksrt-coupon-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.aksrt-coupon-card {
|
||||
border: 1px dashed var(--focus-color, #2f6df6);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: color-mix(in srgb, var(--focus-color, #2f6df6) 6%, #fff);
|
||||
}
|
||||
.aksrt-coupon-card__value {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--focus-color, #2f6df6);
|
||||
}
|
||||
.aksrt-coupon-card__code {
|
||||
font-family: ui-monospace, monospace;
|
||||
margin: 6px 0;
|
||||
}
|
||||
.aksrt-coupon-card__meta {
|
||||
font-size: 12px;
|
||||
color: var(--muted-2-color, #888);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.aksrt-orders-page__field { margin-bottom: 12px; }
|
||||
.aksrt-orders-page__input,
|
||||
.aksrt-orders-page__select,
|
||||
.aksrt-orders-page__textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--main-border-color, #ddd);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Ban appeal form submit.
|
||||
* @package AKSRT
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
function ajaxUrl() {
|
||||
if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl;
|
||||
if (window._win && window._win.ajax_url) return window._win.ajax_url;
|
||||
return '/wp-admin/admin-ajax.php';
|
||||
}
|
||||
function toast(msg) {
|
||||
if (window.aksrt && typeof window.aksrt.toast === 'function') {
|
||||
window.aksrt.toast(msg);
|
||||
return;
|
||||
}
|
||||
window.alert(msg);
|
||||
}
|
||||
function init() {
|
||||
var form = document.querySelector('.aksrt-ban-appeal-form');
|
||||
if (!form) return;
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var btn = form.querySelector('button[type="submit"]');
|
||||
if (btn) btn.disabled = true;
|
||||
var fd = new FormData(form);
|
||||
if (!fd.get('action')) fd.set('action', 'aksrt_ban_appeal_submit');
|
||||
fetch(ajaxUrl(), { method: 'POST', body: fd, credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if (!data || data.error) {
|
||||
toast((data && data.msg) || '提交失败');
|
||||
return;
|
||||
}
|
||||
toast(data.msg || '提交成功');
|
||||
if (data.reload) window.setTimeout(function () { location.reload(); }, 400);
|
||||
})
|
||||
.catch(function () { toast('网络错误'); })
|
||||
.finally(function () { if (btn) btn.disabled = false; });
|
||||
});
|
||||
}
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
||||
else init();
|
||||
})();
|
||||
+143
-2
@@ -617,9 +617,150 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Close modal
|
||||
if (event.target.closest("[data-aksrt-aftersale-modal-close]") || (event.target.classList && event.target.classList.contains("aksrt-modal__overlay"))) {
|
||||
// Return shipping
|
||||
var returnShipBtn = event.target.closest("[data-aksrt-return-ship]");
|
||||
if (returnShipBtn) {
|
||||
openReturnShipModal(returnShipBtn.getAttribute("data-aksrt-return-ship"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel after-sale ticket
|
||||
var asCancelBtn = event.target.closest("[data-aksrt-as-cancel]");
|
||||
if (asCancelBtn) {
|
||||
var tid = asCancelBtn.getAttribute("data-aksrt-as-cancel");
|
||||
if (!tid || !window.confirm("确认取消该售后申请?")) return;
|
||||
asCancelBtn.disabled = true;
|
||||
request("/shop/frontend/after-sale/" + tid + "/cancel", { reason: "" })
|
||||
.then(function (result) {
|
||||
asCancelBtn.disabled = false;
|
||||
if (!result || !result.success) {
|
||||
showToast((result && result.message) || "取消失败");
|
||||
return;
|
||||
}
|
||||
showToast(result.message || "已取消售后");
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(function () {
|
||||
asCancelBtn.disabled = false;
|
||||
showToast("网络错误");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Close after-sale modal
|
||||
if (event.target.closest("[data-aksrt-aftersale-modal-close]") || (event.target.classList && event.target.classList.contains("aksrt-modal__overlay") && event.target.closest("[data-aksrt-aftersale-modal]"))) {
|
||||
closeAfterSaleModal();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close return-ship modal
|
||||
if (event.target.closest("[data-aksrt-return-ship-close]") || (event.target.classList && event.target.classList.contains("aksrt-modal__overlay") && event.target.closest("[data-aksrt-return-ship-modal]"))) {
|
||||
closeReturnShipModal();
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Return shipping modal ── */
|
||||
var returnShipModal = page.querySelector("[data-aksrt-return-ship-modal]");
|
||||
var returnShipForm = page.querySelector("[data-aksrt-return-ship-form]");
|
||||
function openReturnShipModal(ticketId) {
|
||||
if (!returnShipModal || !returnShipForm) return;
|
||||
returnShipForm.reset();
|
||||
var idInput = returnShipForm.querySelector('[name="ticket_id"]');
|
||||
if (idInput) idInput.value = ticketId || "";
|
||||
returnShipModal.hidden = false;
|
||||
}
|
||||
function closeReturnShipModal() {
|
||||
if (returnShipModal) returnShipModal.hidden = true;
|
||||
}
|
||||
if (returnShipForm) {
|
||||
returnShipForm.addEventListener("submit", function (event) {
|
||||
event.preventDefault();
|
||||
var fd = new FormData(returnShipForm);
|
||||
var tid = fd.get("ticket_id");
|
||||
var carrier = (fd.get("carrier") || "").trim();
|
||||
var tracking = (fd.get("tracking_no") || "").trim();
|
||||
if (!carrier || !tracking) {
|
||||
showToast("请填写快递公司与运单号");
|
||||
return;
|
||||
}
|
||||
var btn = returnShipForm.querySelector('button[type="submit"]');
|
||||
if (btn) { btn.disabled = true; }
|
||||
request("/shop/frontend/after-sale/" + tid + "/return-shipping", {
|
||||
carrier: carrier,
|
||||
tracking_no: tracking
|
||||
}).then(function (result) {
|
||||
if (btn) btn.disabled = false;
|
||||
if (!result || !result.success) {
|
||||
showToast((result && result.message) || "提交失败");
|
||||
return;
|
||||
}
|
||||
closeReturnShipModal();
|
||||
showToast(result.message || "退货物流已提交");
|
||||
window.location.reload();
|
||||
}).catch(function () {
|
||||
if (btn) btn.disabled = false;
|
||||
showToast("网络错误");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Coupon wallet ── */
|
||||
var couponRoot = page.querySelector("[data-aksrt-coupon-wallet-list]");
|
||||
if (couponRoot) {
|
||||
request("/shop/frontend/coupons").then(function (result) {
|
||||
var items = (result && result.data && result.data.items) || (result && result.items) || [];
|
||||
// normalize success envelope
|
||||
if (result && result.data && result.data.data && result.data.data.items) {
|
||||
items = result.data.data.items;
|
||||
}
|
||||
if (result && result.success && result.data && result.data.items) {
|
||||
items = result.data.items;
|
||||
}
|
||||
// handle this success() wrapper: {success,data:{items}}
|
||||
if (Array.isArray(result && result.data)) items = result.data;
|
||||
if (!items.length && result && result.data && Array.isArray(result.data.items)) {
|
||||
items = result.data.items;
|
||||
}
|
||||
// fallback parse
|
||||
try {
|
||||
if (!items.length && result && result.data && result.data.data) {
|
||||
items = result.data.data.items || [];
|
||||
}
|
||||
} catch (e) {}
|
||||
if (!items || !items.length) {
|
||||
couponRoot.innerHTML = '<p class="muted-2-color">暂无可用优惠券</p>';
|
||||
return;
|
||||
}
|
||||
var html = '<div class="aksrt-coupon-grid">';
|
||||
items.forEach(function (c) {
|
||||
var val = c.type === "percent" ? (c.value + "%") : ("¥" + Number(c.value || 0).toFixed(2));
|
||||
html += '<div class="aksrt-coupon-card">';
|
||||
html += '<div class="aksrt-coupon-card__value">' + val + '</div>';
|
||||
html += '<div class="aksrt-coupon-card__code">' + (c.code || "") + '</div>';
|
||||
if (c.min_amount > 0) {
|
||||
html += '<div class="aksrt-coupon-card__meta">满 ¥' + Number(c.min_amount).toFixed(2) + ' 可用</div>';
|
||||
}
|
||||
if (c.expires_at) {
|
||||
html += '<div class="aksrt-coupon-card__meta">有效期至 ' + c.expires_at + '</div>';
|
||||
}
|
||||
html += '<button type="button" class="btn-outline btn-sm" data-aksrt-copy-coupon="' + (c.code || "") + '">复制券码</button>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
couponRoot.innerHTML = html;
|
||||
}).catch(function () {
|
||||
couponRoot.innerHTML = '<p class="muted-2-color">加载失败</p>';
|
||||
});
|
||||
page.addEventListener("click", function (e) {
|
||||
var b = e.target.closest("[data-aksrt-copy-coupon]");
|
||||
if (!b) return;
|
||||
var code = b.getAttribute("data-aksrt-copy-coupon") || "";
|
||||
if (!code) return;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(code).then(function () { showToast("已复制 " + code); });
|
||||
} else {
|
||||
showToast(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -264,6 +264,9 @@ if (class_exists('\AKSRT\User\AuthApply')) {
|
||||
if (class_exists('\AKSRT\Core\DocumentNav')) {
|
||||
\AKSRT\Core\DocumentNav::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\User\BanAppeal')) {
|
||||
\AKSRT\User\BanAppeal::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Messages\SiteNotice')) {
|
||||
\AKSRT\Messages\SiteNotice::get_instance()->init();
|
||||
}
|
||||
|
||||
@@ -71,6 +71,30 @@ class ShopController extends ControllerBase {
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/after-sale', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_frontend_after_sales'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/after-sale/(?P<id>\d+)/return-shipping', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'submit_frontend_return_shipping'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/after-sale/(?P<id>\d+)/cancel', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'cancel_frontend_after_sale'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/coupons', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_frontend_coupons'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// ── Address book CRUD (frontend) ──
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/address', [
|
||||
|
||||
@@ -123,6 +123,76 @@ trait ShopControllerFrontendFlowOperations {
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function get_frontend_after_sales(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
$data = \AKSRT\Shop\Shop::get_instance()->get_user_after_sales(get_current_user_id());
|
||||
return $this->success(['data' => $data]);
|
||||
}
|
||||
|
||||
public function submit_frontend_return_shipping(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
$id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$result = \AKSRT\Shop\Shop::get_instance()->submit_after_sale_return(get_current_user_id(), $id, $params);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '提交退货物流失败');
|
||||
}
|
||||
return $this->success([
|
||||
'message' => $result['message'] ?? __('退货物流已提交', 'aksrt'),
|
||||
'data' => $result['data'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function cancel_frontend_after_sale(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
$id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$reason = sanitize_text_field((string) ($params['reason'] ?? ''));
|
||||
$result = \AKSRT\Shop\Shop::get_instance()->cancel_after_sale(get_current_user_id(), $id, $reason);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '取消售后失败');
|
||||
}
|
||||
return $this->success(['message' => $result['message'] ?? __('已取消', 'aksrt')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Active public coupons for wallet UI (not personal inventory — site-wide active codes).
|
||||
*/
|
||||
public function get_frontend_coupons(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Payment\Coupon')) {
|
||||
return $this->error('优惠券不可用', 500);
|
||||
}
|
||||
$list = \AKSRT\Payment\Coupon::get_instance()->get_list([
|
||||
'status' => 'active',
|
||||
'limit' => 50,
|
||||
'offset' => 0,
|
||||
]);
|
||||
$user_id = get_current_user_id();
|
||||
$items = [];
|
||||
foreach ((array) ($list['items'] ?? []) as $c) {
|
||||
$items[] = [
|
||||
'id' => (int) ($c['id'] ?? 0),
|
||||
'code' => (string) ($c['code'] ?? ''),
|
||||
'type' => (string) ($c['type'] ?? ''),
|
||||
'value' => (float) ($c['value'] ?? 0),
|
||||
'min_amount' => (float) ($c['min_amount'] ?? 0),
|
||||
'max_discount' => (float) ($c['max_discount'] ?? 0),
|
||||
'expires_at' => (string) ($c['expires_at'] ?? ''),
|
||||
'user_used' => $user_id > 0
|
||||
? (int) \AKSRT\Payment\Coupon::get_instance()->get_user_usage_count((int) ($c['id'] ?? 0), $user_id)
|
||||
: 0,
|
||||
'per_user_limit' => (int) ($c['per_user_limit'] ?? 0),
|
||||
];
|
||||
}
|
||||
return $this->success(['data' => ['items' => $items]]);
|
||||
}
|
||||
|
||||
public function get_cart(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$shop = \AKSRT\Shop\Shop::get_instance();
|
||||
|
||||
@@ -506,6 +506,29 @@ trait ScriptsPart1b {
|
||||
}
|
||||
}
|
||||
|
||||
// 禁封申诉页
|
||||
if (is_page_template('templates/pages/page-ban-appeal.php')) {
|
||||
$ba_css = get_template_directory() . '/assets/css/routes/ban-appeal.css';
|
||||
if (file_exists($ba_css)) {
|
||||
wp_enqueue_style(
|
||||
'aksrt-route-ban-appeal',
|
||||
$base_uri . '/assets/css/routes/ban-appeal.css',
|
||||
['aksrt-tokens'],
|
||||
(string) filemtime($ba_css)
|
||||
);
|
||||
}
|
||||
$ba_js = get_template_directory() . '/assets/js/ban-appeal-page.js';
|
||||
if (file_exists($ba_js)) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-ban-appeal-page',
|
||||
$base_uri . '/assets/js/ban-appeal-page.js',
|
||||
[],
|
||||
(string) filemtime($ba_js),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 用户身份认证页(zibll user-auth.php)
|
||||
if (is_page_template('templates/pages/page-user-auth.php')) {
|
||||
$ua_css = get_template_directory() . '/assets/css/routes/user-auth.css';
|
||||
|
||||
@@ -49,6 +49,7 @@ class Templates {
|
||||
'templates/pages/page-friends.php' => __('AKSRT - 友情链接', 'aksrt'),
|
||||
'templates/pages/page-links.php' => __('AKSRT - 网址导航', 'aksrt'),
|
||||
'templates/pages/page-user-auth.php' => __('AKSRT - 用户身份认证', 'aksrt'),
|
||||
'templates/pages/page-ban-appeal.php' => __('AKSRT - 禁封申诉', 'aksrt'),
|
||||
'templates/pages/page-orders.php' => __('AKSRT - 订单中心', 'aksrt'),
|
||||
'templates/pages/page-revenue.php' => __('AKSRT - 收益中心', 'aksrt'),
|
||||
'templates/pages/page-cart.php' => __('AKSRT - 购物车', 'aksrt'),
|
||||
|
||||
@@ -75,7 +75,12 @@ class AfterSale {
|
||||
'created_at' => current_time('mysql'),
|
||||
]);
|
||||
|
||||
return $result ? (int) $wpdb->insert_id : 0;
|
||||
if (!$result) {
|
||||
return 0;
|
||||
}
|
||||
$ticket_id = (int) $wpdb->insert_id;
|
||||
$this->append_event($ticket_id, 'created', __('用户提交售后申请', 'aksrt'), $user_id);
|
||||
return $ticket_id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -657,21 +662,133 @@ class AfterSale {
|
||||
return false;
|
||||
}
|
||||
|
||||
$return_data = wp_json_encode([
|
||||
'carrier' => sanitize_text_field($carrier),
|
||||
'tracking_no' => sanitize_text_field($tracking_no),
|
||||
$return_data = [
|
||||
'carrier' => sanitize_text_field($carrier),
|
||||
'tracking_no' => sanitize_text_field($tracking_no),
|
||||
'submitted_at' => current_time('mysql'),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
];
|
||||
|
||||
// Keep human-readable note and structured shipping meta.
|
||||
$note = sprintf(
|
||||
/* translators: 1: carrier 2: tracking */
|
||||
__('用户已寄回:%1$s %2$s', 'aksrt'),
|
||||
$return_data['carrier'],
|
||||
$return_data['tracking_no']
|
||||
);
|
||||
|
||||
$wpdb->update($this->table(), [
|
||||
'note' => $return_data,
|
||||
'note' => $note,
|
||||
'updated_at' => current_time('mysql'),
|
||||
], ['id' => $ticket_id]);
|
||||
|
||||
update_post_meta($ticket_id, '_aksrt_as_progress', self::PROGRESS_WAITING_MERCHANT);
|
||||
update_post_meta($ticket_id, '_aksrt_as_return_shipping', $return_data);
|
||||
$this->append_event($ticket_id, 'return_shipped', $note, $user_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* User cancels a pending after-sale ticket.
|
||||
*/
|
||||
public function user_cancel(int $ticket_id, int $user_id, string $reason = ''): bool {
|
||||
global $wpdb;
|
||||
$record = $this->get_by_id($ticket_id);
|
||||
if (!$record || (int) ($record['user_id'] ?? 0) !== $user_id) {
|
||||
return false;
|
||||
}
|
||||
$status = (string) ($record['status'] ?? '');
|
||||
if (!in_array($status, [self::STATUS_PENDING, self::STATUS_PROCESSING], true)) {
|
||||
return false;
|
||||
}
|
||||
// Only allow cancel before merchant confirmed return completion.
|
||||
$progress = (int) get_post_meta($ticket_id, '_aksrt_as_progress', true);
|
||||
if ($status === self::STATUS_PROCESSING && $progress === self::PROGRESS_DONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$wpdb->update($this->table(), [
|
||||
'status' => self::STATUS_CANCELLED,
|
||||
'updated_at' => current_time('mysql'),
|
||||
], ['id' => $ticket_id]);
|
||||
|
||||
$msg = $reason !== ''
|
||||
? sprintf(__('用户取消售后:%s', 'aksrt'), sanitize_text_field($reason))
|
||||
: __('用户取消售后', 'aksrt');
|
||||
$this->append_event($ticket_id, 'user_cancelled', $msg, $user_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{time:string,event:string,label:string,user_id:int}>
|
||||
*/
|
||||
public function get_events(int $ticket_id): array {
|
||||
$events = get_post_meta($ticket_id, '_aksrt_as_events', true);
|
||||
return is_array($events) ? array_values($events) : [];
|
||||
}
|
||||
|
||||
public function append_event(int $ticket_id, string $event, string $label, int $user_id = 0): void {
|
||||
$events = $this->get_events($ticket_id);
|
||||
$events[] = [
|
||||
'time' => current_time('mysql'),
|
||||
'event' => sanitize_key($event),
|
||||
'label' => sanitize_text_field($label),
|
||||
'user_id' => $user_id,
|
||||
];
|
||||
// Keep last 50 events.
|
||||
if (count($events) > 50) {
|
||||
$events = array_slice($events, -50);
|
||||
}
|
||||
update_post_meta($ticket_id, '_aksrt_as_events', $events);
|
||||
}
|
||||
|
||||
/**
|
||||
* List tickets for a frontend user.
|
||||
*/
|
||||
public function get_user_tickets(int $user_id, int $limit = 50): array {
|
||||
if ($user_id <= 0) {
|
||||
return [];
|
||||
}
|
||||
global $wpdb;
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT * FROM {$this->table()} WHERE user_id = %d ORDER BY created_at DESC LIMIT %d",
|
||||
$user_id,
|
||||
max(1, min(100, $limit))
|
||||
), ARRAY_A);
|
||||
$items = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$items[] = $this->decorate_user_ticket($row);
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $row
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function decorate_user_ticket(array $row): array {
|
||||
$id = (int) ($row['id'] ?? 0);
|
||||
$decorated = method_exists($this, 'decorate_record') ? $this->decorate_record($row) : $row;
|
||||
$progress = (int) get_post_meta($id, '_aksrt_as_progress', true);
|
||||
$labels = self::get_progress_labels();
|
||||
$return = get_post_meta($id, '_aksrt_as_return_shipping', true);
|
||||
$status = (string) ($row['status'] ?? '');
|
||||
$type = (string) ($row['type'] ?? '');
|
||||
$needs_ship = in_array($type, [self::TYPE_REFUND_RETURN, self::TYPE_EXCHANGE, self::TYPE_REPLACEMENT], true)
|
||||
&& $status === self::STATUS_PROCESSING
|
||||
&& $progress === self::PROGRESS_WAITING_USER_SHIP;
|
||||
|
||||
$decorated['id'] = $id;
|
||||
$decorated['progress'] = $progress;
|
||||
$decorated['progress_label'] = $labels[$progress] ?? '';
|
||||
$decorated['return_shipping'] = is_array($return) ? $return : null;
|
||||
$decorated['events'] = $this->get_events($id);
|
||||
$decorated['actions'] = [
|
||||
'can_cancel' => in_array($status, [self::STATUS_PENDING, self::STATUS_PROCESSING], true),
|
||||
'can_return_ship' => $needs_ship,
|
||||
];
|
||||
return $decorated;
|
||||
}
|
||||
|
||||
public function merchant_confirm_return(int $ticket_id, int $operator_id, string $note = ''): bool {
|
||||
global $wpdb;
|
||||
$record = $this->get_by_id($ticket_id);
|
||||
|
||||
@@ -49,6 +49,55 @@ trait Part1 {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* List after-sale tickets for current user.
|
||||
*/
|
||||
public function get_user_after_sales(int $user_id): array {
|
||||
if ($user_id <= 0 || !class_exists('\AKSRT\Payment\AfterSale')) {
|
||||
return ['items' => []];
|
||||
}
|
||||
$items = \AKSRT\Payment\AfterSale::get_instance()->get_user_tickets($user_id, 50);
|
||||
return ['items' => $items];
|
||||
}
|
||||
|
||||
/**
|
||||
* User submits return shipping info.
|
||||
*/
|
||||
public function submit_after_sale_return(int $user_id, int $ticket_id, array $payload): array {
|
||||
if ($user_id <= 0 || $ticket_id <= 0 || !class_exists('\AKSRT\Payment\AfterSale')) {
|
||||
return ['success' => false, 'message' => __('售后系统不可用。', 'aksrt')];
|
||||
}
|
||||
$carrier = sanitize_text_field((string) ($payload['carrier'] ?? ''));
|
||||
$tracking = sanitize_text_field((string) ($payload['tracking_no'] ?? ''));
|
||||
if ($carrier === '' || $tracking === '') {
|
||||
return ['success' => false, 'message' => __('请填写快递公司与运单号。', 'aksrt')];
|
||||
}
|
||||
$ok = \AKSRT\Payment\AfterSale::get_instance()->user_submit_return_shipping($ticket_id, $user_id, $carrier, $tracking);
|
||||
if (!$ok) {
|
||||
return ['success' => false, 'message' => __('提交退货物流失败,请确认工单状态。', 'aksrt')];
|
||||
}
|
||||
$ticket = \AKSRT\Payment\AfterSale::get_instance()->get($ticket_id);
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => __('退货物流已提交', 'aksrt'),
|
||||
'data' => $ticket ? \AKSRT\Payment\AfterSale::get_instance()->decorate_user_ticket($ticket) : ['id' => $ticket_id],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* User cancels after-sale ticket.
|
||||
*/
|
||||
public function cancel_after_sale(int $user_id, int $ticket_id, string $reason = ''): array {
|
||||
if ($user_id <= 0 || $ticket_id <= 0 || !class_exists('\AKSRT\Payment\AfterSale')) {
|
||||
return ['success' => false, 'message' => __('售后系统不可用。', 'aksrt')];
|
||||
}
|
||||
$ok = \AKSRT\Payment\AfterSale::get_instance()->user_cancel($ticket_id, $user_id, $reason);
|
||||
if (!$ok) {
|
||||
return ['success' => false, 'message' => __('无法取消该售后工单。', 'aksrt')];
|
||||
}
|
||||
return ['success' => true, 'message' => __('已取消售后申请', 'aksrt')];
|
||||
}
|
||||
|
||||
public function get_product_archive_view(array $args = []): array {
|
||||
$taxonomy = sanitize_key((string) ($args['taxonomy'] ?? ''));
|
||||
$term_id = (int) ($args['term_id'] ?? 0);
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* Frontend ban appeal (zibll user_ban_appeal parity) on Restriction meta.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\User;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class BanAppeal {
|
||||
|
||||
public const META_STATUS = 'aksrt_ban_appeal_status'; // pending|approved|rejected|''
|
||||
public const META_PAYLOAD = 'aksrt_ban_appeal_payload';
|
||||
public const META_HISTORY = 'aksrt_ban_appeal_history';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('wp_ajax_aksrt_ban_appeal_submit', [$this, 'ajax_submit']);
|
||||
add_action('wp_ajax_aksrt_ban_appeal_status', [$this, 'ajax_status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{enabled:bool,desc:string,keys:string[]}
|
||||
*/
|
||||
public static function settings(): array {
|
||||
$all = class_exists('\AKSRT\Settings\Settings') ? (array) \AKSRT\Settings\Settings::get_all() : [];
|
||||
$keys_raw = (string) ($all['ban_appeal_keys'] ?? '');
|
||||
$keys = array_values(array_filter(array_map('trim', preg_split('/[,,\n]/', $keys_raw) ?: [])));
|
||||
return [
|
||||
'enabled' => !isset($all['user_report_enabled']) || !empty($all['user_report_enabled']),
|
||||
'desc' => (string) ($all['ban_appeal_desc'] ?? ''),
|
||||
'keys' => $keys,
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_status(int $user_id): string {
|
||||
return sanitize_key((string) get_user_meta($user_id, self::META_STATUS, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_payload(int $user_id): array {
|
||||
$p = get_user_meta($user_id, self::META_PAYLOAD, true);
|
||||
return is_array($p) ? $p : [];
|
||||
}
|
||||
|
||||
public function ajax_status(): void {
|
||||
if (!is_user_logged_in()) {
|
||||
wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]);
|
||||
}
|
||||
$user_id = get_current_user_id();
|
||||
$ban = class_exists('\AKSRT\User\Restriction')
|
||||
? \AKSRT\User\Restriction::get_instance()->get_active_ban($user_id)
|
||||
: null;
|
||||
wp_send_json([
|
||||
'error' => 0,
|
||||
'banned' => (bool) $ban,
|
||||
'ban' => $ban,
|
||||
'status' => self::get_status($user_id),
|
||||
'payload'=> self::get_payload($user_id),
|
||||
]);
|
||||
}
|
||||
|
||||
public function ajax_submit(): void {
|
||||
if (!is_user_logged_in()) {
|
||||
wp_send_json(['error' => 1, 'msg' => __('请先登录', 'aksrt')]);
|
||||
}
|
||||
if (!check_ajax_referer('aksrt_ban_appeal', '_wpnonce', false)) {
|
||||
wp_send_json(['error' => 1, 'msg' => __('安全校验失败', 'aksrt')]);
|
||||
}
|
||||
$user_id = get_current_user_id();
|
||||
$s = self::settings();
|
||||
if (!$s['enabled']) {
|
||||
wp_send_json(['error' => 1, 'msg' => __('申诉功能未开启', 'aksrt')]);
|
||||
}
|
||||
|
||||
$banned = class_exists('\AKSRT\User\Restriction')
|
||||
&& \AKSRT\User\Restriction::get_instance()->is_banned($user_id);
|
||||
if (!$banned) {
|
||||
wp_send_json(['error' => 1, 'msg' => __('当前账号未处于禁封状态', 'aksrt')]);
|
||||
}
|
||||
if (self::get_status($user_id) === 'pending') {
|
||||
wp_send_json(['error' => 1, 'msg' => __('申诉正在处理中,请勿重复提交', 'aksrt')]);
|
||||
}
|
||||
|
||||
$reason_key = isset($_POST['reason_key']) ? sanitize_text_field(wp_unslash((string) $_POST['reason_key'])) : '';
|
||||
$reason = isset($_POST['reason']) ? sanitize_textarea_field(wp_unslash((string) $_POST['reason'])) : '';
|
||||
if ($reason === '') {
|
||||
wp_send_json(['error' => 1, 'msg' => __('请填写申诉说明', 'aksrt')]);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'reason_key' => $reason_key,
|
||||
'reason' => $reason,
|
||||
'time' => current_time('Y-m-d H:i'),
|
||||
];
|
||||
update_user_meta($user_id, self::META_PAYLOAD, $payload);
|
||||
update_user_meta($user_id, self::META_STATUS, 'pending');
|
||||
|
||||
$history = get_user_meta($user_id, self::META_HISTORY, true);
|
||||
$history = is_array($history) ? $history : [];
|
||||
$history[] = $payload;
|
||||
update_user_meta($user_id, self::META_HISTORY, array_slice($history, -20));
|
||||
|
||||
// Notify admins via site message table if available.
|
||||
if (class_exists('\AKSRT\Messages\Message')) {
|
||||
$admins = get_users(['role' => 'administrator', 'fields' => 'ID', 'number' => 20]);
|
||||
$summary = sprintf(
|
||||
/* translators: 1: user login 2: reason */
|
||||
__('用户 %1$s 提交禁封申诉:%2$s', 'aksrt'),
|
||||
wp_get_current_user()->user_login,
|
||||
mb_substr($reason, 0, 80)
|
||||
);
|
||||
foreach ($admins as $admin_id) {
|
||||
\AKSRT\Messages\Message::get_instance()->send(
|
||||
(int) $admin_id,
|
||||
'ban_appeal',
|
||||
$summary,
|
||||
[
|
||||
'title' => __('禁封申诉', 'aksrt'),
|
||||
'target_url' => admin_url('users.php?page=aksrt_user_ban&tab=appeals'),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $user_id
|
||||
* @param array $payload
|
||||
*/
|
||||
do_action('aksrt_ban_appeal_submitted', $user_id, $payload);
|
||||
|
||||
wp_send_json([
|
||||
'error' => 0,
|
||||
'msg' => __('申诉已提交,请等待管理员处理', 'aksrt'),
|
||||
'reload' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - 禁封申诉
|
||||
* Template Post Type: page
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
use AKSRT\User\BanAppeal;
|
||||
use AKSRT\User\Restriction;
|
||||
|
||||
get_header();
|
||||
|
||||
if (!is_user_logged_in()) {
|
||||
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink()) : wp_login_url(get_permalink());
|
||||
wp_safe_redirect($login);
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$settings = class_exists(BanAppeal::class) ? BanAppeal::settings() : ['enabled' => true, 'desc' => '', 'keys' => []];
|
||||
$ban = class_exists(Restriction::class) ? Restriction::get_instance()->get_active_ban($user_id) : null;
|
||||
$status = class_exists(BanAppeal::class) ? BanAppeal::get_status($user_id) : '';
|
||||
$payload = class_exists(BanAppeal::class) ? BanAppeal::get_payload($user_id) : [];
|
||||
?>
|
||||
<main class="container page-template-ban-appeal">
|
||||
<div class="content-wrap">
|
||||
<div class="content-layout content-layout--no-sidebar">
|
||||
<div class="theme-box radius8 main-bg main-shadow box-body mb20">
|
||||
<h1 class="article-title"><?php echo esc_html(get_the_title() ?: __('禁封申诉', 'aksrt')); ?></h1>
|
||||
</div>
|
||||
|
||||
<div class="theme-box radius8 main-bg main-shadow box-body aksrt-ban-appeal">
|
||||
<?php if (!$ban) : ?>
|
||||
<div class="muted-2-color text-center" style="padding:40px 0;">
|
||||
<i class="fa fa-check-circle em2x" aria-hidden="true"></i>
|
||||
<p class="mt10"><?php esc_html_e('当前账号未处于禁封状态。', 'aksrt'); ?></p>
|
||||
</div>
|
||||
<?php elseif (empty($settings['enabled'])) : ?>
|
||||
<div class="c-red"><i class="fa fa-info-circle mr6" aria-hidden="true"></i><?php esc_html_e('申诉功能暂未开启', 'aksrt'); ?></div>
|
||||
<?php elseif ($status === 'pending') : ?>
|
||||
<div class="c-red mb10"><?php esc_html_e('您的申诉正在审核中,请耐心等待。', 'aksrt'); ?></div>
|
||||
<?php if (!empty($payload['reason'])) : ?>
|
||||
<div class="muted-2-color"><?php echo esc_html(sprintf(__('申诉内容:%s', 'aksrt'), (string) $payload['reason'])); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($payload['time'])) : ?>
|
||||
<div class="muted-2-color mt6"><?php echo esc_html(sprintf(__('提交时间:%s', 'aksrt'), (string) $payload['time'])); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php if (!empty($ban['reason'])) : ?>
|
||||
<div class="muted-box mb20">
|
||||
<strong><?php esc_html_e('禁封原因', 'aksrt'); ?>:</strong>
|
||||
<?php echo esc_html((string) $ban['reason']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($settings['desc'])) : ?>
|
||||
<div class="muted-color mb20"><?php echo wp_kses_post(nl2br(esc_html((string) $settings['desc']))); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($status === 'rejected') : ?>
|
||||
<div class="c-red mb15"><?php esc_html_e('上次申诉未通过,可修改后重新提交。', 'aksrt'); ?></div>
|
||||
<?php endif; ?>
|
||||
<form class="aksrt-ban-appeal-form">
|
||||
<?php if (!empty($settings['keys'])) : ?>
|
||||
<div class="mb15">
|
||||
<label class="em09 muted-2-color"><?php esc_html_e('申诉类型', 'aksrt'); ?></label>
|
||||
<select name="reason_key" class="form-control">
|
||||
<?php foreach ($settings['keys'] as $key) : ?>
|
||||
<option value="<?php echo esc_attr($key); ?>"><?php echo esc_html($key); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mb15">
|
||||
<label class="em09 muted-2-color"><?php esc_html_e('申诉说明', 'aksrt'); ?> *</label>
|
||||
<textarea name="reason" class="form-control" rows="5" required placeholder="<?php esc_attr_e('请说明误封原因或补充材料说明', 'aksrt'); ?>"><?php echo esc_textarea((string) ($payload['reason'] ?? '')); ?></textarea>
|
||||
</div>
|
||||
<?php wp_nonce_field('aksrt_ban_appeal', '_wpnonce', false); ?>
|
||||
<input type="hidden" name="action" value="aksrt_ban_appeal_submit">
|
||||
<button type="submit" class="but c-blue padding-lg">
|
||||
<i class="fa fa-paper-plane" aria-hidden="true"></i> <?php esc_html_e('提交申诉', 'aksrt'); ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -53,7 +53,11 @@ $type_groups = [
|
||||
'all' => null, // special: all
|
||||
'comment' => ['comment_reply', 'comment_approved', 'pending_comments'],
|
||||
'credits' => ['credits_added', 'credits_deducted'],
|
||||
'system' => ['notice', 'download_ready', 'upload_moderation', 'verify_email'],
|
||||
'pay' => ['pay', 'order_paid', 'order_refund', 'shop_order', 'after_sale'],
|
||||
'vip' => ['vip', 'pay_vip', 'vip_expire', 'vip_upgrade'],
|
||||
'medal' => ['medal', 'badge', 'level_up'],
|
||||
'ban_appeal' => ['ban_appeal', 'user_ban', 'restriction'],
|
||||
'system' => ['notice', 'download_ready', 'upload_moderation', 'verify_email', 'auth_apply', 'auth_apply_reply'],
|
||||
'conversation' => ['conversation'],
|
||||
];
|
||||
|
||||
@@ -94,27 +98,31 @@ $offset = ($current_page - 1) * $per_page;
|
||||
$paged_items = array_slice($filtered, $offset, $per_page);
|
||||
|
||||
// 统计
|
||||
$type_counts = [
|
||||
'all' => count($all_notifications),
|
||||
'unread' => 0,
|
||||
'comment'=> 0,
|
||||
'credits'=> 0,
|
||||
'system' => 0,
|
||||
'conversation' => 0,
|
||||
];
|
||||
foreach ($all_notifications as $n) {
|
||||
$t = (string) ($n['type'] ?? 'notice');
|
||||
if (empty($n['read'])) {
|
||||
$type_counts['unread']++;
|
||||
}
|
||||
foreach (['comment', 'credits', 'system', 'conversation'] as $group) {
|
||||
if (in_array($t, $type_groups[$group], true)) {
|
||||
$type_counts[$group]++;
|
||||
$type_counts = [
|
||||
'all' => count($all_notifications),
|
||||
'unread' => 0,
|
||||
'comment' => 0,
|
||||
'credits' => 0,
|
||||
'pay' => 0,
|
||||
'vip' => 0,
|
||||
'medal' => 0,
|
||||
'ban_appeal' => 0,
|
||||
'system' => 0,
|
||||
'conversation' => 0,
|
||||
];
|
||||
foreach ($all_notifications as $n) {
|
||||
$t = (string) ($n['type'] ?? 'notice');
|
||||
if (empty($n['read'])) {
|
||||
$type_counts['unread']++;
|
||||
}
|
||||
foreach (['comment', 'credits', 'pay', 'vip', 'medal', 'ban_appeal', 'system', 'conversation'] as $group) {
|
||||
if (!empty($type_groups[$group]) && is_array($type_groups[$group]) && in_array($t, $type_groups[$group], true)) {
|
||||
$type_counts[$group]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($pm_enabled && class_exists('\AKSRT\Messages\PrivateMessage')) {
|
||||
if ($pm_enabled && class_exists('\AKSRT\Messages\PrivateMessage')) {
|
||||
// conversation count prefers live PM conversations when available
|
||||
$pm_tmp = \AKSRT\Messages\PrivateMessage::get_instance();
|
||||
if (method_exists($pm_tmp, 'get_conversations')) {
|
||||
@@ -126,17 +134,21 @@ if ($pm_enabled && class_exists('\AKSRT\Messages\PrivateMessage')) {
|
||||
}
|
||||
|
||||
$nav_items = [
|
||||
'unread' => __('未读消息', 'aksrt'),
|
||||
'all' => __('全部消息', 'aksrt'),
|
||||
'comment' => __('评论互动', 'aksrt'),
|
||||
'credits' => __('积分动态', 'aksrt'),
|
||||
'system' => __('系统通知', 'aksrt'),
|
||||
];
|
||||
if ($pm_enabled) {
|
||||
$nav_items['conversation'] = __('私信消息', 'aksrt');
|
||||
}
|
||||
'unread' => __('未读消息', 'aksrt'),
|
||||
'all' => __('全部消息', 'aksrt'),
|
||||
'comment' => __('评论互动', 'aksrt'),
|
||||
'credits' => __('积分动态', 'aksrt'),
|
||||
'pay' => __('交易通知', 'aksrt'),
|
||||
'vip' => __('会员动态', 'aksrt'),
|
||||
'medal' => __('徽章等级', 'aksrt'),
|
||||
'ban_appeal' => __('禁封申诉', 'aksrt'),
|
||||
'system' => __('系统通知', 'aksrt'),
|
||||
];
|
||||
if ($pm_enabled) {
|
||||
$nav_items['conversation'] = __('私信消息', 'aksrt');
|
||||
}
|
||||
|
||||
$icon_map = [
|
||||
$icon_map = [
|
||||
'comment_reply' => 'fas fa-comment-dots',
|
||||
'comment_approved' => 'fas fa-check-circle',
|
||||
'credits_added' => 'fas fa-coins',
|
||||
@@ -146,6 +158,18 @@ $icon_map = [
|
||||
'verify_email' => 'fas fa-envelope-open-text',
|
||||
'pending_comments' => 'fas fa-clock',
|
||||
'conversation' => 'fas fa-envelope',
|
||||
'pay' => 'fas fa-receipt',
|
||||
'order_paid' => 'fas fa-receipt',
|
||||
'order_refund' => 'fas fa-undo',
|
||||
'after_sale' => 'fas fa-undo-alt',
|
||||
'vip' => 'fas fa-crown',
|
||||
'pay_vip' => 'fas fa-crown',
|
||||
'medal' => 'fas fa-medal',
|
||||
'badge' => 'fas fa-certificate',
|
||||
'ban_appeal' => 'fas fa-gavel',
|
||||
'user_ban' => 'fas fa-ban',
|
||||
'auth_apply' => 'fas fa-id-badge',
|
||||
'auth_apply_reply' => 'fas fa-id-badge',
|
||||
'notice' => 'fas fa-bell',
|
||||
];
|
||||
|
||||
|
||||
@@ -71,6 +71,12 @@ $tabs = [
|
||||
?>
|
||||
|
||||
<!-- 5-tab navigation -->
|
||||
<div class="aksrt-orders-page__extra-nav" data-aksrt-orders-extra-nav>
|
||||
<a href="<?php echo esc_url(add_query_arg(['section' => 'after-sale'])); ?>" class="aksrt-orders-page__extra-link<?php echo (sanitize_key((string) ($_GET['section'] ?? '')) === 'after-sale') ? ' is-active' : ''; ?>"><?php esc_html_e('售后工单', 'aksrt'); ?></a>
|
||||
<a href="<?php echo esc_url(add_query_arg(['section' => 'coupons'])); ?>" class="aksrt-orders-page__extra-link<?php echo (sanitize_key((string) ($_GET['section'] ?? '')) === 'coupons') ? ' is-active' : ''; ?>"><?php esc_html_e('优惠券', 'aksrt'); ?></a>
|
||||
<a href="<?php echo esc_url(add_query_arg(['section' => 'favorites'])); ?>" class="aksrt-orders-page__extra-link<?php echo (sanitize_key((string) ($_GET['section'] ?? '')) === 'favorites') ? ' is-active' : ''; ?>"><?php esc_html_e('收藏', 'aksrt'); ?></a>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-orders-page__tabs">
|
||||
<?php foreach ($tabs as $tab_key => $tab_label) :
|
||||
$is_active = $tab === $tab_key;
|
||||
@@ -231,6 +237,110 @@ $tabs = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Return shipping modal -->
|
||||
<div class="aksrt-modal" data-aksrt-return-ship-modal hidden>
|
||||
<div class="aksrt-modal__overlay"></div>
|
||||
<div class="aksrt-modal__dialog">
|
||||
<div class="aksrt-modal__header">
|
||||
<h3><?php esc_html_e('填写退货物流', 'aksrt'); ?></h3>
|
||||
<button type="button" class="aksrt-modal__close" data-aksrt-return-ship-close>×</button>
|
||||
</div>
|
||||
<form class="aksrt-modal__body" data-aksrt-return-ship-form>
|
||||
<input type="hidden" name="ticket_id" value="">
|
||||
<div class="aksrt-orders-page__field">
|
||||
<label><?php esc_html_e('快递公司', 'aksrt'); ?> *</label>
|
||||
<input type="text" name="carrier" class="aksrt-orders-page__input" required placeholder="<?php esc_attr_e('如:顺丰速运', 'aksrt'); ?>">
|
||||
</div>
|
||||
<div class="aksrt-orders-page__field">
|
||||
<label><?php esc_html_e('运单号', 'aksrt'); ?> *</label>
|
||||
<input type="text" name="tracking_no" class="aksrt-orders-page__input" required placeholder="<?php esc_attr_e('快递单号', 'aksrt'); ?>">
|
||||
</div>
|
||||
<div class="aksrt-modal__footer">
|
||||
<button type="button" class="btn-ghost" data-aksrt-return-ship-close><?php esc_html_e('取消', 'aksrt'); ?></button>
|
||||
<button type="submit" class="btn-primary"><?php esc_html_e('提交物流', 'aksrt'); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- After-sale tickets panel -->
|
||||
<?php
|
||||
$as_section = sanitize_key((string) ($_GET['section'] ?? ''));
|
||||
if ($as_section === 'after-sale' && class_exists('\AKSRT\Payment\AfterSale')) :
|
||||
$tickets = \AKSRT\Payment\AfterSale::get_instance()->get_user_tickets($user_id, 50);
|
||||
?>
|
||||
<section class="aksrt-orders-page__aftersale" data-aksrt-aftersale-list>
|
||||
<h2><?php esc_html_e('我的售后', 'aksrt'); ?></h2>
|
||||
<?php if (!empty($tickets)) : ?>
|
||||
<div class="aksrt-as-list">
|
||||
<?php foreach ($tickets as $t) :
|
||||
$tid = (int) ($t['id'] ?? 0);
|
||||
$events = is_array($t['events'] ?? null) ? $t['events'] : [];
|
||||
$actions = is_array($t['actions'] ?? null) ? $t['actions'] : [];
|
||||
?>
|
||||
<article class="aksrt-as-card" data-aksrt-as-ticket="<?php echo esc_attr((string) $tid); ?>">
|
||||
<div class="aksrt-as-card__head">
|
||||
<strong>#<?php echo esc_html((string) $tid); ?></strong>
|
||||
<span class="aksrt-as-card__order"><?php echo esc_html((string) ($t['order_number'] ?? '')); ?></span>
|
||||
<span class="aksrt-as-card__status"><?php echo esc_html((string) ($t['status_label'] ?? $t['status'] ?? '')); ?></span>
|
||||
</div>
|
||||
<div class="aksrt-as-card__meta muted-2-color">
|
||||
<?php echo esc_html((string) ($t['type_label'] ?? $t['type'] ?? '')); ?>
|
||||
· <?php echo esc_html((string) ($t['created_at'] ?? '')); ?>
|
||||
<?php if (!empty($t['progress_label'])) : ?>
|
||||
· <?php echo esc_html((string) $t['progress_label']); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-as-card__reason"><?php echo esc_html((string) ($t['reason'] ?? '')); ?></div>
|
||||
<?php if (!empty($t['return_shipping']['tracking_no'])) : ?>
|
||||
<div class="aksrt-as-card__ship muted-2-color">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1 carrier 2 tracking */
|
||||
esc_html__('退货物流:%1$s %2$s', 'aksrt'),
|
||||
esc_html((string) ($t['return_shipping']['carrier'] ?? '')),
|
||||
esc_html((string) $t['return_shipping']['tracking_no'])
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($events) : ?>
|
||||
<ol class="aksrt-as-timeline">
|
||||
<?php foreach (array_reverse($events) as $ev) : ?>
|
||||
<li>
|
||||
<time><?php echo esc_html((string) ($ev['time'] ?? '')); ?></time>
|
||||
<span><?php echo esc_html((string) ($ev['label'] ?? '')); ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-as-card__actions">
|
||||
<?php if (!empty($actions['can_return_ship'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-return-ship="<?php echo esc_attr((string) $tid); ?>"><?php esc_html_e('填写退货物流', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_cancel'])) : ?>
|
||||
<button type="button" class="btn-ghost btn-sm" data-aksrt-as-cancel="<?php echo esc_attr((string) $tid); ?>"><?php esc_html_e('取消售后', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<p class="muted-2-color"><?php esc_html_e('暂无售后记录', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Coupon wallet -->
|
||||
<?php if ($as_section === 'coupons') : ?>
|
||||
<section class="aksrt-orders-page__coupons" data-aksrt-coupon-wallet>
|
||||
<h2><?php esc_html_e('优惠券', 'aksrt'); ?></h2>
|
||||
<div class="aksrt-coupon-wallet" data-aksrt-coupon-wallet-list>
|
||||
<p class="muted-2-color"><?php esc_html_e('加载中…', 'aksrt'); ?></p>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Favorites section -->
|
||||
<?php
|
||||
$fav_tab = sanitize_key((string) ($_GET['section'] ?? ''));
|
||||
|
||||
Reference in New Issue
Block a user