SpringBoot2+Vue2实战(十七)vue集成markdown实现多级评论功能

新建数据库表

Article

@Data
@TableName("sys_article")
public class Article implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 标题
     */
    private String name;

    /**
     * 内容
     */
    private String content;

    /**
     * 发布人
     */
    private String user;

    /**
     * 时间
     */
    private String time;


}

 ArticleController

@RestController
@RequestMapping("/article")
public class ArticleController {

    @Resource
    private ArticleService articleService;

    //修改或增加
    @PostMapping("/saveArticle")
    public Result saveArticle(@RequestBody Article article) {
        //新增或修改
        return Result.success(articleService.saveOrUpdate(article));
    }

    @GetMapping("/findAll")
    public Result findAll() {
        return Result.success(articleService.list());
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        return Result.success(articleService.removeById(id));
    }

    //批量删除
    @PostMapping("/del/batch")
    public Result deleteBatch(@RequestBody List<Integer> ids) {
        return Result.success(articleService.removeBatchByIds(ids));
    }

    //分页查询 mybatis-plus方式
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "") String name,
                             @RequestParam Integer pageSize,
                             @RequestParam Integer pageNum) {

        IPage<Article> page = new Page<>(pageNum, pageSize);
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
        if (StrUtil.isNotBlank(name)) {
            queryWrapper.like("name", name);
        }
        return Result.success(articleService.page(page, queryWrapper));
    }
}

Article.vue

<template>
  <div>
    <div>
      <div style="margin: 10px 0">
        <el-input style="width: 200px" placeholder="请输入名称" suffix-icon="el-icon-search"
                  v-model="name"></el-input>
        <el-button class="ml-5" type="primary" @click="load">搜索</el-button>
        <el-button type="warning" @click="reset">重置</el-button>
      </div>
      <div style="margin: 10px 0">
        <el-button type="primary" @click="handleAdd" v-if="user.role === 'ROLE_ADMIN'">新增 <i
            class="el-icon-circle-plus-outline"></i></el-button>

        <el-popconfirm
            class="ml-5"
            confirm-button-text='确定'
            cancel-button-text='我再想想'
            icon="el-icon-info"
            icon-color="red"
            title="您确定批量删除这些数据吗?"
            @confirm="delBatch"
        >
          <el-button type="danger" slot="reference" v-if="user.role === 'ROLE_ADMIN'">批量删除 <i
              class="el-icon-remove-outline"></i></el-button>
        </el-popconfirm>
      </div>

      <el-table :data="tableData" border stripe :header-cell-class-name="'headerBg'"
                @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column prop="id" label="ID" width="80"></el-table-column>
        <el-table-column prop="name" label="文章标题"></el-table-column>
        <el-table-column prop="content" label="文章内容"></el-table-column>
        <el-table-column prop="user" label="发布人"></el-table-column>
        <el-table-column prop="time" label="发布时间"></el-table-column>

        <el-table-column label="操作" width="280" align="center">
          <template slot-scope="scope">
            <el-button type="success" @click="handleEdit(scope.row)" v-if="user.role === 'ROLE_ADMIN'">编辑 <i
                class="el-icon-edit"></i></el-button>
            <el-popconfirm
                class="ml-5"
                confirm-button-text='确定'
                cancel-button-text='我再想想'
                icon="el-icon-info"
                icon-color="red"
                title="您确定删除吗?"
                @confirm="del(scope.row.id)"
            >
              <el-button type="danger" slot="reference" v-if="user.role === 'ROLE_ADMIN'">删除 <i
                  class="el-icon-remove-outline"></i></el-button>
            </el-popconfirm>
          </template>
        </el-table-column>
      </el-table>
      <div style="padding: 10px 0">
        <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="pageNum"
            :page-sizes="[2, 5, 10, 20]"
            :page-size="pageSize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="total">
        </el-pagination>
      </div>

      <el-dialog title="文章信息" :visible.sync="dialogFormVisible" width="60%">
        <el-form label-width="80px" size="small">
          <el-form-item label="标题">
            <el-input v-model="form.name" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="内容">
            <el-input v-model="form.content" autocomplete="off"></el-input>
          </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
          <el-button @click="dialogFormVisible = false">取 消</el-button>
          <el-button type="primary" @click="save">确 定</el-button>
        </div>
      </el-dialog>
    </div>
  </div>
