diff --git a/inc/classes/admin/native/class-aksrt-admin-pay-menus.php b/inc/classes/admin/native/class-aksrt-admin-pay-menus.php
index 16a9cf10..a49e4336 100644
--- a/inc/classes/admin/native/class-aksrt-admin-pay-menus.php
+++ b/inc/classes/admin/native/class-aksrt-admin-pay-menus.php
@@ -286,6 +286,13 @@ class AKSRT_Admin_Pay_Menus {
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();
+ return;
+ }
+
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
$search = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
@@ -353,7 +360,8 @@ class AKSRT_Admin_Pay_Menus {
echo '
' . esc_html(number_format((float) ($row['amount'] ?? 0), 2)) . ' ' . esc_html((string) ($row['currency'] ?? '')) . ' | ';
echo '' . esc_html($status_label) . ' | ';
echo '' . esc_html((string) ($row['created_at'] ?? '')) . ' | ';
- echo '';
+ echo ' | ';
+ echo '' . esc_html__('详情', 'aksrt') . '';
echo '';
+
+ echo '' . esc_html__('物流记录', 'aksrt') . '';
+ if (!$shipping_items) {
+ echo '' . esc_html__('暂无物流记录。', 'aksrt') . ' ' . esc_html__('去物流管理', 'aksrt') . ' ';
+ } else {
+ echo '| ID | ' . esc_html__('承运商', 'aksrt') . ' | ' . esc_html__('运单号', 'aksrt') . ' | ' . esc_html__('状态', 'aksrt') . ' | ' . esc_html__('时间', 'aksrt') . ' | ';
+ foreach ($shipping_items as $item) {
+ echo '';
+ echo '| ' . esc_html((string) ($item['id'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($item['carrier'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($item['tracking_no'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($item['status'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($item['created_at'] ?? '')) . ' | ';
+ echo ' ';
+ }
+ echo ' ';
+ }
+
+ echo '' . esc_html__('售后工单', 'aksrt') . '';
+ if (!$after_sale) {
+ echo '' . esc_html__('暂无售后工单。', 'aksrt') . ' ' . esc_html__('去售后管理', 'aksrt') . ' ';
+ } else {
+ echo '| ID | ' . esc_html__('类型', 'aksrt') . ' | ' . esc_html__('状态', 'aksrt') . ' | ' . esc_html__('时间', 'aksrt') . ' | ';
+ foreach ($after_sale as $item) {
+ echo '';
+ echo '| ' . esc_html((string) ($item['id'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($item['type'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($item['status'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($item['created_at'] ?? '')) . ' | ';
+ echo ' ';
+ }
+ echo ' ';
+ }
+ }
+
public function handle_update_order_status(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
@@ -406,9 +510,13 @@ class AKSRT_Admin_Pay_Menus {
$ok = AKSRT_Order_Repository::get_instance()->update_status($order_number, $new_status, ['force' => true]);
}
- wp_safe_redirect($this->page_url('aksrt_orders', [
- 'aksrt_pay_msg' => $ok ? 'order_updated' : 'order_failed',
- ]));
+ $args = ['aksrt_pay_msg' => $ok ? 'order_updated' : 'order_failed'];
+ $redirect_order = isset($_POST['redirect_order']) ? sanitize_text_field((string) wp_unslash($_POST['redirect_order'])) : '';
+ if ($redirect_order !== '') {
+ $args['order'] = $redirect_order;
+ }
+
+ wp_safe_redirect($this->page_url('aksrt_orders', $args));
exit;
}
diff --git a/inc/classes/admin/native/class-aksrt-admin-users-menus.php b/inc/classes/admin/native/class-aksrt-admin-users-menus.php
index e0b63f6f..5d7150c1 100644
--- a/inc/classes/admin/native/class-aksrt-admin-users-menus.php
+++ b/inc/classes/admin/native/class-aksrt-admin-users-menus.php
@@ -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 '' . esc_html__('邀请码管理', 'aksrt') . '';
$this->flash();
- $spa = admin_url('admin.php?page=aksrt-settings#/users/invite-codes');
- echo ' ' . esc_html__('SPA 批量生成(过渡)', 'aksrt') . ' ';
+ echo ' ' . esc_html__('批量生成独立邀请码', 'aksrt') . '';
+ echo ' ';
+
+ if (!empty($_GET['aksrt_generated'])) {
+ echo ' ' . esc_html(sprintf(
+ /* translators: %d count */
+ __('成功生成 %d 个邀请码。', 'aksrt'),
+ (int) $_GET['aksrt_generated']
+ )) . ' ';
+ }
+
+ $standalone = get_option('aksrt_standalone_invite_codes', []);
+ $standalone = is_array($standalone) ? array_reverse(array_values($standalone)) : [];
+
+ echo ' ' . esc_html__('独立邀请码', 'aksrt') . '';
+ echo ' ';
+ echo '| ' . esc_html__('邀请码', 'aksrt') . ' | ' . esc_html__('状态', 'aksrt') . ' | ' . esc_html__('批次', 'aksrt') . ' | ' . esc_html__('备注', 'aksrt') . ' | ' . esc_html__('创建时间', 'aksrt') . ' | ' . esc_html__('操作', 'aksrt') . ' | ';
+ echo ' ';
+ if (!$standalone) {
+ echo '| ' . esc_html__('暂无独立邀请码', 'aksrt') . ' | ';
+ }
+ $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 '';
+ echo '' . esc_html($code) . ' | ';
+ echo '' . esc_html($status) . ' | ';
+ echo '' . esc_html((string) ($entry['batch_id'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($entry['note'] ?? '')) . ' | ';
+ echo '' . esc_html((string) ($entry['created_at'] ?? '')) . ' | ';
+ echo '';
+ if (!$disabled && !$used) {
+ echo '';
+ } else {
+ echo '—';
+ }
+ echo ' | ';
+ $shown++;
+ }
+ echo '
';
global $wpdb;
$rows = $wpdb->get_results(
@@ -269,6 +335,104 @@ class AKSRT_Admin_Users_Menus {
echo ' ';
}
+ 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
* ----------------------------------------------------------------*/
diff --git a/inc/classes/class-aksrt-shop-term-meta.php b/inc/classes/class-aksrt-shop-term-meta.php
index b6873863..d8f13c57 100644
--- a/inc/classes/class-aksrt-shop-term-meta.php
+++ b/inc/classes/class-aksrt-shop-term-meta.php
@@ -45,6 +45,12 @@ class AKSRT_Shop_Term_Meta {
add_action('aksrt_product_cat_edit_form_fields', [$this, 'render_cat_edit_fields'], 10, 1);
add_action('created_aksrt_product_cat', [$this, 'save_cat_term_fields']);
add_action('edited_aksrt_product_cat', [$this, 'save_cat_term_fields']);
+
+ // Product tag display fields.
+ add_action('aksrt_product_tag_add_form_fields', [$this, 'render_tag_add_fields']);
+ add_action('aksrt_product_tag_edit_form_fields', [$this, 'render_tag_edit_fields'], 10, 1);
+ add_action('created_aksrt_product_tag', [$this, 'save_tag_term_fields']);
+ add_action('edited_aksrt_product_tag', [$this, 'save_tag_term_fields']);
}
// ─── Taxonomy Registration ───────────────────────────────────
@@ -677,4 +683,69 @@ class AKSRT_Shop_Term_Meta {
update_term_meta($term_id, $key, $value);
}
}
+
+ public function render_tag_add_fields(): void {
+ foreach ($this->get_tag_field_definitions() as $key => $def) {
+ echo '';
+ echo '';
+ $this->render_tag_field_control($key, $def['default'] ?? '');
+ echo ' ';
+ }
+ }
+
+ public function render_tag_edit_fields($term): void {
+ if (!($term instanceof WP_Term)) {
+ return;
+ }
+ foreach ($this->get_tag_field_definitions() as $key => $def) {
+ $value = get_term_meta($term->term_id, $key, true);
+ if ($value === '' || $value === false) {
+ $value = $def['default'] ?? '';
+ }
+ echo ' | | ';
+ $this->render_tag_field_control($key, $value);
+ echo ' |
';
+ }
+ }
+
+ /**
+ * @param mixed $value
+ */
+ private function render_tag_field_control(string $key, $value): void {
+ $val = (string) $value;
+ if ($key === 'shop_tag_is_important') {
+ echo '';
+ return;
+ }
+ $type = $key === 'shop_tag_priority' ? 'number' : 'text';
+ printf(
+ '',
+ esc_attr($type),
+ esc_attr($key),
+ esc_attr($key),
+ esc_attr($val)
+ );
+ }
+
+ public function save_tag_term_fields(int $term_id): void {
+ if (!current_user_can('manage_categories')) {
+ return;
+ }
+ foreach ($this->get_tag_field_definitions() as $key => $def) {
+ if ($key === 'shop_tag_is_important') {
+ $raw = isset($_POST[$key]) ? '1' : '';
+ } else {
+ if (!isset($_POST[$key])) {
+ continue;
+ }
+ $raw = wp_unslash($_POST[$key]);
+ }
+ if (isset($def['sanitize']) && is_callable($def['sanitize'])) {
+ $value = call_user_func($def['sanitize'], $raw);
+ } else {
+ $value = sanitize_text_field((string) $raw);
+ }
+ update_term_meta($term_id, $key, $value);
+ }
+ }
}