feat: support full community post editing
This commit is contained in:
@@ -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 })
|
||||
}
|
||||
|
||||
// 帖子审核状态
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
<el-table-column label="类型" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.post_type === 1" type="warning">赛事分析</el-tag>
|
||||
<el-tag v-else-if="row.post_type === 2" type="success">战绩分享</el-tag>
|
||||
<el-tag v-else>普通</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -135,8 +136,8 @@
|
||||
@click="handleDetail(row.id)">
|
||||
详情
|
||||
</el-button>
|
||||
<el-button v-perms="['community.communityPost/editContent']" type="primary" link
|
||||
@click="handleEdit(row)">
|
||||
<el-button v-perms="['community.communityPost/edit']" type="primary" link
|
||||
@click="handleEdit(row.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button v-if="row.status === 0" v-perms="['community.communityPost/updateStatus']"
|
||||
@@ -203,16 +204,161 @@
|
||||
<el-button @click="showDetail = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="showEdit" title="编辑帖子内容" width="700px" destroy-on-close>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="帖子内容">
|
||||
<el-input v-model="editData.content" type="textarea" :rows="14" :maxlength="16000"
|
||||
show-word-limit resize="vertical" placeholder="请输入帖子内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-dialog v-model="showEdit" title="编辑帖子" width="920px" destroy-on-close>
|
||||
<el-scrollbar max-height="68vh">
|
||||
<el-form ref="editFormRef" v-loading="editLoading" :model="editData" :rules="editRules"
|
||||
label-width="104px" class="pr-5">
|
||||
<el-divider content-position="left">基础信息</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="帖子ID">
|
||||
<el-input :model-value="String(editData.id || '')" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="用户ID" prop="user_id">
|
||||
<el-input-number v-model="editData.user_id" :min="1" :precision="0"
|
||||
controls-position="right" class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="发布时间" prop="create_time">
|
||||
<el-date-picker v-model="editData.create_time" type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择发布时间" class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="来源标识" prop="origin_id">
|
||||
<el-input v-model="editData.origin_id" maxlength="100" clearable
|
||||
placeholder="例如 truthsocial:123456" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="更新时间">
|
||||
<el-input :model-value="editData.update_time || '-'" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="帖子类型" prop="post_type">
|
||||
<el-select v-model="editData.post_type" class="w-full">
|
||||
<el-option label="普通帖子" :value="0" />
|
||||
<el-option label="赛事分析" :value="1" />
|
||||
<el-option label="战绩分享" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="帖子分类" prop="category_id">
|
||||
<el-select v-model="editData.category_id" filterable class="w-full">
|
||||
<el-option label="未分类" :value="0" />
|
||||
<el-option v-for="item in categoryList" :key="item.id"
|
||||
:label="item.status === 1 ? item.name : `${item.name}(禁用)`"
|
||||
:value="item.id" :disabled="item.status !== 1 && item.id !== editData.category_id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="关联赛事ID" prop="match_id">
|
||||
<el-input-number v-model="editData.match_id" :min="0" :precision="0"
|
||||
controls-position="right" class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="帖子标签" prop="tag_ids">
|
||||
<el-select v-model="editData.tag_ids" multiple filterable :multiple-limit="3"
|
||||
collapse-tags collapse-tags-tooltip class="w-full" placeholder="最多选择3个标签">
|
||||
<el-option v-for="item in tagList" :key="item.id"
|
||||
:label="item.status === 1 ? item.name : `${item.name}(禁用)`"
|
||||
:value="item.id" :disabled="item.status !== 1 && !editData.tag_ids.includes(item.id)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">正文与素材</el-divider>
|
||||
<el-form-item label="帖子内容" prop="content">
|
||||
<el-input v-model="editData.content" type="textarea" :rows="8" :maxlength="16000"
|
||||
show-word-limit resize="vertical" placeholder="帖子内容和图片不能同时为空" />
|
||||
</el-form-item>
|
||||
<el-form-item label="帖子图片">
|
||||
<material-picker v-model="editData.images" :limit="18" />
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">付费设置</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="收费阅读" prop="is_paid">
|
||||
<el-switch v-model="editData.is_paid" :active-value="1" :inactive-value="0"
|
||||
@change="handlePaidChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="积分价格" prop="price_points">
|
||||
<el-input-number v-model="editData.price_points" :min="0" :max="9999" :precision="0"
|
||||
:disabled="editData.is_paid !== 1" controls-position="right" class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="免费预览" prop="free_content_len">
|
||||
<el-input-number v-model="editData.free_content_len" :min="20" :max="500"
|
||||
:precision="0" :disabled="editData.is_paid !== 1" controls-position="right"
|
||||
class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">统计数据(手动修正)</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col v-for="item in countFields" :key="item.key" :span="8">
|
||||
<el-form-item :label="item.label" :prop="item.key">
|
||||
<el-input-number v-model="editData[item.key]" :min="0" :precision="0"
|
||||
controls-position="right" class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">展示与状态</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="审核状态" prop="status">
|
||||
<el-select v-model="editData.status" class="w-full">
|
||||
<el-option label="待审核" :value="0" />
|
||||
<el-option label="已通过" :value="1" />
|
||||
<el-option label="已拒绝/隐藏" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="推荐排序" prop="sort">
|
||||
<el-input-number v-model="editData.sort" :min="0" :max="999999" :precision="0"
|
||||
controls-position="right" class="!w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="展示开关">
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-2">
|
||||
<el-checkbox v-model="editData.is_top" :true-value="1" :false-value="0">置顶</el-checkbox>
|
||||
<el-checkbox v-model="editData.is_recommend" :true-value="1"
|
||||
:false-value="0">推荐</el-checkbox>
|
||||
<el-checkbox v-model="editData.is_hot" :true-value="1" :false-value="0">热门</el-checkbox>
|
||||
<el-checkbox v-model="editData.is_topic" :true-value="1"
|
||||
:false-value="0">话题</el-checkbox>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-divider content-position="left">扩展数据</el-divider>
|
||||
<el-form-item label="扩展JSON" prop="ext">
|
||||
<el-input v-model="editData.ext" type="textarea" :rows="6" resize="vertical"
|
||||
placeholder='请输入合法 JSON,例如 {"source":"manual"}' />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<el-button @click="showEdit = false">取消</el-button>
|
||||
<el-button type="primary" :loading="editSubmitLoading" @click="handleSubmitEdit">
|
||||
<el-button type="primary" :loading="editSubmitLoading" :disabled="editLoading"
|
||||
@click="handleSubmitEdit">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -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<any[]>([])
|
||||
const tagList = ref<any[]>([])
|
||||
|
||||
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<typeof createEditData>
|
||||
type CountFieldKey = 'paid_count' | 'view_count' | 'like_count' | 'comment_count' | 'share_count'
|
||||
|
||||
const handleEdit = (post: any) => {
|
||||
const editData = reactive<EditData>(createEditData())
|
||||
const editFormRef = ref<FormInstance>()
|
||||
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()
|
||||
</script>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
);
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user