339 lines
22 KiB
PHP
339 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* 用户会员后台页的展示层:统计、筛选、会员列表、调整弹窗与变更记录。
|
|
*
|
|
* 弹窗是页面上唯一的写表单:行内「调整」与工具栏「批量调整」都只负责把目标用户
|
|
* 填进弹窗(assets/js/admin-vip.js),提交由 UsersMenusVip::handle_mutate_vip() 处理。
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Admin\Native;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait UsersMenusVipView {
|
|
|
|
private function render_vip_stats(): void {
|
|
$stats = $this->query_vip_stats();
|
|
$names = $this->vip_level_names();
|
|
$window = $this->vip_expiring_window();
|
|
$base = admin_url('users.php?page=' . self::VIP_PAGE_SLUG);
|
|
NativeUi::stats([
|
|
[
|
|
'label' => __('限时会员', 'aksrt'),
|
|
'value' => $stats['active'],
|
|
'url' => add_query_arg('state', 'active', $base),
|
|
'icon' => 'dashicons-clock',
|
|
'hint' => __('有效期内', 'aksrt'),
|
|
],
|
|
[
|
|
'label' => __('永久会员', 'aksrt'),
|
|
'value' => $stats['permanent'],
|
|
'url' => add_query_arg('state', 'permanent', $base),
|
|
'tone' => 'primary',
|
|
'icon' => 'dashicons-awards',
|
|
],
|
|
[
|
|
'label' => __('7 天内到期', 'aksrt'),
|
|
'value' => $stats['expiring'],
|
|
'url' => add_query_arg(['state' => 'active', 'date_from' => $window['from'], 'date_to' => $window['to'], 'orderby' => 'expires', 'order' => 'ASC'], $base),
|
|
'tone' => $stats['expiring'] > 0 ? 'warning' : 'neutral',
|
|
'icon' => 'dashicons-warning',
|
|
'hint' => __('建议提醒续费', 'aksrt'),
|
|
],
|
|
[
|
|
'label' => __('已过期', 'aksrt'),
|
|
'value' => $stats['expired'],
|
|
'url' => add_query_arg('state', 'expired', $base),
|
|
'tone' => 'neutral',
|
|
'icon' => 'dashicons-dismiss',
|
|
],
|
|
[
|
|
'label' => $names[1],
|
|
'value' => $stats['level_1'],
|
|
'tone' => 'info',
|
|
'icon' => 'dashicons-star-half',
|
|
'hint' => __('当前有效人数', 'aksrt'),
|
|
],
|
|
[
|
|
'label' => $names[2],
|
|
'value' => $stats['level_2'],
|
|
'tone' => 'info',
|
|
'icon' => 'dashicons-star-filled',
|
|
'hint' => __('当前有效人数', 'aksrt'),
|
|
],
|
|
]);
|
|
}
|
|
|
|
private function render_vip_tabs(string $tab): void {
|
|
$base = admin_url('users.php?page=' . self::VIP_PAGE_SLUG);
|
|
$tabs = [
|
|
'members' => [__('会员列表', 'aksrt'), $base, 'dashicons-groups'],
|
|
'history' => [__('变更记录', 'aksrt'), add_query_arg('tab', 'history', $base), 'dashicons-backup'],
|
|
];
|
|
echo '<nav class="aksrt-native-tabs" aria-label="' . esc_attr__('会员视图', 'aksrt') . '">';
|
|
foreach ($tabs as $key => [$label, $url, $icon]) {
|
|
$current = $key === $tab;
|
|
echo '<a class="aksrt-native-tabs__link' . ($current ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($current ? ' aria-current="page"' : '') . '>'
|
|
. '<span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' . esc_html($label) . '</a>';
|
|
}
|
|
echo '</nav>';
|
|
}
|
|
|
|
private function render_vip_filters(array $filters): void {
|
|
$names = $this->vip_level_names();
|
|
NativeUi::card_open('', '', false, 'aksrt-native-toolbar aksrt-vip-filters');
|
|
echo '<form method="get" class="aksrt-native-filters">';
|
|
echo '<input type="hidden" name="page" value="' . esc_attr(self::VIP_PAGE_SLUG) . '">';
|
|
echo '<input type="hidden" name="orderby" value="' . esc_attr($filters['orderby']) . '"><input type="hidden" name="order" value="' . esc_attr($filters['order']) . '">';
|
|
echo '<label>UID<input type="number" min="1" name="user_id" class="small-text" value="' . esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('全部', 'aksrt') . '"></label>';
|
|
echo '<label>' . esc_html__('档位', 'aksrt') . '<select name="level">';
|
|
foreach (['all' => __('全部', 'aksrt'), '0' => __('普通用户', 'aksrt'), '1' => $names[1], '2' => $names[2]] as $key => $label) {
|
|
printf('<option value="%s"%s>%s</option>', esc_attr((string) $key), selected($filters['level'], (string) $key, false), esc_html($label));
|
|
}
|
|
echo '</select></label><label>' . esc_html__('状态', 'aksrt') . '<select name="state">';
|
|
foreach ($this->vip_state_labels() as $key => $label) {
|
|
printf('<option value="%s"%s>%s</option>', esc_attr($key), selected($filters['state'], $key, false), esc_html($label));
|
|
}
|
|
echo '</select></label>';
|
|
echo '<label>' . esc_html__('到期日', 'aksrt') . '<input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" aria-label="' . esc_attr__('到期日起', 'aksrt') . '">'
|
|
. '<span aria-hidden="true">—</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" aria-label="' . esc_attr__('到期日止', 'aksrt') . '"></label>';
|
|
echo '<button type="submit" class="button button-primary">' . esc_html__('筛选', 'aksrt') . '</button>';
|
|
if ($this->vip_filters_active($filters)) {
|
|
echo '<a class="button" href="' . esc_url(admin_url('users.php?page=' . self::VIP_PAGE_SLUG)) . '">' . esc_html__('重置', 'aksrt') . '</a>';
|
|
}
|
|
echo '</form>';
|
|
NativeUi::card_close();
|
|
}
|
|
|
|
/** @return array<string,string> */
|
|
private function vip_state_labels(): array {
|
|
return [
|
|
'all' => __('全部', 'aksrt'),
|
|
'active' => __('限时有效', 'aksrt'),
|
|
'permanent' => __('永久', 'aksrt'),
|
|
'expired' => __('已过期', 'aksrt'),
|
|
'none' => __('无会员', 'aksrt'),
|
|
];
|
|
}
|
|
|
|
private function render_vip_table(array $items, array $filters, int $pages, int $page): void {
|
|
$actions = '<span class="aksrt-vip-selection" data-aksrt-vip-selection hidden></span>'
|
|
. '<button type="button" class="button button-primary" data-aksrt-vip-bulk disabled>'
|
|
. '<span class="dashicons dashicons-edit" aria-hidden="true"></span>' . esc_html__('批量调整', 'aksrt') . '</button>';
|
|
NativeUi::table_card_open(__('会员列表', 'aksrt'), $actions);
|
|
echo '<table class="wp-list-table widefat fixed striped aksrt-vip-table"><thead><tr>';
|
|
echo '<td class="manage-column column-cb check-column"><input type="checkbox" data-aksrt-vip-check-all aria-label="' . esc_attr__('选择本页全部用户', 'aksrt') . '"></td>';
|
|
echo '<th class="column-user">' . esc_html__('用户', 'aksrt') . '</th>';
|
|
echo '<th>' . esc_html__('当前会员', 'aksrt') . '</th>';
|
|
echo '<th>' . $this->vip_sort_link($filters) . '</th>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
echo '<th class="column-actions">' . esc_html__('操作', 'aksrt') . '</th></tr></thead><tbody>';
|
|
if ($items === []) {
|
|
$hint = $this->vip_filters_active($filters) ? __('试试调整或重置筛选条件。', 'aksrt') : '';
|
|
echo NativeUi::empty_row(5, __('没有符合条件的用户', 'aksrt'), $hint); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
}
|
|
foreach ($items as $row) {
|
|
$this->render_vip_row($row);
|
|
}
|
|
echo '</tbody></table>';
|
|
NativeUi::table_wrap_close();
|
|
$this->pagination($pages, $page);
|
|
NativeUi::card_close();
|
|
}
|
|
|
|
private function vip_sort_link(array $filters): string {
|
|
$active = $filters['orderby'] === 'expires';
|
|
$next = $active && $filters['order'] === 'ASC' ? 'DESC' : 'ASC';
|
|
$state = $active ? ($filters['order'] === 'ASC' ? ' is-asc' : ' is-desc') : '';
|
|
return '<a class="aksrt-vip-sort' . $state . '" href="' . esc_url($this->vip_list_url($filters, ['orderby' => 'expires', 'order' => $next])) . '">'
|
|
. esc_html__('到期时间', 'aksrt') . '<span class="aksrt-vip-sort__icon" aria-hidden="true"><i></i><i></i></span></a>';
|
|
}
|
|
|
|
private function render_vip_row(array $row): void {
|
|
$id = (int) $row['ID'];
|
|
$names = $this->vip_level_names();
|
|
$now = time();
|
|
$level = (int) $row['level'];
|
|
$expires = (int) $row['expires'];
|
|
$expired = !empty($row['is_expired']);
|
|
$fell_back = $level > 0 && ((int) $row['raw_level'] !== $level || (int) $row['raw_expires'] !== $expires);
|
|
$base_level = (int) $row['base_level'];
|
|
$base_expires = (int) $row['base_expires'];
|
|
$label = $level > 0 ? ($names[$level] ?? ('VIP' . $level)) : __('普通用户', 'aksrt');
|
|
|
|
if ($expired) {
|
|
$lapsed = max((int) $row['raw_level'], (int) $row['expired_level']);
|
|
$badge = NativeUi::badge(sprintf(__('%s · 已过期', 'aksrt'), $names[$lapsed] ?? __('会员', 'aksrt')), 'neutral');
|
|
} else {
|
|
$badge = NativeUi::badge($label, $level > 0 ? ($fell_back ? 'warning' : ($expires === 0 ? 'primary' : 'success')) : 'neutral', $level > 0);
|
|
}
|
|
$note = '';
|
|
if ($fell_back) {
|
|
$note = __('限时档位已到期,按基础档位计', 'aksrt');
|
|
} elseif ($level > 0 && $base_level > 0) {
|
|
$note = sprintf(__('到期后回退:%s', 'aksrt'), $names[$base_level] ?? ('VIP' . $base_level));
|
|
}
|
|
|
|
$expiry_label = $this->vip_expiry_label($level, $expires, $expired);
|
|
$expiry_html = esc_html($expiry_label);
|
|
if ($level > 0 && $expires > 0 && !$expired) {
|
|
$days = (int) ceil(($expires - $now) / DAY_IN_SECONDS);
|
|
$expiry_html .= '<span class="aksrt-vip-remaining ' . ($days <= 7 ? 'aksrt-native-text--warning' : 'aksrt-native-muted') . '">'
|
|
. esc_html(sprintf(__('剩余 %s', 'aksrt'), human_time_diff($now, $expires))) . '</span>';
|
|
}
|
|
|
|
$can_extend = $level > 0 && $expires > 0 && !$expired;
|
|
$can_restore = $base_level > 0 && ($base_expires === 0 || $base_expires > $now);
|
|
$user_name = (string) ($row['display_name'] !== '' ? $row['display_name'] : $row['user_login']);
|
|
$data = [
|
|
'user-id' => (string) $id,
|
|
'user-name' => $user_name,
|
|
'current' => $expired ? __('已过期', 'aksrt') : $label . ($level > 0 ? ' · ' . $expiry_label : ''),
|
|
'level' => (string) max(1, $level),
|
|
'can-extend' => $can_extend ? '1' : '0',
|
|
'can-restore' => $can_restore ? '1' : '0',
|
|
];
|
|
$attrs = '';
|
|
foreach ($data as $key => $value) {
|
|
$attrs .= ' data-' . $key . '="' . esc_attr($value) . '"';
|
|
}
|
|
|
|
echo '<tr>';
|
|
echo '<th scope="row" class="check-column"><input type="checkbox" class="aksrt-vip-check" value="' . esc_attr((string) $id) . '"' . $attrs . ' aria-label="' . esc_attr(sprintf(__('选择 %s', 'aksrt'), $user_name)) . '"></th>'; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
|
|
echo '<td class="column-user">' . NativeUi::user_cell($id, $user_name) . '<span class="aksrt-native-muted aksrt-vip-login">@' . esc_html((string) $row['user_login']) . '</span></td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
echo '<td>' . $badge . ($note !== '' ? '<span class="aksrt-vip-note aksrt-native-muted">' . esc_html($note) . '</span>' : '') . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
echo '<td class="aksrt-vip-expiry">' . $expiry_html . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above.
|
|
echo '<td class="column-actions"><button type="button" class="button button-small" data-aksrt-vip-edit' . $attrs . '>' // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
|
|
. '<span class="dashicons dashicons-edit" aria-hidden="true"></span>' . esc_html__('调整', 'aksrt') . '</button></td>';
|
|
echo '</tr>';
|
|
}
|
|
|
|
private function vip_expiry_label(int $level, int $expires, bool $expired): string {
|
|
if ($expired) {
|
|
return __('已过期', 'aksrt');
|
|
}
|
|
if ($level <= 0) {
|
|
return '—';
|
|
}
|
|
return $expires === 0 ? __('永久有效', 'aksrt') : wp_date('Y-m-d H:i', $expires, wp_timezone());
|
|
}
|
|
|
|
private function render_vip_dialog(array $filters): void {
|
|
$names = $this->vip_level_names();
|
|
$today = (new \DateTimeImmutable('today', wp_timezone()))->format('Y-m-d');
|
|
$strings = [
|
|
'single' => __('调整会员:%s', 'aksrt'),
|
|
'bulk' => __('批量调整会员(%d 人)', 'aksrt'),
|
|
'selected' => __('已选 %d 人', 'aksrt'),
|
|
'bulk-note' => __('不满足条件的用户会被跳过并计入失败,结果会在提交后汇总显示。', 'aksrt'),
|
|
'extend-off' => __('该用户不是限时有效会员,无法延长。', 'aksrt'),
|
|
'restore-off' => __('该用户没有可恢复的回退会员。', 'aksrt'),
|
|
];
|
|
$attrs = '';
|
|
foreach ($strings as $key => $value) {
|
|
$attrs .= ' data-i18n-' . $key . '="' . esc_attr($value) . '"';
|
|
}
|
|
echo '<dialog class="aksrt-vip-dialog" id="aksrt-vip-dialog" aria-labelledby="aksrt-vip-dialog-title"' . $attrs . '>'; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
|
|
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-vip-dialog__form">';
|
|
echo '<input type="hidden" name="action" value="aksrt_users_mutate_vip">';
|
|
wp_nonce_field('aksrt_users_mutate_vip');
|
|
$this->render_vip_return_fields($filters);
|
|
echo '<div data-aksrt-vip-ids></div>';
|
|
|
|
echo '<header class="aksrt-vip-dialog__head"><h2 id="aksrt-vip-dialog-title" class="aksrt-vip-dialog__title" data-aksrt-vip-title></h2>'
|
|
. '<button type="button" class="aksrt-vip-dialog__close" data-aksrt-vip-close aria-label="' . esc_attr__('关闭', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
|
|
echo '<div class="aksrt-vip-dialog__body">';
|
|
echo '<p class="aksrt-vip-dialog__target" data-aksrt-vip-target></p>';
|
|
|
|
echo '<fieldset class="aksrt-vip-ops"><legend class="aksrt-native-field__label">' . esc_html__('操作', 'aksrt') . '</legend>';
|
|
$ops = [
|
|
'set' => [__('设置会员', 'aksrt'), 'dashicons-awards'],
|
|
'extend' => [__('延长有效期', 'aksrt'), 'dashicons-clock'],
|
|
'restore' => [__('恢复回退会员', 'aksrt'), 'dashicons-undo'],
|
|
'disable' => [__('停用会员', 'aksrt'), 'dashicons-dismiss'],
|
|
];
|
|
foreach ($ops as $key => [$label, $icon]) {
|
|
echo '<label class="aksrt-vip-op' . ($key === 'disable' ? ' is-danger' : '') . '"><input type="radio" name="vip_operation" value="' . esc_attr($key) . '"' . checked($key, 'set', false) . '>'
|
|
. '<span><span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' . esc_html($label) . '</span></label>';
|
|
}
|
|
echo '</fieldset>';
|
|
echo '<p class="aksrt-vip-op-hint aksrt-native-muted" data-aksrt-vip-op-hint hidden></p>';
|
|
|
|
echo '<div class="aksrt-native-form-grid aksrt-vip-pane" data-aksrt-vip-pane="set">';
|
|
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('会员档位', 'aksrt') . '</span><select name="level" data-aksrt-vip-level>';
|
|
foreach ($names as $value => $label) {
|
|
echo '<option value="' . esc_attr((string) $value) . '">' . esc_html($label) . '</option>';
|
|
}
|
|
echo '</select></label>';
|
|
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">' . esc_html__('到期日', 'aksrt') . '</span><input type="date" name="expires_date" min="' . esc_attr($today) . '" required data-aksrt-vip-date>'
|
|
. '<span class="aksrt-native-field__hint">' . esc_html__('当天 23:59:59 到期', 'aksrt') . '</span></label>';
|
|
echo '<label class="aksrt-vip-switch is-wide"><input type="checkbox" name="permanent" value="1" data-aksrt-vip-permanent>' . esc_html__('永久会员(不设到期日)', 'aksrt') . '</label>';
|
|
echo '<p class="aksrt-native-field__hint is-wide">' . esc_html__('设置会员会直接覆盖当前档位与到期时间,并清除回退会员。', 'aksrt') . '</p>';
|
|
echo '</div>';
|
|
|
|
echo '<div class="aksrt-native-form-grid aksrt-vip-pane" data-aksrt-vip-pane="extend" hidden>';
|
|
echo '<div class="aksrt-native-field is-wide"><span class="aksrt-native-field__label">' . esc_html__('延长时长', 'aksrt') . '</span><div class="aksrt-vip-duration">'
|
|
. '<input type="number" name="amount" min="1" max="3650" value="30" required aria-label="' . esc_attr__('延长数量', 'aksrt') . '">'
|
|
. '<select name="unit" aria-label="' . esc_attr__('延长单位', 'aksrt') . '"><option value="day">' . esc_html__('天', 'aksrt') . '</option><option value="month">' . esc_html__('月', 'aksrt') . '</option></select></div>'
|
|
. '<span class="aksrt-native-field__hint">' . esc_html__('从当前到期时间起顺延;仅对限时有效会员生效。', 'aksrt') . '</span></div>';
|
|
echo '</div>';
|
|
|
|
echo '<div class="aksrt-native-callout is-warning aksrt-vip-pane" data-aksrt-vip-pane="restore" hidden>' . esc_html__('将取消当前限时升级,恢复为回退会员(档位与到期时间按升级前保存的记录)。', 'aksrt') . '</div>';
|
|
echo '<div class="aksrt-native-callout is-danger aksrt-vip-pane" data-aksrt-vip-pane="disable" hidden>' . esc_html__('停用后立即撤销前端会员权益,并清除回退会员记录。此操作无法撤销,只能重新设置会员。', 'aksrt') . '</div>';
|
|
|
|
echo '<label class="aksrt-native-field aksrt-vip-reason"><span class="aksrt-native-field__label">' . esc_html__('变更原因', 'aksrt') . ' <span class="aksrt-native-text--danger" aria-hidden="true">*</span></span>'
|
|
. '<input type="text" name="reason" required maxlength="255" placeholder="' . esc_attr__('例如:活动赠送、客服补偿、退款撤销', 'aksrt') . '">'
|
|
. '<span class="aksrt-native-field__hint">' . esc_html__('写入变更记录,便于后续追溯。', 'aksrt') . '</span></label>';
|
|
echo '<p class="aksrt-native-muted aksrt-vip-bulk-note" data-aksrt-vip-bulk-note hidden></p>';
|
|
echo '</div>';
|
|
|
|
echo '<footer class="aksrt-vip-dialog__foot"><button type="button" class="button" data-aksrt-vip-close>' . esc_html__('取消', 'aksrt') . '</button>'
|
|
. '<button type="submit" class="button button-primary" data-aksrt-vip-submit>' . esc_html__('确认变更', 'aksrt') . '</button></footer>';
|
|
echo '</form></dialog>';
|
|
}
|
|
|
|
private function render_vip_history(int $page): void {
|
|
$result = (new \AKSRT\Content\VipMembershipEventRepository())->query($page, self::VIP_PER_PAGE);
|
|
$ops = [
|
|
'set' => [__('设置会员', 'aksrt'), 'success'],
|
|
'extend' => [__('延长有效期', 'aksrt'), 'info'],
|
|
'restore' => [__('恢复回退会员', 'aksrt'), 'warning'],
|
|
'disable' => [__('停用会员', 'aksrt'), 'danger'],
|
|
'expire_recover' => [__('到期自动回退', 'aksrt'), 'neutral'],
|
|
];
|
|
$statuses = [
|
|
'succeeded' => [__('成功', 'aksrt'), 'success'],
|
|
'failed' => [__('失败', 'aksrt'), 'danger'],
|
|
'pending' => [__('处理中', 'aksrt'), 'warning'],
|
|
];
|
|
NativeUi::table_card_open(__('变更记录', 'aksrt'), '<span class="aksrt-native-muted">' . esc_html(sprintf(__('共 %d 条', 'aksrt'), (int) $result['total'])) . '</span>');
|
|
echo '<table class="wp-list-table widefat fixed striped aksrt-vip-history"><thead><tr><th class="column-time">' . esc_html__('时间', 'aksrt') . '</th><th class="column-user">' . esc_html__('用户', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th><th>' . esc_html__('操作人', 'aksrt') . '</th><th class="column-reason">' . esc_html__('原因', 'aksrt') . '</th><th>' . esc_html__('结果', 'aksrt') . '</th></tr></thead><tbody>';
|
|
if ($result['items'] === []) {
|
|
echo NativeUi::empty_row(6, __('暂无变更记录', 'aksrt'), __('在会员列表中调整会员后会出现在这里。', 'aksrt')); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
}
|
|
foreach ($result['items'] as $event) {
|
|
$created = (string) $event['created_at'];
|
|
$time = $created !== '' ? get_date_from_gmt($created, 'Y-m-d H:i') : '—';
|
|
$actor = (string) ($event['actor_name'] ?? '');
|
|
if ($actor === '') {
|
|
$actor = ((string) ($event['source'] ?? '') === 'admin') ? '—' : __('系统', 'aksrt');
|
|
}
|
|
$result_html = NativeUi::status_badge((string) $event['status'], $statuses);
|
|
if ((string) $event['status'] === 'failed' && (string) ($event['error_message'] ?? '') !== '') {
|
|
$result_html .= '<span class="aksrt-vip-note aksrt-native-text--danger">' . esc_html((string) $event['error_message']) . '</span>';
|
|
}
|
|
echo '<tr><td class="column-time">' . esc_html($time) . (!empty($event['batch_uuid']) ? '<span class="aksrt-vip-note aksrt-native-muted">' . esc_html__('批量', 'aksrt') . '</span>' : '') . '</td>'
|
|
. '<td class="column-user">' . NativeUi::user_cell((int) $event['user_id'], (string) ($event['display_name'] ?: $event['user_login'] ?: '')) . '</td>' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
. '<td>' . NativeUi::status_badge((string) $event['operation'], $ops) . '</td>' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
. '<td>' . esc_html($actor) . '</td>'
|
|
. '<td class="column-reason">' . esc_html((string) $event['reason'] !== '' ? (string) $event['reason'] : '—') . '</td>'
|
|
. '<td>' . $result_html . '</td></tr>'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
|
|
}
|
|
echo '</tbody></table>';
|
|
NativeUi::table_wrap_close();
|
|
$this->pagination($result['pages'], $page);
|
|
NativeUi::card_close();
|
|
}
|
|
}
|