</template>

<script>
export default {
  name: "Article",
  data() {
    return {
      form: {},
      tableData: [],
      name: "",
      multipleSelection: [],
      pageNum: 1,
      pageSize: 5,
      total: 0,
      dialogFormVisible: false,
      user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {}
    }
  }, created() {
    this.load()
  },
  methods: {
    load() {
      this.request.get("/article/selectPage", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name
        }
      }).then(res => {
        //注意data
        this.tableData = res.data.records
        this.total = res.data.total
      })
    },
    changeEnable(row) {
      this.request.post("/article/update", row).then(res => {
        if (res.code === '200') {
          this.$message.success("更新成功")
        }
      })
    },
    del(id) {
      this.request.delete("/article/" + id).then(res => {
        if (res.code === '200') {
          this.$message.success("删除成功")
          this.load()
        } else {
          this.$message.error("删除失败")
        }
      })
    },

    save() {
      this.request.post("/article/saveArticle", this.form).then(res => {
        if (res.code === '200') {
          this.$message.success("保存成功")
          this.dialogFormVisible = false
          this.load()
        } else {
          this.$message.error("保存失败")
        }
      })
    },
    handleAdd() {
      this.dialogFormVisible = true
      this.form = {}
    },
    handleEdit(row) {
      this.form = JSON.parse(JSON.stringify(row))
      this.dialogFormVisible = true
    },

    handleSelectionChange(val) {
      console.log(val)
      this.multipleSelection = val
    },
    delBatch() {
      let ids = this.multipleSelection.map(v => v.id)  // [{}, {}, {}] => [1,2,3]
      this.request.post("/article/del/batch", ids).then(res => {
        if (res.data) {
          this.$message.success("批量删除成功")
          this.load()
        } else {
          this.$message.error("批量删除失败")
        }
      })
    },
    reset() {
      this.name = ""
      this.load()
    },
    handleSizeChange(pageSize) {
      console.log(pageSize)
      this.pageSize = pageSize
      this.load()
    },
    handleCurrentChange(pageNum) {
      console.log(pageNum)
      this.pageNum = pageNum
      this.load()
    },
    handleFileUploadSuccess(res) {
      console.log(res)
      this.load()
    },
    download(url) {
      window.open(url)
    }
  }
}
</script>

<style>

</style>

一、集成markdown

1.下载

npm install [email protected] -S

2.全局注册

main.js

import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'

Vue.use(mavonEditor)

二、文章上传功能实现

Article.vue

//绑定事件
<el-form-item label="文章内容">
            <mavon-editor ref="md" v-model="form.content" :ishljs="true" @imgAdd="imgAdd"/>
          </el-form-item>


//导入包

import axios from "axios";


//方法

//绑定@imgAdd event
    imgAdd(pos,$file){
      let $vm =this.$refs.md
      //第一步,将图片上传到服务器
      const formData = new FormData();
      formData.append('file',$file);
      axios({
        url:'http://localhost:9090/file/upload',
        method:'post',
        data:formData,
        heards:{'Content-Type':'multipart/form-data'},
      }).then((res) =>{
        //第二步,将要返回的url替换到原本位置![...](./0) -> ![...](url)
        $vm.$img2Url(pos,res.data)
      })
    },

 ArticleController

 //修改或增加
    @PostMapping("/saveArticle")
    public Result saveArticle(@RequestBody Article article) {
        if (article.getId() == null){//新增
            article.setTime(DateUtil.now());
            article.setUser(TokenUtils.getCurrentUser().getNickname());
        }
        //新增或修改
        return Result.success(articleService.saveOrUpdate(article));
    }

 内容预览实现

