feat(admin): order detail view, invite-code batching, product tag term fields

Add native order detail page under 支付中心订单, standalone invite-code
batch generate/disable on users.php, and aksrt_product_tag taxonomy form
fields. Source only; no SPA build artifacts.
This commit is contained in:
2026-07-15 02:32:25 +08:00
parent 1770e8e0db
commit e2f7fcad89
3 changed files with 357 additions and 14 deletions
@@ -32,6 +32,8 @@ class AKSRT_Admin_Users_Menus {
add_action('admin_post_aksrt_users_update_report', [$this, 'handle_update_report']);
add_action('admin_post_aksrt_users_add_restriction', [$this, 'handle_add_restriction']);
add_action('admin_post_aksrt_users_remove_restriction', [$this, 'handle_remove_restriction']);
add_action('admin_post_aksrt_users_batch_invite_codes', [$this, 'handle_batch_invite_codes']);
add_action('admin_post_aksrt_users_disable_invite_code', [$this, 'handle_disable_invite_code']);
}
public function register_menus(): void {
@@ -120,15 +122,19 @@ class AKSRT_Admin_Users_Menus {
}
$code = sanitize_key((string) wp_unslash($_GET['aksrt_users_msg']));
$map = [
'message_sent' => __('消息已发送。', 'aksrt'),
'message_failed' => __('消息发送失败。', 'aksrt'),
'verify_updated' => __('认证状态已更新。', 'aksrt'),
'verify_failed' => __('认证审核失败。', 'aksrt'),
'report_updated' => __('举报状态已更新。', 'aksrt'),
'report_failed' => __('举报更新失败。', 'aksrt'),
'restriction_added' => __('限制已添加。', 'aksrt'),
'message_sent' => __('消息已发送。', 'aksrt'),
'message_failed' => __('消息发送失败。', 'aksrt'),
'verify_updated' => __('认证状态已更新。', 'aksrt'),
'verify_failed' => __('认证审核失败。', 'aksrt'),
'report_updated' => __('举报状态已更新。', 'aksrt'),
'report_failed' => __('举报更新失败。', 'aksrt'),
'restriction_added' => __('限制已添加。', 'aksrt'),
'restriction_removed' => __('限制已移除。', 'aksrt'),
'restriction_failed'=> __('限制操作失败。', 'aksrt'),
'restriction_failed' => __('限制操作失败。', 'aksrt'),
'invite_batch_ok' => __('邀请码批量生成完成。', 'aksrt'),
'invite_batch_failed' => __('邀请码批量生成失败。', 'aksrt'),
'invite_disabled' => __('邀请码已禁用。', 'aksrt'),
'invite_failed' => __('邀请码操作失败。', 'aksrt'),
];
if (!isset($map[$code])) {
return;
@@ -233,8 +239,68 @@ class AKSRT_Admin_Users_Menus {
echo '<div class="wrap"><h1>' . esc_html__('邀请码管理', 'aksrt') . '</h1>';
$this->flash();
$spa = admin_url('admin.php?page=aksrt-settings#/users/invite-codes');
echo '<p><a class="button" href="' . esc_url($spa) . '">' . esc_html__('SPA 批量生成(过渡)', 'aksrt') . '</a></p>';
echo '<h2>' . esc_html__('批量生成独立邀请码', 'aksrt') . '</h2>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="margin:12px 0 20px;display:flex;flex-wrap:wrap;gap:8px;align-items:end;">';
echo '<input type="hidden" name="action" value="aksrt_users_batch_invite_codes" />';
wp_nonce_field('aksrt_users_batch_invite_codes');
echo '<p><label>' . esc_html__('数量', 'aksrt') . '<br><input type="number" name="count" min="1" max="100" value="10" /></label></p>';
echo '<p><label>' . esc_html__('长度', 'aksrt') . '<br><input type="number" name="length" min="4" max="12" value="6" /></label></p>';
echo '<p><label>' . esc_html__('前缀', 'aksrt') . '<br><input type="text" name="prefix" value="" /></label></p>';
echo '<p><label>' . esc_html__('备注', 'aksrt') . '<br><input type="text" name="note" value="" class="regular-text" /></label></p>';
submit_button(__('生成', 'aksrt'), 'primary', '', false);
echo '</form>';
if (!empty($_GET['aksrt_generated'])) {
echo '<div class="notice notice-success"><p>' . esc_html(sprintf(
/* translators: %d count */
__('成功生成 %d 个邀请码。', 'aksrt'),
(int) $_GET['aksrt_generated']
)) . '</p></div>';
}
$standalone = get_option('aksrt_standalone_invite_codes', []);
$standalone = is_array($standalone) ? array_reverse(array_values($standalone)) : [];
echo '<h2>' . esc_html__('独立邀请码', 'aksrt') . '</h2>';
echo '<table class="wp-list-table widefat fixed striped"><thead><tr>';
echo '<th>' . esc_html__('邀请码', 'aksrt') . '</th><th>' . esc_html__('状态', 'aksrt') . '</th><th>' . esc_html__('批次', 'aksrt') . '</th><th>' . esc_html__('备注', 'aksrt') . '</th><th>' . esc_html__('创建时间', 'aksrt') . '</th><th>' . esc_html__('操作', 'aksrt') . '</th>';
echo '</tr></thead><tbody>';
if (!$standalone) {
echo '<tr><td colspan="6">' . esc_html__('暂无独立邀请码', 'aksrt') . '</td></tr>';
}
$shown = 0;
foreach ($standalone as $entry) {
if ($shown >= 200) {
break;
}
$code = sanitize_key((string) ($entry['code'] ?? ''));
if ($code === '') {
continue;
}
$disabled = !empty($entry['disabled']);
$used = !empty($entry['used']) || !empty($entry['used_by']);
$status = $disabled ? __('已禁用', 'aksrt') : ($used ? __('已使用', 'aksrt') : __('可用', 'aksrt'));
echo '<tr>';
echo '<td><code>' . esc_html($code) . '</code></td>';
echo '<td>' . esc_html($status) . '</td>';
echo '<td>' . esc_html((string) ($entry['batch_id'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($entry['note'] ?? '')) . '</td>';
echo '<td>' . esc_html((string) ($entry['created_at'] ?? '')) . '</td>';
echo '<td>';
if (!$disabled && !$used) {
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
echo '<input type="hidden" name="action" value="aksrt_users_disable_invite_code" />';
echo '<input type="hidden" name="code" value="' . esc_attr($code) . '" />';
wp_nonce_field('aksrt_users_disable_invite_code_' . $code);
submit_button(__('禁用', 'aksrt'), 'small', '', false);
echo '</form>';
} else {
echo '—';
}
echo '</td></tr>';
$shown++;
}
echo '</tbody></table>';
global $wpdb;
$rows = $wpdb->get_results(
@@ -269,6 +335,104 @@ class AKSRT_Admin_Users_Menus {
echo '</tbody></table></div>';
}
public function handle_batch_invite_codes(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
check_admin_referer('aksrt_users_batch_invite_codes');
$count = isset($_POST['count']) ? max(1, min(100, (int) $_POST['count'])) : 10;
$length = isset($_POST['length']) ? max(4, min(12, (int) $_POST['length'])) : 6;
$prefix = isset($_POST['prefix']) ? sanitize_key((string) wp_unslash($_POST['prefix'])) : '';
$note = isset($_POST['note']) ? sanitize_textarea_field((string) wp_unslash($_POST['note'])) : '';
$batch = 'INV-' . gmdate('YmdHis') . '-' . wp_generate_password(4, false, false);
$alphabet = 'abcdefghjkmnpqrstuvwxyz23456789';
$codes = get_option('aksrt_standalone_invite_codes', []);
$codes = is_array($codes) ? array_values($codes) : [];
$existing = [];
foreach ($codes as $entry) {
$c = sanitize_key((string) ($entry['code'] ?? ''));
if ($c !== '') {
$existing[$c] = true;
}
}
global $wpdb;
$generated = [];
for ($i = 0; $i < $count; $i++) {
$code = '';
for ($attempt = 0; $attempt < 20; $attempt++) {
$candidate = $prefix;
for ($j = 0; $j < $length; $j++) {
$candidate .= $alphabet[random_int(0, strlen($alphabet) - 1)];
}
$candidate = sanitize_key($candidate);
if ($candidate === '' || isset($existing[$candidate])) {
continue;
}
$user_hit = $wpdb->get_var($wpdb->prepare(
"SELECT umeta_id FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_invite_code' AND meta_value = %s LIMIT 1",
$candidate
));
if ($user_hit) {
continue;
}
$code = $candidate;
break;
}
if ($code === '') {
continue;
}
$existing[$code] = true;
$codes[] = [
'code' => $code,
'created_at' => current_time('mysql'),
'used' => false,
'used_by' => 0,
'used_at' => '',
'disabled' => false,
'note' => $note,
'batch_id' => $batch,
'created_by' => get_current_user_id(),
];
$generated[] = $code;
}
update_option('aksrt_standalone_invite_codes', array_values($codes), false);
$ok = !empty($generated);
$this->redirect('aksrt_invite_codes', $ok ? 'invite_batch_ok' : 'invite_batch_failed', [
'aksrt_generated' => count($generated),
]);
}
public function handle_disable_invite_code(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
}
$code = isset($_POST['code']) ? sanitize_key((string) wp_unslash($_POST['code'])) : '';
check_admin_referer('aksrt_users_disable_invite_code_' . $code);
$ok = false;
if ($code !== '') {
$codes = get_option('aksrt_standalone_invite_codes', []);
$codes = is_array($codes) ? $codes : [];
foreach ($codes as $i => $entry) {
if (sanitize_key((string) ($entry['code'] ?? '')) !== $code) {
continue;
}
$entry['disabled'] = true;
$codes[$i] = $entry;
$ok = true;
break;
}
if ($ok) {
update_option('aksrt_standalone_invite_codes', array_values($codes), false);
}
}
$this->redirect('aksrt_invite_codes', $ok ? 'invite_disabled' : 'invite_failed');
}
/* ------------------------------------------------------------------
* Verify
* ----------------------------------------------------------------*/