From e2f7fcad894f7db66623e4422800f8be41a2af4b Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 15 Jul 2026 02:32:25 +0800 Subject: [PATCH 1/2] feat(admin): order detail view, invite-code batching, product tag term fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../native/class-aksrt-admin-pay-menus.php | 116 ++++++++++- .../native/class-aksrt-admin-users-menus.php | 184 +++++++++++++++++- inc/classes/class-aksrt-shop-term-meta.php | 71 +++++++ 3 files changed, 357 insertions(+), 14 deletions(-) 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 ''; echo ''; @@ -392,6 +400,102 @@ class AKSRT_Admin_Pay_Menus { $this->render_footer(); } + private function render_order_detail_page(string $order_number): void { + $repo = AKSRT_Order_Repository::get_instance(); + $order = $repo->get($order_number); + if (!$order) { + echo '

' . esc_html__('订单不存在。', 'aksrt') . '

'; + echo '

' . esc_html__('返回订单列表', 'aksrt') . '

'; + return; + } + + $labels = class_exists('AKSRT_Domain_Model') + ? AKSRT_Domain_Model::order_status_labels() + : []; + $context = $repo->get_admin_order_context($order_number); + $user_id = (int) ($order['user_id'] ?? 0); + $user = $user_id > 0 ? get_userdata($user_id) : null; + $status = (string) ($order['status'] ?? ''); + + $shipping_items = class_exists('AKSRT_Shipping') + ? AKSRT_Shipping::get_instance()->get_by_order($order_number) + : []; + $after_sale = class_exists('AKSRT_After_Sale') + ? (AKSRT_After_Sale::get_instance()->get_list(['order_number' => $order_number, 'limit' => 20])['items'] ?? []) + : []; + + echo '

← ' . esc_html__('返回订单列表', 'aksrt') . '

'; + + echo ''; + $rows = [ + __('订单号', 'aksrt') => $order_number, + __('用户', 'aksrt') => $user ? $user->display_name . ' (#' . $user_id . ')' : (string) $user_id, + __('商品类型', 'aksrt') => (string) ($order['product_type'] ?? ''), + __('商品 ID', 'aksrt') => (string) ($order['product_id'] ?? ''), + __('金额', 'aksrt') => number_format((float) ($order['amount'] ?? 0), 2) . ' ' . (string) ($order['currency'] ?? ''), + __('状态', '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') => (string) ($order['note'] ?? ''), + ]; + foreach ($rows as $label => $value) { + echo ''; + } + if (!empty($context)) { + echo ''; + } + echo '
' . esc_html($label) . '' . esc_html((string) $value) . '
' . esc_html__('附加上下文', 'aksrt') . '
' . esc_html(wp_json_encode($context, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '
'; + + echo '

' . esc_html__('更新状态', 'aksrt') . '

'; + echo ''; + echo ''; + echo ''; + echo ''; + wp_nonce_field('aksrt_pay_update_order_status_' . $order_number); + echo ' '; + submit_button(__('保存状态', 'aksrt'), 'primary', '', false); + echo '
'; + + echo '

' . esc_html__('物流记录', 'aksrt') . '

'; + if (!$shipping_items) { + echo '

' . esc_html__('暂无物流记录。', 'aksrt') . ' ' . esc_html__('去物流管理', 'aksrt') . '

'; + } else { + echo ''; + foreach ($shipping_items as $item) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
ID' . esc_html__('承运商', 'aksrt') . '' . esc_html__('运单号', 'aksrt') . '' . esc_html__('状态', 'aksrt') . '' . esc_html__('时间', 'aksrt') . '
' . esc_html((string) ($item['id'] ?? '')) . '' . esc_html((string) ($item['carrier'] ?? '')) . '' . esc_html((string) ($item['tracking_no'] ?? '')) . '' . esc_html((string) ($item['status'] ?? '')) . '' . esc_html((string) ($item['created_at'] ?? '')) . '
'; + } + + echo '

' . esc_html__('售后工单', 'aksrt') . '

'; + if (!$after_sale) { + echo '

' . esc_html__('暂无售后工单。', 'aksrt') . ' ' . esc_html__('去售后管理', 'aksrt') . '

'; + } else { + echo ''; + foreach ($after_sale as $item) { + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + } + echo '
ID' . esc_html__('类型', 'aksrt') . '' . esc_html__('状态', 'aksrt') . '' . esc_html__('时间', 'aksrt') . '
' . esc_html((string) ($item['id'] ?? '')) . '' . esc_html((string) ($item['type'] ?? '')) . '' . esc_html((string) ($item['status'] ?? '')) . '' . esc_html((string) ($item['created_at'] ?? '')) . '
'; + } + } + 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 '
'; + echo ''; + wp_nonce_field('aksrt_users_batch_invite_codes'); + echo '

'; + echo '

'; + echo '

'; + echo '

'; + submit_button(__('生成', 'aksrt'), 'primary', '', false); + 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 ''; + echo ''; + if (!$standalone) { + echo ''; + } + $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 ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + $shown++; + } + echo '
' . esc_html__('邀请码', 'aksrt') . '' . esc_html__('状态', 'aksrt') . '' . esc_html__('批次', 'aksrt') . '' . esc_html__('备注', 'aksrt') . '' . esc_html__('创建时间', 'aksrt') . '' . esc_html__('操作', 'aksrt') . '
' . esc_html__('暂无独立邀请码', 'aksrt') . '
' . esc_html($code) . '' . esc_html($status) . '' . esc_html((string) ($entry['batch_id'] ?? '')) . '' . esc_html((string) ($entry['note'] ?? '')) . '' . esc_html((string) ($entry['created_at'] ?? '')) . ''; + if (!$disabled && !$used) { + echo '
'; + echo ''; + echo ''; + wp_nonce_field('aksrt_users_disable_invite_code_' . $code); + submit_button(__('禁用', 'aksrt'), 'small', '', false); + echo '
'; + } else { + echo '—'; + } + 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); + } + } } From 8eb5c273efe2ba6a8e8981bd6a8af3b22eb66834 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 15 Jul 2026 02:34:31 +0800 Subject: [PATCH 2/2] feat(admin): order shipping/after-sale create and plate list columns Allow creating shipping records and after-sale tickets from the native order detail page, and show plate moderator/stats columns on the forum plate list table. Source only; no SPA build artifacts. --- functions.php | 5 + inc/bootstrap.php | 1 + .../native/class-aksrt-admin-pay-menus.php | 99 +++++++++++++++- .../forum/class-aksrt-plate-admin-list.php | 107 ++++++++++++++++++ 4 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 inc/classes/forum/class-aksrt-plate-admin-list.php diff --git a/functions.php b/functions.php index e5e06080..56694bc9 100644 --- a/functions.php +++ b/functions.php @@ -315,6 +315,11 @@ if (is_admin() && class_exists('AKSRT_Topic_Admin_List')) { if (is_admin() && class_exists('AKSRT_Topic_Meta_Box')) { AKSRT_Topic_Meta_Box::get_instance()->init(); } +// 论坛版块列表列 +if (is_admin() && class_exists('AKSRT_Plate_Admin_List')) { + AKSRT_Plate_Admin_List::get_instance()->init(); +} + diff --git a/inc/bootstrap.php b/inc/bootstrap.php index 21fcc75a..accb5d0f 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -325,6 +325,7 @@ require_once AKSRT_DIR . '/inc/classes/shop/class-aksrt-product-variants-meta-bo require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-plate-meta-box.php'; require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-topic-admin-list.php'; require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-topic-meta-box.php'; +require_once AKSRT_DIR . '/inc/classes/forum/class-aksrt-plate-admin-list.php'; require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-1.php'; require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-2.php'; require_once AKSRT_DIR . '/inc/classes/shop/shop-traits/trait-aksrt-shop-part-3.php'; 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 a49e4336..34f523b9 100644 --- a/inc/classes/admin/native/class-aksrt-admin-pay-menus.php +++ b/inc/classes/admin/native/class-aksrt-admin-pay-menus.php @@ -35,7 +35,9 @@ class AKSRT_Admin_Pay_Menus { add_action('admin_post_aksrt_pay_disable_charge_card', [$this, 'handle_disable_charge_card']); add_action('admin_post_aksrt_pay_batch_charge_cards', [$this, 'handle_batch_charge_cards']); add_action('admin_post_aksrt_pay_update_shipping', [$this, 'handle_update_shipping']); + add_action('admin_post_aksrt_pay_create_shipping', [$this, 'handle_create_shipping']); add_action('admin_post_aksrt_pay_update_after_sale', [$this, 'handle_update_after_sale']); + add_action('admin_post_aksrt_pay_create_after_sale', [$this, 'handle_create_after_sale']); add_action('admin_post_aksrt_pay_create_coupon', [$this, 'handle_create_coupon']); add_action('admin_post_aksrt_pay_disable_coupon', [$this, 'handle_disable_coupon']); add_action('admin_post_aksrt_pay_settle_income', [$this, 'handle_settle_income']); @@ -202,8 +204,12 @@ class AKSRT_Admin_Pay_Menus { 'card_batch_failed' => __('卡密批量生成失败。', 'aksrt'), 'shipping_updated' => __('物流状态已更新。', 'aksrt'), 'shipping_failed' => __('物流状态更新失败。', 'aksrt'), + 'shipping_created' => __('发货记录已创建。', 'aksrt'), + 'shipping_create_failed' => __('发货记录创建失败。', 'aksrt'), 'after_sale_updated' => __('售后状态已更新。', 'aksrt'), 'after_sale_failed' => __('售后状态更新失败。', 'aksrt'), + 'after_sale_created' => __('售后工单已创建。', 'aksrt'), + 'after_sale_create_failed' => __('售后工单创建失败。', 'aksrt'), 'coupon_created' => __('优惠码已创建。', 'aksrt'), 'coupon_create_failed'=> __('优惠码创建失败(可能重复)。', 'aksrt'), 'coupon_disabled' => __('优惠码已禁用。', 'aksrt'), @@ -463,8 +469,18 @@ class AKSRT_Admin_Pay_Menus { echo ''; echo '

' . esc_html__('物流记录', 'aksrt') . '

'; + echo '
'; + echo ''; + echo ''; + wp_nonce_field('aksrt_pay_create_shipping_' . $order_number); + echo '

'; + echo '

'; + echo '

'; + submit_button(__('创建发货', 'aksrt'), 'secondary', '', false); + echo ' ' . esc_html__('物流管理', 'aksrt') . ''; + echo '
'; if (!$shipping_items) { - echo '

' . esc_html__('暂无物流记录。', 'aksrt') . ' ' . esc_html__('去物流管理', 'aksrt') . '

'; + echo '

' . esc_html__('暂无物流记录。', 'aksrt') . '

'; } else { echo ''; foreach ($shipping_items as $item) { @@ -480,8 +496,29 @@ class AKSRT_Admin_Pay_Menus { } echo '

' . esc_html__('售后工单', 'aksrt') . '

'; + echo ''; + echo ''; + echo ''; + echo ''; + wp_nonce_field('aksrt_pay_create_after_sale_' . $order_number); + echo '

'; + echo '

'; + echo '

'; + echo '

'; + submit_button(__('创建售后', 'aksrt'), 'secondary', '', false); + echo ' ' . esc_html__('售后管理', 'aksrt') . ''; + echo ''; if (!$after_sale) { - echo '

' . esc_html__('暂无售后工单。', 'aksrt') . ' ' . esc_html__('去售后管理', 'aksrt') . '

'; + echo '

' . esc_html__('暂无售后工单。', 'aksrt') . '

'; } else { echo '
ID' . esc_html__('承运商', 'aksrt') . '' . esc_html__('运单号', 'aksrt') . '' . esc_html__('状态', 'aksrt') . '' . esc_html__('时间', 'aksrt') . '
'; foreach ($after_sale as $item) { @@ -1019,6 +1056,64 @@ class AKSRT_Admin_Pay_Menus { exit; } + public function handle_create_shipping(): void { + if (!current_user_can(self::CAPABILITY)) { + wp_die(esc_html__('权限不足', 'aksrt')); + } + $order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : ''; + check_admin_referer('aksrt_pay_create_shipping_' . $order_number); + + $ok = false; + if ($order_number !== '' && class_exists('AKSRT_Shipping')) { + $id = AKSRT_Shipping::get_instance()->create([ + 'order_number' => $order_number, + 'carrier' => sanitize_text_field((string) wp_unslash($_POST['carrier'] ?? '')), + 'tracking_no' => sanitize_text_field((string) wp_unslash($_POST['tracking_no'] ?? '')), + 'note' => sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? '')), + 'operator_id' => get_current_user_id(), + ]); + $ok = $id > 0; + } + + wp_safe_redirect($this->page_url('aksrt_orders', [ + 'order' => $order_number, + 'aksrt_pay_msg' => $ok ? 'shipping_created' : 'shipping_create_failed', + ])); + exit; + } + + public function handle_create_after_sale(): void { + if (!current_user_can(self::CAPABILITY)) { + wp_die(esc_html__('权限不足', 'aksrt')); + } + $order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : ''; + check_admin_referer('aksrt_pay_create_after_sale_' . $order_number); + + $ok = false; + if ($order_number !== '' && class_exists('AKSRT_After_Sale')) { + $user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0; + if ($user_id <= 0 && class_exists('AKSRT_Order_Repository')) { + $order = AKSRT_Order_Repository::get_instance()->get($order_number); + $user_id = (int) ($order['user_id'] ?? 0); + } + $id = AKSRT_After_Sale::get_instance()->create([ + 'order_number' => $order_number, + 'user_id' => $user_id, + 'type' => sanitize_key((string) wp_unslash($_POST['type'] ?? 'refund')), + 'reason' => sanitize_textarea_field((string) wp_unslash($_POST['reason'] ?? '')), + 'amount' => (float) ($_POST['amount'] ?? 0), + 'note' => sanitize_textarea_field((string) wp_unslash($_POST['note'] ?? '')), + ]); + $ok = $id > 0; + } + + wp_safe_redirect($this->page_url('aksrt_orders', [ + 'order' => $order_number, + 'aksrt_pay_msg' => $ok ? 'after_sale_created' : 'after_sale_create_failed', + ])); + exit; + } + /* ------------------------------------------------------------------ * After-sale * ----------------------------------------------------------------*/ diff --git a/inc/classes/forum/class-aksrt-plate-admin-list.php b/inc/classes/forum/class-aksrt-plate-admin-list.php new file mode 100644 index 00000000..5ef7cb95 --- /dev/null +++ b/inc/classes/forum/class-aksrt-plate-admin-list.php @@ -0,0 +1,107 @@ + $label) { + $new[$key] = $label; + if ($key === 'title') { + $new['aksrt_plate_mods'] = __('版主数', 'aksrt'); + $new['aksrt_plate_posts'] = __('帖子数', 'aksrt'); + $new['aksrt_plate_reply'] = __('回复数', 'aksrt'); + $new['aksrt_plate_views'] = __('浏览', 'aksrt'); + $new['aksrt_plate_follow'] = __('关注', 'aksrt'); + $new['aksrt_plate_pending_apps'] = __('待审申请', 'aksrt'); + } + } + return $new; + } + + public function sortable_columns(array $columns): array { + $columns['aksrt_plate_posts'] = 'aksrt_posts_count'; + $columns['aksrt_plate_views'] = 'aksrt_plate_views'; + return $columns; + } + + public function render_column(string $column, int $post_id): void { + switch ($column) { + case 'aksrt_plate_mods': + $mods = get_post_meta($post_id, '_aksrt_moderators', true); + $count = is_array($mods) ? count(array_filter(array_map('intval', $mods))) : 0; + echo esc_html((string) $count); + break; + case 'aksrt_plate_posts': + echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_posts_count', true)); + break; + case 'aksrt_plate_reply': + echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_reply_count', true)); + break; + case 'aksrt_plate_views': + echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_plate_views', true)); + break; + case 'aksrt_plate_follow': + echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_follow_count', true)); + break; + case 'aksrt_plate_pending_apps': + $apps = get_post_meta($post_id, '_aksrt_moderator_applications', true); + $pending = 0; + if (is_array($apps)) { + foreach ($apps as $app) { + if (is_array($app) && (($app['status'] ?? '') === 'pending')) { + $pending++; + } + } + } + if ($pending > 0) { + echo '' . esc_html((string) $pending) . ''; + } else { + echo '0'; + } + break; + } + } + + public function apply_orderby(WP_Query $query): void { + if (!is_admin() || !$query->is_main_query()) { + return; + } + if ($query->get('post_type') !== 'aksrt_forum_plate') { + return; + } + $orderby = $query->get('orderby'); + if ($orderby === 'aksrt_posts_count') { + $query->set('meta_key', '_aksrt_posts_count'); + $query->set('orderby', 'meta_value_num'); + } elseif ($orderby === 'aksrt_plate_views') { + $query->set('meta_key', '_aksrt_plate_views'); + $query->set('orderby', 'meta_value_num'); + } + } +}
ID' . esc_html__('类型', 'aksrt') . '' . esc_html__('状态', 'aksrt') . '' . esc_html__('时间', 'aksrt') . '