//绑定事件
<el-table-column prop="content" label="文章内容">
          <template slot-scope="scope">
            <el-button @click="view(scope.row.content)" type="primary">查看内容</el-button>
          </template>
        </el-table-column>


//显示文章内容
<el-dialog title="文章信息" :visible.sync="viewDialogVis" width="60%">
        <el-card>
          <div>
            <mavon-editor
                class="md"
                :value="content"
                :subfield="false"
                :defaultOpen="'preview'"
                :toolbarsFlag="false"
                :scrollStyle="true"
                :ishljs="true"
            />
          </div>
        </el-card>
      </el-dialog>


//定义变量

 content: '',
      viewDialogVis: false


//编写方法

view(content) {
      this.content = content
      this.viewDialogVis = true
    },

前台显示:

router/index.js

{
                path: 'article',
                name: 'FrontArticle',
                component: () => import('../views/front/Article.vue')
            },

front/Article.vue

<template>
  <div>
    <div>
      <div style="margin: 10px 0">
        <el-input style="width: 300px" placeholder="请输入名称" suffix-icon="el-icon-search"
                  v-model="name"></el-input>
        <el-button class="ml-5" type="primary" @click="load" size="small">搜索</el-button>
        <el-button type="warning" @click="reset" size="small">重置</el-button>
      </div>

      <div style="padding: 10px 0">
        <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="pageNum"
            :page-sizes="[2, 5, 10, 20]"
            :page-size="pageSize"
            layout="total, prev,pager, next"
            :total="total">
        </el-pagination>
      </div>

      <el-dialog title="文章信息" :visible.sync="dialogFormVisible" width="60%">
        <el-form label-width="80px" size="small">
          <el-form-item label="文章标题">
            <el-input v-model="form.name" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="文章内容">
            <mavon-editor ref="md" v-model="form.content" :ishljs="true" @imgAdd="imgAdd"/>
          </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
          <el-button @click="dialogFormVisible = false">取 消</el-button>
          <el-button type="primary" @click="save">确 定</el-button>
        </div>
      </el-dialog>


      <el-dialog title="文章信息" :visible.sync="viewDialogVis" width="60%">
        <el-card>
          <div>
            <mavon-editor
                class="md"
                :value="content"
                :subfield="false"
                :defaultOpen="'preview'"
                :toolbarsFlag="false"
                :scrollStyle="true"
                :ishljs="true"
            />
          </div>
        </el-card>
      </el-dialog>

    </div>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "Article",
  data() {
    return {
      form: {},
      tableData: [],
      name: "",
      multipleSelection: [],
      pageNum: 1,
      pageSize: 5,
      total: 0,
      dialogFormVisible: false,
      user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {},
      content: '',
      viewDialogVis: false
    }
  }, created() {
    this.load()
  },
  methods: {

    view(content) {
      this.content = content
      this.viewDialogVis = true
    },


    //绑定@imgAdd event
    imgAdd(pos, $file) {
      let $vm = this.$refs.md
      //第一步,将图片上传到服务器
      const formData = new FormData();
      formData.append('file', $file);
      axios({
        url: 'http://localhost:9090/file/upload',
        method: 'post',
        data: formData,
        headers: {'Content-Type': 'multipart/form-data'},
      }).then((res) => {
        //第二步,将要返回的url替换到原本位置![...](./0) -> ![...](url)
        $vm.$img2Url(pos, res.data)
      })
    },


    load() {
      this.request.get("/article/selectPage", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name
        }
      }).then(res => {
        //注意data
        this.tableData = res.data.records
        this.total = res.data.total
      })
    },
    changeEnable(row) {
      this.request.post("/article/update", row).then(res => {
        if (res.code === '200') {
          this.$message.success("更新成功")
        }
      })
    },
    del(id) {
      this.request.delete("/article/" + id).then(res => {
        if (res.code === '200') {
          this.$message.success("删除成功")
          this.load()
        } else {
          this.$message.error("删除失败")
        }
      })
    },

    save() {
      this.request.post("/article/saveArticle", this.form).then(res => {
        if (res.code === '200') {
          this.$message.success("保存成功")
          this.dialogFormVisible = false
          this.load()
        } else {
          this.$message.error("保存失败")
        }
      })
    },
    handleAdd() {
      this.dialogFormVisible = true
      this.form = {}
    },
    handleEdit(row) {
      this.form = JSON.parse(JSON.stringify(row))
      this.dialogFormVisible = true
    },

    handleSelectionChange(val) {
      console.log(val)
      this.multipleSelection = val
    },
    delBatch() {
      let ids = this.multipleSelection.map(v => v.id)  // [{}, {}, {}] => [1,2,3]
      this.request.post("/article/del/batch", ids).then(res => {
        if (res.data) {
          this.$message.success("批量删除成功")
          this.load()
        } else {
          this.$message.error("批量删除失败")
        }
      })
    },
    reset() {
      this.name = ""
      this.load()
    },
    handleSizeChange(pageSize) {
      console.log(pageSize)
      this.pageSize = pageSize
      this.load()
    },
    handleCurrentChange(pageNum) {
      console.log(pageNum)
      this.pageNum = pageNum
      this.load()
    },
    handleFileUploadSuccess(res) {
      console.log(res)
      this.load()
    },
    download(url) {
      window.open(url)
    }
  }
}
</script>

