Files
wordpress-theme-aksrt/templates/pages/page-messages.php
T
kate522 9375b1c30c refactor(assets): vendor 资产迁入标准目录并全仓去除 zibll 标识
- assets/vendor 全量迁移: fontawesome5→assets/fontawesome5, css→assets/css,
  js→assets/js/runtime(避开 14 个同名自有文件), img 合并去重→assets/img,
  fonts→assets/fonts; 删除 assets/vendor
- libs 五个与 widgets/third-party 字节相同的库不重复入库, loader paths 改指向
- 运行时切换: _win.uri=assets 根, loader baseUrl=/js/runtime, enlighter 注入
  路径同步; PHP 7 个引用文件切换并改名 $zibll_*→$runtime_*
- 去品牌: Qinver 头注释/品牌 console.log/演示链接清除, 默认文案中性化
- 功能标识符改名: zib__→aksrt__, zib_ajax→aksrt_ajax, window.zib→
  window.aksrtLegacy, zibllblock/*→aksrtblock/*, .zib-widget→.aksrt-panel,
  .zib-slider→.aksrt-slider, zib_menu_options meta 键等
- 自有 CSS 改名: legacy-utilities/compat-vars/footer shell
- admin SPA 重建; 契约测试同步(路径/断言/文件更名)
- 例外保留: tests/visual 对照环境与 fixtures 前缀(外部 Zibll 产品引用)

验证: php -l 133 个改动文件通过; runtime 86 个 JS node --check 通过;
契约测试 68/70(2 个失败经 stash 对照确认为存量问题)
2026-08-22 16:16:30 +08:00

351 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Template Name: 消息中心
* Template Post Type: page
*
* 独立消息中心页面 — 两栏布局(左侧导航 + 右侧消息列表)
* 对齐 legacy msg-center:未读 / 评论 / 系统 / 私信(可开关)
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
get_header();
\AKSRT\WidgetSystem\WidgetAreas::render('msg_top_fluid', 'fluid-widget-wrap');
if (!is_user_logged_in()) {
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink());
wp_safe_redirect($login_url);
exit;
}
$messages_enabled = !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::message_enabled();
$pm_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::private_message_settings() : ['enabled' => false];
$pm_enabled = !empty($pm_settings['enabled']);
if (!$messages_enabled) :
?>
<main id="main-content" class="site-main content-shell" tabindex="-1">
<div class="container messages-center messages-center--disabled">
<div class="messages-empty">
<span class="messages-empty__icon"><i class="fa fas fa-bell-slash"></i></span>
<p class="messages-empty__text"><?php esc_html_e('站内消息功能未开启', 'aksrt'); ?></p>
<p class="messages-empty__hint"><?php esc_html_e('请联系管理员在后台「用户 → 消息通知」中启用。', 'aksrt'); ?></p>
</div>
</div>
</main>
<?php
\AKSRT\WidgetSystem\WidgetAreas::render('msg_bottom_fluid', 'fluid-widget-wrap');
get_footer();
return;
endif;
$user_id = get_current_user_id();
$all_notifications = function_exists('aksrt_get_user_all_notifications')
? aksrt_get_user_all_notifications($user_id)
: [];
$current_type = isset($_GET['msg_type']) ? sanitize_key(wp_unslash((string) $_GET['msg_type'])) : 'unread';
// 类型分组:对齐 legacy 主 tab 语义,同时兼容历史 type 键。
$type_groups = [
'unread' => null, // special: !read
'all' => null, // special: all
'comment' => ['comment_reply', 'comment_approved', 'pending_comments'],
'credits' => ['credits_added', 'credits_deducted'],
'pay' => ['pay', 'order_paid', 'order_refund', 'shop_order', 'after_sale'],
'vip' => ['vip', 'pay_vip', 'vip_expire', 'vip_upgrade'],
'medal' => ['medal', 'badge', 'level_up'],
'ban_appeal' => ['ban_appeal', 'user_ban', 'restriction'],
'system' => ['notice', 'download_ready', 'upload_moderation', 'verify_email', 'auth_apply', 'auth_apply_reply'],
'conversation' => ['conversation'],
];
if ($current_type === 'conversation' && !$pm_enabled) {
$current_type = 'unread';
}
$allowed_types = array_keys($type_groups);
if (!in_array($current_type, $allowed_types, true)) {
// 兼容旧 URL:msg_type=comment_reply 等
$mapped = 'all';
foreach ($type_groups as $group => $types) {
if (is_array($types) && in_array($current_type, $types, true)) {
$mapped = $group;
break;
}
}
$current_type = $mapped;
}
$filtered = $all_notifications;
if ($current_type === 'unread') {
$filtered = array_filter($filtered, static fn($n) => empty($n['read']));
} elseif ($current_type !== 'all' && $current_type !== 'conversation') {
$types = $type_groups[$current_type] ?? [];
$filtered = array_filter($filtered, static function ($n) use ($types) {
$t = (string) ($n['type'] ?? 'notice');
return in_array($t, $types, true);
});
}
$filtered = array_values($filtered);
$per_page = 20;
$current_page = max(1, (int) ($_GET['msg_page'] ?? 1));
$total = count($filtered);
$total_pages = max(1, (int) ceil($total / $per_page));
$offset = ($current_page - 1) * $per_page;
$paged_items = array_slice($filtered, $offset, $per_page);
// 统计
$type_counts = [
'all' => count($all_notifications),
'unread' => 0,
'comment' => 0,
'credits' => 0,
'pay' => 0,
'vip' => 0,
'medal' => 0,
'ban_appeal' => 0,
'system' => 0,
'conversation' => 0,
];
foreach ($all_notifications as $n) {
$t = (string) ($n['type'] ?? 'notice');
if (empty($n['read'])) {
$type_counts['unread']++;
}
foreach (['comment', 'credits', 'pay', 'vip', 'medal', 'ban_appeal', 'system', 'conversation'] as $group) {
if (!empty($type_groups[$group]) && is_array($type_groups[$group]) && in_array($t, $type_groups[$group], true)) {
$type_counts[$group]++;
}
}
}
if ($pm_enabled && class_exists('\AKSRT\Messages\PrivateMessage')) {
// conversation count prefers live PM conversations when available
$pm_tmp = \AKSRT\Messages\PrivateMessage::get_instance();
if (method_exists($pm_tmp, 'get_conversations')) {
$convs = $pm_tmp->get_conversations($user_id, 100);
if (is_array($convs)) {
$type_counts['conversation'] = count($convs);
}
}
}
$nav_items = [
'unread' => __('未读消息', 'aksrt'),
'all' => __('全部消息', 'aksrt'),
'comment' => __('评论互动', 'aksrt'),
'credits' => __('积分动态', 'aksrt'),
'pay' => __('交易通知', 'aksrt'),
'vip' => __('会员动态', 'aksrt'),
'medal' => __('徽章等级', 'aksrt'),
'ban_appeal' => __('禁封申诉', 'aksrt'),
'system' => __('系统通知', 'aksrt'),
];
if ($pm_enabled) {
$nav_items['conversation'] = __('私信消息', 'aksrt');
}
$icon_map = [
'comment_reply' => 'fa fas fa-comment-dots',
'comment_approved' => 'fa fas fa-check-circle',
'credits_added' => 'fa fas fa-money-bill',
'credits_deducted' => 'fa fas fa-money-bill',
'download_ready' => 'fa fas fa-download',
'upload_moderation' => 'fa fas fa-image',
'verify_email' => 'fa fas fa-envelope-open',
'pending_comments' => 'fa fas fa-clock',
'conversation' => 'fa fas fa-envelope',
'pay' => 'fa fas fa-file-alt',
'order_paid' => 'fa fas fa-file-alt',
'order_refund' => 'fa fas fa-undo',
'after_sale' => 'fa fas fa-undo',
'vip' => 'fa fas fa-certificate',
'pay_vip' => 'fa fas fa-certificate',
'medal' => 'fa fas fa-certificate',
'badge' => 'fa fas fa-certificate',
'ban_appeal' => 'fa fas fa-gavel',
'user_ban' => 'fa fas fa-ban',
'auth_apply' => 'fa fas fa-id-badge',
'auth_apply_reply' => 'fa fas fa-id-badge',
'notice' => 'fa fas fa-bell',
];
$messages_base = function_exists('aksrt_get_messages_page_url') ? aksrt_get_messages_page_url() : get_permalink();
if (!$messages_base) {
$messages_base = get_permalink();
}
?>
<main id="main-content" class="site-main content-shell" tabindex="-1">
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
<div class="container">
<header class="messages-center__header">
<h1 class="messages-center__title"><?php esc_html_e('消息中心', 'aksrt'); ?></h1>
<p class="messages-center__subtitle"><?php esc_html_e('查看站内通知与互动提醒', 'aksrt'); ?></p>
</header>
<div class="messages-center">
<aside class="messages-center__nav" aria-label="<?php esc_attr_e('消息分类', 'aksrt'); ?>">
<ul class="messages-nav__list">
<?php foreach ($nav_items as $key => $label) :
$count = (int) ($type_counts[$key] ?? 0);
// 始终显示核心 tab;其余无数据时可隐藏(保留 unread/all/system)
if (!in_array($key, ['unread', 'all', 'system', 'conversation'], true) && $count < 1) {
continue;
}
$is_active = $current_type === $key;
$tab_url = add_query_arg(['msg_type' => $key, 'msg_page' => 1], $messages_base);
?>
<li>
<a href="<?php echo esc_url($tab_url); ?>"
class="messages-nav__item<?php echo $is_active ? ' is-active' : ''; ?>"
data-type="<?php echo esc_attr($key); ?>">
<span><?php echo esc_html($label); ?></span>
<?php if ($count > 0) : ?>
<span class="messages-nav__badge" data-type="<?php echo esc_attr($key); ?>" data-count="<?php echo esc_attr((string) $count); ?>"><?php echo esc_html((string) $count); ?></span>
<?php endif; ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<div class="messages-nav__actions">
<button type="button" class="messages-nav__action-btn js-msg-mark-all-read"><?php esc_html_e('全部已读', 'aksrt'); ?></button>
<button type="button" class="messages-nav__action-btn js-msg-clear-read"><?php esc_html_e('清除已读', 'aksrt'); ?></button>
</div>
</aside>
<section class="messages-center__content">
<?php if ($current_type === 'conversation' && $pm_enabled) :
$pm = class_exists('\AKSRT\Messages\PrivateMessage') ? \AKSRT\Messages\PrivateMessage::get_instance() : null;
$conversations = $pm ? $pm->get_conversations($user_id, 30) : [];
$pm_nonce = wp_create_nonce('aksrt_pm');
$pm_placeholder = (string) ($pm_settings['placeholder'] ?? __('请输入消息...', 'aksrt'));
$pm_submit = (string) ($pm_settings['submit_text'] ?? __('发送', 'aksrt'));
?>
<div class="messages-conversations" id="aksrt-pm-conversations"
data-ajax="<?php echo esc_url(admin_url('admin-ajax.php')); ?>"
data-nonce="<?php echo esc_attr($pm_nonce); ?>">
<?php if (!empty($conversations)) : ?>
<div class="aksrt-pm__list">
<?php foreach ($conversations as $conv) : ?>
<div class="aksrt-pm__item<?php echo !empty($conv['unread']) ? ' is-unread' : ''; ?>"
data-conv-id="<?php echo esc_attr((string) ($conv['id'] ?? '')); ?>"
data-other-id="<?php echo esc_attr((string) ($conv['other_user_id'] ?? '')); ?>">
<img<?php echo aksrt_get_lazy_attr('lazy_private', (string) ($conv['other_avatar'] ?? ''), 'aksrt-pm__avatar', aksrt_get_lazy_thumb('sm')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="">
<div class="aksrt-pm__info">
<div class="aksrt-pm__name">
<?php echo esc_html((string) ($conv['other_name'] ?? '')); ?>
<?php if (!empty($conv['unread'])) : ?>
<span class="aksrt-pm__badge"><?php echo esc_html((string) $conv['unread']); ?></span>
<?php endif; ?>
</div>
<div class="aksrt-pm__preview"><?php echo esc_html(mb_substr((string) ($conv['last_message'] ?? ''), 0, 50)); ?></div>
</div>
<div class="aksrt-pm__time"><?php echo esc_html(!empty($conv['last_message_at']) ? mysql2date('m-d H:i', (string) $conv['last_message_at']) : ''); ?></div>
</div>
<?php endforeach; ?>
</div>
<?php else : ?>
<div class="messages-empty">
<span class="messages-empty__icon"><i class="fa fas fa-envelope"></i></span>
<p class="messages-empty__text"><?php esc_html_e('暂无私信', 'aksrt'); ?></p>
<p class="messages-empty__hint"><?php esc_html_e('你可以在用户主页或文章作者栏发起私信。', 'aksrt'); ?></p>
</div>
<?php endif; ?>
</div>
<div class="aksrt-pm__thread" id="aksrt-pm-thread" hidden>
<div class="aksrt-pm__thread-header">
<button type="button" class="aksrt-pm__back-btn" id="aksrt-pm-back"><i class="fa fas fa-arrow-left"></i> <?php esc_html_e('返回', 'aksrt'); ?></button>
<span class="aksrt-pm__thread-name" id="aksrt-pm-thread-name"></span>
</div>
<div class="aksrt-pm__messages" id="aksrt-pm-messages"></div>
<div class="aksrt-pm__compose">
<textarea id="aksrt-pm-input" rows="2" placeholder="<?php echo esc_attr($pm_placeholder); ?>"></textarea>
<button type="button" id="aksrt-pm-send" class="aksrt-pm__send-btn" aria-label="<?php echo esc_attr($pm_submit); ?>"><i class="fa fas fa-paper-plane"></i></button>
</div>
</div>
<?php elseif (!empty($paged_items)) : ?>
<div class="messages-list">
<?php foreach ($paged_items as $notice) :
$n_type = (string) ($notice['type'] ?? 'notice');
$n_title = (string) ($notice['title'] ?? '');
$n_desc = (string) ($notice['desc'] ?? '');
$n_url = (string) ($notice['url'] ?? '');
$n_id = (string) ($notice['id'] ?? '');
$is_read = !empty($notice['read']);
$is_state = !empty($notice['stateful']);
$created = (string) ($notice['created_at'] ?? '');
$time_str = $is_state
? __('状态提醒', 'aksrt')
: ($created !== '' ? get_date_from_gmt($created, 'Y-m-d H:i') : '');
$icon_cls = $icon_map[$n_type] ?? 'fa fas fa-bell';
?>
<article class="message-item<?php echo !$is_read ? ' is-unread' : ''; ?>" data-notification-id="<?php echo esc_attr($n_id); ?>">
<span class="message-item__icon">
<i class="<?php echo esc_attr($icon_cls); ?>" aria-hidden="true"></i>
</span>
<div class="message-item__body">
<div class="message-item__title">
<?php echo esc_html($n_title); ?>
<?php if (!$is_read && !$is_state) : ?>
<span class="message-item__new-badge"><?php esc_html_e('新', 'aksrt'); ?></span>
<?php endif; ?>
</div>
<?php if ($n_desc !== '') : ?>
<div class="message-item__desc"><?php echo esc_html($n_desc); ?></div>
<?php endif; ?>
<?php if ($time_str !== '') : ?>
<div class="message-item__time"><?php echo esc_html($time_str); ?></div>
<?php endif; ?>
</div>
<div class="message-item__actions">
<?php if ($n_url !== '') : ?>
<a href="<?php echo esc_url($n_url); ?>" class="message-item__action-btn"><?php esc_html_e('查看', 'aksrt'); ?></a>
<?php endif; ?>
<?php if (!$is_state && !$is_read) : ?>
<button type="button" class="message-item__action-btn js-msg-mark-read" data-id="<?php echo esc_attr($n_id); ?>"><?php esc_html_e('已读', 'aksrt'); ?></button>
<?php endif; ?>
<?php if (!$is_state) : ?>
<button type="button" class="message-item__action-btn message-item__action-btn--danger js-msg-delete" data-id="<?php echo esc_attr($n_id); ?>"><?php esc_html_e('删除', 'aksrt'); ?></button>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
<?php if ($total_pages > 1) : ?>
<nav class="messages-pagination" aria-label="<?php esc_attr_e('消息分页', 'aksrt'); ?>">
<?php for ($i = 1; $i <= $total_pages; $i++) :
$page_url = add_query_arg(['msg_type' => $current_type, 'msg_page' => $i], $messages_base);
?>
<?php if ($i === $current_page) : ?>
<span class="profile-status-chip is-active"><?php echo esc_html((string) $i); ?></span>
<?php else : ?>
<a href="<?php echo esc_url($page_url); ?>" class="profile-status-chip"><?php echo esc_html((string) $i); ?></a>
<?php endif; ?>
<?php endfor; ?>
</nav>
<?php endif; ?>
<?php else : ?>
<div class="messages-empty">
<span class="messages-empty__icon"><i class="fa fas fa-bell-slash"></i></span>
<p class="messages-empty__text"><?php esc_html_e('暂无消息', 'aksrt'); ?></p>
</div>
<?php endif; ?>
</section>
</div>
</div>
</main>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('msg_bottom_fluid', 'fluid-widget-wrap'); ?>
<?php get_footer(); ?>