This commit is contained in:
zpc 2025-11-16 20:05:18 +08:00
parent db8c230511
commit 0211caf572
4 changed files with 406 additions and 45 deletions

View File

@ -111,5 +111,31 @@ namespace ZR.LiveForum.Model.Liveforum.Dto
public string? IsTopLabel { get; set; }
[ExcelColumn(Name = "帖子状态")]
public string? StatusLabel { get; set; }
/// <summary>
/// 帖子图片列表
/// </summary>
public List<PostImageDto> Images { get; set; } = new List<PostImageDto>();
}
/// <summary>
/// 帖子图片简单DTO
/// </summary>
public class PostImageDto
{
/// <summary>
/// 图片ID
/// </summary>
public long Id { get; set; }
/// <summary>
/// 图片URL
/// </summary>
public string ImageUrl { get; set; }
/// <summary>
/// 排序顺序
/// </summary>
public int SortOrder { get; set; }
}
}

View File

@ -64,6 +64,33 @@ namespace ZR.Service.Liveforum
var resp = ToPage(response, parm);
// 批量查询并填充图片列表
if (resp.Result != null && resp.Result.Any())
{
var postIds = resp.Result.Select(x => x.Id).ToList();
var images = Context.Queryable<T_PostImages>()
.Where(x => postIds.Contains(x.PostId))
.OrderBy(x => x.SortOrder)
.ToList();
var imagesDict = images
.GroupBy(x => x.PostId)
.ToDictionary(
g => g.Key,
g => g.Select(x => new PostImageDto
{
Id = x.Id,
ImageUrl = x.ImageUrl,
SortOrder = x.SortOrder
}).ToList()
);
Context.ThenMapper(resp.Result, item =>
{
item.Images = imagesDict.GetValueOrDefault(item.Id, new List<PostImageDto>());
});
}
return resp;
}

View File