<style>

</style>

渲染列表:

<template>
  <div style="color:#666">
    <div style="margin: 10px 0">
      <el-input style="width: 300px" placeholder="请输入名称" suffix-icon="el-icon-search"
                v-model="name"></el-input>
      <el-button class="ml-5" type="primary" @click="load" size="small">搜索</el-button>
      <el-button type="warning" @click="reset" size="small">重置</el-button>
    </div>

    <div style="margin: 10px 0">
      <div style="padding: 10px 0; border-bottom: 1px dashed #ccc" v-for="item in tableData" :key="item.id">
        <div class="pd-10" style="font-size: 20px;color:#3F5EF8;cursor: pointer"
             @click="$router.push('/front/articleDetail?id='+item.id)">{
   
   { item.name }}
        </div>
        <div style="font-size: 14px">
          <i class="el-icon-user-solid"></i><span>{
   
   { item.user }}</span>
          <i class="el-icon-time" style="margin-left: 10px"></i><span>{
   
   { item.time }}</span>
        </div>
      </div>
    </div>

    <div style="padding: 10px 0">
      <el-pagination
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
          :current-page="pageNum"
          :page-sizes="[2, 5, 10, 20]"
          :page-size="pageSize"
          layout="total, prev,pager, next"
          :total="total">
      </el-pagination>
    </div>

  </div>
</template>

<script>

export default {
  name: "Article",
  data() {
    return {
      tableData: [],
      name: "",
      pageNum: 1,
      pageSize: 5,
      total: 0,
      user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {},
      content: '',
    }
  }, created() {
    this.load()
  },
  methods: {

    load() {
      this.request.get("/article/selectPage", {
        params: {
          pageNum: this.pageNum,
          pageSize: this.pageSize,
          name: this.name
        }
      }).then(res => {
        //注意data
        this.tableData = res.data.records
        this.total = res.data.total
      })
    },
    reset() {
      this.name = ""
      this.load()
    },
    handleSizeChange(pageSize) {
      console.log(pageSize)
      this.pageSize = pageSize
      this.load()
    },
    handleCurrentChange(pageNum) {
      console.log(pageNum)
      this.pageNum = pageNum
      this.load()
    },
  }
}
</script>

<style>

</style>

三、前台文章详情显示

ArticelController

@Resource
    private ArticleMapper articleMapper;



    @GetMapping("/detail/{id}")
    public Result getById(@PathVariable("id") Integer id){
        return Result.success(articleMapper.selectById(id));
    }

 ArticleDetail

