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

+45 -47
View File
@@ -129,21 +129,17 @@ trait UsersMenusTickets {
'send_user' => $send_user,
], static function ($v) { return $v !== '' && $v !== 0; }), $action_url);
// 对齐参考主题页面样式块
echo '<style>
.table-box>table{min-width:740px;}
.preview-item{position:relative;display:inline-block;width:160px;height:160px;border-radius:4px;background:#fff;margin:5px;vertical-align:top;}
.preview-item img{object-fit:contain;width:100%;height:100%;}
</style>';
$rows = $this->collect_ticket_rows($type, $status, $s, $send_user, $orderby, $order);
$total = count($rows);
$pages = max(1, (int) ceil($total / self::TICKETS_PER_PAGE));
$paged = min($paged, $pages);
$tickets = array_slice($rows, ($paged - 1) * self::TICKETS_PER_PAGE, self::TICKETS_PER_PAGE);
NativeUi::card_open();
echo '<div class="aksrt-native-toolbar">';
// 类型筛选按钮组(对齐参考主题:全部 / 禁封申诉 / 用户举报)
echo '<div style="margin:8px 0;">';
echo '<div class="aksrt-native-inline">';
foreach (['' => __('全部', 'aksrt'), 'appeal' => __('禁封申诉', 'aksrt'), 'report' => __('用户举报', 'aksrt')] as $t => $label) {
$url = $t === ''
? remove_query_arg(['s', 'type', 'action', 'id'], $page_url)
@@ -152,6 +148,16 @@ trait UsersMenusTickets {
}
echo '</div>';
// 搜索(对齐参考主题:右侧搜索记录表单)
echo '<form class="aksrt-native-filters" method="get">';
echo '<input type="hidden" name="page" value="aksrt_user_ban" /><input type="hidden" name="tab" value="tickets" />';
if ($type !== '') {
echo '<input type="hidden" name="type" value="' . esc_attr($type) . '" />';
}
echo '<input type="search" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('搜索记录', 'aksrt') . '" />';
submit_button(__('提交', 'aksrt'), 'primary', '', false);
echo '</form></div>';
// 状态 subsubsub(对齐参考主题:全部 | 待处理 | 已批准 | 已拒绝)
echo '<ul class="subsubsub">';
$status_links = [
@@ -164,31 +170,22 @@ trait UsersMenusTickets {
foreach ($status_links as $key => $label) {
$url = $key === '' ? remove_query_arg(['s', 'status'], $page_url) : add_query_arg('status', $key, remove_query_arg('s', $page_url));
$sep = ++$i < count($status_links) ? ' |' : '';
printf('<li><a href="%s">%s</a>%s</li> ', esc_url($url), esc_html($label), esc_html($sep));
$current = $status === $key ? ' class="current" aria-current="page"' : '';
printf('<li><a%s href="%s">%s</a>%s</li> ', $current, esc_url($url), esc_html($label), esc_html($sep));
}
echo '</ul>';
// 搜索(对齐参考主题:右侧搜索记录表单)
echo '<div class="order-header" style="margin:6px 0 12px;float:right;">';
echo '<form class="form-inline" method="get">';
echo '<input type="hidden" name="page" value="aksrt_user_ban" /><input type="hidden" name="tab" value="tickets" />';
if ($type !== '') {
echo '<input type="hidden" name="type" value="' . esc_attr($type) . '" />';
}
echo '<input type="text" name="s" value="' . esc_attr($s) . '" placeholder="' . esc_attr__('搜索记录', 'aksrt') . '" />';
submit_button(__('提交', 'aksrt'), 'primary', '', false);
echo '</form></div>';
if ($s !== '') {
echo '<div class="mb10">"' . esc_html($s) . '" ' . esc_html__('的搜索结果', 'aksrt') . '</div>';
echo '<p class="aksrt-native-muted">"' . esc_html($s) . '" ' . esc_html__('的搜索结果', 'aksrt') . '</p>';
}
NativeUi::card_close();
$process_id = isset($_GET['id']) ? (string) wp_unslash($_GET['id']) : '';
// 处理视图不设“查看全部”链接(参考主题无此元素):重置过滤由顶部
// 类型按钮组与状态 subsubsub 承担,工单行内直接展开处理表单。
echo '<div class="table-box" style="overflow-y:auto;width:100%;">';
echo '<table class="widefat fixed striped posts" style="min-width:1000px;">';
NativeUi::table_card_open();
echo '<table class="widefat fixed striped posts">';
echo '<thead><tr>';
// 表头(对齐参考主题:状态/类型/提交用户/详情/提交时间/更新时间 + 排序箭头)
@@ -214,7 +211,7 @@ trait UsersMenusTickets {
echo '</tr></thead><tbody>';
if (!$tickets) {
echo '<tr><td colspan="6" align="center"><strong>' . esc_html__('暂无申请记录', 'aksrt') . '</strong></td></tr>';
echo NativeUi::empty_row(6, __('暂无申请记录', 'aksrt'));
}
foreach ($tickets as $row) {
@@ -235,7 +232,10 @@ trait UsersMenusTickets {
echo '</td></tr>';
}
echo '</tbody></table></div>';
echo '</tbody></table>';
NativeUi::table_wrap_close();
$this->ticket_pagenavi($total, self::TICKETS_PER_PAGE);
NativeUi::card_close();
// 行内展开/收起的轻量交互(管理员环境必有 jQuery)。
echo '<script>
@@ -273,8 +273,6 @@ trait UsersMenusTickets {
});
})(jQuery);
</script>';
$this->ticket_pagenavi($total, self::TICKETS_PER_PAGE);
}
/** 分页(对齐参考主题 zibpay_admin_pagenavi:« ‹ 第X页共Y页 › » + 每页显示数) */
@@ -309,22 +307,22 @@ trait UsersMenusTickets {
$is_processing = isset($_GET['action']) && sanitize_key((string) wp_unslash($_GET['action'])) === 'process'
&& isset($_GET['id']) && (string) wp_unslash($_GET['id']) === $kind . '-' . (string) $row['id'];
if ($is_processing) {
$status_html = '<span style="color:#fb4444;">' . esc_html__('正在处理', 'aksrt') . '</span>';
$status_html = NativeUi::badge(__('正在处理', 'aksrt'), 'warning');
} elseif ($status === 'pending') {
// JS 切换展开(无整页刷新);无 JS 时 href 整页兜底。
$status_html = '<a class="button" data-aksrt-ticket-toggle="' . esc_attr($process_key) . '" href="' . esc_url($url) . '">' . esc_html__('立即处理', 'aksrt') . '</a>';
} elseif ($status === 'resolved') {
$status_html = $kind === 'appeal'
? '<span style="color:#16a60a;">' . esc_html__('已批准', 'aksrt') . '</span>'
: '<span style="color:#0989fd;">' . esc_html__('处理完成', 'aksrt') . '</span>';
? NativeUi::badge(__('已批准', 'aksrt'), 'success')
: NativeUi::badge(__('处理完成', 'aksrt'), 'info');
} else {
$status_html = '<span style="color:#fb4444;">' . esc_html__('已拒绝', 'aksrt') . '</span>';
$status_html = NativeUi::badge(__('已拒绝', 'aksrt'), 'danger');
}
// 类型列(对齐参考主题:不良举报红 / 禁封申诉绿)
$type_html = $kind === 'report'
? '<span style="color:#fb4444;">' . esc_html__('不良举报', 'aksrt') . '</span>'
: '<span style="color:#16a60a;">' . esc_html__('禁封申诉', 'aksrt') . '</span>';
? NativeUi::badge(__('不良举报', 'aksrt'), 'danger', false)
: NativeUi::badge(__('禁封申诉', 'aksrt'), 'success', false);
// 提交用户列(显示名链接 + 管理|查看 row-actions)
$user = $user_id > 0 ? get_userdata($user_id) : null;
@@ -345,7 +343,7 @@ trait UsersMenusTickets {
echo '<td>' . $status_html . '</td>';
echo '<td>' . $type_html . '</td>';
echo '<td>' . $user_html . '</td>';
echo '<td><div style="max-height:39px;overflow:hidden;">' . $desc_html . '</div></td>';
echo '<td><div class="aksrt-native-clamp">' . $desc_html . '</div></td>';
echo '<td>' . esc_html((string) $row['time']) . '</td>';
echo '<td>' . esc_html((string) ($row['updated'] !== '' ? $row['updated'] : '—')) . '</td>';
echo '</tr>';
@@ -358,9 +356,9 @@ trait UsersMenusTickets {
$link = $this->ticket_target_link($target_type, $target_id);
if ($target_type === 'user') {
$html = '<div style="color:#fb4444;">' . esc_html__('举报用户:', 'aksrt') . ($link !== '' ? $link : esc_html('#' . $target_id)) . '</div>';
$html = '<div class="aksrt-native-text--danger">' . esc_html__('举报用户:', 'aksrt') . ($link !== '' ? $link : esc_html('#' . $target_id)) . '</div>';
} else {
$html = '<div style="color:#fb4444;">' . esc_html__('举报内容:', 'aksrt') . ($link !== '' ? $link : esc_html($target_type . ' #' . $target_id)) . '</div>';
$html = '<div class="aksrt-native-text--danger">' . esc_html__('举报内容:', 'aksrt') . ($link !== '' ? $link : esc_html($target_type . ' #' . $target_id)) . '</div>';
}
$html .= '<div>' . esc_html(sprintf(__('举报原因:%s', 'aksrt'), (string) ($r['reason'] ?? ''))) . '</div>';
if (!empty($r['category'])) {
@@ -393,7 +391,7 @@ trait UsersMenusTickets {
if ($ban) {
$end = !empty($ban['expires_at']) ? (string) $ban['expires_at'] : __('永久', 'aksrt');
$html = '<div style="color:#fd6a11;">' . esc_html(sprintf(__('禁封状态:%s', 'aksrt'), __('封禁中(禁止登录)', 'aksrt'))) . '</div>';
$html = '<div class="aksrt-native-text--warning">' . esc_html(sprintf(__('禁封状态:%s', 'aksrt'), __('封禁中(禁止登录)', 'aksrt'))) . '</div>';
$html .= '<div>' . esc_html(sprintf(__('禁封原因:%s', 'aksrt'), (string) ($ban['reason'] ?? ''))) . '</div>';
$html .= '<div>' . esc_html(sprintf(__('开始时间:%s', 'aksrt'), (string) ($ban['created_at'] ?? ''))) . '</div>';
$html .= '<div>' . esc_html(sprintf(__('结束时间:%s', 'aksrt'), $end)) . '</div>';
@@ -401,24 +399,24 @@ trait UsersMenusTickets {
}
if ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_SUSPEND)) {
return '<div style="color:#fd6a11;">' . esc_html(sprintf(__('限制状态:%s', 'aksrt'), __('停权中', 'aksrt'))) . '</div>';
return '<div class="aksrt-native-text--warning">' . esc_html(sprintf(__('限制状态:%s', 'aksrt'), __('停权中', 'aksrt'))) . '</div>';
}
if ($svc->is_restricted($user_id, \AKSRT\User\Restriction::TYPE_MUTE)) {
return '<div style="color:#fd6a11;">' . esc_html(sprintf(__('限制状态:%s', 'aksrt'), __('禁言中', 'aksrt'))) . '</div>';
return '<div class="aksrt-native-text--warning">' . esc_html(sprintf(__('限制状态:%s', 'aksrt'), __('禁言中', 'aksrt'))) . '</div>';
}
// 对齐参考主题:申诉列表行中用户已无禁封 → 显示已解封并自动结案
if ($context === 'appeal_row' && class_exists('\AKSRT\User\BanAppeal') && \AKSRT\User\BanAppeal::get_status($user_id) === 'pending') {
update_user_meta($user_id, \AKSRT\User\BanAppeal::META_STATUS, 'approved');
return '<div style="color:#fd6a11;">' . esc_html__('此用户已解封', 'aksrt') . '</div>';
return '<div class="aksrt-native-text--warning">' . esc_html__('此用户已解封', 'aksrt') . '</div>';
}
// 对齐参考主题:申诉处理表单中用户已解封的提示
if ($context === 'appeal_form') {
return '<div style="color:#16a60a;">' . esc_html__('当前用户已解封', 'aksrt') . '</div>';
return '<div class="aksrt-native-text--success">' . esc_html__('当前用户已解封', 'aksrt') . '</div>';
}
return '<div style="color:#16a60a;">' . esc_html__('用户状态正常', 'aksrt') . '</div>';
return '<div class="aksrt-native-text--success">' . esc_html__('用户状态正常', 'aksrt') . '</div>';
}
private function ticket_target_link(string $target_type, int $target_id): string {
@@ -480,7 +478,7 @@ trait UsersMenusTickets {
// 与参考主题一致:跳转到前台该用户公开主页,经封面右下角三点
// 菜单的「禁封此用户」打开封禁弹窗完成处理(后台不提供禁封表单)。
$ban_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($target_id) : get_author_posts_url($target_id);
echo '<div style="color:#16a60a;">' . esc_html__('用户状态正常', 'aksrt') . '</div>';
echo '<div class="aksrt-native-text--success">' . esc_html__('用户状态正常', 'aksrt') . '</div>';
echo '<p class="description">' . esc_html__('请查阅相关资料以及举证,如举报事实清楚,可以', 'aksrt');
echo ' <a target="_blank" href="' . esc_url($ban_url) . '">' . esc_html__('点击此处', 'aksrt') . '</a> ';
echo esc_html__('对用户做禁封处理,并将处理结果填写至下方', 'aksrt') . '</p>';
@@ -489,7 +487,7 @@ trait UsersMenusTickets {
}
echo '<tr><th>' . esc_html__('处理说明', 'aksrt') . '</th><td>';
echo '<textarea style="width:95%;max-width:500px;" rows="2" cols="40" name="note" placeholder="' . esc_attr__('请输入本次处理的方式及说明', 'aksrt') . '"></textarea>';
echo '<textarea rows="2" cols="40" name="note" placeholder="' . esc_attr__('请输入本次处理的方式及说明', 'aksrt') . '"></textarea>';
echo '<p class="description">' . esc_html__('此处内容会反馈给举报人', 'aksrt') . '</p>';
echo '</td></tr>';
@@ -518,12 +516,12 @@ trait UsersMenusTickets {
echo '</td></tr>';
echo '<tr><th>' . esc_html__('处理方式', 'aksrt') . '</th><td>';
echo '<p><input type="radio" name="decision" id="aksrt-process-1-' . $uid . '" value="approve" checked="checked"><label for="aksrt-process-1-' . $uid . '" style="color:#036ee2;">' . esc_html__('申诉通过(解封该用户)', 'aksrt') . '</label></p>';
echo '<p><input type="radio" name="decision" id="aksrt-process-2-' . $uid . '" value="reject"><label for="aksrt-process-2-' . $uid . '" style="color:#eb1b65;">' . esc_html__('申诉拒绝(保持原禁封状态)', 'aksrt') . '</label></p>';
echo '<p><input type="radio" name="decision" id="aksrt-process-1-' . $uid . '" value="approve" checked="checked"><label for="aksrt-process-1-' . $uid . '" class="aksrt-native-text--info">' . esc_html__('申诉通过(解封该用户)', 'aksrt') . '</label></p>';
echo '<p><input type="radio" name="decision" id="aksrt-process-2-' . $uid . '" value="reject"><label for="aksrt-process-2-' . $uid . '" class="aksrt-native-text--danger">' . esc_html__('申诉拒绝(保持原禁封状态)', 'aksrt') . '</label></p>';
echo '</td></tr>';
echo '<tr><th>' . esc_html__('处理留言', 'aksrt') . '</th><td>';
echo '<textarea style="width:95%;max-width:500px;" rows="2" cols="40" name="note" placeholder="' . esc_attr__('给用户留言', 'aksrt') . '"></textarea>';
echo '<textarea rows="2" cols="40" name="note" placeholder="' . esc_attr__('给用户留言', 'aksrt') . '"></textarea>';
echo '<p class="description">' . esc_html__('如需给用户留言请填写此处,如果拒绝请填写拒绝原因', 'aksrt') . '</p>';
echo '</td></tr>';