完善帖子评论展示
This commit is contained in:
parent
47b71807e1
commit
44b65c5c11
@ -100,4 +100,10 @@ public class ForumController {
|
|||||||
@RequestAttribute(Const.ATTR_USER_ID) int id) {
|
@RequestAttribute(Const.ATTR_USER_ID) int id) {
|
||||||
return utils.messageHandle(() -> topicService.createComment(vo, id));
|
return utils.messageHandle(() -> topicService.createComment(vo, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/comments")
|
||||||
|
public RestBean<List<CommentVO>> comments(@RequestParam @Min(0) int tid,
|
||||||
|
@RequestParam @Min(0) int page) {
|
||||||
|
return RestBean.success(topicService.comments(tid, page + 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.example.entity.vo.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CommentVO {
|
||||||
|
int id;
|
||||||
|
String content;
|
||||||
|
Date time;
|
||||||
|
String quote;
|
||||||
|
User user;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class User {
|
||||||
|
Integer uid;
|
||||||
|
String username;
|
||||||
|
String avatar;
|
||||||
|
boolean gender;
|
||||||
|
String qq;
|
||||||
|
String wx;
|
||||||
|
String phone;
|
||||||
|
String email;
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ public class TopicDetailVO {
|
|||||||
Date time;
|
Date time;
|
||||||
User user;
|
User user;
|
||||||
Interact interact;
|
Interact interact;
|
||||||
|
Long commentCount;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@ -7,6 +7,7 @@ import com.example.entity.dto.TopicType;
|
|||||||
import com.example.entity.vo.request.AddCommentVO;
|
import com.example.entity.vo.request.AddCommentVO;
|
||||||
import com.example.entity.vo.request.TopicCreateVO;
|
import com.example.entity.vo.request.TopicCreateVO;
|
||||||
import com.example.entity.vo.request.TopicUpdateVO;
|
import com.example.entity.vo.request.TopicUpdateVO;
|
||||||
|
import com.example.entity.vo.response.CommentVO;
|
||||||
import com.example.entity.vo.response.TopicPreviewVO;
|
import com.example.entity.vo.response.TopicPreviewVO;
|
||||||
import com.example.entity.vo.response.TopicTopVO;
|
import com.example.entity.vo.response.TopicTopVO;
|
||||||
import com.example.entity.vo.response.TopicDetailVO;
|
import com.example.entity.vo.response.TopicDetailVO;
|
||||||
@ -23,4 +24,5 @@ public interface TopicService extends IService<Topic> {
|
|||||||
void interact(Interact interact, boolean state);
|
void interact(Interact interact, boolean state);
|
||||||
List<TopicPreviewVO> listCollectTopic(int uid);
|
List<TopicPreviewVO> listCollectTopic(int uid);
|
||||||
String createComment(AddCommentVO vo, int uid);
|
String createComment(AddCommentVO vo, int uid);
|
||||||
|
List<CommentVO> comments(int tid, int pageNumber);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import com.example.entity.dto.*;
|
|||||||
import com.example.entity.vo.request.AddCommentVO;
|
import com.example.entity.vo.request.AddCommentVO;
|
||||||
import com.example.entity.vo.request.TopicCreateVO;
|
import com.example.entity.vo.request.TopicCreateVO;
|
||||||
import com.example.entity.vo.request.TopicUpdateVO;
|
import com.example.entity.vo.request.TopicUpdateVO;
|
||||||
|
import com.example.entity.vo.response.CommentVO;
|
||||||
import com.example.entity.vo.response.TopicDetailVO;
|
import com.example.entity.vo.response.TopicDetailVO;
|
||||||
import com.example.entity.vo.response.TopicPreviewVO;
|
import com.example.entity.vo.response.TopicPreviewVO;
|
||||||
import com.example.entity.vo.response.TopicTopVO;
|
import com.example.entity.vo.response.TopicTopVO;
|
||||||
@ -26,6 +27,7 @@ import java.util.*;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
|
public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService {
|
||||||
@ -144,6 +146,7 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
|||||||
TopicDetailVO.User user = new TopicDetailVO.User();
|
TopicDetailVO.User user = new TopicDetailVO.User();
|
||||||
BeanUtils.copyProperties(topic, user);
|
BeanUtils.copyProperties(topic, user);
|
||||||
vo.setUser(this.fillUserDetailsByPrivacy(user, topic.getUid()));
|
vo.setUser(this.fillUserDetailsByPrivacy(user, topic.getUid()));
|
||||||
|
vo.setCommentCount(commentMapper.selectCount(Wrappers.<TopicComment>query().eq("tid", tid)));
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +172,27 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CommentVO> comments(int tid, int pageNumber) {
|
||||||
|
Page<TopicComment> comments = Page.of(pageNumber, 10);
|
||||||
|
commentMapper.selectPage(comments, Wrappers.<TopicComment>query().eq("tid", tid));
|
||||||
|
return comments.getRecords().stream().map(dto -> {
|
||||||
|
CommentVO vo = new CommentVO();
|
||||||
|
BeanUtils.copyProperties(dto, vo);
|
||||||
|
if(dto.getQuote() > 0) {
|
||||||
|
JSONObject object = JSONObject.parseObject(commentMapper.selectOne(Wrappers.<TopicComment>query()
|
||||||
|
.eq("id", dto.getQuote())).getContent());
|
||||||
|
StringBuilder text = new StringBuilder();
|
||||||
|
this.shortContent(object.getJSONArray("ops"), text, ignore -> {});
|
||||||
|
vo.setQuote(text.toString());
|
||||||
|
}
|
||||||
|
CommentVO.User user = new CommentVO.User();
|
||||||
|
this.fillUserDetailsByPrivacy(user, dto.getUid());
|
||||||
|
vo.setUser(user);
|
||||||
|
return vo;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasInteract(int tid, int uid, String type){
|
private boolean hasInteract(int tid, int uid, String type){
|
||||||
String key = tid + ":" + uid;
|
String key = tid + ":" + uid;
|
||||||
if(template.opsForHash().hasKey(type, key))
|
if(template.opsForHash().hasKey(type, key))
|
||||||
@ -232,18 +256,22 @@ public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements
|
|||||||
List<String> images = new ArrayList<>();
|
List<String> images = new ArrayList<>();
|
||||||
StringBuilder previewText = new StringBuilder();
|
StringBuilder previewText = new StringBuilder();
|
||||||
JSONArray ops = JSONObject.parseObject(topic.getContent()).getJSONArray("ops");
|
JSONArray ops = JSONObject.parseObject(topic.getContent()).getJSONArray("ops");
|
||||||
|
this.shortContent(ops, previewText, obj -> images.add(obj.toString()));
|
||||||
|
vo.setText(previewText.toString());
|
||||||
|
vo.setImages(images);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shortContent(JSONArray ops, StringBuilder previewText, Consumer<Object> imageHandler) {
|
||||||
for (Object op : ops) {
|
for (Object op : ops) {
|
||||||
Object insert = JSONObject.from(op).get("insert");
|
Object insert = JSONObject.from(op).get("insert");
|
||||||
if(insert instanceof String text) {
|
if(insert instanceof String text) {
|
||||||
if(previewText.length() >= 300) continue;
|
if(previewText.length() >= 300) continue;
|
||||||
previewText.append(text);
|
previewText.append(text);
|
||||||
} else if(insert instanceof Map<?, ?> map) {
|
} else if(insert instanceof Map<?, ?> map) {
|
||||||
Optional.ofNullable(map.get("image")).ifPresent(obj -> images.add(obj.toString()));
|
Optional.ofNullable(map.get("image")).ifPresent(imageHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vo.setText(previewText.toString());
|
|
||||||
vo.setImages(images);
|
|
||||||
return vo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean textLimitCheck(JSONObject object) {
|
private boolean textLimitCheck(JSONObject object) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {QuillEditor} from "@vueup/vue-quill";
|
import {Delta, QuillEditor} from "@vueup/vue-quill";
|
||||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||||
import {post} from "@/net";
|
import {post} from "@/net";
|
||||||
import {reactive, ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -10,10 +10,14 @@ const props = defineProps({
|
|||||||
show: Boolean,
|
show: Boolean,
|
||||||
tid: String
|
tid: String
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close', 'comment'])
|
||||||
|
|
||||||
const content = ref()
|
const content = ref()
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
content.value = new Delta()
|
||||||
|
}
|
||||||
|
|
||||||
function submitComment() {
|
function submitComment() {
|
||||||
post('/api/forum/add-comment', {
|
post('/api/forum/add-comment', {
|
||||||
tid: props.tid,
|
tid: props.tid,
|
||||||
@ -21,7 +25,7 @@ function submitComment() {
|
|||||||
quote: props.quote
|
quote: props.quote
|
||||||
}, () => {
|
}, () => {
|
||||||
ElMessage.success('评论成功')
|
ElMessage.success('评论成功')
|
||||||
emit('close')
|
emit('comment')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -29,7 +33,7 @@ function submitComment() {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-drawer :model-value="show"
|
<el-drawer :model-value="show"
|
||||||
title="发表回复"
|
title="发表回复" @open="init"
|
||||||
:size="270" direction="btt"
|
:size="270" direction="btt"
|
||||||
@close="emit('close')"
|
@close="emit('close')"
|
||||||
:show-close="false">
|
:show-close="false">
|
||||||
|
@ -23,7 +23,8 @@ const topic = reactive({
|
|||||||
data: null,
|
data: null,
|
||||||
like: false,
|
like: false,
|
||||||
collect: false,
|
collect: false,
|
||||||
comments: []
|
comments: [],
|
||||||
|
page: 1
|
||||||
})
|
})
|
||||||
const edit = ref(false)
|
const edit = ref(false)
|
||||||
const comment = reactive({
|
const comment = reactive({
|
||||||
@ -38,14 +39,15 @@ function init() {
|
|||||||
topic.like = data.interact.like
|
topic.like = data.interact.like
|
||||||
topic.collect = data.interact.collect
|
topic.collect = data.interact.collect
|
||||||
})
|
})
|
||||||
|
loadComments(0)
|
||||||
}
|
}
|
||||||
init()
|
init()
|
||||||
|
|
||||||
const content = computed(() => {
|
function convertToHtml(content){
|
||||||
const ops = JSON.parse(topic.data.content).ops
|
const ops = JSON.parse(content).ops
|
||||||
const converter = new QuillDeltaToHtmlConverter(ops, { inlineStyles: true });
|
const converter = new QuillDeltaToHtmlConverter(ops, {inlineStyles: true});
|
||||||
return converter.convert();
|
return converter.convert();
|
||||||
})
|
}
|
||||||
|
|
||||||
function interact(type, message) {
|
function interact(type, message) {
|
||||||
get(`/api/forum/interact?tid=${tid}&type=${type}&state=${!topic[type]}`, () => {
|
get(`/api/forum/interact?tid=${tid}&type=${type}&state=${!topic[type]}`, () => {
|
||||||
@ -69,6 +71,12 @@ function updateTopic(editor) {
|
|||||||
init()
|
init()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadComments(page){
|
||||||
|
topic.comments = null
|
||||||
|
topic.page = page + 1
|
||||||
|
get(`/api/forum/comments?tid=${tid}&page=${page}`, data => topic.comments = data)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -109,7 +117,7 @@ function updateTopic(editor) {
|
|||||||
<div class="desc" style="margin: 0 5px">{{topic.data.user.desc}}</div>
|
<div class="desc" style="margin: 0 5px">{{topic.data.user.desc}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic-main-right">
|
<div class="topic-main-right">
|
||||||
<div class="topic-content" v-html="content"></div>
|
<div class="topic-content" v-html="convertToHtml(topic.data.content)"></div>
|
||||||
<el-divider/>
|
<el-divider/>
|
||||||
<div style="font-size: 13px;color: grey;text-align: center">
|
<div style="font-size: 13px;color: grey;text-align: center">
|
||||||
<div>发帖时间: {{new Date(topic.data.time).toLocaleString()}}</div>
|
<div>发帖时间: {{new Date(topic.data.time).toLocaleString()}}</div>
|
||||||
@ -131,15 +139,53 @@ function updateTopic(editor) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topic-main">
|
<transition name="el-fade-in-linear" mode="out-in">
|
||||||
|
<div v-if="topic.comments">
|
||||||
|
<div class="topic-main" style="margin-top: 10px" v-for="item in topic.comments">
|
||||||
|
<div class="topic-main-left">
|
||||||
|
<el-avatar :src="axios.defaults.baseURL + '/images' + item.user.avatar"
|
||||||
|
:size="60"></el-avatar>
|
||||||
|
<div style="margin-left: 10px">
|
||||||
|
<div style="font-weight: bold;font-size: 18px">
|
||||||
|
{{item.user.username}}
|
||||||
|
<span style="color: hotpink" v-if="item.user.gender">
|
||||||
|
<el-icon><Female/></el-icon>
|
||||||
|
</span>
|
||||||
|
<span style="color: dodgerblue" v-if="item.user.gender === false">
|
||||||
|
<el-icon><Male/></el-icon>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="desc">{{item.user.email || '已隐藏电子邮件'}}</div>
|
||||||
|
</div>
|
||||||
|
<el-divider style="margin: 10px 0"></el-divider>
|
||||||
|
<div style="text-align: left;margin: 0 5px">
|
||||||
|
<div class="desc">微信号: {{item.user.wx}}</div>
|
||||||
|
<div class="desc">QQ号: {{item.user.qq}}</div>
|
||||||
|
<div class="desc">手机号: {{item.user.phone}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="topic-main-right">
|
||||||
|
<div style="font-size: 13px;color: grey">
|
||||||
|
<div>评论时间: {{new Date(item.time).toLocaleString()}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="topic-content" v-html="convertToHtml(item.content)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="width: fit-content;margin: 20px auto">
|
||||||
|
<el-pagination background layout="prev, pager, next"
|
||||||
|
v-model:current-page="topic.page"
|
||||||
|
@current-change="number => loadComments(number - 1)"
|
||||||
|
:total="topic.data.commentCount" :page-size="10" hide-on-single-page/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
<topic-editor :show="edit" @close="edit = false" v-if="topic.data"
|
<topic-editor :show="edit" @close="edit = false" v-if="topic.data"
|
||||||
:default-title="topic.data.title" :default-text="topic.data.content"
|
:default-title="topic.data.title" :default-text="topic.data.content"
|
||||||
:default-type="topic.data.type" submit-button="更新帖子内容"
|
:default-type="topic.data.type" submit-button="更新帖子内容"
|
||||||
:submit="updateTopic"/>
|
:submit="updateTopic"/>
|
||||||
<topic-comment-editor :show="comment.show" @close="comment.show = false"
|
<topic-comment-editor :show="comment.show" @close="comment.show = false"
|
||||||
:quote="comment.quote" :tid="tid"/>
|
:quote="comment.quote" :tid="tid"
|
||||||
|
@comment="comment.show = false;loadComments(Math.min(1, Math.ceil(topic.data.commentCount / 10)))"/>
|
||||||
<div class="add-comment" @click="comment.show = true">
|
<div class="add-comment" @click="comment.show = true">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon><Plus /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user