<template>
  <div style="color:#666">
    <div>
      <div class="pd-10" style="font-size: 20px;color:#3F5EF8;cursor: pointer">{
   
   { article.name }}</div>
      <div style="font-size: 14px">
        <i class="el-icon-user-solid"></i><span>{
   
   { article.user }}</span>
        <i class="el-icon-time" style="margin-left: 10px"></i><span>{
   
   { article.time }}</span>
      </div>
    </div>
    <div>
      <mavon-editor
          class="md"
          :value="article.content"
          :subfield="false"
          :defaultOpen="'preview'"
          :toolbarsFlag="false"
          :scrollStyle="true"
          :ishljs="true"
      />
    </div>
  </div>
</template>

<script>

export default {
  name: "Article",
  data() {
    return {
      article: {},
      user: localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : {},
    }
  }, created() {
    this.load()
  },
  methods: {

    load() {
      let id = this.$route.query.id
      this.request.get("/article/detail/"+id, {
      }).then(res => {
        //注意data
        this.article = res.data
      })
    },
  }
}
</script>

<style>

</style>

四、评论功能

新建数据库表:

 Comment

@Data
@TableName("t_comment")
public class Comment implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 评论内容
     */
    private String content;

    /**
     * 用户id,用于关联用户头像
     */
    private Integer userId;

    /**
     * 时间
     */
    private String time;

    /**
     * 父id
     */
    private Integer pid;

    /**
     * 最上级评论id
     */
    private Integer originId;

    /**
     * 关联文章的article
     */
    private Integer articleId;


    @TableField(exist = false)
    private String nickname;

    @TableField(exist = false)
    private String avatarUrl;

}

CommentController

@RestController
@RequestMapping("/comment")
public class CommentController {

    @Resource
    private CommentService commentService;

    @Resource
    private CommentMapper commentMapper;

    @GetMapping("/detail/{id}")
    public Result getById(@PathVariable("id") Integer id){
        return Result.success(commentMapper.selectById(id));
    }

    //修改或增加
    @PostMapping("/saveComment")
    public Result saveComment(@RequestBody Comment comment) {
        //新增或修改
        return Result.success(commentService.saveOrUpdate(comment));
    }

    @GetMapping("/findAll")
    public Result findAll() {
        return Result.success(commentService.list());
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable("id") Integer id) {
        return Result.success(commentService.removeById(id));
    }

    //批量删除
    @PostMapping("/del/batch")
    public Result deleteBatch(@RequestBody List<Integer> ids) {
        return Result.success(commentService.removeBatchByIds(ids));
    }

    //分页查询 mybatis-plus方式
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "") String name,
                             @RequestParam Integer pageSize,
                             @RequestParam Integer pageNum) {

        IPage<Comment> page = new Page<>(pageNum, pageSize);
        QueryWrapper<Comment> queryWrapper = new QueryWrapper<>();
        if (StrUtil.isNotBlank(name)) {
            queryWrapper.like("name", name);
        }
        return Result.success(commentService.page(page, queryWrapper));
    }

}

实现

①查询评论实现

Comment

 @TableField(exist = false)
    private String nickname;

    @TableField(exist = false)
    private String avatarUrl;

CommentController

 @GetMapping("/findCommentDetail/{articleId}")
    public Result findCommentDetail(@PathVariable("articleId") Integer articleId) {
        List<Comment> articleComments = commentService.findCommentDetail(articleId);
        return Result.success(articleComments);
    }

CommentService

List<Comment> findCommentDetail(Integer articleId);

CommentServiceImpl

@Resource
    private CommentMapper commentMapper;

    @Override
    public List<Comment> findCommentDetail(Integer articleId) {
        return commentMapper.findCommentDetail(articleId);
    }

CommentMapper

@Select("select c.*,u.nickname,u.avatar_url from t_comment c left join sys_user u on c.user_id = u.id where  c.article_id = #{articleId}")
    List<Comment> findCommentDetail(@Param("articleId") Integer articleId);

 ArticleDetail.vue

