Files
jiapu/public/js/notification-pages.js
T
2026-08-03 16:49:38 +08:00

400 lines
16 KiB
JavaScript

(function (root, factory) {
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.NotificationPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.NotificationPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
var notificationsById = Object.create(null);
var selectedNotificationId = '';
var writePending = false;
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function query(selector, node) {
return documentRef ? (node || documentRef).querySelector(selector) : null;
}
function queryAll(selector, node) {
return documentRef ? Array.prototype.slice.call((node || documentRef).querySelectorAll(selector)) : [];
}
function normalizeId(value) {
if (value === undefined || value === null || value === '') return '';
if (typeof value === 'number' && !Number.isSafeInteger(value)) return '';
return /^[1-9][0-9]*$/.test(String(value)) ? String(value) : '';
}
function normalizeNullableId(value) {
var id;
if (value === undefined || value === null || value === '') return null;
id = normalizeId(value);
return id || undefined;
}
function stringValue(value) {
return value === undefined || value === null ? '' : String(value);
}
function buildNotificationQuery(readStatus) {
return readStatus === '0' || readStatus === '1' ? { readStatus: readStatus } : {};
}
function normalizeNotification(item) {
var notificationId = normalizeId(item && item.notificationId);
var genealogyId = normalizeNullableId(item && item.genealogyId);
var senderUserId = normalizeNullableId(item && item.senderUserId);
var bizId = normalizeNullableId(item && item.bizId);
var readStatus = stringValue(item && item.readStatus);
var status = stringValue(item && item.status);
if (!notificationId || genealogyId === undefined || senderUserId === undefined || bizId === undefined) return null;
if ((readStatus !== '0' && readStatus !== '1') || (status !== '0' && status !== '1')) return null;
return {
notificationId: notificationId,
genealogyId: genealogyId,
genealogyNo: stringValue(item.genealogyNo),
genealogyName: stringValue(item.genealogyName),
senderUserId: senderUserId,
senderNickName: stringValue(item.senderNickName),
senderPhone: stringValue(item.senderPhone),
noticeType: stringValue(item.noticeType),
noticeTitle: stringValue(item.noticeTitle),
noticeContent: stringValue(item.noticeContent),
bizType: stringValue(item.bizType),
bizId: bizId,
bizSummary: stringValue(item.bizSummary),
publishTime: stringValue(item.publishTime),
readStatus: readStatus,
readTime: item.readTime === undefined || item.readTime === null ? null : String(item.readTime),
status: status,
remark: stringValue(item.remark)
};
}
function normalizeNotifications(data) {
var normalized;
if (!Array.isArray(data)) return [];
normalized = data.map(normalizeNotification);
return normalized.some(function (item) { return !item; }) ? [] : normalized;
}
function getUnreadCount(data) {
var count = Number(data);
return Number.isSafeInteger(count) && count >= 0 ? count : 0;
}
function matchesNotification(item, expectedNotificationId) {
var notification = normalizeNotification(item);
return Boolean(notification && notification.notificationId === normalizeId(expectedNotificationId));
}
function canMarkRead(notification) {
return Boolean(notification && notification.status === '0' && notification.readStatus === '0');
}
function escapeHtml(value) {
return stringValue(value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderNotificationRow(notification) {
var statusText = notification.readStatus === '0' ? '未读' : '已读';
var action = canMarkRead(notification)
? '<button class="pill" type="button" data-notification-read-id="' + escapeHtml(notification.notificationId) + '">标记已读</button>'
: '';
return '<article class="module-row notification-row' + (notification.readStatus === '0' ? ' is-unread' : '') + '">' +
'<div><h3>' + escapeHtml(notification.noticeTitle || '通知') + '</h3><p>' +
escapeHtml(notification.noticeContent || '暂无通知内容') + '</p><p>' +
escapeHtml([notification.publishTime, statusText].filter(Boolean).join(' · ')) +
'</p></div><div class="row-actions"><button class="pill" type="button" data-notification-detail-id="' +
escapeHtml(notification.notificationId) + '">查看详情</button>' + action + '</div></article>';
}
function detailValue(label, value) {
return '<p><span>' + escapeHtml(label) + '</span><b>' + escapeHtml(value || '未提供') + '</b></p>';
}
function renderNotificationDetail(notification) {
var action;
if (!notification) return '<div class="api-empty">请选择一条通知查看详情</div>';
action = canMarkRead(notification)
? '<div class="bottom-actions"><button class="btn ghost" type="button" data-notification-read-id="' +
escapeHtml(notification.notificationId) + '">标记为已读</button></div>'
: '';
return '<div class="form-like notification-detail">' +
detailValue('通知标题', notification.noticeTitle) +
detailValue('通知内容', notification.noticeContent) +
detailValue('通知类型', notification.noticeType) +
detailValue('家谱', notification.genealogyName) +
detailValue('发送人', notification.senderNickName) +
detailValue('业务类型', notification.bizType) +
detailValue('业务摘要', notification.bizSummary) +
detailValue('发布时间', notification.publishTime) +
detailValue('阅读状态', notification.readStatus === '0' ? '未读' : '已读') +
detailValue('阅读时间', notification.readTime) +
detailValue('备注', notification.remark) +
'</div>' + action;
}
function showMessage(message) {
if (root.layui && root.layui.layer) root.layui.layer.msg(message);
else if (root.alert) root.alert(message);
}
function shouldRedirectToLogin(api, error) {
var status = error && (error.status || error.code);
return !api || !api.getToken || !api.getToken() || Number(status) === 401;
}
function redirectUnauthorized(api, error) {
if (!shouldRedirectToLogin(api, error)) return false;
if (api && api.clearToken) api.clearToken();
if (!root.location) return true;
if (typeof root.location.replace === 'function') root.location.replace('login.html');
else root.location.href = 'login.html';
return true;
}
function isForbidden(error) {
return Number(error && (error.status || error.code)) === 403;
}
function setNotificationState(target, type, message) {
if (!target) return;
if (root.ProfileUI && root.ProfileUI.setApiState && type) {
root.ProfileUI.setApiState(target, type, message);
return;
}
target.textContent = message || '';
}
function updateStatus(type, message) {
var status = query('[data-notification-status]');
setNotificationState(status, type, message);
}
function setWritePending(value) {
writePending = Boolean(value);
queryAll('[data-notification-read-id], [data-notification-read-all], [data-notification-refresh], [data-notification-filter]').forEach(function (control) {
control.disabled = writePending;
});
}
function renderNotifications(data) {
var container = query('[data-notification-list]');
var normalized = Array.isArray(data) ? data.map(normalizeNotification) : [];
var invalidCount = normalized.filter(function (item) { return !item; }).length;
var notifications = normalized.filter(Boolean);
notificationsById = Object.create(null);
notifications.forEach(function (notification) {
notificationsById[notification.notificationId] = notification;
});
if (!container) return notifications;
if (!Array.isArray(data) || invalidCount) {
setNotificationState(container, 'error', '通知列表响应无效,请稍后重试。');
return [];
}
if (!notifications.length) {
setNotificationState(container, 'empty', '当前暂无通知。');
return [];
}
container.innerHTML = notifications.map(renderNotificationRow).join('');
if (writePending) setWritePending(true);
return notifications;
}
function renderDetail(notification) {
var container = query('[data-notification-detail]');
if (!container) return;
if (!notification) {
setNotificationState(container, 'empty', '请选择一条通知查看详情。');
return;
}
container.innerHTML = renderNotificationDetail(notification);
}
async function loadNotificationDetail(notificationId) {
var api = getApi();
var notification;
if (!normalizeId(notificationId) || redirectUnauthorized(api)) return null;
setNotificationState(query('[data-notification-detail]'), 'loading', '正在读取通知详情…');
try {
notification = normalizeNotification(await api.notificationDetail(notificationId));
if (!notification || !matchesNotification(notification, notificationId)) {
throw new Error('通知详情未返回同一条稳定 NotificationView');
}
selectedNotificationId = notification.notificationId;
notificationsById[notification.notificationId] = notification;
renderDetail(notification);
return notification;
} catch (error) {
if (redirectUnauthorized(api, error)) return null;
setNotificationState(query('[data-notification-detail]'), isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看该通知详情。' : '通知详情加载失败,请稍后重试。');
updateStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看该通知详情。' : (error.message || '通知详情加载失败,请稍后重试。'));
return null;
}
}
function currentFilter() {
var filter = query('[data-notification-filter]');
return filter ? filter.value : '';
}
async function loadNotifications() {
var api = getApi();
var countTarget = query('[data-notification-unread-count]');
var result;
var notifications;
var nextSelectedId;
if (redirectUnauthorized(api)) return;
if (!api.notifications || !api.notificationDetail || !api.unreadNotificationCount) {
setNotificationState(query('[data-notification-list]'), 'error', '消息服务初始化失败,请刷新后重试。');
updateStatus('error', '消息服务初始化失败,请刷新后重试。');
return;
}
setNotificationState(query('[data-notification-list]'), 'loading', '正在同步消息…');
updateStatus('loading', '正在同步消息…');
try {
result = await Promise.all([
api.notifications(buildNotificationQuery(currentFilter())),
api.unreadNotificationCount()
]);
notifications = renderNotifications(result[0]);
if (countTarget) countTarget.textContent = String(getUnreadCount(result[1]));
nextSelectedId = selectedNotificationId && notificationsById[selectedNotificationId]
? selectedNotificationId
: (notifications[0] && notifications[0].notificationId);
if (nextSelectedId) await loadNotificationDetail(nextSelectedId);
else renderDetail(null);
updateStatus('', '消息已同步');
} catch (error) {
if (redirectUnauthorized(api, error)) return;
renderNotifications(null);
setNotificationState(query('[data-notification-detail]'), isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看通知详情。' : '消息同步失败,请稍后重试。');
updateStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限查看通知列表。' : (error.message || '消息同步失败,请稍后重试。'));
}
}
async function markNotificationRead(notificationId) {
var api = getApi();
var notification = notificationsById[notificationId];
if (writePending || !normalizeId(notificationId) || !canMarkRead(notification) || redirectUnauthorized(api)) return;
setWritePending(true);
updateStatus('loading', '正在标记通知为已读…');
try {
await api.markNotificationRead(notificationId);
selectedNotificationId = notificationId;
showMessage('通知已标记为已读');
await loadNotifications();
} catch (error) {
if (redirectUnauthorized(api, error)) return;
updateStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限标记该通知为已读。' : (error.message || '标记通知已读失败,请稍后重试。'));
} finally {
setWritePending(false);
}
}
async function markAllRead() {
var api = getApi();
if (writePending || redirectUnauthorized(api)) return;
if (!api.markAllNotificationsRead) {
updateStatus('error', '消息服务初始化失败,请刷新后重试。');
return;
}
setWritePending(true);
updateStatus('loading', '正在将全部通知标记为已读…');
try {
await api.markAllNotificationsRead();
showMessage('已全部标记为已读');
await loadNotifications();
} catch (error) {
if (redirectUnauthorized(api, error)) return;
updateStatus(isForbidden(error) ? 'forbidden' : 'error', isForbidden(error) ? '暂无权限标记全部通知为已读。' : (error.message || '全部标记已读失败,请稍后重试。'));
} finally {
setWritePending(false);
}
}
function bindActions() {
if (!documentRef) return;
documentRef.addEventListener('click', function (event) {
var detailButton = event.target.closest('[data-notification-detail-id]');
var readButton = event.target.closest('[data-notification-read-id]');
if (detailButton) {
event.preventDefault();
loadNotificationDetail(detailButton.getAttribute('data-notification-detail-id'));
return;
}
if (readButton) {
event.preventDefault();
markNotificationRead(readButton.getAttribute('data-notification-read-id'));
}
});
if (query('[data-notification-read-all]')) {
query('[data-notification-read-all]').addEventListener('click', markAllRead);
}
if (query('[data-notification-refresh]')) {
query('[data-notification-refresh]').addEventListener('click', loadNotifications);
}
if (query('[data-notification-filter]')) {
query('[data-notification-filter]').addEventListener('change', function () {
selectedNotificationId = '';
loadNotifications();
});
}
}
function init() {
if (!documentRef || !query('[data-notification-list]')) return;
bindActions();
loadNotifications();
}
return {
buildNotificationQuery: buildNotificationQuery,
normalizeNotification: normalizeNotification,
normalizeNotifications: normalizeNotifications,
getUnreadCount: getUnreadCount,
matchesNotification: matchesNotification,
canMarkRead: canMarkRead,
renderNotificationRow: renderNotificationRow,
renderNotificationDetail: renderNotificationDetail,
shouldRedirectToLogin: shouldRedirectToLogin,
isForbidden: isForbidden,
loadNotifications: loadNotifications,
init: init
};
});