diff --git a/.zcode/plans/plan-sess_c9ca98f3-5ff7-43eb-a99a-534e80b09c65.md b/.zcode/plans/plan-sess_c9ca98f3-5ff7-43eb-a99a-534e80b09c65.md new file mode 100644 index 00000000..e29886e6 --- /dev/null +++ b/.zcode/plans/plan-sess_c9ca98f3-5ff7-43eb-a99a-534e80b09c65.md @@ -0,0 +1,42 @@ +# 复刻 Zibll 粉丝/关注用户列表(公开主页 follow TAB) + +## 问题根源 + +`templates/parts/user-home/panel-follow.php:46-65` 手写的列表 markup(`.uh-follow__list/item/avatar/name`)在全仓库没有任何 CSS 定义,头像+用户名以内联文本堆叠显示。列表项也只有头像和用户名,没有 Zibll 的卡片结构。 + +关键事实:AKSRT 已 vendored Zibll 的 `main.css`(`.author-minicard` 卡片规则、工具类齐全),且 `inc/helpers/aksrt-graphic-card.php:215` 已有 `aksrt_author_minicard()` —— Zibll `zib_author_card()` 的移植版(42px 懒加载头像、用户名+勋章/认证/等级图标、逐项关注按钮、个性签名一行省略、默认文案"这家伙很懒,什么都没有写…"),搜索页在用。**修复 = 粉丝/关注列表改走这条既有链路**,不新写卡片样式。 + +## 改动清单 + +### 1. `templates/parts/user-home/panel-follow.php`(核心) +- 列表循环改为 `aksrt_author_minicard((int) $row['user_id'])`,删除手写的 `.uh-follow__item/avatar/name` markup(渲染链路与搜索页用户列表完全一致)。 +- 子切换复刻 Zibll 结构(`zib-author.php:407-410`):居中文字链接「粉丝 N | 关注 N」(`list-inline splitters` + 激活 `focus-color` / 非激活 `muted-color`),**顺序改为粉丝在前、默认子列表改为粉丝**(Zibll 逻辑:TAB 名"粉丝"、默认显示粉丝列表,`zib_get_follow_user_list` 默认 `type='followed'`)。链接仍走现有 `?follow_sub=&follow_page=` 路由。 +- 空状态图标换心形 SVG(见第 6 条),继续用 `aksrt_render_profile_empty` 组件(对齐 Zibll null-love 爱心语义)。 +- 分页保持 `paginate_links` 服务端翻页:Zibll 的 AJAX"加载更多"框架(post_ajax/ajax-next)未移植进 AKSRT(main.js 仅 27 行),且公开主页所有 TAB 统一为服务端渲染 + URL 参数翻页,不为此引入独立 AJAX 体系。 + +### 2. `inc/helpers/aksrt-graphic-card.php` — `aksrt_author_minicard()` +- 关注按钮改为**服务端渲染真实状态**(复刻 Zibll 逻辑):已登录且 `\AKSRT\User\Follow::is_following(current, target)` 为真时输出 `is-following` class +「已关注」文案。消除每次进页"全部显示关注→异步闪变"的问题。搜索页同样受益。 + +### 3. `templates/parts/user/author-hero.php` +- hero 关注按钮(L151-163)同样服务端渲染真实初始状态(`is-following` +「已关注」),与卡片按钮同一逻辑。 + +### 4. `assets/js/follow.js` +- 删除逐按钮 `/follow/status` 轮询(L54-69):服务端已输出真值,不留双路。保留点击 toggle、`updateFollowButton` 状态切换、用户中心取关移除行、计数更新。 + +### 5. `assets/css/routes/user-home.css` — follow 区样式替换 +- `.uh-follow__list { text-align: center; }`:对齐 Zibll pane 的 `text-center`(卡片为 inline-block,自动 3 列居中换行,移动端单列规则 main.css 已有)。 +- 子切换:删除旧 pill 按钮样式块(L486-519),替换为 Zibll 风格的少量布局规则(居中、间距),颜色/激活态复用 vendored 工具类。 +- 同步删除不再被引用的 `__subtab-count` 等旧规则。 + +### 6. `inc/helpers/aksrt-user-home.php` +- context 数组新增 `svg_empty_follow`(心形 stroke SVG,风格同现有 `svg_empty_post` 等)。 + +## 不做的事(明确排除) +- 不引入 AJAX"加载更多"(无框架基础,破坏公开主页统一服务端分页架构)。 +- 不动数据层 `src/User/Follow.php`(`get_user_list` 已提供所需 user_id;徽章/签名由 minicard 自行取)。 +- 不写任何双路、兼容、迁移代码:旧的手写列表 markup 和 pill 子切换样式直接替换删除。 + +## 验证 +- `php -l` 检查全部改动的 PHP 文件。 +- `node --test tests/widget-system/*.test.cjs` 确认不破坏既有契约(无该面板专属测试)。 +- 桌面 + 移动宽度下目视核对:卡片 3 列/单列、关注按钮初始状态、子切换、空状态、分页。 \ No newline at end of file diff --git a/assets/css/routes/user-home.css b/assets/css/routes/user-home.css index cec8bb34..ab5e5ddd 100644 --- a/assets/css/routes/user-home.css +++ b/assets/css/routes/user-home.css @@ -482,40 +482,43 @@ } } -/* User-home follow tab */ +/* User-home follow tab — Zibll parity + * 子切换复刻 zibll inc/functions/zib-author.php:407-410(list-inline splitters + * 居中文字链接,激活 focus-color);列表项为 .author-minicard inline-block + * 三列卡片流(卡片规则、640px 单列均在 vendored main.css),容器居中对齐 + * zibll pane 的 text-center。 */ .aksrt-user-home .uh-follow__subtabs { display: flex; + justify-content: center; gap: 4px; - margin-bottom: 16px; + margin: 10px 0 16px; + padding: 0; padding-bottom: 12px; - border-bottom: 1px solid var(--c-border, #e8e8e8); + border-bottom: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.06)); + list-style: none; } .aksrt-user-home .uh-follow__subtab { display: inline-flex; - align-items: center; + align-items: baseline; gap: 4px; - padding: 6px 14px; - border-radius: var(--radius-pill, 999px); - font-size: 13px; - color: var(--c-text-3, #999); + padding: 4px 12px; + font-size: 14px; text-decoration: none; - transition: all .2s; + transition: color .2s; } .aksrt-user-home .uh-follow__subtab:hover { - color: var(--c-text-1, #333); - background: var(--c-fill-1, #f5f5f5); -} - -.aksrt-user-home .uh-follow__subtab.is-active { - background: var(--c-primary, #1890ff); - color: #fff; + color: var(--focus-color, var(--color-primary, #f04494)); } .aksrt-user-home .uh-follow__subtab-count { - font-size: 11px; - opacity: .7; + font-size: 12px; + opacity: .8; +} + +.aksrt-user-home .uh-follow__list { + text-align: center; } .aksrt-user-home .uh-follow__pagination { diff --git a/assets/js/follow.js b/assets/js/follow.js index 34c27645..cff5027d 100644 --- a/assets/js/follow.js +++ b/assets/js/follow.js @@ -15,6 +15,7 @@ })(); var NONCE = (window.aksrtData && window.aksrtData.restNonce) || ''; + // 初始关注状态由服务端渲染(is-following class + 文案),无需逐按钮查询。 function initFollowButtons() { document.addEventListener('click', function (e) { var btn = e.target.closest('[data-aksrt-follow]'); @@ -50,23 +51,6 @@ btn.disabled = false; }); }); - - // Check initial follow status for all follow buttons - document.querySelectorAll('[data-aksrt-follow]').forEach(function (btn) { - var targetUserId = parseInt(btn.getAttribute('data-aksrt-follow')); - if (!targetUserId) return; - - fetch(API_ROOT + '/follow/status?target_user_id=' + targetUserId, { - headers: { 'X-WP-Nonce': NONCE }, - }) - .then(function (res) { return res.json(); }) - .then(function (result) { - if (result.success && result.data) { - updateFollowButton(btn, result.data.is_following, result.data.follower_count); - } - }) - .catch(function () { /* silent */ }); - }); } function updateFollowButton(btn, isFollowing, count) { diff --git a/inc/helpers/aksrt-graphic-card.php b/inc/helpers/aksrt-graphic-card.php index 352b0e02..393262cb 100644 --- a/inc/helpers/aksrt-graphic-card.php +++ b/inc/helpers/aksrt-graphic-card.php @@ -242,11 +242,19 @@ if (!function_exists('aksrt_author_minicard')) : : '' . esc_html($user->display_name) . ''; // Follow control (separate from name, legacy places both in dt). + // 服务端直接输出真实关注状态(legacy parity: zib_get_user_follow 恒渲染当前态)。 $follow = ''; $current = get_current_user_id(); if ($current !== $user_id) { + $is_following = false; + if (is_user_logged_in() && class_exists('\AKSRT\User\Follow')) { + $is_following = \AKSRT\User\Follow::get_instance()->is_following($current, $user_id); + } + $follow_label = $is_following ? __('已关注', 'aksrt') : __('关注', 'aksrt'); + $follow_title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt'); + $follow_state = $is_following ? ' is-following' : ''; if (is_user_logged_in()) { - $follow = ''; + $follow = ''; } else { $login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(); $follow = ' ' . esc_html__('关注', 'aksrt') . ''; diff --git a/inc/helpers/aksrt-user-home.php b/inc/helpers/aksrt-user-home.php index 8866e2ae..a4cbb205 100644 --- a/inc/helpers/aksrt-user-home.php +++ b/inc/helpers/aksrt-user-home.php @@ -137,6 +137,7 @@ if (!function_exists('aksrt_get_user_home_page_context')) { 'svg_empty_post' => '', 'svg_empty_comment' => '', 'svg_empty_favorite' => '', + 'svg_empty_follow' => '', ]; } } diff --git a/templates/parts/user-home/panel-follow.php b/templates/parts/user-home/panel-follow.php index b85180ad..a1596762 100644 --- a/templates/parts/user-home/panel-follow.php +++ b/templates/parts/user-home/panel-follow.php @@ -2,6 +2,10 @@ /** * Public user home — follow tab panel (social following/followers). * + * Legacy parity: 粉丝/关注用户列表复刻 zib_get_follow_user_list + zib_author_card + * (inc/functions/zib-author.php:395-454, 685-716):子切换「粉丝 | 关注」居中文字链接, + * 列表项走 aksrt_author_minicard(.author-minicard 三列 inline-block 卡片流)。 + * * @package AKSRT */ @@ -15,8 +19,8 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext
> get_following_count((int) $user_id) : 0; @@ -30,38 +34,28 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext $items = is_array($list['items'] ?? null) ? $list['items'] : []; $total_pages = max(1, (int) ($list['total_pages'] ?? 1)); ?> -
- - - - - - - - -
+
- - - - alt="" width="48" height="48"> - - - - - - - + echo aksrt_author_minicard((int) ($row['user_id'] ?? 0)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + endforeach; ?>
1) : ?>
@@ -81,7 +75,7 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext $empty_msg = $follow_sub === 'followers' ? __('暂无粉丝', 'aksrt') : __('暂未关注任何人', 'aksrt'); - aksrt_render_profile_empty($svg_empty_post, $empty_msg); + aksrt_render_profile_empty($svg_empty_follow ?? $svg_empty_post ?? '', $empty_msg); ?>
diff --git a/templates/parts/user/author-hero.php b/templates/parts/user/author-hero.php index 8c267ceb..3813678f 100644 --- a/templates/parts/user/author-hero.php +++ b/templates/parts/user/author-hero.php @@ -149,11 +149,16 @@ $messages_url = function_exists('aksrt_get_messages_page_url') ? aksrt_get_messa - - + + is_following(get_current_user_id(), (int) $user_id) + : false; + ?> +