<div style="margin: 10px">
      <div style="margin: 30px 0">
        <div style="border-bottom: 1px solid orangered; padding: 10px 0;font-size: 20px">评论</div>
        <el-input type="textarea" v-model="content"></el-input>
      </div>
      <div class="pd-10" style="text-align: right">
        <el-button type="primary" size="small">评论</el-button>
      </div>
    </div>

    <div>
      <!--评论列表-->
      <div v-for="item in comments" :key="item.id" style="border-bottom: 1px solid #ccc;padding: 10px 0; display: flex">
        <!--头像-->
        <div style="width: 100px;text-align: center">
          <el-image :src="item.avatarUrl" style="width: 50px;height: 50px;border-radius: 50% "></el-image>
        </div>
        <!--内容-->
        <div style="flex: 1;font-size: 14px;padding: 5px 0;line-height: 25px">
          <span>{
   
   { item.nickname }}:</span>
          <span>{
   
   { item.content }}</span>

          <div style="display: flex;line-height: 20px;margin-top: 5px">
            <div style="text-align: right">
              <i class="el-icon-time"></i><span style="margin-left: 5px">{
   
   { item.time }}</span></div>
          </div>
          <div style="text-align: right;flex: 1">
            <el-button style="margin-left: 5px" type="text">回复</el-button>
            <el-button type="text" style="color: red">删除</el-button>
          </div>

        </div>
      </div>
    </div>



content: '',
      comments: []




created() {
    this.load()
    this.loadComment()
  },
  methods: {
    load() {
      const id = this.$route.query.id
      this.request.get("/article/detail/" + id).then(res => {
        //注意data
        this.article = res.data
      })
    },

    loadComment(){
    const id = this.$route.query.id
      this.request.get("/comment/findCommentDetail/" + id).then(res => {
        //注意data
        this.comments = res.data
      })
    }
  }

 

 ②评论功能实现

 CommentController

//修改或增加
    @PostMapping("/saveComment")
    public Result saveComment(@RequestBody Comment comment) {
        //新增评论
        if(comment.getId() == null){
            comment.setUserId(TokenUtils.getCurrentUser().getId());
            comment.setTime(DateUtil.now());
        }
        //新增或修改
        return Result.success(commentService.saveOrUpdate(comment));
    }

ArticleDetail

<div style="border-bottom: 1px solid orangered; padding: 10px 0;font-size: 20px">评论</div>
        <el-input type="textarea" v-model="commentForm.content"></el-input>



 commentForm: {},
//将id设为全局变量
      id: this.$route.query.id


save() {
      if (!this.user.id){
        this.$message.warning("请登录之后操作")
        return
      }
      this.commentForm.articleId = this.id
      this.request.post("/comment/saveComment", this.commentForm).then(res => {
        if (res.code === '200') {
          this.$message.success("评论成功")
          this.loadComment()
          //初始化评论对象内容
          this.commentForm = {}
        } else {
          this.$message.error(res.msg)
        }
      })
    },

 ③删除评论功能实现

 ArticleDetail.vue

            <el-button type="text" style="color: red" @click="del(item.id)" v-if="user.id === item.userId">删除</el-button>



del(id) {
      this.request.delete("/comment/" + id).then(res => {
        if (res.code === '200') {
          this.$message.success("删除成功")
          this.loadComment()
        } else {
          this.$message.error("删除失败")
        }
      })
    },

④多级评论功能实现

回复对话框

ArticleDetail

 <el-button style="margin-left: 5px" type="text" @click="handleReply(item.id)">回复</el-button>



<el-dialog title="回复表单" :visible.sync="dialogFormVisible" width="30%">
      <el-form label-width="80px" size="small">
        <el-form-item label="内容">
          <el-input v-model="commentForm.contentReply" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="save">确 定</el-button>
      </div>
    </el-dialog>



dialogFormVisible: false



