Files
2026-07-11 21:36:26 +08:00

458 lines
15 KiB
JavaScript

(function (root, factory) {
// 家族圈动态模块同时支持浏览器页面和 Node 单元测试。
if (typeof module === 'object' && module.exports) {
module.exports = factory(root);
return;
}
root.FeedPages = factory(root);
if (root.document) {
root.document.addEventListener('DOMContentLoaded', function () {
root.FeedPages.init();
});
}
})(typeof globalThis !== 'undefined' ? globalThis : window, function (root) {
'use strict';
var documentRef = root.document;
function getApi() {
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
function query(selector, rootNode) {
return documentRef ? (rootNode || documentRef).querySelector(selector) : null;
}
function queryAll(selector, rootNode) {
return documentRef ? Array.prototype.slice.call((rootNode || documentRef).querySelectorAll(selector)) : [];
}
function showMessage(message) {
if (root.layui && root.layui.layer) {
root.layui.layer.msg(message);
return;
}
if (root.alert) root.alert(message);
}
function getQueryParam(search, name) {
var params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
return params.get(name) || '';
}
function getCurrentGenealogyId(search) {
var page = query('[data-feed-page], [data-feed-edit-page]');
var source = search === undefined && root.location ? root.location.search : search;
return getQueryParam(source, 'genealogyId') || (page && page.getAttribute('data-genealogy-id')) || '';
}
function getCurrentFeedId(search) {
var source = search === undefined && root.location ? root.location.search : search;
return getQueryParam(source, 'feedId');
}
function trimOrUndefined(value) {
var text = String(value === undefined || value === null ? '' : value).trim();
return text || undefined;
}
function toNumberOrUndefined(value) {
var text = trimOrUndefined(value);
var number;
if (!text) return undefined;
number = Number(text);
return Number.isFinite(number) ? number : undefined;
}
function buildFeedBody(values) {
// FamilyFeedBody 只提交最新接口文档定义的字段。
var source = values || {};
var body = {
feedType: trimOrUndefined(source.feedType) || 'text',
feedContent: String(source.feedContent || '').trim(),
mediaOssIds: trimOrUndefined(source.mediaOssIds),
status: trimOrUndefined(source.status)
};
var sortOrder = toNumberOrUndefined(source.sortOrder);
if (sortOrder !== undefined) body.sortOrder = sortOrder;
return body;
}
function buildCommentBody(values) {
// FamilyFeedCommentBody 的 content 是唯一必填字段,其余字段按需携带。
var source = values || {};
var body = {
content: String(source.content || '').trim()
};
var parentCommentId = toNumberOrUndefined(source.parentCommentId);
var replyUserId = toNumberOrUndefined(source.replyUserId);
if (parentCommentId !== undefined) body.parentCommentId = parentCommentId;
if (replyUserId !== undefined) body.replyUserId = replyUserId;
return body;
}
function getFeedId(item) {
var value = item && item.feedId;
var text;
if (value === undefined || value === null || value === '') return '';
text = String(value);
return /^\d+$/.test(text) ? text : '';
}
function getCommentId(item) {
var value = item && item.commentId;
var text;
if (value === undefined || value === null || value === '') return '';
text = String(value);
return /^\d+$/.test(text) ? text : '';
}
function normalizeFeed(item) {
var feedId = getFeedId(item);
var feedContent = item && item.feedContent;
var feed;
if (!feedId || feedContent === undefined || feedContent === null) return null;
feed = {
feedId: feedId,
feedContent: String(feedContent)
};
if (trimOrUndefined(item.mediaOssIds)) feed.mediaOssIds = trimOrUndefined(item.mediaOssIds);
if (trimOrUndefined(item.feedType)) feed.feedType = trimOrUndefined(item.feedType);
if (trimOrUndefined(item.status)) feed.status = trimOrUndefined(item.status);
return feed;
}
function normalizeComment(item) {
var commentId = getCommentId(item);
var content = item && item.content;
if (!commentId || content === undefined || content === null) return null;
return {
commentId: commentId,
content: String(content)
};
}
function normalizeList(data) {
// AxiosRequestUtil 已解包 ListResult 与 PageResult,这里只接收对应的 data 或 rows。
if (Array.isArray(data)) return data;
if (data && Array.isArray(data.rows)) return data.rows;
return [];
}
function escapeHtml(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function getListUrl(genealogyId, feedId) {
var params = new URLSearchParams();
params.set('genealogyId', genealogyId);
if (feedId) params.set('feedId', feedId);
return 'profile-feed' + (feedId ? '-edit' : '') + '.html?' + params.toString();
}
function renderFeedRow(feed, genealogyId) {
return '<article class="module-row feed-row" data-feed-id="' + escapeHtml(feed.feedId) + '">' +
'<div><h3>家族动态</h3><p>' + escapeHtml(feed.feedContent) + '</p></div>' +
'<div class="row-actions">' +
'<button class="pill" type="button" data-feed-like="' + escapeHtml(feed.feedId) + '">点赞</button>' +
'<button class="pill" type="button" data-feed-unlike="' + escapeHtml(feed.feedId) + '">取消点赞</button>' +
'<button class="pill" type="button" data-feed-comments="' + escapeHtml(feed.feedId) + '">查看评论</button>' +
'<button class="pill" type="button" data-feed-comment="' + escapeHtml(feed.feedId) + '">发表评论</button>' +
'<a class="pill" href="' + escapeHtml(getListUrl(genealogyId, feed.feedId)) + '">编辑</a>' +
'<button class="pill" type="button" data-feed-delete="' + escapeHtml(feed.feedId) + '">删除</button>' +
'</div></article>';
}
function renderFeeds(data, genealogyId) {
var container = query('[data-feed-list]');
var items = normalizeList(data).map(normalizeFeed);
var invalidCount = items.filter(function (item) { return !item; }).length;
var feeds = items.filter(Boolean);
if (!container) return;
if (invalidCount) {
container.innerHTML = '<div class="api-empty">动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。</div>';
return;
}
if (!feeds.length) {
container.innerHTML = '<div class="api-empty">暂无家族动态</div>';
return;
}
container.innerHTML = feeds.map(function (feed) {
return renderFeedRow(feed, genealogyId);
}).join('');
}
function renderComments(feedId, data) {
var row = query('[data-feed-id="' + feedId + '"]');
var comments = normalizeList(data).map(normalizeComment);
var invalidCount = comments.filter(function (item) { return !item; }).length;
var list = comments.filter(Boolean);
var previous = row && row.nextElementSibling;
if (!row) return;
if (previous && previous.getAttribute('data-feed-comment-list') === feedId) previous.remove();
if (invalidCount) {
row.insertAdjacentHTML('afterend', '<div class="api-empty" data-feed-comment-list="' + escapeHtml(feedId) + '">评论响应缺少 commentId 或 content,请联系后端补充 DTO。</div>');
return;
}
if (!list.length) {
row.insertAdjacentHTML('afterend', '<div class="api-empty" data-feed-comment-list="' + escapeHtml(feedId) + '">暂无评论</div>');
return;
}
row.insertAdjacentHTML('afterend', '<div class="module-list" data-feed-comment-list="' + escapeHtml(feedId) + '">' + list.map(function (comment) {
return '<div class="module-row"><p>' + escapeHtml(comment.content) + '</p><button class="pill" type="button" data-feed-comment-delete="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">删除评论</button></div>';
}).join('') + '</div>');
}
function getFormValues(form) {
var values = {};
queryAll('[name]', form).forEach(function (field) {
values[field.name] = field.value;
});
return values;
}
function fillFeedForm(data) {
var feed = normalizeFeed(data);
if (!feed) throw new Error('动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。');
queryAll('[data-feed-form] [name]').forEach(function (field) {
if (feed[field.name] !== undefined && feed[field.name] !== null) field.value = feed[field.name];
});
}
function syncGenealogyLinks(genealogyId) {
queryAll('[data-feed-create-link], [data-feed-list-link]').forEach(function (link) {
link.href = getListUrl(genealogyId);
});
}
function requireGenealogyId() {
var genealogyId = getCurrentGenealogyId();
var container;
if (!genealogyId) {
container = query('[data-feed-list]');
if (container) container.innerHTML = '<div class="api-empty">请从具体家谱进入家族圈动态</div>';
showMessage('请从具体家谱进入家族圈动态');
}
return genealogyId;
}
async function loadFeeds() {
var api = getApi();
var genealogyId = requireGenealogyId();
if (!api || !genealogyId) return;
syncGenealogyLinks(genealogyId);
try {
renderFeeds(await api.feedsPage(genealogyId, { pageNum: 1, pageSize: 20 }), genealogyId);
} catch (error) {
showMessage(error.message || '家族动态加载失败');
}
}
async function loadFeedForEdit() {
var api = getApi();
var genealogyId = requireGenealogyId();
var feedId = getCurrentFeedId();
if (!api || !genealogyId) return;
syncGenealogyLinks(genealogyId);
if (!feedId) return;
try {
fillFeedForm(await api.feedDetail(genealogyId, feedId));
} catch (error) {
showMessage(error.message || '动态详情加载失败');
}
}
async function submitFeed(form) {
var api = getApi();
var genealogyId = requireGenealogyId();
var feedId = getCurrentFeedId();
var body;
if (!api || !genealogyId) return;
// 富文本编辑器在提交前同步回 textarea,保证发送的是用户当前输入。
if (root.KindEditor && root.KindEditor.sync) root.KindEditor.sync('#feedContent');
body = buildFeedBody(getFormValues(form));
if (!body.feedContent) {
showMessage('请填写动态内容');
return;
}
try {
if (feedId) {
await api.updateFeed(genealogyId, feedId, body);
} else {
await api.createFeed(genealogyId, body);
}
showMessage('动态已保存');
root.location.href = getListUrl(genealogyId);
} catch (error) {
showMessage(error.message || '动态保存失败');
}
}
async function likeFeed(feedId, shouldLike) {
var api = getApi();
var genealogyId = requireGenealogyId();
if (!api || !genealogyId || !feedId) return;
try {
if (shouldLike) {
await api.likeFeed(genealogyId, feedId);
} else {
await api.unlikeFeed(genealogyId, feedId);
}
await loadFeeds();
} catch (error) {
showMessage(error.message || (shouldLike ? '点赞失败' : '取消点赞失败'));
}
}
async function deleteFeed(feedId) {
var api = getApi();
var genealogyId = requireGenealogyId();
if (!api || !genealogyId || !feedId) return;
if (root.confirm && !root.confirm('确认删除这条动态吗?')) return;
try {
await api.deleteFeed(genealogyId, feedId);
await loadFeeds();
} catch (error) {
showMessage(error.message || '删除动态失败');
}
}
async function showComments(feedId) {
var api = getApi();
var genealogyId = requireGenealogyId();
if (!api || !genealogyId || !feedId) return;
try {
renderComments(feedId, await api.feedComments(genealogyId, feedId));
} catch (error) {
showMessage(error.message || '评论加载失败');
}
}
async function createComment(feedId) {
var api = getApi();
var genealogyId = requireGenealogyId();
var content;
var body;
if (!api || !genealogyId || !feedId) return;
content = root.prompt ? root.prompt('请输入评论内容', '') : '';
body = buildCommentBody({ content: content });
if (!body.content) return;
try {
await api.createFeedComment(genealogyId, feedId, body);
await showComments(feedId);
} catch (error) {
showMessage(error.message || '评论发布失败');
}
}
async function deleteComment(feedId, commentId) {
var api = getApi();
var genealogyId = requireGenealogyId();
if (!api || !genealogyId || !feedId || !commentId) return;
if (root.confirm && !root.confirm('确认删除这条评论吗?')) return;
try {
await api.deleteFeedComment(genealogyId, feedId, commentId);
await showComments(feedId);
} catch (error) {
showMessage(error.message || '删除评论失败');
}
}
function bindActions() {
if (!documentRef) return;
documentRef.addEventListener('submit', function (event) {
var form = event.target.closest('[data-feed-form]');
if (!form) return;
event.preventDefault();
submitFeed(form);
});
documentRef.addEventListener('click', function (event) {
var target = event.target;
var feedId;
var commentId;
if (target.closest('[data-feed-like]')) {
event.preventDefault();
likeFeed(target.closest('[data-feed-like]').getAttribute('data-feed-like'), true);
}
if (target.closest('[data-feed-unlike]')) {
event.preventDefault();
likeFeed(target.closest('[data-feed-unlike]').getAttribute('data-feed-unlike'), false);
}
if (target.closest('[data-feed-comments]')) {
event.preventDefault();
feedId = target.closest('[data-feed-comments]').getAttribute('data-feed-comments');
showComments(feedId);
}
if (target.closest('[data-feed-comment]')) {
event.preventDefault();
feedId = target.closest('[data-feed-comment]').getAttribute('data-feed-comment');
createComment(feedId);
}
if (target.closest('[data-feed-delete]')) {
event.preventDefault();
deleteFeed(target.closest('[data-feed-delete]').getAttribute('data-feed-delete'));
}
if (target.closest('[data-feed-comment-delete]')) {
event.preventDefault();
commentId = target.closest('[data-feed-comment-delete]').getAttribute('data-feed-comment-delete');
feedId = target.closest('[data-feed-comment-delete]').getAttribute('data-feed-id');
deleteComment(feedId, commentId);
}
});
}
function init() {
if (!documentRef) return;
bindActions();
if (query('[data-feed-page]')) loadFeeds();
if (query('[data-feed-edit-page]')) loadFeedForEdit();
}
return {
getCurrentGenealogyId: getCurrentGenealogyId,
getCurrentFeedId: getCurrentFeedId,
buildFeedBody: buildFeedBody,
buildCommentBody: buildCommentBody,
normalizeFeed: normalizeFeed,
normalizeComment: normalizeComment,
normalizeList: normalizeList,
init: init
};
});