@ -43,7 +43,7 @@
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" align="center"/>
<el-table-column prop="id" label="评论ID,主键自增" align="center" v-if="columns.showColumn('id')"/>
<el-table-column prop="id" label="评论ID" align="center" v-if="columns.showColumn('id')"/>
<el-table-column prop="postId" label="帖子ID" align="center" v-if="columns.showColumn('postId')"/>
<el-table-column prop="userId" label="用户Id" align="center" v-if="columns.showColumn('userId')"/>
<el-table-column prop="parentCommentId" label="父评论Id" align="center" v-if="columns.showColumn('parentCommentId')"/>
@ -79,26 +79,26 @@
<el-dialog :title="title" :lock-scroll="false" v-model="open" >
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :lg="12">
<el-form-item label="帖子ID" prop="postId">
<el-input v-model.number="form.postId" placeholder="请输入帖子ID" />
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="用户Id" prop="userId">
<el-input v-model.number="form.userId" placeholder="请输入用户Id" />
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="父评论Id" prop="parentCommentId">
<el-input v-model.number="form.parentCommentId" placeholder="请输入父评论Id" />
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="回复用户Id" prop="replyToUserId">
<el-input v-model.number="form.replyToUserId" placeholder="请输入回复用户Id" />
@ -110,16 +110,16 @@
<el-input type="textarea" v-model="form.content" placeholder="请输入评论内容"/>
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="评论状态" prop="status">
<el-select v-model="form.status" placeholder="请选择评论状态">
<el-option
v-for="item in options.liveforum_posts_comments"
:key="item.dictValue"
:label="item.dictLabel"
v-for="item in options.liveforum_posts_comments"
:key="item.dictValue"
:label="item.dictLabel"
:value="parseInt(item.dictValue)"></el-option>
</el-select>
</el-form-item>
@ -141,9 +141,9 @@
<script setup name="tcomments">
import { listtcomments,
deltcomments,
updatetcomments,gettcomments,
}
deltcomments,
updatetcomments,gettcomments,
}
from '@/api/liveforum/tcomments.js'
const { proxy } = getCurrentInstance()
const ids = ref([])
@ -371,4 +371,4 @@ function handleDelete(row) {
handleQuery()
</script>
</script>

View File

@ -67,59 +67,73 @@
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" align="center"/>
<el-table-column prop="id" label="id" align="center" v-if="columns.showColumn('id')"/>
<el-table-column prop="userId" label="用户ID" align="center" v-if="columns.showColumn('userId')"/>
<el-table-column prop="nickName" label="用户昵称" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('nickName')"/>
<el-table-column prop="title" label="标题" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('title')"/>
<el-table-column prop="coverImage" label="封面图片" align="center" v-if="columns.showColumn('coverImage')">
<el-table-column prop="id" label="id" align="center" width="80" v-if="columns.showColumn('id')"/>
<el-table-column prop="userId" label="用户ID" align="center" width="100" v-if="columns.showColumn('userId')"/>
<el-table-column prop="nickName" label="用户昵称" align="center" width="120" :show-overflow-tooltip="true" v-if="columns.showColumn('nickName')"/>
<el-table-column prop="title" label="标题" align="center" width="200" :show-overflow-tooltip="true" v-if="columns.showColumn('title')"/>
<el-table-column prop="coverImage" label="封面图片" align="center" width="120" v-if="columns.showColumn('coverImage')">
<template #default="scope">
<ImagePreview :src="scope.row.coverImage"></ImagePreview>
</template>
</el-table-column>
<el-table-column prop="content" label="正文内容" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('content')"/>
<el-table-column prop="categoryId" label="分类ID" align="center" v-if="columns.showColumn('categoryId')"/>
<el-table-column prop="viewCount" label="浏览次数" align="center" v-if="columns.showColumn('viewCount')"/>
<el-table-column prop="likeCount" label="点赞数量" align="center" v-if="columns.showColumn('likeCount')"/>
<el-table-column prop="commentCount" label="评论数量" align="center" v-if="columns.showColumn('commentCount')"/>
<el-table-column prop="shareCount" label="分享次数" align="center" v-if="columns.showColumn('shareCount')"/>
<el-table-column prop="isTop" label="是否置顶" align="center" v-if="columns.showColumn('isTop')">
<el-table-column prop="images" label="图片列表" align="center" width="200" v-if="columns.showColumn('images')">
<template #default="scope">
<div style="display: flex; flex-wrap: wrap; gap: 5px; justify-content: center;">
<ImagePreview
v-for="img in scope.row.images"
:key="img.id"
:src="img.imageUrl"
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;"
/>
<span v-if="!scope.row.images || scope.row.images.length === 0" style="color: #999;">暂无图片</span>
</div>
</template>
</el-table-column>
<el-table-column prop="content" label="正文内容" align="center" width="250" :show-overflow-tooltip="true" v-if="columns.showColumn('content')"/>
<el-table-column prop="categoryId" label="分类ID" align="center" width="100" v-if="columns.showColumn('categoryId')"/>
<el-table-column prop="viewCount" label="浏览次数" align="center" width="100" v-if="columns.showColumn('viewCount')"/>
<el-table-column prop="likeCount" label="点赞数量" align="center" width="100" v-if="columns.showColumn('likeCount')"/>
<el-table-column prop="commentCount" label="评论数量" align="center" width="100" v-if="columns.showColumn('commentCount')"/>
<el-table-column prop="shareCount" label="分享次数" align="center" width="100" v-if="columns.showColumn('shareCount')"/>
<el-table-column prop="isTop" label="是否置顶" align="center" width="100" v-if="columns.showColumn('isTop')">
<template #default="scope">
<dict-tag :options=" options.liveforum_action_bool " :value="scope.row.isTop" />
</template>
</el-table-column>
<el-table-column prop="isHot" label="是否热门" align="center" v-if="columns.showColumn('isHot')">
<el-table-column prop="isHot" label="是否热门" align="center" width="100" v-if="columns.showColumn('isHot')">
<template #default="scope">
<dict-tag :options=" options.liveforum_action_bool " :value="scope.row.isHot" />
</template>
</el-table-column>
<el-table-column prop="isEssence" label="是否精华" align="center" v-if="columns.showColumn('isEssence')">
<el-table-column prop="isEssence" label="是否精华" align="center" width="100" v-if="columns.showColumn('isEssence')">
<template #default="scope">
<dict-tag :options=" options.liveforum_action_bool " :value="scope.row.isEssence" />
</template>
</el-table-column>
<el-table-column prop="status" label="帖子状态" align="center" v-if="columns.showColumn('status')">
<el-table-column prop="status" label="帖子状态" align="center" width="100" v-if="columns.showColumn('status')">
<template #default="scope">
<dict-tag :options=" options.liveforum_posts_status " :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column prop="publishTime" label="发布时间" :show-overflow-tooltip="true" v-if="columns.showColumn('publishTime')"/>
<el-table-column prop="isDeleted" label="是否已删除" align="center" v-if="columns.showColumn('isDeleted')">
<el-table-column prop="publishTime" label="发布时间" width="180" :show-overflow-tooltip="true" v-if="columns.showColumn('publishTime')"/>
<el-table-column prop="isDeleted" label="是否已删除" align="center" width="100" v-if="columns.showColumn('isDeleted')">
<template #default="scope">
<dict-tag :options=" options.liveforum_action_bool " :value="scope.row.isDeleted" />
</template>
</el-table-column>
<el-table-column prop="deletedAt" label="删除时间" :show-overflow-tooltip="true" v-if="columns.showColumn('deletedAt')"/>
<el-table-column prop="createdAt" label="创建时间" :show-overflow-tooltip="true" v-if="columns.showColumn('createdAt')"/>
<el-table-column prop="updatedAt" label="更新时间" :show-overflow-tooltip="true" v-if="columns.showColumn('updatedAt')"/>
<el-table-column label="操作" width="160">
<el-table-column prop="deletedAt" label="删除时间" width="180" :show-overflow-tooltip="true" v-if="columns.showColumn('deletedAt')"/>
<el-table-column prop="createdAt" label="创建时间" width="180" :show-overflow-tooltip="true" v-if="columns.showColumn('createdAt')"/>
<el-table-column prop="updatedAt" label="更新时间" width="180" :show-overflow-tooltip="true" v-if="columns.showColumn('updatedAt')"/>
<el-table-column label="操作" width="220" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" icon="view" title="详情" @click="handlePreview(scope.row)"></el-button>
<el-button type="info" size="small" icon="picture" title="图片管理" @click="handleManageImages(scope.row)"></el-button>
<el-button type="success" size="small" icon="edit" title="编辑" v-hasPermi="['tposts:edit']" @click="handleUpdate(scope.row)"></el-button>
<el-button type="danger" size="small" icon="delete" title="删除" v-hasPermi="['tposts:delete']" @click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<pagination :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
<el-dialog :title="title" :lock-scroll="false" v-model="open" >
@ -224,6 +238,114 @@
<el-button type="primary" :loading="state.submitLoading" @click="submitForm">{{ $t('btn.submit') }}</el-button>
</template>
</el-dialog>
<!-- 图片管理弹窗 -->
<el-dialog
:title="'图片管理 - ' + currentPost.title"
v-model="imageManageDialogOpen"
width="80%"
:lock-scroll="false"
@close="closeImageManageDialog">
<!-- 工具区域 -->
<el-row :gutter="15" class="mb10">
<el-col :span="1.5">
<el-button type="primary" plain icon="plus" @click="handleAddImage">
{{ $t('btn.add') }}
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" :disabled="imageMultiple" plain icon="delete" @click="handleBatchDeleteImage">
{{ $t('btn.delete') }}
</el-button>
</el-col>
</el-row>
<!-- 图片列表表格 -->
<el-table
:data="imageList"
v-loading="imageLoading"
border
header-cell-class-name="el-table-header-cell"
highlight-current-row
@selection-change="handleImageSelectionChange">
<el-table-column type="selection" width="50" align="center"/>
<el-table-column prop="id" label="id" align="center" width="80"/>
<el-table-column prop="imageUrl" label="图片" align="center" width="150">
<template #default="scope">
<ImagePreview :src="scope.row.imageUrl"></ImagePreview>
</template>
</el-table-column>
<el-table-column prop="thumbnailUrl" label="缩略图" align="center" width="150">
<template #default="scope">
<ImagePreview :src="scope.row.thumbnailUrl" v-if="scope.row.thumbnailUrl"></ImagePreview>
<span v-else style="color: #999;"></span>
</template>
</el-table-column>
<el-table-column prop="sortOrder" label="排序顺序" align="center" width="100"/>
<el-table-column prop="imageWidth" label="宽度(像素)" align="center" width="120"/>
<el-table-column prop="imageHeight" label="高度(像素)" align="center" width="120"/>
<el-table-column prop="fileSize" label="大小(字节)" align="center" width="120"/>
<el-table-column prop="createdAt" label="上传时间" :show-overflow-tooltip="true" width="180"/>
<el-table-column label="操作" width="100" align="center">
<template #default="scope">
<el-button type="danger" size="small" icon="delete" title="删除" @click="handleDeleteImage(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination :total="imageTotal" :page="imageQueryParams.pageNum" :limit="imageQueryParams.pageSize" @pagination="getImageList" />
<!-- 添加图片表单对话框 -->
<el-dialog
title="添加图片"
v-model="imageFormOpen"
width="600px"
:lock-scroll="false"
append-to-body>
<el-form ref="imageFormRef" :model="imageForm" :rules="imageFormRules" label-width="100px">
<el-row :gutter="20">
<el-col :lg="24">
<el-form-item label="帖子ID" prop="postId">
<el-input v-model.number="imageForm.postId" disabled />
</el-form-item>
</el-col>
<el-col :lg="24">
<el-form-item label="图片" prop="imageUrl">
<UploadImage v-model="imageForm.imageUrl" :data="{ uploadType: 1 }" />
</el-form-item>
</el-col>
<el-col :lg="24">
<el-form-item label="缩略图" prop="thumbnailUrl">
<UploadImage v-model="imageForm.thumbnailUrl" :data="{ uploadType: 1 }" />
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="宽度(像素)" prop="imageWidth">
<el-input-number v-model.number="imageForm.imageWidth" :controls="true" controls-position="right" placeholder="请输入宽度(像素)" />
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="高度(像素)" prop="imageHeight">
<el-input-number v-model.number="imageForm.imageHeight" :controls="true" controls-position="right" placeholder="请输入高度(像素)" />
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="大小(字节)" prop="fileSize">
<el-input v-model.number="imageForm.fileSize" placeholder="请输入大小(字节)" />
</el-form-item>
</el-col>
<el-col :lg="12">
<el-form-item label="排序顺序" prop="sortOrder">
<el-input v-model.number="imageForm.sortOrder" placeholder="请输入排序顺序" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button text @click="cancelImageForm">{{ $t('btn.cancel') }}</el-button>
<el-button type="primary" :loading="imageFormSubmitLoading" @click="submitImageForm">{{ $t('btn.submit') }}</el-button>
</template>
</el-dialog>
</el-dialog>
</div>
</template>
@ -233,6 +355,10 @@ import { listtposts,
updatetposts,gettposts,
}
from '@/api/liveforum/tposts.js'
import { listtpostimages,
addtpostimages, deltpostimages,
}
from '@/api/liveforum/tpostimages.js'
const { proxy } = getCurrentInstance()
const ids = ref([])
const loading = ref(false)
@ -253,18 +379,19 @@ const columns = ref([
{ visible: true, align: 'center', type: '', prop: 'nickName', label: '用户昵称' ,showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'title', label: '标题' ,showOverflowTooltip: true },
{ visible: true, align: 'center', type: 'img', prop: 'coverImage', label: '封面图片' ,showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'images', label: '图片列表' ,showOverflowTooltip: false },
{ visible: true, align: 'center', type: '', prop: 'content', label: '正文内容' ,showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'categoryId', label: '分类ID' },
{ visible: true, align: 'center', type: '', prop: 'viewCount', label: '浏览次数' },
{ visible: true, align: 'center', type: '', prop: 'likeCount', label: '点赞数量' },
{ visible: false, align: 'center', type: '', prop: 'commentCount', label: '评论数量' },
{ visible: true, align: 'center', type: '', prop: 'commentCount', label: '评论数量' },
{ visible: false, align: 'center', type: '', prop: 'shareCount', label: '分享次数' },
{ visible: false, align: 'center', type: 'dict', prop: 'isTop', label: '是否置顶' ,dictType: 'liveforum_action_bool' },
{ visible: false, align: 'center', type: 'dict', prop: 'isHot', label: '是否热门' ,dictType: 'liveforum_action_bool' },
{ visible: false, align: 'center', type: 'dict', prop: 'isEssence', label: '是否精华' ,dictType: 'liveforum_action_bool' },
{ visible: false, align: 'center', type: 'dict', prop: 'status', label: '帖子状态' ,dictType: 'liveforum_posts_status' },
{ visible: false, align: 'center', type: '', prop: 'publishTime', label: '发布时间' ,showOverflowTooltip: true },
{ visible: false, align: 'center', type: 'dict', prop: 'isDeleted', label: '是否已删除' ,dictType: 'liveforum_action_bool' },
{ visible: true, align: 'center', type: 'dict', prop: 'isTop', label: '是否置顶' ,dictType: 'liveforum_action_bool' },
{ visible: true, align: 'center', type: 'dict', prop: 'isHot', label: '是否热门' ,dictType: 'liveforum_action_bool' },
{ visible: true, align: 'center', type: 'dict', prop: 'isEssence', label: '是否精华' ,dictType: 'liveforum_action_bool' },
{ visible: true, align: 'center', type: 'dict', prop: 'status', label: '帖子状态' ,dictType: 'liveforum_posts_status' },
{ visible: true, align: 'center', type: '', prop: 'publishTime', label: '发布时间' ,showOverflowTooltip: true },
{ visible: true, align: 'center', type: 'dict', prop: 'isDeleted', label: '是否已删除' ,dictType: 'liveforum_action_bool' },
{ visible: false, align: 'center', type: '', prop: 'deletedAt', label: '删除时间' ,showOverflowTooltip: true },
{ visible: false, align: 'center', type: '', prop: 'createdAt', label: '创建时间' ,showOverflowTooltip: true },
{ visible: false, align: 'center', type: '', prop: 'updatedAt', label: '更新时间' ,showOverflowTooltip: true },
@ -275,6 +402,42 @@ const dataList = ref([])
const queryRef = ref()
const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)])
//
const imageManageDialogOpen = ref(false)
const currentPost = ref({ id: null, title: '' })
const imageList = ref([])
const imageTotal = ref(0)
const imageLoading = ref(false)
const imageQueryParams = reactive({
pageNum: 1,
pageSize: 10,
sort: 'SortOrder',
sortType: 'asc',
postId: undefined,
})
const imageIds = ref([])
const imageSingle = ref(true)
const imageMultiple = ref(true)
//
const imageFormOpen = ref(false)
const imageFormRef = ref()
const imageFormSubmitLoading = ref(false)
const imageForm = reactive({
postId: null,
imageUrl: null,
thumbnailUrl: null,
imageWidth: null,
imageHeight: null,
fileSize: null,
sortOrder: 0,
})
const imageFormRules = {
postId: [{ required: true, message: "帖子ID不能为空", trigger: "blur", type: "number" }],
imageUrl: [{ required: true, message: "图片不能为空", trigger: "blur" }],
sortOrder: [{ required: true, message: "排序顺序不能为空", trigger: "blur", type: "number" }],
}
var dictParams = [
"liveforum_action_bool",
@ -504,5 +667,150 @@ function handleExport() {
})
}
/*************** 图片管理相关方法 ***************/
//
function handleManageImages(row) {
currentPost.value = {
id: row.id,
title: row.title || '未命名帖子'
}
imageQueryParams.postId = row.id
imageQueryParams.pageNum = 1
imageManageDialogOpen.value = true
getImageList()
}
//
function getImageList() {
imageLoading.value = true
listtpostimages(imageQueryParams).then(res => {
const { code, data } = res
if (code == 200) {
imageList.value = data.result
imageTotal.value = data.totalNum
imageLoading.value = false
}
})
}
//
function handleImageSelectionChange(selection) {
imageIds.value = selection.map((item) => item.id)
imageSingle.value = selection.length != 1
imageMultiple.value = !selection.length
}
//
function handleAddImage() {
resetImageForm()
imageForm.postId = currentPost.value.id
// +1
if (imageList.value && imageList.value.length > 0) {
const maxSort = Math.max(...imageList.value.map(img => img.sortOrder || 0))
imageForm.sortOrder = maxSort + 1
} else {
imageForm.sortOrder = 1
}
imageFormOpen.value = true
imageFormSubmitLoading.value = false
}
//
function resetImageForm() {
imageForm.postId = null
imageForm.imageUrl = null
imageForm.thumbnailUrl = null
imageForm.imageWidth = null
imageForm.imageHeight = null
imageForm.fileSize = null
imageForm.sortOrder = 0
proxy.resetForm("imageFormRef")
}
//
function cancelImageForm() {
imageFormOpen.value = false
resetImageForm()
}
//
function submitImageForm() {
proxy.$refs["imageFormRef"].validate((valid) => {
if (valid) {
imageFormSubmitLoading.value = true
addtpostimages(imageForm).then((res) => {
proxy.$modal.msgSuccess("新增成功")
imageFormOpen.value = false
resetImageForm()
getImageList()
//
getList()
})
.finally(() => {
setTimeout(() => {
imageFormSubmitLoading.value = false
}, 800)
})
}
})
}
//
function handleDeleteImage(row) {
const Ids = row.id
proxy
.$confirm('是否确认删除该图片?', "警告", {
confirmButtonText: proxy.$t('common.ok'),
cancelButtonText: proxy.$t('common.cancel'),
type: "warning",
})
.then(function () {
return deltpostimages(Ids)
})
.then(() => {
getImageList()
//
getList()
proxy.$modal.msgSuccess("删除成功")
})
}
//
function handleBatchDeleteImage() {
const Ids = imageIds.value
if (!Ids || Ids.length === 0) {
proxy.$modal.msgWarning("请选择要删除的图片")
return
}
proxy
.$confirm('是否确认删除选中的' + Ids.length + '张图片?', "警告", {
confirmButtonText: proxy.$t('common.ok'),
cancelButtonText: proxy.$t('common.cancel'),
type: "warning",
})
.then(function () {
return deltpostimages(Ids.join(','))
})
.then(() => {
getImageList()
//
getList()
proxy.$modal.msgSuccess("删除成功")
})
}
//
function closeImageManageDialog() {
imageManageDialogOpen.value = false
currentPost.value = { id: null, title: '' }
imageQueryParams.postId = undefined
imageList.value = []
imageTotal.value = 0
imageIds.value = []
}
handleQuery()
</script>