完成10%

This commit is contained in:
rain
2026-07-24 18:11:16 +08:00
parent 8716a68fdb
commit 8870136d1b
33 changed files with 3853 additions and 336 deletions
+160 -36
View File
@@ -20,6 +20,24 @@
return root.GenealogyApi && root.GenealogyApi.defaultClient;
}
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');
return true;
}
root.location.href = 'login.html';
return true;
}
function query(selector, rootNode) {
return documentRef ? (rootNode || documentRef).querySelector(selector) : null;
}
@@ -62,7 +80,7 @@
return text || undefined;
}
function toNumberOrUndefined(value) {
function toIntegerOrUndefined(value) {
var text = trimOrUndefined(value);
var number;
@@ -80,23 +98,21 @@
mediaOssIds: trimOrUndefined(source.mediaOssIds),
status: trimOrUndefined(source.status)
};
var sortOrder = toNumberOrUndefined(source.sortOrder);
var sortOrder = toIntegerOrUndefined(source.sortOrder);
if (sortOrder !== undefined) body.sortOrder = sortOrder;
return body;
}
function buildCommentBody(values) {
// FamilyFeedCommentBody 的 content 是唯一必填字段,其余字段按需携带
// FamilyFeedCommentBody 仅接受 commentContent 和可选的 parentCommentId
var source = values || {};
var body = {
content: String(source.content || '').trim()
commentContent: String(source.commentContent || '').trim()
};
var parentCommentId = toNumberOrUndefined(source.parentCommentId);
var replyUserId = toNumberOrUndefined(source.replyUserId);
var parentCommentId = trimOrUndefined(source.parentCommentId);
if (parentCommentId !== undefined) body.parentCommentId = parentCommentId;
if (replyUserId !== undefined) body.replyUserId = replyUserId;
return body;
}
@@ -136,12 +152,17 @@
function normalizeComment(item) {
var commentId = getCommentId(item);
var content = item && item.content;
var commentContent = item && item.commentContent;
var userDeleted;
if (!commentId || content === undefined || content === null) return null;
if (!commentId || commentContent === undefined) return null;
userDeleted = String(item && item.userDeleted || '') === '1';
return {
commentId: commentId,
content: String(content)
commentContent: commentContent === null ? '该评论已删除' : String(commentContent),
appUserNickName: trimOrUndefined(item && item.appUserNickName),
replyCount: item && item.replyCount,
userDeleted: userDeleted || commentContent === null
};
}
@@ -203,17 +224,46 @@
}).join('');
}
function hasReplies(comment) {
var count = comment && comment.replyCount;
return count !== undefined && count !== null && String(count) !== '' && String(count) !== '0';
}
function renderCommentRow(feedId, comment) {
var author = comment.appUserNickName ? '<strong>' + escapeHtml(comment.appUserNickName) + '</strong> ' : '';
var actions = '';
if (!comment.userDeleted) {
actions += '<button class="pill" type="button" data-feed-comment-reply="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">回复</button>';
actions += '<button class="pill" type="button" data-feed-comment-delete="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">删除评论</button>';
}
if (hasReplies(comment)) {
actions += '<button class="pill" type="button" data-feed-comment-replies="' + escapeHtml(comment.commentId) + '" data-feed-id="' + escapeHtml(feedId) + '">查看回复</button>';
}
return '<div class="module-row" data-feed-comment-node="' + escapeHtml(comment.commentId) + '"><p>' + author + escapeHtml(comment.commentContent) + '</p>' + actions + '</div>';
}
function normalizeComments(data) {
var comments = normalizeList(data).map(normalizeComment);
return {
invalidCount: comments.filter(function (item) { return !item; }).length,
comments: comments.filter(Boolean)
};
}
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 result = normalizeComments(data);
var list = result.comments;
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>');
if (result.invalidCount) {
row.insertAdjacentHTML('afterend', '<div class="api-empty" data-feed-comment-list="' + escapeHtml(feedId) + '">评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。</div>');
return;
}
if (!list.length) {
@@ -222,7 +272,28 @@
}
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>';
return renderCommentRow(feedId, comment);
}).join('') + '</div>');
}
function renderCommentReplies(feedId, parentCommentId, data) {
var row = query('[data-feed-comment-node="' + parentCommentId + '"]');
var result = normalizeComments(data);
var previous = row && row.nextElementSibling;
if (!row) return;
if (previous && previous.getAttribute('data-feed-reply-list') === parentCommentId) previous.remove();
if (result.invalidCount) {
row.insertAdjacentHTML('afterend', '<div class="api-empty" data-feed-reply-list="' + escapeHtml(parentCommentId) + '">回复响应缺少 commentId 或 commentContent,请联系后端补充 DTO。</div>');
return;
}
if (!result.comments.length) {
row.insertAdjacentHTML('afterend', '<div class="api-empty" data-feed-reply-list="' + escapeHtml(parentCommentId) + '">暂无直接回复</div>');
return;
}
row.insertAdjacentHTML('afterend', '<div class="module-list" data-feed-reply-list="' + escapeHtml(parentCommentId) + '">' + result.comments.map(function (comment) {
return renderCommentRow(feedId, comment);
}).join('') + '</div>');
}
@@ -264,39 +335,47 @@
async function loadFeeds() {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
if (!api || !genealogyId) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncGenealogyLinks(genealogyId);
try {
renderFeeds(await api.feedsPage(genealogyId, { pageNum: 1, pageSize: 20 }), genealogyId);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '家族动态加载失败');
}
}
async function loadFeedForEdit() {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
var feedId = getCurrentFeedId();
if (!api || !genealogyId) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncGenealogyLinks(genealogyId);
if (!feedId) return;
try {
fillFeedForm(await api.feedDetail(genealogyId, feedId));
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '动态详情加载失败');
}
}
async function submitFeed(form) {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
var feedId = getCurrentFeedId();
var body;
if (!api || !genealogyId) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
// 富文本编辑器在提交前同步回 textarea,保证发送的是用户当前输入。
if (root.KindEditor && root.KindEditor.sync) root.KindEditor.sync('#feedContent');
body = buildFeedBody(getFormValues(form));
@@ -313,15 +392,18 @@
showMessage('动态已保存');
root.location.href = getListUrl(genealogyId);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '动态保存失败');
}
}
async function likeFeed(feedId, shouldLike) {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
if (!api || !genealogyId || !feedId) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId) return;
try {
if (shouldLike) {
await api.likeFeed(genealogyId, feedId);
@@ -330,64 +412,92 @@
}
await loadFeeds();
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || (shouldLike ? '点赞失败' : '取消点赞失败'));
}
}
async function deleteFeed(feedId) {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
if (!api || !genealogyId || !feedId) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId) return;
if (root.confirm && !root.confirm('确认删除这条动态吗?')) return;
try {
await api.deleteFeed(genealogyId, feedId);
await loadFeeds();
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '删除动态失败');
}
}
async function showComments(feedId) {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
if (!api || !genealogyId || !feedId) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId) return;
try {
renderComments(feedId, await api.feedComments(genealogyId, feedId));
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '评论加载失败');
}
}
async function createComment(feedId) {
async function showCommentReplies(feedId, commentId) {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId || !commentId) return;
try {
renderCommentReplies(feedId, commentId, await api.feedCommentReplies(genealogyId, feedId, commentId));
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '回复加载失败');
}
}
async function createComment(feedId, parentCommentId) {
var api = getApi();
var genealogyId;
var content;
var body;
if (!api || !genealogyId || !feedId) return;
content = root.prompt ? root.prompt('请输入评论内容', '') : '';
body = buildCommentBody({ content: content });
if (!body.content) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId) return;
content = root.prompt ? root.prompt(parentCommentId ? '请输入回复内容' : '请输入评论内容', '') : '';
body = buildCommentBody({ commentContent: content, parentCommentId: parentCommentId });
if (!body.commentContent) return;
try {
await api.createFeedComment(genealogyId, feedId, body);
await showComments(feedId);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '评论发布失败');
}
}
async function deleteComment(feedId, commentId) {
var api = getApi();
var genealogyId = requireGenealogyId();
var genealogyId;
if (!api || !genealogyId || !feedId || !commentId) return;
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId || !commentId) return;
if (root.confirm && !root.confirm('确认删除这条评论吗?')) return;
try {
await api.deleteFeedComment(genealogyId, feedId, commentId);
await showComments(feedId);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '删除评论失败');
}
}
@@ -424,6 +534,18 @@
feedId = target.closest('[data-feed-comment]').getAttribute('data-feed-comment');
createComment(feedId);
}
if (target.closest('[data-feed-comment-reply]')) {
event.preventDefault();
commentId = target.closest('[data-feed-comment-reply]').getAttribute('data-feed-comment-reply');
feedId = target.closest('[data-feed-comment-reply]').getAttribute('data-feed-id');
createComment(feedId, commentId);
}
if (target.closest('[data-feed-comment-replies]')) {
event.preventDefault();
commentId = target.closest('[data-feed-comment-replies]').getAttribute('data-feed-comment-replies');
feedId = target.closest('[data-feed-comment-replies]').getAttribute('data-feed-id');
showCommentReplies(feedId, commentId);
}
if (target.closest('[data-feed-delete]')) {
event.preventDefault();
deleteFeed(target.closest('[data-feed-delete]').getAttribute('data-feed-delete'));
@@ -451,7 +573,9 @@
buildCommentBody: buildCommentBody,
normalizeFeed: normalizeFeed,
normalizeComment: normalizeComment,
normalizeComments: normalizeComments,
normalizeList: normalizeList,
shouldRedirectToLogin: shouldRedirectToLogin,
init: init
};
});