Files
mce/templates/MetaLab-2026/html/messages/index.html
Victor_Jay 67444434b9 feat: 对接稿件审核通知系统 + 消息页面系统消息区域
- 新增通知类型 post_approved / post_rejected
- PostService.Approve/Reject 完成后通知作者(通知失败不影响主流程)
- NotificationService 新增 NotifyPostApproved/NotifyPostRejected 方法
- 消息页面顶部增加系统消息标识和分隔线,之下为全部消息列表
- 稿件审核通过通知卡片点击跳转到 /posts/{id} 页面
- 稿件审核拒绝通知卡片点击跳转到 /studio/posts 创作中心
- 新增 postNotifier 接口遵循 ISP 原则
2026-05-30 21:24:30 +08:00

208 lines
8.4 KiB
HTML
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.

{{template "layout/header.html" .}}
<meta name="robots" content="noindex,nofollow">
<body>
{{template "layout/nav.html" .}}
<main class="msg-layout">
<aside class="msg-sidebar">
<div class="msg-sidebar-header">
<h2>消息中心</h2>
</div>
<nav class="msg-sidebar-nav">
<span class="msg-sidebar-item active">
<svg class="msg-sidebar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
全部消息
{{if .UnreadCount}}
<span class="msg-sidebar-count">{{.UnreadCount}}</span>
{{end}}
</span>
</nav>
</aside>
<div class="msg-main">
<!-- 系统消息区域 -->
<div class="msg-system-section">
<div class="msg-system-header">
<svg class="msg-system-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
系统消息
</div>
<div class="msg-system-divider"></div>
</div>
{{if .Messages}}
<div class="msg-toolbar">
{{if .UnreadCount}}
<button id="markAllReadBtn" class="msg-mark-all-btn">全部标为已读</button>
{{end}}
</div>
<div class="msg-list">
{{range $i, $m := .Messages}}
{{$link := ""}}
{{if or (eq $m.NotifyType "post_approved") (eq $m.NotifyType "post_rejected")}}
{{if eq $m.NotifyType "post_approved"}}
{{$link = printf "/posts/%d" $m.RelatedID}}
{{else}}
{{$link = "/studio/posts"}}
{{end}}
{{end}}
{{if $link}}
<a href="{{$link}}" class="msg-card-link">
{{end}}
<div class="msg-card{{if not $m.IsRead}} unread{{end}}" data-id="{{$m.ID}}" data-type="{{$m.NotifyType}}" data-link="{{$link}}">
<div class="msg-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
</div>
<div class="msg-body">
<div class="msg-title">{{$m.Title}}</div>
<div class="msg-content">{{$m.Content}}</div>
<div class="msg-meta">
<span class="msg-type">{{index $.NotifyTypeNames $m.NotifyType}}</span>
<span class="msg-time">{{$m.CreatedAt.Format "2006-01-02 15:04"}}</span>
</div>
</div>
{{if not $m.IsRead}}
<span class="msg-badge"></span>
{{end}}
</div>
{{if $link}}
</a>
{{end}}
{{end}}
</div>
{{if .TotalPages}}
<div class="msg-pagination">
{{if .HasPrev}}
<a href="/messages?page={{.PrevPage}}" class="msg-page-btn">上一页</a>
{{else}}
<span class="msg-page-btn disabled">上一页</span>
{{end}}
<span class="msg-page-info">{{.Page}} / {{.TotalPages}}</span>
{{if .HasNext}}
<a href="/messages?page={{.NextPage}}" class="msg-page-btn">下一页</a>
{{else}}
<span class="msg-page-btn disabled">下一页</span>
{{end}}
</div>
{{end}}
{{else}}
<div class="msg-empty">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="48" height="48" class="msg-empty-icon"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><line x1="4" y1="17" x2="20" y2="17"/><line x1="9" y1="11" x2="10" y2="11"/><line x1="14" y1="11" x2="15" y2="11"/></svg>
<p>暂无消息</p>
</div>
{{end}}
</div>
</main>
{{template "layout/footer.html" .}}
<script>
(function () {
'use strict';
// ---- 单条标记已读 ----
var cards = document.querySelectorAll('.msg-card.unread');
for (var i = 0; i < cards.length; i++) {
(function (card) {
card.addEventListener('click', function (e) {
var link = card.getAttribute('data-link');
// 如果是可跳转的通知post 审核),不阻止默认行为,只标记已读
// 标记已读通过 fetch 发送,不阻止链接跳转
var id = card.getAttribute('data-id');
if (!id) return;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/messages/' + id + '/read', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
card.classList.remove('unread');
var badge = card.querySelector('.msg-badge');
if (badge) badge.remove();
updateUnreadCounts();
}
};
xhr.send();
// 如果有链接,在标记已读后跳转(非 post 通知直接标记已读)
if (link) {
e.preventDefault();
var self = this;
setTimeout(function () {
window.location.href = link;
}, 200);
}
});
})(cards[i]);
}
// ---- 全部标为已读 ----
var markAllBtn = document.getElementById('markAllReadBtn');
if (markAllBtn) {
markAllBtn.addEventListener('click', function () {
markAllBtn.disabled = true;
markAllBtn.textContent = '处理中...';
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/messages/read-all', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
markAllBtn.disabled = false;
markAllBtn.textContent = '全部标为已读';
if (xhr.status === 200) {
// 本地清除所有 unread 样式
var allCards = document.querySelectorAll('.msg-card.unread');
for (var j = 0; j < allCards.length; j++) {
allCards[j].classList.remove('unread');
var b = allCards[j].querySelector('.msg-badge');
if (b) b.remove();
}
markAllBtn.remove();
updateUnreadCounts();
}
}
};
xhr.send();
});
}
// ---- 更新侧边栏和导航栏未读计数 ----
function updateUnreadCounts() {
// 更新侧边栏计数
var visibleUnread = document.querySelectorAll('.msg-card.unread').length;
var sidebarCount = document.querySelector('.msg-sidebar-count');
if (sidebarCount) {
if (visibleUnread > 0) {
sidebarCount.textContent = visibleUnread;
} else {
sidebarCount.remove();
}
} else if (visibleUnread > 0) {
var sidebarItem = document.querySelector('.msg-sidebar-item.active');
if (sidebarItem) {
var el = document.createElement('span');
el.className = 'msg-sidebar-count';
el.textContent = visibleUnread;
sidebarItem.appendChild(el);
}
}
// 更新导航栏铃铛计数
var navBadge = document.getElementById('msgBadge');
if (navBadge) {
fetch('/api/messages/unread')
.then(function (res) { return res.json(); })
.then(function (data) {
if (data && data.success && data.data) {
var count = data.data.unread || 0;
navBadge.textContent = count > 99 ? '99+' : count;
navBadge.style.display = count > 0 ? 'flex' : 'none';
}
})
.catch(function () {});
}
}
})();
</script>
<script src="/static/js/common.js"></script>
</body>
</html>