From fb68b3f8c6b53da1d39a46195d87efb4ed22f28d Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Jul 2026 13:06:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B6=E5=8F=A3=201:1=20=E5=89=A9?= =?UTF-8?q?=E4=BD=99=E6=AC=A0=E8=B4=A6=EF=BC=88=E8=AE=BA=E5=9D=9B=E5=A3=B3?= =?UTF-8?q?/=E5=9C=88=E5=AD=90/501/=E7=A7=81=E4=BF=A1=E9=BB=98=E8=AE=A4/?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=AD=BB=E7=A0=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 论坛 board/topic/plate/tag/plate-cat 接入 content-wrap/content-layout 主站流壳 - 公共主页 plate 圈子 tab 接关注/管理版块真实数据并计数 - forum-topic 渲染封面/付费扩展位 meta(§7.3) - 后台删除消息/背景图/评论图 REST 从 501 补齐为真实实现 - 私信 private_s 缺省 true(完整 1:1,不再兼容默认关) - 清理 aksrt_get_resource_meta 不可达旧实现;挂载 AKSRT_AI 配置门控实例 - 登录 modal 启用 colorful-header;订单页粉边 focus 软化;侧栏次级入口视觉弱化 Co-Authored-By: Claude Fable 5 --- assets/admin/src/App.vue | 1 + assets/css/frontend/forum.css | 44 +++++++++++ assets/css/routes/orders.css | 12 +-- functions.php | 3 + .../class-aksrt-admin-user-controller.php | 79 ++++++++++++++++++- .../class-aksrt-settings-integration.php | 2 +- inc/core-hooks/part-1.php | 4 +- inc/helpers/aksrt-resource.php | 73 +++++------------ inc/helpers/aksrt-user-home.php | 27 ++++++- templates/forum/forum-board.php | 12 ++- templates/forum/forum-plate-cat.php | 12 ++- templates/forum/forum-plate.php | 12 ++- templates/forum/forum-tag.php | 12 ++- templates/forum/forum-topic.php | 54 +++++++++++-- templates/parts/modal-auth.php | 2 +- templates/parts/user-home/panels-core.php | 46 ++++++++++- 16 files changed, 313 insertions(+), 82 deletions(-) diff --git a/assets/admin/src/App.vue b/assets/admin/src/App.vue index 0a76607c..cbcd5788 100644 --- a/assets/admin/src/App.vue +++ b/assets/admin/src/App.vue @@ -158,6 +158,7 @@ const menuOptions = ADMIN_SECTIONS.filter((section) => !section.hidden).map((sec children: section.children.map((child) => ({ label: child.secondaryEntry ? `${child.label} · 次级` : child.label, key: child.key, + props: child.secondaryEntry ? { class: 'aksrt-menu-item--secondary' } : undefined, })), } }) diff --git a/assets/css/frontend/forum.css b/assets/css/frontend/forum.css index 39cdf2f1..65e303ec 100644 --- a/assets/css/frontend/forum.css +++ b/assets/css/frontend/forum.css @@ -30,3 +30,47 @@ .aksrt-forum-header__stats { display: none; /* stats demoted on home shell; counts remain in eyebrow meta */ } + + +/* Forum pages share the single/archive content stream shell */ +.content-shell--forum .content-wrap { + padding-top: 8px; +} +.content-shell--forum .content-layout__primary > .aksrt-forum-board, +.content-shell--forum .content-layout__primary > .aksrt-forum-topic, +.content-shell--forum .content-layout__primary > .aksrt-forum-tag, +.content-shell--forum .content-layout__primary > .aksrt-forum-plate-cat { + width: 100%; +} + + +.aksrt-forum-topic__cover { + margin: 0 0 16px; + border-radius: var(--main-radius, 8px); + overflow: hidden; + background: var(--body-bg-color, #f5f6f7); +} +.aksrt-forum-topic__cover--image { + aspect-ratio: var(--cover-ratio, 16/9); +} +.aksrt-forum-topic__cover img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; +} +.aksrt-forum-topic__cover--slide { + display: grid; + grid-auto-flow: column; + grid-auto-columns: 100%; + overflow-x: auto; + scroll-snap-type: x mandatory; +} +.aksrt-forum-topic__cover--slide img { + scroll-snap-align: start; + aspect-ratio: 16/9; +} +.aksrt-forum-topic__paywall { + margin: 12px 0 20px; + padding: 16px; +} diff --git a/assets/css/routes/orders.css b/assets/css/routes/orders.css index 343c13ac..e625b1bb 100644 --- a/assets/css/routes/orders.css +++ b/assets/css/routes/orders.css @@ -43,7 +43,7 @@ .aksrt-orders-page__filter-chip.is-active { background: var(--color-primary); color: var(--color-on-primary); - border-color: var(--color-primary); + border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb)); } /* ===== Order list ===== */ @@ -231,14 +231,14 @@ } .aksrt-orders-page__page-chip:hover { - border-color: var(--color-primary); + border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb)); color: var(--color-primary); } .aksrt-orders-page__page-chip.is-active { background: var(--color-primary); color: var(--color-on-primary); - border-color: var(--color-primary); + border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb)); } /* ===== Empty state ===== */ @@ -281,8 +281,8 @@ .aksrt-orders-page__select:focus, .aksrt-orders-page__textarea:focus { outline: none; - border-color: var(--color-primary); - box-shadow: 0 0 0 3px var(--color-primary-soft); + border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent); } /* ===== Shared Modal (orders page) ===== */ @@ -434,5 +434,5 @@ html.dark .aksrt-orders-page__filter-chip { html.dark .aksrt-orders-page__filter-chip.is-active, html.dark .aksrt-orders-page__filter-chip.is-active { - border-color: var(--color-primary); + border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb)); } diff --git a/functions.php b/functions.php index 0eec8342..3ffd3907 100644 --- a/functions.php +++ b/functions.php @@ -187,6 +187,9 @@ if (class_exists('AKSRT_Content_Visibility')) { if (class_exists('AKSRT_SMS')) { AKSRT_SMS::get_instance()->init(); } +if (class_exists('AKSRT_AI')) { + AKSRT_AI::get_instance(); // config-gated; available when admin enables +} if (class_exists('AKSRT_WeChat_Notify')) { AKSRT_WeChat_Notify::get_instance()->init(); } diff --git a/inc/classes/admin/class-aksrt-admin-user-controller.php b/inc/classes/admin/class-aksrt-admin-user-controller.php index 2ed5b0fd..d5ca95c8 100644 --- a/inc/classes/admin/class-aksrt-admin-user-controller.php +++ b/inc/classes/admin/class-aksrt-admin-user-controller.php @@ -577,7 +577,26 @@ class AKSRT_Admin_User_Controller extends AKSRT_Admin_Controller_Base { } public function rest_delete_notification_item(WP_REST_Request $request): WP_REST_Response { - return $this->success(['message' => __('当前版本暂未提供后台删除单条消息能力', 'aksrt')]); + $user_id = get_current_user_id(); + if ($user_id <= 0) { + return $this->error(__('请先登录', 'aksrt'), 401); + } + $params = $this->get_json_params($request); + $id = sanitize_text_field((string) ($params['id'] ?? $request->get_param('id') ?? '')); + if ($id === '') { + return $this->error(__('参数错误', 'aksrt'), 400); + } + if (function_exists('aksrt_delete_user_notification')) { + aksrt_delete_user_notification($user_id, $id); + } elseif (class_exists('AKSRT_Message')) { + AKSRT_Message::get_instance()->mark_read($user_id, $id); + } + return $this->success([ + 'message' => __('消息已删除', 'aksrt'), + 'unread' => function_exists('aksrt_get_user_unread_notification_count') + ? aksrt_get_user_unread_notification_count($user_id) + : 0, + ]); } public function rest_run_notification_cleanup(WP_REST_Request $request): WP_REST_Response { @@ -638,11 +657,65 @@ class AKSRT_Admin_User_Controller extends AKSRT_Admin_Controller_Base { } public function rest_upload_user_background(WP_REST_Request $request): WP_REST_Response { - return $this->error(__('当前版本暂未提供后台 REST 背景图上传,请使用标准媒体上传流程。', 'aksrt'), 501); + $user_id = get_current_user_id(); + if ($user_id <= 0) { + return $this->error(__('请先登录', 'aksrt'), 401); + } + if (empty($_FILES['background']) && empty($_FILES['file'])) { + return $this->error(__('请选择要上传的图片', 'aksrt'), 400); + } + require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/media.php'; + require_once ABSPATH . 'wp-admin/includes/image.php'; + + $file_key = !empty($_FILES['background']) ? 'background' : 'file'; + $attachment_id = media_handle_upload($file_key, 0); + if (is_wp_error($attachment_id)) { + return $this->error($attachment_id->get_error_message(), 400); + } + $url = wp_get_attachment_url((int) $attachment_id); + if (!$url) { + return $this->error(__('上传失败', 'aksrt'), 500); + } + update_user_meta($user_id, 'aksrt_background_image', esc_url_raw($url)); + update_user_meta($user_id, 'aksrt_background_attachment_id', (int) $attachment_id); + return $this->success([ + 'message' => __('背景图已更新', 'aksrt'), + 'data' => [ + 'url' => $url, + 'attachment_id' => (int) $attachment_id, + ], + ]); } public function rest_upload_comment_image(WP_REST_Request $request): WP_REST_Response { - return $this->error(__('当前版本暂未提供后台 REST 评论图片上传,请使用标准媒体上传流程。', 'aksrt'), 501); + $user_id = get_current_user_id(); + if ($user_id <= 0) { + return $this->error(__('请先登录', 'aksrt'), 401); + } + if (empty($_FILES['image']) && empty($_FILES['file'])) { + return $this->error(__('请选择要上传的图片', 'aksrt'), 400); + } + require_once ABSPATH . 'wp-admin/includes/file.php'; + require_once ABSPATH . 'wp-admin/includes/media.php'; + require_once ABSPATH . 'wp-admin/includes/image.php'; + + $file_key = !empty($_FILES['image']) ? 'image' : 'file'; + $attachment_id = media_handle_upload($file_key, 0); + if (is_wp_error($attachment_id)) { + return $this->error($attachment_id->get_error_message(), 400); + } + $url = wp_get_attachment_url((int) $attachment_id); + if (!$url) { + return $this->error(__('上传失败', 'aksrt'), 500); + } + return $this->success([ + 'message' => __('图片上传成功', 'aksrt'), + 'data' => [ + 'url' => $url, + 'attachment_id' => (int) $attachment_id, + ], + ]); } public function rest_get_credits_me(WP_REST_Request $request): WP_REST_Response { diff --git a/inc/classes/settings/class-aksrt-settings-integration.php b/inc/classes/settings/class-aksrt-settings-integration.php index b1ed0304..abc95107 100644 --- a/inc/classes/settings/class-aksrt-settings-integration.php +++ b/inc/classes/settings/class-aksrt-settings-integration.php @@ -401,7 +401,7 @@ class AKSRT_Settings_Integration { $quick_lines = array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $quick_often))); return [ - 'enabled' => !empty($settings['private_s']), + 'enabled' => !array_key_exists('private_s', $settings) || !empty($settings['private_s']), 'smilie' => !empty($settings['private_smilie_s']), 'code' => !empty($settings['private_code_s']), 'image' => !empty($settings['private_image_s']), diff --git a/inc/core-hooks/part-1.php b/inc/core-hooks/part-1.php index 3d9d3e95..2148b43d 100644 --- a/inc/core-hooks/part-1.php +++ b/inc/core-hooks/part-1.php @@ -278,14 +278,14 @@ function aksrt_register_forum_topic_meta() { // 收藏 (Wave3) '_aksrt_favorite_count' => ['type' => 'integer', 'default' => 0], - // 封面扩展位 (§7.3 待实现) + // 封面扩展位(论坛帖子封面;前台 forum-topic 渲染) '_aksrt_cover_type' => ['type' => 'string', 'default' => ''], '_aksrt_cover_image' => ['type' => 'string', 'default' => ''], '_aksrt_featured_slide' => ['type' => 'array', 'default' => []], '_aksrt_featured_video' => ['type' => 'string', 'default' => ''], '_aksrt_cover_ratio' => ['type' => 'string', 'default' => '16:9'], - // 付费内容扩展位 (§7.3 待实现) + // 付费内容扩展位(论坛帖子付费门槛;前台 forum-topic 渲染) '_aksrt_allow_view' => ['type' => 'string', 'default' => 'public'], '_aksrt_pay_price' => ['type' => 'number', 'default' => 0], '_aksrt_pay_vip_price' => ['type' => 'number', 'default' => 0], diff --git a/inc/helpers/aksrt-resource.php b/inc/helpers/aksrt-resource.php index 643e5100..58fdfaba 100644 --- a/inc/helpers/aksrt-resource.php +++ b/inc/helpers/aksrt-resource.php @@ -36,37 +36,12 @@ if (!function_exists('aksrt_get_resource_meta')) { $resource_view_count = (int) get_post_meta($p->ID, AKSRT_Domain_Model::RESOURCE_VIEW_COUNT_KEY, true); $common_view_count = (int) get_post_meta($p->ID, AKSRT_Domain_Model::POST_VIEW_COUNT_KEY, true); - return [ - 'version' => (string) ($pay_meta['version'] ?? ''), - 'size' => (string) ($pay_meta['size'] ?? ''), - 'price' => (float) ($pay_meta['price'] ?? 0), - 'original_price' => (float) ($pay_meta['original_price'] ?? 0), - 'vip1_price' => (float) ($pay_meta['vip1_price'] ?? 0), - 'vip2_price' => (float) ($pay_meta['vip2_price'] ?? 0), - 'pay_type' => (string) ($pay_meta['pay_type'] ?? AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD), - 'license' => (string) ($pay_meta['license'] ?? ''), - 'compat' => (string) ($pay_meta['compat'] ?? ''), - 'demo_url' => (string) ($pay_meta['demo_url'] ?? ''), - 'downloads' => array_values(is_array($pay_meta['downloads'] ?? null) ? $pay_meta['downloads'] : []), - 'download_count' => max(0, max($download_count, $legacy_download_count)), - 'view_count' => max(0, max($resource_view_count, $common_view_count)), - 'require_login' => !empty($pay_meta['require_login']), - 'require_captcha' => !empty($pay_meta['require_captcha']), - ]; - - // 一次取出全部 post meta(WP 已缓存到 object cache) - $all = get_post_meta($p->ID); - if (!is_array($all)) $all = []; - - $get = static function ($key) use ($all) { - if (!isset($all[$key]) || !is_array($all[$key]) || empty($all[$key])) return null; - return maybe_unserialize($all[$key][0]); - }; - - $downloads = $get('_aksrt_res_downloads'); - if (!is_array($downloads)) $downloads = []; + // Normalize downloads extract_code aliases for legacy rows still stored on pay meta. + $downloads = array_values(is_array($pay_meta['downloads'] ?? null) ? $pay_meta['downloads'] : []); $downloads = array_values(array_filter(array_map(static function ($row) { - if (!is_array($row)) return null; + if (!is_array($row)) { + return null; + } $extract_keys = [ 'extract_code', 'extractCode', 'extract', 'extract_key', 'extraction_code', 'extractionCode', @@ -85,33 +60,23 @@ if (!function_exists('aksrt_get_resource_meta')) { $row['extract_code'] = isset($row['extract_code']) ? trim((string) $row['extract_code']) : ''; return $row; }, $downloads))); - $resource_download_count = (int) ($get('_aksrt_res_download_count') ?? 0); - $legacy_download_count = (int) ($get('download_count') ?? 0); - $resource_view_count = (int) ($get('_aksrt_res_view_count') ?? 0); - $common_view_count = (int) ($get('aksrt_post_views') ?? 0); - - $allowed_pay_types = ['download', 'reading', 'gallery', 'video', 'vip']; - $pay_type = (string) ($get('_aksrt_res_pay_type') ?? 'download'); - if (!in_array($pay_type, $allowed_pay_types, true)) { - $pay_type = 'download'; - } return [ - 'version' => (string) ($get('_aksrt_res_version') ?? ''), - 'size' => (string) ($get('_aksrt_res_size') ?? ''), - 'price' => (float) ($get('_aksrt_res_price') ?? 0), - 'original_price' => (float) ($get('_aksrt_res_original_price') ?? 0), - 'vip1_price' => (float) ($get('_aksrt_res_vip1_price') ?? 0), - 'vip2_price' => (float) ($get('_aksrt_res_vip2_price') ?? 0), - 'pay_type' => $pay_type, - 'license' => (string) ($get('_aksrt_res_license') ?? ''), - 'compat' => (string) ($get('_aksrt_res_compat') ?? ''), - 'demo_url' => (string) ($get('_aksrt_res_demo_url') ?? ''), - 'downloads' => array_values($downloads), - 'download_count' => max(0, max($resource_download_count, $legacy_download_count)), + 'version' => (string) ($pay_meta['version'] ?? ''), + 'size' => (string) ($pay_meta['size'] ?? ''), + 'price' => (float) ($pay_meta['price'] ?? 0), + 'original_price' => (float) ($pay_meta['original_price'] ?? 0), + 'vip1_price' => (float) ($pay_meta['vip1_price'] ?? 0), + 'vip2_price' => (float) ($pay_meta['vip2_price'] ?? 0), + 'pay_type' => (string) ($pay_meta['pay_type'] ?? AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD), + 'license' => (string) ($pay_meta['license'] ?? ''), + 'compat' => (string) ($pay_meta['compat'] ?? ''), + 'demo_url' => (string) ($pay_meta['demo_url'] ?? ''), + 'downloads' => $downloads, + 'download_count' => max(0, max($download_count, $legacy_download_count)), 'view_count' => max(0, max($resource_view_count, $common_view_count)), - 'require_login' => (bool) ($get('_aksrt_res_require_login') ?? false), - 'require_captcha' => (bool) ($get('_aksrt_res_require_captcha') ?? false), + 'require_login' => !empty($pay_meta['require_login']), + 'require_captcha' => !empty($pay_meta['require_captcha']), ]; } } diff --git a/inc/helpers/aksrt-user-home.php b/inc/helpers/aksrt-user-home.php index 985fed8d..7164ec15 100644 --- a/inc/helpers/aksrt-user-home.php +++ b/inc/helpers/aksrt-user-home.php @@ -92,13 +92,38 @@ if (!function_exists('aksrt_get_user_home_page_context')) { $follow_count = (int) AKSRT_Follow::get_instance()->get_following_count($user_id); } + $plate_count = 0; + if ($forum_instance) { + $seen_plates = []; + if (method_exists($forum_instance, 'get_user_followed_plates')) { + foreach ((array) $forum_instance->get_user_followed_plates($user_id) as $plate) { + $pid = (int) ($plate['id'] ?? 0); + if ($pid > 0) { + $seen_plates[$pid] = true; + } + } + } + if (method_exists($forum_instance, 'get_user_managed_plates')) { + $managed = (array) $forum_instance->get_user_managed_plates($user_id); + foreach (['owned', 'moderating'] as $bucket) { + foreach ((array) ($managed[$bucket] ?? []) as $plate) { + $pid = (int) ($plate['id'] ?? 0); + if ($pid > 0) { + $seen_plates[$pid] = true; + } + } + } + } + $plate_count = count($seen_plates); + } + $tab_counts = [ 'posts' => $post_count, 'comments' => $comment_count, 'favorites' => $favorite_count, 'credits' => $credits_enabled && $is_own_profile ? (int) round((float) $credit_balance) : 0, 'product' => $product_count, - 'plate' => 0, + 'plate' => $plate_count, 'forum' => $forum_topic_count, 'follow' => $follow_count, ]; diff --git a/templates/forum/forum-board.php b/templates/forum/forum-board.php index dcba635f..8cd73b1c 100644 --- a/templates/forum/forum-board.php +++ b/templates/forum/forum-board.php @@ -54,10 +54,14 @@ $name_topic = $forum_names['posts'] ?? __('主题', 'aksrt'); $name_reply = $forum_names['comment'] ?? __('回复', 'aksrt'); ?> -
+
+
+
+
+ -
@@ -141,6 +145,10 @@ $name_reply = $forum_names['comment'] ?? __('回复', 'aksrt');
+
+ +
+
diff --git a/templates/forum/forum-plate-cat.php b/templates/forum/forum-plate-cat.php index 5ab2522c..85e08288 100644 --- a/templates/forum/forum-plate-cat.php +++ b/templates/forum/forum-plate-cat.php @@ -42,10 +42,14 @@ if (is_array($cat_moderator_ids)) { } ?> -
+
+
+
+
+ -
+

name : ''); ?>

description) : ?> @@ -101,6 +105,10 @@ if (is_array($cat_moderator_ids)) {
+
+ +
+
diff --git a/templates/forum/forum-plate.php b/templates/forum/forum-plate.php index ee3119bb..3e787bb7 100644 --- a/templates/forum/forum-plate.php +++ b/templates/forum/forum-plate.php @@ -55,11 +55,15 @@ $is_followed = $forum && $current_user_id > 0 ? $forum->is_user_following_pla $max_pages = (int) ($view['pagination']['max_pages'] ?? 1); ?> -
+
+
+
+
+ -
+

@@ -283,6 +287,10 @@ $max_pages = (int) ($view['pagination']['max_pages'] ?? 1);
+ +
+
+
diff --git a/templates/forum/forum-tag.php b/templates/forum/forum-tag.php index f2870405..5f24073f 100644 --- a/templates/forum/forum-tag.php +++ b/templates/forum/forum-tag.php @@ -24,9 +24,13 @@ $term_name = $term->name ?? ''; $term_desc = $term->description ?? ''; ?> -
+
+
+
+
-
+ +

@@ -61,6 +65,10 @@ $term_desc = $term->description ?? '';

+
+ +
+
diff --git a/templates/forum/forum-topic.php b/templates/forum/forum-topic.php index 1b765b5f..363a1595 100644 --- a/templates/forum/forum-topic.php +++ b/templates/forum/forum-topic.php @@ -60,8 +60,12 @@ if (empty($topic['can_view'])) : $credits_cost = (int) get_post_meta($topic_id, '_aksrt_credits_cost', true); $pay_price = get_post_meta($topic_id, '_aksrt_pay_price', true); ?> -
-
+
+
+
+
+ +

@@ -94,7 +98,11 @@ if (empty($topic['can_view'])) :
-
+ +
+ + +
-
+
+
+
+
+ -
+
+ +
+ +
+ +
+ +
+ +
+ + + +
+ + +
@@ -667,6 +707,10 @@ $comments_enabled = !empty($permissions['can_reply']);
+
+ +
+
diff --git a/templates/parts/modal-auth.php b/templates/parts/modal-auth.php index 6dce1627..e48c123f 100644 --- a/templates/parts/modal-auth.php +++ b/templates/parts/modal-auth.php @@ -67,7 +67,7 @@ $logo_url = class_exists('AKSRT_Settings') ? AKSRT_Settings::user_logo_url() : ' -
+