等待后端更新接口

This commit is contained in:
2026-08-03 16:49:26 +08:00
parent ac5823547a
commit 5b79893650
131 changed files with 5324 additions and 1392 deletions
+307 -59
View File
@@ -65,6 +65,125 @@
if (root.alert) root.alert(message);
}
function getApiStateType(error) {
return Number(error && (error.status || error.code)) === 403 ? 'forbidden' : 'error';
}
function setFeedFormStatus(message, type) {
var target = query('[data-feed-form-status]');
if (!target) return;
if (!message) {
target.innerHTML = '';
return;
}
if (root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(target, type || 'error', message);
return;
}
target.textContent = message;
}
function setFeedContainerState(selector, type, message) {
var target = query(selector);
if (!target) return;
if (root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(target, type, message);
return;
}
target.innerHTML = '<div class="api-state api-state--' + type + '" data-api-state="' + type + '">' +
escapeHtml(message) + '</div>';
}
function setFeedFormEnabled(enabled) {
var form = query('[data-feed-form]');
if (form) {
queryAll('input, textarea, select, button', form).forEach(function (field) {
field.disabled = !enabled;
});
}
queryAll('[data-feed-editor-action]').forEach(function (action) {
action.hidden = !enabled;
action.disabled = !enabled;
});
}
function setFeedSubmitPending(pending) {
var selectors = '[data-feed-form] [type="submit"], [type="submit"][form="feed-edit-form"]';
queryAll(selectors).forEach(function (button) {
if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent;
button.disabled = Boolean(pending);
button.textContent = pending ? '正在保存…' : button.dataset.defaultLabel;
button.setAttribute('aria-busy', pending ? 'true' : 'false');
});
}
function setFeedCommentFormStatus(message, type) {
var target = query('[data-feed-comment-form-status]');
if (!target) return;
if (!message) {
target.innerHTML = '';
return;
}
if (root.ProfileUI && root.ProfileUI.setApiState) {
root.ProfileUI.setApiState(target, type || 'error', message);
return;
}
target.textContent = message;
}
function setFeedCommentFormEnabled(enabled) {
var form = query('[data-feed-comment-form]');
if (!form) return;
queryAll('input, textarea, button', form).forEach(function (field) {
field.disabled = !enabled;
});
}
function setFeedCommentSubmitPending(pending) {
queryAll('[data-feed-comment-form] [type="submit"]').forEach(function (button) {
if (!button.dataset.defaultLabel) button.dataset.defaultLabel = button.textContent;
button.textContent = pending ? '正在发布…' : button.dataset.defaultLabel;
button.setAttribute('aria-busy', pending ? 'true' : 'false');
});
}
function setFeedCommentTarget(parentCommentId) {
var form = query('[data-feed-comment-form]');
var target = query('[data-feed-comment-form-target]');
var parent = parentCommentId ? toId(parentCommentId) : '';
if (!form) return;
if (query('[name="parentCommentId"]', form)) query('[name="parentCommentId"]', form).value = parent;
if (target) target.textContent = parent ? '正在回复这条评论' : '发表公开评论';
}
function openFeedCommentForm(feedId, parentCommentId) {
var genealogyId = getCurrentGenealogyId();
var form;
var submitButton;
if (!feedId || !genealogyId) return;
if (!query('[data-feed-detail-page]')) {
if (root.location) root.location.href = buildFeedUrl('detail', genealogyId, feedId);
return;
}
if (getCurrentFeedId() !== toId(feedId)) return;
form = query('[data-feed-comment-form]');
submitButton = form && query('[type="submit"]', form);
if (!form || !submitButton || submitButton.disabled) return;
setFeedCommentTarget(parentCommentId);
setFeedCommentFormStatus('');
if (query('[name="commentContent"]', form) && query('[name="commentContent"]', form).focus) {
query('[name="commentContent"]', form).focus();
}
}
function getQueryParam(search, name) {
var params = new URLSearchParams(String(search || '').replace(/^\?/, ''));
@@ -280,6 +399,39 @@
.replace(/'/g, '&#39;');
}
function renderFeedState(type, message) {
if (root.ProfileUI && root.ProfileUI.renderApiState) {
return root.ProfileUI.renderApiState(type, message);
}
return '<div class="api-state api-state--' + escapeHtml(type) + '" data-api-state="' +
escapeHtml(type) + '">' + escapeHtml(message) + '</div>';
}
function replaceInlineCommentList(feedId, content) {
var row = query('[data-feed-id="' + feedId + '"]');
var previous = row && row.nextElementSibling;
if (!row) return;
if (previous && previous.getAttribute('data-feed-comment-list') === feedId) previous.remove();
row.insertAdjacentHTML('afterend', '<div data-feed-comment-list="' + escapeHtml(feedId) + '">' + content + '</div>');
}
function replaceReplyList(parentCommentId, content) {
var row = query('[data-feed-comment-node="' + parentCommentId + '"]');
var previous = row && row.nextElementSibling;
if (!row) return;
if (previous && previous.getAttribute('data-feed-reply-list') === parentCommentId) previous.remove();
row.insertAdjacentHTML('afterend', content);
}
function setReplyState(parentCommentId, type, message) {
replaceReplyList(
parentCommentId,
'<div data-feed-reply-list="' + escapeHtml(parentCommentId) + '">' + renderFeedState(type, message) + '</div>'
);
}
function getListUrl(genealogyId, feedId) {
return buildFeedUrl(feedId ? 'edit' : 'list', genealogyId, feedId);
}
@@ -323,11 +475,11 @@
if (!container) return;
if (invalidCount) {
container.innerHTML = '<div class="api-empty">动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。</div>';
setFeedContainerState('[data-feed-list]', 'error', '动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。');
return;
}
if (!feeds.length) {
container.innerHTML = '<div class="api-empty">暂无家族动态</div>';
setFeedContainerState('[data-feed-list]', 'empty', '暂无家族动态');
return;
}
@@ -393,21 +545,19 @@
var row = query('[data-feed-id="' + feedId + '"]');
var result = normalizeComments(data);
var list = result.comments;
var previous = row && row.nextElementSibling;
var content;
if (!row && !detailContainer) return;
if (previous && previous.getAttribute('data-feed-comment-list') === feedId) previous.remove();
if (result.invalidCount) {
content = '<div class="api-empty">评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。</div>';
if (detailContainer) detailContainer.innerHTML = content;
else row.insertAdjacentHTML('afterend', '<div data-feed-comment-list="' + escapeHtml(feedId) + '">' + content + '</div>');
content = renderFeedState('error', '评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。');
if (detailContainer) setFeedContainerState('[data-feed-detail-comments]', 'error', '评论响应缺少 commentId 或 commentContent,请联系后端补充 DTO。');
else replaceInlineCommentList(feedId, content);
return;
}
if (!list.length) {
content = '<div class="api-empty">暂无评论</div>';
if (detailContainer) detailContainer.innerHTML = content;
else row.insertAdjacentHTML('afterend', '<div data-feed-comment-list="' + escapeHtml(feedId) + '">' + content + '</div>');
content = renderFeedState('empty', '暂无评论');
if (detailContainer) setFeedContainerState('[data-feed-detail-comments]', 'empty', '暂无评论');
else replaceInlineCommentList(feedId, content);
return;
}
@@ -415,26 +565,24 @@
return renderCommentRow(feedId, comment);
}).join('') + '</div>' + renderPageControls('comments', feedId, '', currentCommentPages[feedId] || 1, getPageTotal(data));
if (detailContainer) detailContainer.innerHTML = content;
else row.insertAdjacentHTML('afterend', '<div data-feed-comment-list="' + escapeHtml(feedId) + '">' + content + '</div>');
else replaceInlineCommentList(feedId, content);
}
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>');
setReplyState(parentCommentId, 'error', '回复响应缺少 commentId 或 commentContent,请联系后端补充 DTO。');
return;
}
if (!result.comments.length) {
row.insertAdjacentHTML('afterend', '<div class="api-empty" data-feed-reply-list="' + escapeHtml(parentCommentId) + '">暂无直接回复</div>');
setReplyState(parentCommentId, 'empty', '暂无直接回复');
return;
}
row.insertAdjacentHTML('afterend', '<div class="module-list" data-feed-reply-list="' + escapeHtml(parentCommentId) + '">' + result.comments.map(function (comment) {
replaceReplyList(parentCommentId, '<div class="module-list" data-feed-reply-list="' + escapeHtml(parentCommentId) + '">' + result.comments.map(function (comment) {
return renderCommentRow(feedId, comment);
}).join('') + renderPageControls('replies', feedId, parentCommentId, currentReplyPages[parentCommentId] || 1, getPageTotal(data)) + '</div>');
}
@@ -471,11 +619,21 @@
});
}
function canEditFeeds(genealogy) {
return Boolean(genealogy && (genealogy.canEditContent === true || genealogy.canManage === true));
}
function setFeedContentEditingEnabled(enabled) {
queryAll('[data-feed-create-link]').forEach(function (link) {
link.hidden = !enabled;
});
}
function normalizeViewerContext(genealogy, profile) {
return {
userId: toId(profile && profile.userId),
canEditContent: genealogy && genealogy.canEditContent === true,
canManage: genealogy && genealogy.canManage === true
canEditContent: canEditFeeds(genealogy),
canManage: Boolean(genealogy && genealogy.canManage === true)
};
}
@@ -494,6 +652,7 @@
profile = null;
}
viewerContext = normalizeViewerContext(genealogy, profile);
setFeedContentEditingEnabled(viewerContext.canEditContent);
return viewerContext;
}
@@ -544,7 +703,11 @@
if (!genealogyId) {
container = query('[data-feed-list]');
if (container) container.innerHTML = '<div class="api-empty">请从具体家谱进入家族圈动态</div>';
if (container) setFeedContainerState('[data-feed-list]', 'forbidden', '请从具体家谱进入家族圈动态。');
setFeedFormEnabled(false);
setFeedFormStatus('请从具体家谱进入动态发布。', 'forbidden');
setFeedCommentFormEnabled(false);
setFeedCommentFormStatus('请从具体家谱进入动态详情后发表评论。', 'forbidden');
showMessage('请从具体家谱进入家族圈动态');
}
return genealogyId;
@@ -560,6 +723,7 @@
if (!genealogyId) return;
syncGenealogyLinks(genealogyId);
currentFeedPage = buildPageQuery(pageNum || getQueryParam(root.location && root.location.search, 'pageNum')).pageNum;
setFeedContainerState('[data-feed-list]', 'loading', '正在加载家族动态…');
try {
await loadViewerContext(api, genealogyId);
data = await api.feedsPage(genealogyId, buildPageQuery(currentFeedPage));
@@ -567,7 +731,11 @@
updateListPager(data);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (query('[data-feed-list]')) query('[data-feed-list]').innerHTML = '<div class="api-empty">家族动态加载失败,请稍后重试。</div>';
setFeedContainerState(
'[data-feed-list]',
getApiStateType(error),
getApiStateType(error) === 'forbidden' ? '当前账号无权查看该家谱的动态。' : '家族动态加载失败,请稍后重试。'
);
showMessage(error.message || '家族动态加载失败');
}
}
@@ -581,19 +749,33 @@
genealogyId = requireGenealogyId();
if (!genealogyId) return;
syncGenealogyLinks(genealogyId);
setFeedFormEnabled(false);
setFeedFormStatus('正在读取家谱权限…', 'loading');
try {
await loadViewerContext(api, genealogyId);
if (!viewerContext.canEditContent) {
setFeedFormStatus('当前账号无权发布或编辑该家谱的动态。', 'forbidden');
return;
}
setFeedFormEnabled(true);
if (query('[data-feed-advanced]')) {
query('[data-feed-advanced]').hidden = !viewerContext.canManage;
queryAll('[name]', query('[data-feed-advanced]')).forEach(function (field) {
field.disabled = !viewerContext.canManage;
});
}
if (!feedId) return;
if (!feedId) {
setFeedFormStatus('');
return;
}
fillFeedForm(await api.feedDetail(genealogyId, feedId));
setFeedFormStatus('');
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '动态详情加载失败');
setFeedFormStatus(
getApiStateType(error) === 'forbidden' ? '当前账号无权维护该家谱的动态。' : (error.message || '动态详情加载失败'),
getApiStateType(error)
);
}
}
@@ -604,21 +786,35 @@
var feed;
if (redirectUnauthorized(api)) return;
setFeedCommentFormEnabled(false);
setFeedCommentFormStatus('正在读取动态评论权限…', 'loading');
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId) {
if (query('[data-feed-detail]')) query('[data-feed-detail]').innerHTML = '<div class="api-empty">动态链接缺少有效编号</div>';
setFeedContainerState('[data-feed-detail]', 'error', '动态链接缺少有效编号。');
setFeedCommentFormStatus('动态链接缺少有效编号。', 'error');
return;
}
syncGenealogyLinks(genealogyId);
setFeedContainerState('[data-feed-detail]', 'loading', '正在加载动态详情…');
try {
await loadViewerContext(api, genealogyId);
feed = normalizeFeed(await api.feedDetail(genealogyId, feedId));
if (!feed) throw new Error('动态响应缺少 feedId 或 feedContent,请联系后端补充 DTO。');
renderFeedDetail(feed, genealogyId);
setFeedCommentFormEnabled(true);
setFeedCommentFormStatus('');
await showComments(feedId, currentCommentPages[feedId] || 1);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (query('[data-feed-detail]')) query('[data-feed-detail]').innerHTML = '<div class="api-empty">动态详情加载失败,请返回列表重试。</div>';
setFeedContainerState(
'[data-feed-detail]',
getApiStateType(error),
getApiStateType(error) === 'forbidden' ? '当前账号无权查看该动态。' : '动态详情加载失败,请返回列表重试。'
);
setFeedCommentFormStatus(
getApiStateType(error) === 'forbidden' ? '当前账号无权评论该动态。' : '动态详情加载失败,暂不能发表评论。',
getApiStateType(error)
);
showMessage(error.message || '动态详情加载失败');
}
}
@@ -632,24 +828,28 @@
if (redirectUnauthorized(api)) return;
genealogyId = requireGenealogyId();
if (!genealogyId) return;
if (!viewerContext.canEditContent) {
setFeedFormStatus('当前账号无权发布或编辑该家谱的动态。', 'forbidden');
return;
}
// 富文本编辑器在提交前同步回 textarea,保证发送的是用户当前输入。
if (root.AppRichEditor && root.AppRichEditor.syncAll) root.AppRichEditor.syncAll();
try {
body = buildFeedBody(getFormValues(form));
} catch (error) {
showMessage(error.message || '动态表单校验失败');
setFeedFormStatus(error.message || '动态表单校验失败', 'error');
return;
}
if (!body.feedContent) {
showMessage('请填写动态内容');
setFeedFormStatus('请填写动态内容', 'error');
return;
}
return withActionLock('save:' + (feedId || 'new'), async function () {
var saved;
var savedFeedId;
var buttons = queryAll('[type="submit"]', form);
buttons.forEach(function (button) { button.disabled = true; });
setFeedSubmitPending(true);
setFeedFormStatus('正在保存动态…', 'loading');
try {
if (feedId) {
saved = await api.updateFeed(genealogyId, feedId, body);
@@ -665,9 +865,12 @@
root.location.href = buildPostSaveUrl(genealogyId, savedFeedId, body.status);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '动态保存失败');
setFeedFormStatus(
getApiStateType(error) === 'forbidden' ? '当前账号无权保存该家谱的动态。' : (error.message || '动态保存失败'),
getApiStateType(error)
);
} finally {
buttons.forEach(function (button) { button.disabled = false; });
setFeedSubmitPending(false);
}
});
}
@@ -723,6 +926,11 @@
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId) return;
currentCommentPages[feedId] = buildPageQuery(pageNum).pageNum;
if (query('[data-feed-detail-comments]')) {
setFeedContainerState('[data-feed-detail-comments]', 'loading', '正在加载评论…');
} else {
replaceInlineCommentList(feedId, renderFeedState('loading', '正在加载评论…'));
}
try {
renderComments(
feedId,
@@ -731,7 +939,19 @@
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (query('[data-feed-detail-comments]')) {
query('[data-feed-detail-comments]').innerHTML = '<div class="api-empty">评论加载失败,请稍后重试。</div>';
setFeedContainerState(
'[data-feed-detail-comments]',
getApiStateType(error),
getApiStateType(error) === 'forbidden' ? '当前账号无权查看评论。' : '评论加载失败,请稍后重试。'
);
} else {
replaceInlineCommentList(
feedId,
renderFeedState(
getApiStateType(error),
getApiStateType(error) === 'forbidden' ? '当前账号无权查看评论。' : '评论加载失败,请稍后重试。'
)
);
}
showMessage(error.message || '评论加载失败');
}
@@ -745,6 +965,7 @@
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId || !commentId) return;
currentReplyPages[commentId] = buildPageQuery(pageNum).pageNum;
setReplyState(commentId, 'loading', '正在加载回复…');
try {
renderCommentReplies(
feedId,
@@ -758,46 +979,60 @@
);
} catch (error) {
if (redirectUnauthorized(api, error)) return;
if (query('[data-feed-comment-node="' + commentId + '"]')) {
query('[data-feed-comment-node="' + commentId + '"]').insertAdjacentHTML(
'afterend',
'<div class="api-empty">回复加载失败,请稍后重试。</div>'
);
}
setReplyState(
commentId,
getApiStateType(error),
getApiStateType(error) === 'forbidden' ? '当前账号无权查看回复。' : '回复加载失败,请稍后重试。'
);
showMessage(error.message || '回复加载失败');
}
}
async function createComment(feedId, parentCommentId) {
async function submitComment(form) {
var api = getApi();
var genealogyId;
var content;
var feedId = getCurrentFeedId();
var body;
var values;
var forbidden = false;
if (redirectUnauthorized(api)) return;
if (redirectUnauthorized(api) || !form) return;
genealogyId = requireGenealogyId();
if (!genealogyId || !feedId) return;
content = root.prompt ? root.prompt(parentCommentId ? '请输入回复内容' : '请输入评论内容', '') : '';
body = buildCommentBody({ commentContent: content, parentCommentId: parentCommentId });
if (!body.commentContent) return;
if (body.commentContent.length > 1000) {
showMessage('评论不能超过 1000 字');
values = getFormValues(form);
body = buildCommentBody(values);
if (!body.commentContent) {
setFeedCommentFormStatus('请填写评论内容。', 'error');
return;
}
return withActionLock('comment:' + feedId + ':' + (parentCommentId || 'root'), async function () {
if (body.commentContent.length > 1000) {
setFeedCommentFormStatus('评论不能超过 1000 字。', 'error');
return;
}
return withActionLock('comment:' + feedId + ':' + (body.parentCommentId || 'root'), async function () {
setFeedCommentFormEnabled(false);
setFeedCommentSubmitPending(true);
setFeedCommentFormStatus('正在发布评论…', 'loading');
try {
await api.createFeedComment(genealogyId, feedId, body);
await showComments(feedId, currentCommentPages[feedId] || 1);
if (parentCommentId) await showCommentReplies(feedId, parentCommentId, currentReplyPages[parentCommentId] || 1);
if (query('[data-feed-detail-page]')) {
var feed = normalizeFeed(await api.feedDetail(genealogyId, feedId));
if (feed) renderFeedDetail(feed, genealogyId);
} else {
await loadFeeds(currentFeedPage);
}
if (body.parentCommentId) await showCommentReplies(feedId, body.parentCommentId, currentReplyPages[body.parentCommentId] || 1);
form.reset();
setFeedCommentTarget('');
setFeedCommentFormStatus('');
var feed = normalizeFeed(await api.feedDetail(genealogyId, feedId));
if (feed) renderFeedDetail(feed, genealogyId);
showMessage('评论已发布');
} catch (error) {
if (redirectUnauthorized(api, error)) return;
showMessage(error.message || '评论发布失败');
forbidden = getApiStateType(error) === 'forbidden';
setFeedCommentFormStatus(
forbidden ? '当前账号无权评论该动态。' : (error.message || '评论发布失败,请稍后重试。'),
getApiStateType(error)
);
} finally {
setFeedCommentSubmitPending(false);
if (!forbidden) setFeedCommentFormEnabled(true);
}
});
}
@@ -832,9 +1067,16 @@
documentRef.addEventListener('submit', function (event) {
var form = event.target.closest('[data-feed-form]');
if (!form) return;
event.preventDefault();
submitFeed(form);
if (form) {
event.preventDefault();
submitFeed(form);
return;
}
form = event.target.closest('[data-feed-comment-form]');
if (form) {
event.preventDefault();
submitComment(form);
}
});
documentRef.addEventListener('click', function (event) {
var target = event.target;
@@ -858,13 +1100,18 @@
if (target.closest('[data-feed-comment]')) {
event.preventDefault();
feedId = target.closest('[data-feed-comment]').getAttribute('data-feed-comment');
createComment(feedId);
openFeedCommentForm(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);
openFeedCommentForm(feedId, commentId);
}
if (target.closest('[data-feed-comment-cancel]')) {
event.preventDefault();
setFeedCommentTarget('');
setFeedCommentFormStatus('');
}
if (target.closest('[data-feed-comment-replies]')) {
event.preventDefault();
@@ -937,6 +1184,7 @@
withActionLock: withActionLock,
normalizeFeed: normalizeFeed,
normalizeComment: normalizeComment,
canEditFeeds: canEditFeeds,
normalizeViewerContext: normalizeViewerContext,
normalizeComments: normalizeComments,
normalizeList: normalizeList,