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>
|
||||
|
||||
Reference in New Issue
Block a user