save() {
      if (!this.user.id) {
        this.$message.warning("请登录之后操作")
        return
      }
      this.commentForm.articleId = this.id
      if (this.commentForm.contentReply) {
        this.commentForm.content = this.commentForm.contentReply
      }
      this.request.post("/comment/saveComment", this.commentForm).then(res => {
        if (res.code === '200') {
          this.$message.success("评论成功")
          //初始化评论对象内容
          this.commentForm = {}
          this.loadComment()
          this.dialogFormVisible = false
        } else {
          this.$message.error(res.msg)
        }
      })
    },



handleReply(pid) {
      this.commentForm = {pid: pid, originId: pid}
      this.dialogFormVisible = true
    }

 评论显示:

<div v-if="item.children.length" style="padding-left:200px;">
          <div v-for="subItem in item.children" :key="subItem.id" style="background-color: #f0f0f0;padding: 5px 20px">
          <!--            回复列表-->
          <div style="font-size: 14px;padding: 5px 0;line-height: 25px">
            <span>{
   
   { subItem.nickname }}:</span>
            <span>{
   
   { subItem.content }}</span>

            <div style="display: flex;line-height: 20px;margin-top: 5px">
              <div style="text-align: right">
                <i class="el-icon-time"></i><span style="margin-left: 5px">{
   
   { subItem.time }}</span></div>
            </div>
            <div style="text-align: right;flex: 1">
              <el-button style="margin-left: 5px" type="text" @click="handleReply(subItem.id)">回复</el-button>
              <el-button type="text" style="color: red" @click="del(subItem.id)" v-if="user.id === subItem.userId">删除
              </el-button>
            </div>
          </div>
          </div>
        </div>


handleReply(pid) {
      this.commentForm = {pid: pid}
      this.dialogFormVisible = true
    }

Comment

@TableField(exist = false)
    private List<Comment> children;

    @TableField(exist = false)
    //父节点的用户昵称
    private String pNickname;

    @TableField(exist = false)
    //父节点的用户id
    private Integer pUserId;

CommentController

//修改或增加
    @PostMapping("/saveComment")
    public Result saveComment(@RequestBody Comment comment) {
        //新增评论
        if (comment.getId() == null) {
            comment.setUserId(TokenUtils.getCurrentUser().getId());
            comment.setTime(DateUtil.now());

            //判断如果是回复,进行处理
            if (comment.getPid() != null) {
                Integer pid = comment.getPid();
                Comment pComment = commentService.getById(pid);
                //如果当前回复的父级有祖宗,那么设置相同的祖宗
                if (pComment.getOriginId() != null) {
                    comment.setOriginId(pComment.getOriginId());
                    //否则设置为当前回复的祖宗
                } else {
                    comment.setOriginId(comment.getPid());
                }
            }
        }
        //新增或修改
        commentService.saveOrUpdate(comment);
        return Result.success();
    }


@GetMapping("/findCommentDetail/{articleId}")
    public Result findCommentDetail(@PathVariable("articleId") Integer articleId) {
        //查询所有的评论数据
        List<Comment> articleComments = commentService.findCommentDetail(articleId);
        //查询评论数据,不包括回复
        List<Comment> originList = articleComments.stream().filter(comment -> comment.getOriginId() == null).collect(Collectors.toList());

        //设置评论数据的子节点,也就是回复内容
        for (Comment origin : originList) {
            //表示回复对象集合
            List<Comment> comments = articleComments.stream().filter(comment -> origin.getId().equals(comment.getOriginId())).collect(Collectors.toList());
            comments.forEach(comment -> {
                //找到父级评论的用户id和用户昵称,并设置给当前的回复对象
                articleComments.stream().filter(c1 -> c1.getId().equals(comment.getPid())).findFirst().ifPresent(v -> {
                    comment.setPUserId(v.getUserId());
                    comment.setPNickname(v.getNickname());
                });
            });
            origin.setChildren(comments);
        }
        return Result.success(originList);
    }

 回复人显示

ArticleDetail.vue

<div>
              <b style="color: #3a8ee6" v-if="subItem.pnickname">@{
   
   {subItem.pnickname}}:</b>
            </div>

猜你喜欢

转载自blog.csdn.net/m0_64393446/article/details/131683072