feat(admin): unify native admin pages on a shared modern UI

Add NativeUi helper + native-ui.css (tokens mirror the SPA Naive UI theme)
and move the pay center, orders, products, coupons, charge cards, income,
withdrawals, tickets, invite codes, VIP, verify, messages and report/ban
pages onto shared header, stat cards, cards, filters, badges and empty
states. Remove inline styles and dead helpers; split PayChargeCards into
sibling traits to stay under the file size limit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-30 15:05:38 +08:00
1 parent aa692f3c9f
commit 18951b01d6
29 files changed
+2503 -1593

No files matched your search

+80 -64
View File
@@ -34,18 +34,20 @@ class PayOrders {
public function render_orders_page(): void {
$this->ensure_cap();
$this->render_header(__('订单明细', 'aksrt'));
NativeUi::page_open();
NativeUi::header(__('订单明细', 'aksrt'), '', 'dashicons-list-view');
$this->render_flash();
if (!class_exists('\AKSRT\Payment\OrderRepository')) {
echo '<div class="notice notice-error"><p>' . esc_html__('订单服务不可用。', 'aksrt') . '</p></div>';
$this->render_footer();
NativeUi::page_close();
return;
}
$detail_order = isset($_GET['order']) ? sanitize_text_field((string) wp_unslash($_GET['order'])) : '';
if ($detail_order !== '') {
$this->render_order_detail_page($detail_order);
$this->render_footer();
NativeUi::page_close();
return;
}
@@ -139,8 +141,7 @@ class PayOrders {
});
// Type tabs.
echo '<ul class="subsubsub" style="float:none;margin:8px 0 12px;">';
$tab_bits = [];
echo '<div class="aksrt-native-tabs">';
foreach ($type_tabs as $tkey => $tlabel) {
$url = $this->page_url('aksrt_orders', array_filter(
['product_type' => $tkey !== '' ? $tkey : null] + $query_args,
@@ -148,15 +149,13 @@ class PayOrders {
return $value !== null;
}
));
$cls = ($product_type === $tkey) ? 'current' : '';
$tab_bits[] = '<li><a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($tlabel) . '</a></li>';
$cls = ($product_type === $tkey) ? ' is-active' : '';
echo '<a class="aksrt-native-tabs__link' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($tlabel) . '</a>';
}
echo implode(' | ', $tab_bits);
echo '</ul><div style="clear:both;"></div>';
echo '</div>';
// Payment method quick tabs.
echo '<ul class="subsubsub" style="float:none;margin:0 0 12px;">';
$m_bits = [];
echo '<div class="aksrt-native-tabs">';
foreach ($method_tabs as $mkey => $mlabel) {
$url = $this->page_url('aksrt_orders', array_filter(
['payment_method' => $mkey !== '' ? $mkey : null] + $query_args,
@@ -164,13 +163,13 @@ class PayOrders {
return $value !== null;
}
));
$cls = ($pay_method === $mkey) ? 'current' : '';
$m_bits[] = '<li><a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($mlabel) . '</a></li>';
$cls = ($pay_method === $mkey) ? ' is-active' : '';
echo '<a class="aksrt-native-tabs__link' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($mlabel) . '</a>';
}
echo implode(' | ', $m_bits);
echo '</ul><div style="clear:both;"></div>';
echo '</div>';
echo '<form method="get" style="margin:12px 0;">';
NativeUi::card_open(__('筛选', 'aksrt'));
echo '<form method="get" class="aksrt-native-filters">';
echo '<input type="hidden" name="page" value="aksrt_orders" />';
if ($product_type !== '') {
echo '<input type="hidden" name="product_type" value="' . esc_attr($product_type) . '" />';
@@ -213,21 +212,23 @@ class PayOrders {
}
if ($date_from !== '') $filter_bits[] = sprintf(__('开始日期=%s', 'aksrt'), $date_from);
if ($date_to !== '') $filter_bits[] = sprintf(__('结束日期=%s', 'aksrt'), $date_to);
echo ' <span class="description">' . esc_html(implode(' · ', $filter_bits)) . '</span>';
echo ' <span class="aksrt-native-muted">' . esc_html(implode(' · ', $filter_bits)) . '</span>';
echo ' <a class="button button-small" href="' . esc_url($this->page_url('aksrt_orders')) . '">' . esc_html__('清除过滤', 'aksrt') . '</a>';
}
echo '</form>';
// Closed-order cleanup.
echo '<p style="margin:8px 0 12px;">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;" onsubmit="return confirm(\'' . esc_js(__('将删除 14 天前所有已关闭订单,不可恢复。确认清理?', 'aksrt')) . '\');">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-native-inline" onsubmit="return confirm(\'' . esc_js(__('将删除 14 天前所有已关闭订单,不可恢复。确认清理?', 'aksrt')) . '\');">';
echo '<input type="hidden" name="action" value="aksrt_pay_clear_closed_orders" />';
echo '<input type="hidden" name="days" value="14" />';
wp_nonce_field('aksrt_pay_clear_closed_orders');
submit_button(__('清理 14 天前关闭订单', 'aksrt'), 'secondary', '', false);
echo '</form>';
echo '</p>';
NativeUi::card_close();
$status_map = $this->status_badge_map($labels);
NativeUi::table_card_open(__('订单明细', 'aksrt'));
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>' . esc_html__('订单号', 'aksrt') . '</th>';
@@ -240,15 +241,13 @@ class PayOrders {
echo '</tr></thead><tbody>';
if (!$items) {
echo '<tr><td colspan="7">' . esc_html__('暂无订单', 'aksrt') . '</td></tr>';
echo NativeUi::empty_row(7, __('暂无订单', 'aksrt'));
}
foreach ($items as $row) {
$order_number = (string) ($row['order_number'] ?? '');
$user_id = (int) ($row['user_id'] ?? 0);
$user = $user_id > 0 ? get_userdata($user_id) : null;
$status_key = (string) ($row['status'] ?? '');
$status_label = $labels[$status_key] ?? $status_key;
$product_id = (int) ($row['product_id'] ?? 0);
$product_type = sanitize_key((string) ($row['product_type'] ?? ''));
$type_labels = \AKSRT\Core\DomainModel::order_product_type_labels();
@@ -259,17 +258,17 @@ class PayOrders {
}
echo '<tr>';
echo '<td><code>' . esc_html($order_number) . '</code></td>';
echo '<td>' . ($user ? esc_html($user->display_name . ' (#' . $user_id . ')') : esc_html((string) $user_id)) . '</td>';
echo '<td><strong>' . esc_html($product_title !== '' ? $product_title : ($type_labels[$product_type] ?? $product_type)) . '</strong><br><span class="description">' . esc_html($type_labels[$product_type] ?? $product_type) . ($product_id > 0 ? ' #' . esc_html((string) $product_id) : '') . '</span></td>';
echo '<td><strong>' . esc_html($this->order_amount($row)) . '</strong><br><span class="description">' . esc_html((string) ($row['payment_method'] ?? '')) . '</span></td>';
echo '<td>' . esc_html($status_label) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '<br><span class="description">' . esc_html((string) ($row['paid_at'] ?? '') ?: '—') . '</span></td>';
echo '<td style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;">';
echo '<td><code class="aksrt-native-mono">' . esc_html($order_number) . '</code></td>';
echo '<td>' . NativeUi::user_cell($user_id) . '</td>';
echo '<td><strong>' . esc_html($product_title !== '' ? $product_title : ($type_labels[$product_type] ?? $product_type)) . '</strong><br><span class="aksrt-native-muted">' . esc_html($type_labels[$product_type] ?? $product_type) . ($product_id > 0 ? ' #' . esc_html((string) $product_id) : '') . '</span></td>';
echo '<td><strong class="aksrt-native-amount">' . esc_html($this->order_amount($row)) . '</strong><br><span class="aksrt-native-muted">' . esc_html((string) ($row['payment_method'] ?? '')) . '</span></td>';
echo '<td>' . NativeUi::status_badge($status_key, $status_map) . '</td>';
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '<br><span class="aksrt-native-muted">' . esc_html((string) ($row['paid_at'] ?? '') ?: '—') . '</span></td>';
echo '<td class="aksrt-native-inline">';
echo '<a class="button button-small" href="' . esc_url($this->page_url('aksrt_orders', ['order' => $order_number])) . '">' . esc_html__('详情', 'aksrt') . '</a>';
// 已支付是已退款的唯一前置状态。
if ($status_key === \AKSRT\Core\DomainModel::ORDER_STATUS_PAID) {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;" onsubmit="return confirm(\'' . esc_js(__('确认将该订单标记为已退款?不可恢复。', 'aksrt')) . '\');">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-native-inline" onsubmit="return confirm(\'' . esc_js(__('确认将该订单标记为已退款?不可恢复。', 'aksrt')) . '\');">';
echo '<input type="hidden" name="action" value="aksrt_pay_refund_order" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
wp_nonce_field('aksrt_pay_refund_order_' . $order_number);
@@ -278,7 +277,7 @@ class PayOrders {
}
$this->render_order_quick_status_buttons($order_number, $status_key);
if ($status_key === 'closed') {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;" onsubmit="return confirm(\'' . esc_js(__('确认永久删除此订单?不可恢复。', 'aksrt')) . '\');">';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-native-inline" onsubmit="return confirm(\'' . esc_js(__('确认永久删除此订单?不可恢复。', 'aksrt')) . '\');">';
echo '<input type="hidden" name="action" value="aksrt_pay_delete_order" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
wp_nonce_field('aksrt_pay_delete_order_' . $order_number);
@@ -290,10 +289,31 @@ class PayOrders {
}
echo '</tbody></table>';
NativeUi::table_wrap_close();
AdminListPagination::render(self::PAGE_SLUG, $total, $page_num, $per_page, $query_args);
NativeUi::card_close();
$this->render_footer();
NativeUi::page_close();
}
/**
* 订单状态键 → [文案, 色调] 映射,供 NativeUi::status_badge() 用。
*
* @param array<string, string> $labels DomainModel::order_status_labels()
* @return array<string, array{0:string,1:string}>
*/
private function status_badge_map(array $labels): array {
$tones = [
\AKSRT\Core\DomainModel::ORDER_STATUS_PENDING => 'warning',
\AKSRT\Core\DomainModel::ORDER_STATUS_PAID => 'success',
\AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDED => 'info',
\AKSRT\Core\DomainModel::ORDER_STATUS_CLOSED => 'neutral',
];
$map = [];
foreach ($labels as $key => $label) {
$map[$key] = [(string) $label, $tones[$key] ?? 'neutral'];
}
return $map;
}
private function render_order_detail_page(string $order_number): void {
@@ -319,43 +339,49 @@ class PayOrders {
$admin_note = (string) $order['note'];
}
echo '<p><a class="button" href="' . esc_url($this->page_url('aksrt_orders')) . '">&larr; ' . esc_html__('返回订单列表', 'aksrt') . '</a></p>';
$back = NativeUi::button_link(__('返回订单列表', 'aksrt'), $this->page_url('aksrt_orders'), false, 'dashicons-arrow-left-alt');
echo '<table class="widefat striped" style="max-width:960px;"><tbody>';
NativeUi::card_open(sprintf(__('订单 %s', 'aksrt'), $order_number), $back);
echo '<table class="widefat striped"><tbody>';
$rows = [
__('订单号', 'aksrt') => $order_number,
__('用户', 'aksrt') => $user ? $user->display_name . ' (#' . $user_id . ')' : (string) $user_id,
__('商品类型', 'aksrt') => \AKSRT\Core\DomainModel::order_product_type_labels()[(string) ($order['product_type'] ?? '')] ?? (string) ($order['product_type'] ?? ''),
__('商品 ID', 'aksrt') => (string) ($order['product_id'] ?? ''),
__('金额', 'aksrt') => $this->order_amount($order),
__('状态', 'aksrt') => $labels[$status] ?? $status,
__('支付方式', 'aksrt') => (string) ($order['payment_method'] ?? ''),
__('渠道交易号', 'aksrt') => (string) ($order['payment_transaction_id'] ?? ''),
__('创建时间', 'aksrt') => (string) ($order['created_at'] ?? ''),
__('支付时间', 'aksrt') => (string) ($order['paid_at'] ?? ''),
__('买家备注', 'aksrt') => $customer_note,
__('订单号', 'aksrt') => esc_html($order_number),
__('用户', 'aksrt') => $user_id > 0 ? NativeUi::user_cell($user_id) : esc_html((string) $user_id),
__('商品类型', 'aksrt') => esc_html(\AKSRT\Core\DomainModel::order_product_type_labels()[(string) ($order['product_type'] ?? '')] ?? (string) ($order['product_type'] ?? '')),
__('商品 ID', 'aksrt') => esc_html((string) ($order['product_id'] ?? '')),
__('金额', 'aksrt') => esc_html($this->order_amount($order)),
__('状态', 'aksrt') => NativeUi::status_badge($status, $this->status_badge_map($labels)),
__('支付方式', 'aksrt') => esc_html((string) ($order['payment_method'] ?? '')),
__('渠道交易号', 'aksrt') => esc_html((string) ($order['payment_transaction_id'] ?? '')),
__('创建时间', 'aksrt') => esc_html((string) ($order['created_at'] ?? '')),
__('支付时间', 'aksrt') => esc_html((string) ($order['paid_at'] ?? '')),
__('买家备注', 'aksrt') => esc_html($customer_note),
];
foreach ($rows as $label => $value) {
echo '<tr><th style="width:160px;">' . esc_html($label) . '</th><td>' . esc_html((string) $value) . '</td></tr>';
echo '<tr><th style="width:160px;">' . esc_html($label) . '</th><td>' . $value . '</td></tr>';
}
if (!empty($context)) {
echo '<tr><th>' . esc_html__('附加上下文', 'aksrt') . '</th><td><pre style="white-space:pre-wrap;margin:0;">' . esc_html(wp_json_encode($context, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '</pre></td></tr>';
echo '<tr><th>' . esc_html__('附加上下文', 'aksrt') . '</th><td><pre class="aksrt-native-mono" style="white-space:pre-wrap;margin:0;">' . esc_html(wp_json_encode($context, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '</pre></td></tr>';
}
echo '</tbody></table>';
NativeUi::card_close();
echo '<h2 style="margin-top:20px;">' . esc_html__('运营备注', 'aksrt') . '</h2>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="max-width:960px;">';
NativeUi::card_open(__('运营备注', 'aksrt'));
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-native-form-grid">';
echo '<input type="hidden" name="action" value="aksrt_pay_update_order_note" />';
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
wp_nonce_field('aksrt_pay_update_order_note_' . $order_number);
echo '<p><textarea name="admin_note" rows="3" class="large-text">' . esc_textarea($admin_note) . '</textarea></p>';
echo '<p class="aksrt-native-field is-wide"><textarea name="admin_note" rows="3" class="large-text">' . esc_textarea($admin_note) . '</textarea></p>';
echo '<div class="aksrt-native-form-foot">';
submit_button(__('保存备注', 'aksrt'), 'secondary', '', false);
echo '</div>';
echo '</form>';
NativeUi::card_close();
echo '<h2 style="margin-top:20px;">' . esc_html__('更新状态', 'aksrt') . '</h2>';
echo '<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px;">';
NativeUi::card_open(__('更新状态', 'aksrt'));
echo '<div class="aksrt-native-toolbar">';
$this->render_order_quick_status_buttons($order_number, $status, true);
echo '</div>';
NativeUi::card_close();
}
public function handle_update_order_status(): void {
@@ -537,16 +563,6 @@ class PayOrders {
return $this->cash_decimal((string) ($order['amount'] ?? '0')) . ' ' . strtoupper((string) ($order['currency'] ?? 'CNY'));
}
private function render_header(string $title): void {
echo '<div class="wrap">';
echo '<h1>' . esc_html($title) . '</h1>';
$this->render_flash();
}
private function render_footer(): void {
echo '</div>';
}
private function render_flash(): void {
if (empty($_GET['aksrt_pay_msg'])) {
return;