398 lines
15 KiB
JavaScript
398 lines
15 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, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
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();
|
|
root.NavigationUtil.open('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
|
|
};
|
|
});
|