diff --git a/DESIGN.md b/DESIGN.md index 3857f01..094bc37 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -565,7 +565,7 @@ Life 是社区生活分享信息流,类似轻量社交动态。 Life Post 可配置: - Post 内容正文 -- 标签:使用 Life 标签配置,可多选 +- 标签:使用 Life 标签配置,至少选择 1 个,可多选 - 创建者、最后编辑者、创建时间、最后编辑时间 - 评论 - 评论回复:仅支持回复顶层评论,不做无限嵌套 @@ -578,7 +578,7 @@ Life Post 可配置: - 已注册并完成邮箱验证且拥有 `life.posts.create` 权限的用户可以发布 Life Post。 - 作者本人拥有 `life.posts.update` / `life.posts.delete` 权限时可以编辑、删除自己的 Life Post;删除 Life Post 使用软删除。 - 拥有 `life.posts.update-any` / `life.posts.delete-any` 权限的用户可以管理其他用户的 Life Post。 -- 已注册并完成邮箱验证且拥有 `life.posts.create` 或 `life.posts.update` 权限的用户发布或编辑 Life Post 时可以选择一个或多个 Life 标签。 +- 已注册并完成邮箱验证且拥有 `life.posts.create` 或 `life.posts.update` 权限的用户发布或编辑 Life Post 时必须选择至少 1 个 Life 标签,可选择多个。 - 已注册并完成邮箱验证且拥有 `life.comments.create` 权限的用户可以评论 Life Post,并回复顶层评论。 - 评论作者拥有 `life.comments.delete` 权限时可以删除自己的评论;拥有 `life.comments.delete-any` 权限的用户可以删除其他用户评论;删除评论后正文不再展示,已有回复保留在原位置。 - 已软删除的 Life Post 不出现在信息流、搜索或标签筛选结果中,也不能继续编辑、评论或设置 Reaction。 diff --git a/backend/src/queries.ts b/backend/src/queries.ts index aa623c9..d7cd201 100644 --- a/backend/src/queries.ts +++ b/backend/src/queries.ts @@ -2089,10 +2089,14 @@ function cleanLifePostPayload(payload: Record): LifePostPayload if (body.length > 2000) { throw validationError('Post is too long'); } + const tagIds = cleanIds(payload.tagIds); + if (tagIds.length === 0) { + throw validationError('server.validation.lifeTagRequired'); + } return { body, - tagIds: cleanIds(payload.tagIds) + tagIds }; } diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 3f1066e..05fe20b 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -486,7 +486,7 @@ export interface DailyChecklistPayload { export interface LifePostPayload { body: string; - tagIds?: number[]; + tagIds: number[]; } export interface LifeCommentPayload { diff --git a/frontend/src/views/LifeView.vue b/frontend/src/views/LifeView.vue index 1ec45ec..88570cf 100644 --- a/frontend/src/views/LifeView.vue +++ b/frontend/src/views/LifeView.vue @@ -218,10 +218,14 @@ function resetForm() { function payload() { return { body: body.value.trim(), - tagIds: selectedTagIds.value.map((tagId) => Number(tagId)).filter((tagId) => Number.isInteger(tagId) && tagId > 0) + tagIds: selectedLifeTagIds() }; } +function selectedLifeTagIds() { + return selectedTagIds.value.map((tagId) => Number(tagId)).filter((tagId) => Number.isInteger(tagId) && tagId > 0); +} + function submitSearch() { const nextSearch = searchDraft.value.trim(); if (nextSearch === submittedSearch.value && !loadError.value) { @@ -281,6 +285,12 @@ async function submitPost() { return; } + if (selectedLifeTagIds().length === 0) { + formError.value = t('pages.life.tagRequired'); + document.getElementById('life-post-tags')?.focus(); + return; + } + busy.value = true; formError.value = ''; diff --git a/system-wordings.ts b/system-wordings.ts index 8bcc3c6..37a8982 100644 --- a/system-wordings.ts +++ b/system-wordings.ts @@ -436,6 +436,7 @@ export const systemWordingMessages = { saveFailed: 'Save failed', deleteFailed: 'Delete failed', bodyRequired: 'Please enter a post.', + tagRequired: 'Please select at least one tag.', byUnknown: 'Community member', edited: 'Edited', deleteConfirm: 'Delete this post?', @@ -648,6 +649,7 @@ export const systemWordingMessages = { taskDoesNotExist: 'Task does not exist', postRequired: 'Please enter a post', postTooLong: 'Post is too long', + lifeTagRequired: 'Please select at least one tag', commentRequired: 'Please enter a comment', commentTooLong: 'Comment is too long', reactionInvalid: 'Reaction is invalid', @@ -1144,6 +1146,7 @@ export const systemWordingMessages = { saveFailed: '保存失败', deleteFailed: '删除失败', bodyRequired: '请输入动态内容。', + tagRequired: '请至少选择 1 个标签。', byUnknown: '社区成员', edited: '已编辑', deleteConfirm: '确认删除这条动态?', @@ -1356,6 +1359,7 @@ export const systemWordingMessages = { taskDoesNotExist: '任务不存在', postRequired: '请输入动态内容', postTooLong: '动态内容过长', + lifeTagRequired: '请至少选择 1 个标签', commentRequired: '请输入评论内容', commentTooLong: '评论内容过长', reactionInvalid: '互动类型不合法',