From 2358603edabe2a1d845859a21e8fe1d5fc3a5878 Mon Sep 17 00:00:00 2001 From: hajimi Date: Sun, 2 Aug 2026 20:38:47 +0800 Subject: [PATCH] feat: support full community post editing --- admin/src/api/community.ts | 6 +- admin/src/views/community/post/index.vue | 345 ++++++++++++++++-- docs/sql/create_community_post_categories.sql | 4 +- ...community_post_edit_content_permission.sql | 24 -- .../upsert_community_post_edit_permission.sql | 71 ++++ .../community/CommunityPostController.php | 6 +- .../logic/community/CommunityPostLogic.php | 108 +++++- .../logic/community/CommunityTagLogic.php | 2 +- .../community/CommunityPostValidate.php | 144 +++++++- 9 files changed, 638 insertions(+), 72 deletions(-) delete mode 100644 docs/sql/insert_community_post_edit_content_permission.sql create mode 100644 docs/sql/upsert_community_post_edit_permission.sql diff --git a/admin/src/api/community.ts b/admin/src/api/community.ts index 61995b4..6a1346f 100644 --- a/admin/src/api/community.ts +++ b/admin/src/api/community.ts @@ -10,9 +10,9 @@ export function communityPostDetail(params: any) { return request.get({ url: '/community.communityPost/detail', params }) } -// 编辑帖子内容 -export function communityPostEditContent(params: any) { - return request.post({ url: '/community.communityPost/editContent', params }) +// 编辑帖子 +export function communityPostEdit(params: any) { + return request.post({ url: '/community.communityPost/edit', params }) } // 帖子审核状态 diff --git a/admin/src/views/community/post/index.vue b/admin/src/views/community/post/index.vue index 96cceae..015a537 100644 --- a/admin/src/views/community/post/index.vue +++ b/admin/src/views/community/post/index.vue @@ -68,6 +68,7 @@ @@ -135,8 +136,8 @@ @click="handleDetail(row.id)"> 详情 - + 编辑 关闭 - - - - - - + + + + 基础信息 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 正文与素材 + + + + + + + + 付费设置 + + + + + + + + + + + + + + + + + + + 统计数据(手动修正) + + + + + + + + + 展示与状态 + + + + + + + + + + + + + + + + + +
+ 置顶 + 推荐 + 热门 + 话题 +
+
+
+
+ + 扩展数据 + + + +
+
@@ -223,7 +369,7 @@ import { communityPostLists, communityPostDetail, - communityPostEditContent, + communityPostEdit, communityPostStatus, communityPostSetTop, communityPostSetHot, @@ -231,11 +377,13 @@ import { communityPostSetSort, communityPostSetTopic, communityPostSetCategory, + communityTagAll, communityCategoryAll, communityPostDelete } from '@/api/community' import { usePaging } from '@/hooks/usePaging' import feedback from '@/utils/feedback' +import type { FormInstance, FormItemRule, FormRules } from 'element-plus' const queryParams = reactive({ status: '', @@ -247,6 +395,7 @@ const queryParams = reactive({ }) const categoryList = ref([]) +const tagList = ref([]) const { pager, getLists, resetPage, resetParams } = usePaging({ fetchFun: communityPostLists, @@ -335,33 +484,167 @@ const handleDetail = async (id: number) => { } const showEdit = ref(false) +const editLoading = ref(false) const editSubmitLoading = ref(false) -const editData = reactive({ +const createEditData = () => ({ id: 0, - content: '' + origin_id: '', + user_id: 0, + content: '', + images: [] as string[], + post_type: 0, + category_id: 0, + match_id: 0, + tag_ids: [] as number[], + is_paid: 0, + price_points: 0, + free_content_len: 100, + paid_count: 0, + view_count: 0, + like_count: 0, + comment_count: 0, + share_count: 0, + ext: '', + is_top: 0, + is_hot: 0, + is_recommend: 0, + is_topic: 0, + sort: 0, + status: 1, + create_time: '', + update_time: '' }) +type EditData = ReturnType +type CountFieldKey = 'paid_count' | 'view_count' | 'like_count' | 'comment_count' | 'share_count' -const handleEdit = (post: any) => { +const editData = reactive(createEditData()) +const editFormRef = ref() +const countFields: Array<{ key: CountFieldKey; label: string }> = [ + { key: 'paid_count', label: '购买人数' }, + { key: 'view_count', label: '浏览数' }, + { key: 'like_count', label: '点赞数' }, + { key: 'comment_count', label: '评论数' }, + { key: 'share_count', label: '分享数' } +] + +const validatePostBody = (_rule: any, _value: any, callback: (error?: Error) => void) => { + if (!editData.content.trim() && editData.images.length === 0) { + callback(new Error('帖子内容和图片不能同时为空')) + return + } + callback() +} + +const validatePaidPrice = (_rule: any, value: number, callback: (error?: Error) => void) => { + if (editData.is_paid === 1 && (!value || value < 1 || value > 9999)) { + callback(new Error('付费帖子积分价格范围为1~9999')) + return + } + callback() +} + +const validateExt = (_rule: any, value: string, callback: (error?: Error) => void) => { + if (!value.trim()) { + callback() + return + } + try { + JSON.parse(value) + callback() + } catch { + callback(new Error('请输入合法的 JSON')) + } +} + +const nonNegativeRule: FormItemRule[] = [ + { required: true, type: 'number', min: 0, message: '请输入不小于0的整数' } +] +const editRules: FormRules = { + user_id: [{ required: true, type: 'number', min: 1, message: '请输入有效的用户ID', trigger: 'blur' }], + content: [{ validator: validatePostBody, trigger: 'blur' }], + post_type: [{ required: true, type: 'number', message: '请选择帖子类型', trigger: 'change' }], + category_id: nonNegativeRule, + match_id: nonNegativeRule, + tag_ids: [{ type: 'array', max: 3, message: '帖子标签最多选择3个', trigger: 'change' }], + price_points: [{ validator: validatePaidPrice, trigger: 'blur' }], + free_content_len: [{ required: true, type: 'number', min: 20, max: 500, message: '免费预览字数为20~500' }], + paid_count: nonNegativeRule, + view_count: nonNegativeRule, + like_count: nonNegativeRule, + comment_count: nonNegativeRule, + share_count: nonNegativeRule, + sort: nonNegativeRule, + status: [{ required: true, type: 'number', message: '请选择审核状态', trigger: 'change' }], + create_time: [{ required: true, message: '请选择发布时间', trigger: 'change' }], + ext: [{ validator: validateExt, trigger: 'blur' }] +} + +const normalizeExt = (value: any) => { + if (value === null || value === undefined || value === '') return '' + if (typeof value === 'string') return value + return JSON.stringify(value, null, 2) +} + +const handleEdit = async (id: number) => { showEdit.value = true - editData.id = post.id - editData.content = post.content || '' + editLoading.value = true + Object.assign(editData, createEditData()) + try { + const res = await communityPostDetail({ id }) + Object.assign(editData, createEditData(), res, { + id: Number(res.id || id), + origin_id: String(res.origin_id || ''), + user_id: Number(res.user_id || 0), + content: String(res.content || ''), + images: Array.isArray(res.images) ? res.images : [], + post_type: Number(res.post_type || 0), + category_id: Number(res.category_id || 0), + match_id: Number(res.match_id || 0), + tag_ids: Array.isArray(res.tag_ids) ? res.tag_ids.map(Number) : [], + is_paid: Number(res.is_paid || 0), + ext: normalizeExt(res.ext), + price_points: Number(res.is_paid) === 1 ? Number(res.price_points || 0) : 0, + free_content_len: Number(res.is_paid) === 1 ? Number(res.free_content_len || 100) : 100, + paid_count: Number(res.paid_count || 0), + view_count: Number(res.view_count || 0), + like_count: Number(res.like_count || 0), + comment_count: Number(res.comment_count || 0), + share_count: Number(res.share_count || 0), + is_top: Number(res.is_top || 0), + is_hot: Number(res.is_hot || 0), + is_recommend: Number(res.is_recommend || 0), + is_topic: Number(res.is_topic || 0), + sort: Number(res.sort || 0), + status: Number(res.status || 0), + create_time: String(res.create_time || ''), + update_time: String(res.update_time || '') + }) + } finally { + editLoading.value = false + } +} + +const handlePaidChange = (value: any) => { + if (Number(value) !== 1) { + editData.price_points = 0 + editData.free_content_len = 100 + } } const handleSubmitEdit = async () => { - if (!editData.content.trim()) { - feedback.msgError('请输入帖子内容') - return - } + const valid = await editFormRef.value?.validate().catch(() => false) + if (!valid) return editSubmitLoading.value = true try { - await communityPostEditContent({ - id: editData.id, - content: editData.content + await communityPostEdit({ + ...editData, + origin_id: editData.origin_id.trim(), + content: editData.content, + images: [...editData.images], + tag_ids: [...editData.tag_ids], + ext: editData.ext.trim() }) showEdit.value = false - if (detailData.value.id === editData.id) { - detailData.value.content = editData.content - } getLists() } finally { editSubmitLoading.value = false @@ -377,6 +660,16 @@ const loadCategories = async () => { } } +const loadTags = async () => { + try { + const tags = await communityTagAll() + tagList.value = Array.isArray(tags) ? tags : [] + } catch { + tagList.value = [] + } +} + loadCategories() +loadTags() getLists() diff --git a/docs/sql/create_community_post_categories.sql b/docs/sql/create_community_post_categories.sql index eb215f1..2b5420f 100644 --- a/docs/sql/create_community_post_categories.sql +++ b/docs/sql/create_community_post_categories.sql @@ -204,7 +204,7 @@ FROM ( SELECT '设置推荐' AS `name`, 'community.communityPost/setRecommend' AS `perms` UNION ALL SELECT '设置话题', 'community.communityPost/setTopic' UNION ALL SELECT '设置分类', 'community.communityPost/setCategory' - UNION ALL SELECT '编辑内容', 'community.communityPost/editContent' + UNION ALL SELECT '编辑帖子', 'community.communityPost/edit' ) permission WHERE @post_menu_id IS NOT NULL AND NOT EXISTS ( @@ -224,7 +224,7 @@ WHERE menu.`perms` IN ( 'community.communityPost/setRecommend', 'community.communityPost/setTopic', 'community.communityPost/setCategory', - 'community.communityPost/editContent' + 'community.communityPost/edit' ) AND NOT EXISTS ( SELECT 1 diff --git a/docs/sql/insert_community_post_edit_content_permission.sql b/docs/sql/insert_community_post_edit_content_permission.sql deleted file mode 100644 index dc0e03e..0000000 --- a/docs/sql/insert_community_post_edit_content_permission.sql +++ /dev/null @@ -1,24 +0,0 @@ -SET @post_menu_id = COALESCE( - (SELECT `id` FROM `la_system_menu` WHERE `perms` = 'community.communityPost/lists' LIMIT 1), - (SELECT `id` FROM `la_system_menu` WHERE `component` = 'community/post/index' LIMIT 1) -); - -INSERT INTO `la_system_menu` - (`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`) -SELECT - @post_menu_id, 'A', '编辑内容', '', 0, 'community.communityPost/editContent', '', '', '', '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() -WHERE @post_menu_id IS NOT NULL - AND NOT EXISTS ( - SELECT 1 FROM `la_system_menu` WHERE `perms` = 'community.communityPost/editContent' - ); - -INSERT INTO `la_system_role_menu` (`role_id`, `menu_id`) -SELECT 1, menu.`id` -FROM `la_system_menu` menu -WHERE menu.`perms` = 'community.communityPost/editContent' - AND NOT EXISTS ( - SELECT 1 - FROM `la_system_role_menu` role_menu - WHERE role_menu.`role_id` = 1 - AND role_menu.`menu_id` = menu.`id` - ); diff --git a/docs/sql/upsert_community_post_edit_permission.sql b/docs/sql/upsert_community_post_edit_permission.sql new file mode 100644 index 0000000..c2a449f --- /dev/null +++ b/docs/sql/upsert_community_post_edit_permission.sql @@ -0,0 +1,71 @@ +SET @post_menu_id = COALESCE( + (SELECT `id` FROM `la_system_menu` WHERE `perms` = 'community.communityPost/lists' LIMIT 1), + (SELECT `id` FROM `la_system_menu` WHERE `component` = 'community/post/index' LIMIT 1) +); + +SET @old_edit_menu_id = ( + SELECT `id` FROM `la_system_menu` + WHERE `perms` = 'community.communityPost/editContent' + LIMIT 1 +); + +SET @edit_menu_id = ( + SELECT `id` FROM `la_system_menu` + WHERE `perms` = 'community.communityPost/edit' + LIMIT 1 +); + +UPDATE `la_system_menu` +SET `name` = '编辑帖子', + `perms` = 'community.communityPost/edit', + `update_time` = UNIX_TIMESTAMP() +WHERE `id` = @old_edit_menu_id + AND @edit_menu_id IS NULL; + +INSERT INTO `la_system_menu` + (`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_cache`, `is_show`, `is_disable`, `create_time`, `update_time`) +SELECT + @post_menu_id, 'A', '编辑帖子', '', 0, 'community.communityPost/edit', '', '', '', '', 0, 0, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP() +WHERE @post_menu_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM `la_system_menu` WHERE `perms` = 'community.communityPost/edit' + ); + +SET @edit_menu_id = ( + SELECT `id` FROM `la_system_menu` + WHERE `perms` = 'community.communityPost/edit' + LIMIT 1 +); + +INSERT INTO `la_system_role_menu` (`role_id`, `menu_id`) +SELECT 1, @edit_menu_id +WHERE @edit_menu_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 + FROM `la_system_role_menu` + WHERE `role_id` = 1 + AND `menu_id` = @edit_menu_id + ); + +INSERT INTO `la_system_role_menu` (`role_id`, `menu_id`) +SELECT old_role_menu.`role_id`, @edit_menu_id +FROM `la_system_role_menu` old_role_menu +WHERE old_role_menu.`menu_id` = @old_edit_menu_id + AND @old_edit_menu_id IS NOT NULL + AND @old_edit_menu_id <> @edit_menu_id + AND NOT EXISTS ( + SELECT 1 + FROM `la_system_role_menu` current_role_menu + WHERE current_role_menu.`role_id` = old_role_menu.`role_id` + AND current_role_menu.`menu_id` = @edit_menu_id + ); + +DELETE FROM `la_system_role_menu` +WHERE `menu_id` = @old_edit_menu_id + AND @old_edit_menu_id IS NOT NULL + AND @old_edit_menu_id <> @edit_menu_id; + +DELETE FROM `la_system_menu` +WHERE `id` = @old_edit_menu_id + AND @old_edit_menu_id IS NOT NULL + AND @old_edit_menu_id <> @edit_menu_id; diff --git a/server/app/adminapi/controller/community/CommunityPostController.php b/server/app/adminapi/controller/community/CommunityPostController.php index c4d0ddd..6604f08 100644 --- a/server/app/adminapi/controller/community/CommunityPostController.php +++ b/server/app/adminapi/controller/community/CommunityPostController.php @@ -21,10 +21,10 @@ class CommunityPostController extends BaseAdminController return $this->data($result); } - public function editContent() + public function edit() { - $params = (new CommunityPostValidate())->post()->goCheck('editContent'); - $result = CommunityPostLogic::editContent($params); + $params = (new CommunityPostValidate())->post()->goCheck('edit'); + $result = CommunityPostLogic::edit($params); if (true === $result) { return $this->success('保存成功', [], 1, 1); } diff --git a/server/app/adminapi/logic/community/CommunityPostLogic.php b/server/app/adminapi/logic/community/CommunityPostLogic.php index cbb7759..eb55c00 100644 --- a/server/app/adminapi/logic/community/CommunityPostLogic.php +++ b/server/app/adminapi/logic/community/CommunityPostLogic.php @@ -5,6 +5,7 @@ namespace app\adminapi\logic\community; use app\common\logic\BaseLogic; use app\common\model\community\CommunityCategory; use app\common\model\community\CommunityPost; +use app\common\model\community\CommunityTag; use app\common\model\user\User; use app\common\service\ai\KbSyncService; use think\facade\Db; @@ -26,6 +27,19 @@ class CommunityPostLogic extends BaseLogic $post['category_name'] = (int) ($post['category_id'] ?? 0) > 0 ? (string) CommunityCategory::where('id', $post['category_id'])->value('name') : ''; + $post['tag_ids'] = array_map('intval', Db::name('community_post_tag') + ->where('post_id', $params['id']) + ->column('tag_id')); + if (is_array($post['ext'] ?? null)) { + $post['ext'] = json_encode($post['ext'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } elseif (is_string($post['ext'] ?? null) && trim($post['ext']) !== '') { + $decodedExt = json_decode($post['ext'], true); + $post['ext'] = json_last_error() === JSON_ERROR_NONE + ? json_encode($decodedExt, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) + : $post['ext']; + } else { + $post['ext'] = ''; + } return $post; } @@ -50,7 +64,7 @@ class CommunityPostLogic extends BaseLogic } } - public static function editContent(array $params): bool + public static function edit(array $params): bool { $post = CommunityPost::findOrEmpty($params['id']); if ($post->isEmpty()) { @@ -58,17 +72,79 @@ class CommunityPostLogic extends BaseLogic return false; } + $oldCategoryId = (int) $post->category_id; + $oldTagIds = array_map('intval', Db::name('community_post_tag') + ->where('post_id', $params['id']) + ->column('tag_id')); + $newCategoryId = (int) $params['category_id']; + $newTagIds = array_values(array_unique(array_filter( + array_map('intval', $params['tag_ids'] ?? []), + static fn (int $tagId): bool => $tagId > 0 + ))); + $isPaid = (int) $params['is_paid'] === 1; + $extValue = $params['ext'] ?? ''; + $ext = is_array($extValue) + ? json_encode($extValue, JSON_UNESCAPED_UNICODE) + : trim((string) $extValue); + + Db::startTrans(); try { - $post->content = (string) $params['content']; - $post->save(); - if ((int) $post->status === 1) { - KbSyncService::enqueue('post', 'post', (int) $post->id, 'upsert', 30); + $post->save([ + 'origin_id' => trim((string) ($params['origin_id'] ?? '')) ?: null, + 'user_id' => (int) $params['user_id'], + 'content' => (string) ($params['content'] ?? ''), + 'images' => array_values($params['images'] ?? []), + 'post_type' => (int) $params['post_type'], + 'category_id' => $newCategoryId, + 'match_id' => (int) $params['match_id'], + 'is_paid' => $isPaid ? 1 : 0, + 'price_points' => $isPaid ? (int) $params['price_points'] : 0, + 'free_content_len' => $isPaid ? (int) $params['free_content_len'] : 100, + 'paid_count' => (int) $params['paid_count'], + 'view_count' => (int) $params['view_count'], + 'like_count' => (int) $params['like_count'], + 'comment_count' => (int) $params['comment_count'], + 'share_count' => (int) $params['share_count'], + 'ext' => $ext === '' ? null : $ext, + 'is_top' => (int) $params['is_top'], + 'is_hot' => (int) $params['is_hot'], + 'is_recommend' => (int) $params['is_recommend'], + 'is_topic' => (int) $params['is_topic'], + 'sort' => (int) $params['sort'], + 'status' => (int) $params['status'], + 'create_time' => strtotime((string) $params['create_time']), + 'update_time' => time(), + ]); + + Db::name('community_post_tag')->where('post_id', $post->id)->delete(); + if ($newTagIds) { + Db::name('community_post_tag')->insertAll(array_map( + static fn (int $tagId): array => [ + 'post_id' => (int) $post->id, + 'tag_id' => $tagId, + ], + $newTagIds + )); } - return true; + + self::refreshCategoryCount($oldCategoryId); + self::refreshCategoryCount($newCategoryId); + self::refreshTagCounts(array_merge($oldTagIds, $newTagIds)); + Db::commit(); } catch (\Exception $e) { + Db::rollback(); self::setError($e->getMessage()); return false; } + + KbSyncService::enqueue( + 'post', + 'post', + (int) $post->id, + (int) $post->status === 1 ? 'upsert' : 'delete', + 30 + ); + return true; } public static function setTop(array $params): bool @@ -188,4 +264,24 @@ class CommunityPostLogic extends BaseLogic 'update_time' => time(), ]); } + + private static function refreshTagCounts(array $tagIds): void + { + $tagIds = array_values(array_unique(array_filter( + array_map('intval', $tagIds), + static fn (int $tagId): bool => $tagId > 0 + ))); + foreach ($tagIds as $tagId) { + $postCount = Db::name('community_post_tag') + ->alias('post_tag') + ->join('community_post post', 'post.id = post_tag.post_id') + ->where('post_tag.tag_id', $tagId) + ->whereNull('post.delete_time') + ->count(); + CommunityTag::where('id', $tagId)->update([ + 'post_count' => $postCount, + 'update_time' => time(), + ]); + } + } } diff --git a/server/app/adminapi/logic/community/CommunityTagLogic.php b/server/app/adminapi/logic/community/CommunityTagLogic.php index bde744a..cfd09b3 100644 --- a/server/app/adminapi/logic/community/CommunityTagLogic.php +++ b/server/app/adminapi/logic/community/CommunityTagLogic.php @@ -48,6 +48,6 @@ class CommunityTagLogic extends BaseLogic public static function all(): array { - return CommunityTag::where('status', 1)->order('sort', 'desc')->order('id', 'desc')->select()->toArray(); + return CommunityTag::order('sort', 'desc')->order('id', 'desc')->select()->toArray(); } } diff --git a/server/app/adminapi/validate/community/CommunityPostValidate.php b/server/app/adminapi/validate/community/CommunityPostValidate.php index 8b5dd38..f9f7dd8 100644 --- a/server/app/adminapi/validate/community/CommunityPostValidate.php +++ b/server/app/adminapi/validate/community/CommunityPostValidate.php @@ -5,11 +5,30 @@ namespace app\adminapi\validate\community; use app\common\validate\BaseValidate; use app\common\model\community\CommunityCategory; use app\common\model\community\CommunityPost; +use app\common\model\community\CommunityTag; +use app\common\model\user\User; +use think\facade\Db; class CommunityPostValidate extends BaseValidate { protected $rule = [ 'id' => 'require|checkPost', + 'origin_id' => 'max:100|checkOriginId', + 'user_id' => 'require|integer|gt:0|checkUser|checkBody', + 'content' => 'max:16000', + 'images' => 'array|checkImages', + 'post_type' => 'require|in:0,1,2', + 'match_id' => 'require|integer|egt:0|checkMatch', + 'tag_ids' => 'array|checkTags', + 'is_paid' => 'require|in:0,1|checkPayment', + 'price_points' => 'require|integer|egt:0|elt:10000', + 'free_content_len' => 'require|integer|egt:0|elt:501|checkFreeContentLen', + 'paid_count' => 'require|integer|egt:0|elt:4294967296', + 'view_count' => 'require|integer|egt:0|elt:4294967296', + 'like_count' => 'require|integer|egt:0|elt:4294967296', + 'comment_count' => 'require|integer|egt:0|elt:4294967296', + 'share_count' => 'require|integer|egt:0|elt:4294967296', + 'ext' => 'checkExt', 'status' => 'require|in:0,1,2', 'is_top' => 'require|in:0,1', 'is_hot' => 'require|in:0,1', @@ -17,14 +36,22 @@ class CommunityPostValidate extends BaseValidate 'sort' => 'require|integer|egt:0|elt:1000000', 'is_topic' => 'require|in:0,1', 'category_id' => 'require|integer|egt:0|checkCategory', - 'content' => 'require|max:16000', + 'create_time' => 'require|checkDateTime', ]; protected $message = [ 'id.require' => '帖子id不能为空', 'status.require' => '状态不能为空', - 'content.require' => '帖子内容不能为空', 'content.max' => '帖子内容不能超过16000个字符', + 'origin_id.max' => '来源标识不能超过100个字符', + 'user_id.require' => '用户ID不能为空', + 'images.array' => '帖子图片格式错误', + 'post_type.in' => '帖子类型错误', + 'match_id.egt' => '赛事ID不能小于0', + 'tag_ids.array' => '帖子标签格式错误', + 'price_points.elt' => '积分价格范围为0~9999', + 'free_content_len.elt' => '免费预览字数不能超过500', + 'create_time.require' => '发布时间不能为空', ]; public function sceneDetail() @@ -42,9 +69,15 @@ class CommunityPostValidate extends BaseValidate return $this->only(['id', 'status']); } - public function sceneEditContent() + public function sceneEdit() { - return $this->only(['id', 'content']); + return $this->only([ + 'id', 'origin_id', 'user_id', 'content', 'images', 'post_type', 'category_id', + 'match_id', 'tag_ids', 'is_paid', 'price_points', 'free_content_len', + 'paid_count', 'view_count', 'like_count', 'comment_count', 'share_count', + 'ext', 'is_top', 'is_hot', 'is_recommend', 'is_topic', 'sort', 'status', + 'create_time', + ]); } public function sceneTop() @@ -86,14 +119,111 @@ class CommunityPostValidate extends BaseValidate return true; } - public function checkCategory($value) + public function checkOriginId($value, $rule, $data) + { + $originId = trim((string) $value); + if ($originId === '') { + return true; + } + $exists = Db::name('community_post') + ->where('origin_id', $originId) + ->where('id', '<>', (int) ($data['id'] ?? 0)) + ->count(); + return $exists > 0 ? '来源标识已存在' : true; + } + + public function checkUser($value) + { + return User::where('id', $value)->count() > 0 ? true : '用户不存在'; + } + + public function checkBody($value, $rule, $data) + { + if (trim((string) ($data['content'] ?? '')) === '' && empty($data['images'])) { + return '帖子内容和图片不能同时为空'; + } + return true; + } + + public function checkImages($value) + { + if (count($value) > 18) { + return '帖子图片最多18张'; + } + foreach ($value as $image) { + if (!is_string($image) || trim($image) === '') { + return '帖子图片地址格式错误'; + } + } + return true; + } + + public function checkMatch($value) { if ((int) $value === 0) { return true; } - $category = CommunityCategory::where('id', $value)->where('status', 1)->findOrEmpty(); + return Db::name('match')->where('id', $value)->count() > 0 ? true : '关联赛事不存在'; + } + + public function checkTags($value) + { + $tagIds = array_values(array_unique(array_map('intval', $value))); + if (count($tagIds) > 3) { + return '帖子标签最多选择3个'; + } + if (!$tagIds) { + return true; + } + return CommunityTag::whereIn('id', $tagIds)->count() === count($tagIds) + ? true + : '帖子标签不存在'; + } + + public function checkPayment($value, $rule, $data) + { + if ((int) $value === 1 && (int) ($data['price_points'] ?? 0) <= 0) { + return '付费帖子必须设置积分价格'; + } + return true; + } + + public function checkFreeContentLen($value, $rule, $data) + { + if ((int) ($data['is_paid'] ?? 0) === 1 && (int) $value < 20) { + return '付费帖子免费预览字数不能少于20'; + } + return true; + } + + public function checkExt($value) + { + if (is_array($value) || trim((string) $value) === '') { + return true; + } + json_decode((string) $value, true); + return json_last_error() === JSON_ERROR_NONE ? true : '扩展数据必须是有效的JSON'; + } + + public function checkDateTime($value) + { + return strtotime((string) $value) !== false ? true : '发布时间格式错误'; + } + + public function checkCategory($value, $rule, $data) + { + if ((int) $value === 0) { + return true; + } + $category = CommunityCategory::where('id', $value)->findOrEmpty(); if ($category->isEmpty()) { - return '帖子分类不存在或已禁用'; + return '帖子分类不存在'; + } + if ((int) $category->status !== 1) { + $currentCategoryId = (int) CommunityPost::where('id', $data['id'] ?? 0)->value('category_id'); + if ($currentCategoryId !== (int) $value) { + return '帖子分类已禁用'; + } } return true; }