【前后端的那些事】评论功能实现

文章目录

    • 聊天模块
      • 1. 数据库表
      • 2. 后端初始化
        • 2.1 controller
        • 2.2 service
        • 2.3 dao
        • 2.4 mapper
      • 3. 前端初始化
        • 3.1 路由创建
        • 3.2 目录创建
        • 3.3 tailwindCSS安装
      • 4. tailwindUI
      • 5. 前端代码编写

前言:最近写项目,发现了一些很有意思的功能,想写文章,录视频把这些内容记录下。但这些功能太零碎,如果为每个功能都单独搭建一个项目,这明显不合适。于是我想,就搭建一个项目,把那些我想将的小功能全部整合到一起。实现 搭一次环境,处处使用。

本文主要实现以下功能

  1. 评论功能

环境搭建
文章链接

已录制视频
视频链接

仓库地址
https://github.com/xuhuafeifei/fgbg-font-and-back.git

聊天模块

效果展示

在这里插入图片描述

1. 数据库表

CREATE TABLE `communicate` (`id` int NOT NULL AUTO_INCREMENT,`content` varchar(255) COLLATE utf8mb4_croatian_ci DEFAULT NULL,`create_time` datetime DEFAULT NULL,`pid` int DEFAULT NULL,`user_id` int DEFAULT NULL,`reply_user_id` int DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_croatian_ci;

2. 后端初始化

2.1 controller

CommunicateController

package com.fgbg.demo.controller;import com.fgbg.common.utils.R;
import com.fgbg.demo.entity.Communicate;
import com.fgbg.demo.service.CommunicateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RequestMapping("comm")
@RestController
public class CommunicateController {@Autowiredprivate CommunicateService service;/*** 返回树形结构评论数据*/@RequestMapping("/list")public R list() {List<Communicate> list = service.listTree();return R.ok().put("data", list);}/*** 保存评论*/@RequestMapping("/save")public R save(@RequestBody Communicate entity) {service.save(entity);return R.ok();}
}
2.2 service

CommunicateServiceImpl

package com.fgbg.demo.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fgbg.demo.dao.CommunicateDao;
import com.fgbg.demo.entity.Communicate;
import com.fgbg.demo.service.CommunicateService;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;/****/
@Service
public class CommunicateServiceImpl extends ServiceImpl<CommunicateDao, Communicate>implements CommunicateService{/*** 返回树形评论数据** @return*/@Overridepublic List<Communicate> listTree() {List<Communicate> list = this.list();// 映射id->indexHashMap<Integer, Integer> map = new HashMap<>();for (int index = 0; index < list.size(); index++) {map.put(list.get(index).getId(), index);}// 遍历寻找父节点for (Communicate communicate : list) {Integer pid = communicate.getPid();// 有父节点if (pid != null) {// 获取父节点idInteger indexFather = map.get(pid);Communicate father = list.get(indexFather);if (father.getChildren() == null) {father.setChildren(new ArrayList<>());}// 在父节点上添加当前节点father.getChildren().add(communicate);}}// 过滤出一级节点List<Communicate> ans = list.stream().filter(child -> child.getPid() == null).collect(Collectors.toList());return ans;}
}
2.3 dao

CommunicateDao

package com.fgbg.demo.dao;import com.fgbg.demo.entity.Communicate;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;/*** @Entity com.fgbg.demo.entity.Communicate*/
@Mapper
public interface CommunicateDao extends BaseMapper<Communicate> {}
2.4 mapper

CommunicateMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fgbg.demo.dao.CommunicateDao"><resultMap id="BaseResultMap" type="com.fgbg.demo.entity.Communicate"><id property="id" column="id" jdbcType="INTEGER"/><result property="content" column="content" jdbcType="VARCHAR"/><result property="createTime" column="create_time" jdbcType="TIMESTAMP"/><result property="pid" column="pid" jdbcType="INTEGER"/><result property="userId" column="user_id" jdbcType="INTEGER"/><result property="replyUserId" column="reply_user_id" jdbcType="INTEGER"/></resultMap><sql id="Base_Column_List">id,content,create_time,pid,user_id</sql>
</mapper>

3. 前端初始化

3.1 路由创建

/src/router/modules/communicate.ts

const { VITE_HIDE_HOME } = import.meta.env;
const Layout = () => import("@/layout/index.vue");export default {path: "/communicate",name: "communicate",component: Layout,redirect: "/communicate",meta: {icon: "homeFilled",title: "沟通",rank: 0},children: [{path: "/communicate",name: "communicate",component: () => import("@/views/communicate/communicate.vue"),meta: {title: "评论",showLink: VITE_HIDE_HOME === "true" ? false : true}}]
} as RouteConfigsTable;
3.2 目录创建

/src/views/communicate/communicate.vue

3.3 tailwindCSS安装
  • 安装

    pnpm install -D tailwindcss postcss autoprefixer
    
  • 输入命令初始化tailwind和postcss配置文件

    npx tailwindcss init -p
    
  • 打开vue项目,在src目录下新建一个css文件:index.css,在文件中写入

    @tailwind base;@tailwind components;@tailwind utilities;
    
  • main.ts中引入

    import './index.css'
    

检查tailwind.config.js。我的项目中,文件代码为

/** @type {import('tailwindcss').Config} */
module.exports = {darkMode: "class",corePlugins: {preflight: false},content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],theme: {extend: {colors: {bg_color: "var(--el-bg-color)",primary: "var(--el-color-primary)",text_color_primary: "var(--el-text-color-primary)",text_color_regular: "var(--el-text-color-regular)"}}}
};

stylelint.config.js

module.exports = {root: true,extends: ["stylelint-config-standard","stylelint-config-html/vue","stylelint-config-recess-order"],plugins: ["stylelint-order", "stylelint-prettier", "stylelint-scss"],overrides: [{files: ["**/*.(css|html|vue)"],customSyntax: "postcss-html"},{files: ["*.scss", "**/*.scss"],customSyntax: "postcss-scss",extends: ["stylelint-config-standard-scss","stylelint-config-recommended-vue/scss"]}],rules: {"selector-class-pattern": null,"no-descending-specificity": null,"scss/dollar-variable-pattern": null,"selector-pseudo-class-no-unknown": [true,{ignorePseudoClasses: ["deep", "global"]}],"selector-pseudo-element-no-unknown": [true,{ignorePseudoElements: ["v-deep", "v-global", "v-slotted"]}],"at-rule-no-unknown": [true,{ignoreAtRules: ["tailwind","apply","variants","responsive","screen","function","if","each","include","mixin","use"]}],"rule-empty-line-before": ["always",{ignore: ["after-comment", "first-nested"]}],"unit-no-unknown": [true, { ignoreUnits: ["rpx"] }],"order/order": [["dollar-variables","custom-properties","at-rules","declarations",{type: "at-rule",name: "supports"},{type: "at-rule",name: "media"},"rules"],{ severity: "warning" }]},ignoreFiles: ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"]
};
  • 测试是否引入成功

    <p class=" text-2xl font-bold">Hello Tailwind!</p>
    

    如果出现css样式,则表明引入成功

4. tailwindUI

聊天框样式涉及参考了这个大神的UI设计

网址:[comment聊天](comment | Cards (tailwindcomponents.com))

源代码

<div class="flex justify-center relative top-1/3"><!-- This is an example component -->
<div class="relative grid grid-cols-1 gap-4 p-4 mb-8 border rounded-lg bg-white shadow-lg"><div class="relative flex gap-4"><img src="https://icons.iconarchive.com/icons/diversity-avatars/avatars/256/charlie-chaplin-icon.png" class="relative rounded-lg -top-8 -mb-4 bg-white border h-20 w-20" alt="" loading="lazy"><div class="flex flex-col w-full"><div class="flex flex-row justify-between"><p class="relative text-xl whitespace-nowrap truncate overflow-hidden">COMMENTOR</p><a class="text-gray-500 text-xl" href="#"><i class="fa-solid fa-trash"></i></a></div><p class="text-gray-400 text-sm">20 April 2022, at 14:88 PM</p></div></div><p class="-mt-4 text-gray-500">Lorem ipsum dolor sit amet consectetur adipisicing elit. <br>Maxime quisquam vero adipisci beatae voluptas dolor ame.</p>
</div></div>

在这里插入图片描述

tip: 本项目编写时,对其代码进行一定程度的调整

5. 前端代码编写

笔者考虑篇幅问题,没有封装组件。读者可以尝试着将聊天代码封装为组件,一级评论一个样式,回复评论一个样式。通过这样的方式实现代码复用。

<script setup lang="ts">
import { CommunicateEntity, list, save } from "/src/api/communicate.ts";
import { ElMessage } from "element-plus";
import { ref, onMounted } from "vue";const input = ref("");const replyInput = ref("");const chatList = ref<Array<CommunicateEntity>>();const submit = (replyUserId?: Number, pid?: Number) => {const entity = new CommunicateEntity();entity.replyUserId = replyUserId;entity.content = input.value;entity.pid = pid;console.log(entity);save(entity).then(res => {if (res.code === 0) {ElMessage.success("提交成功");getData();} else {ElMessage.error("提交失败: " + res.msg);}});
};onMounted(() => {getData();
});const getData = () => {list().then(res => {console.log(res);chatList.value = res.data;});
};// 模拟获取用户信息(一般用户信息会在登陆时, 存储在浏览器本地)
const getUser = (userId: Number) => {return "测试人员";
};
</script><template><div style="border: 1px solid #ccc; margin-top: 10px"><el-input v-model="input" textarea style="height: 200px" /><el-button @click="submit()">提交</el-button><el-divider /><div v-for="item in chatList" :key="item.id"><!-- This is an example component --><div class="relative gap-4 p-6 rounded-lg mb-8 bg-white border"><div class="relative flex gap-4"><imgsrc="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"class="relative rounded-lg -top-8 -mb-4 bg-white border h-20 w-20"alt=""loading="lazy"/><div class="flex flex-col w-full"><div class="flex flex-row justify-between"><pclass="relative text-xl whitespace-nowrap truncate overflow-hidden">{{ getUser(item.id) }}</p><a class="text-gray-500 text-xl" href="#"><i class="fa-solid fa-trash"/></a></div><p class="text-gray-400 text-sm">{{ item.createTime }}</p></div></div><p class="-mt-4 text-gray-500">{{ item.content }}</p><!-- 回复按钮 --><div><el-popover placement="bottom-start" trigger="click" :width="200"><template #reference><el-button style="float: right" link type="primary">回复</el-button></template><el-input v-model="input" /><el-button @click="submit(item.userId, item.id)" style="width: 100%">确定</el-button></el-popover></div></div><!-- 回复 --><div v-for="subItem in item.children" :key="subItem.id"><divclass="relative gap-4 p-6 rounded-lg mb-8 bg-white border"style="margin-left: 50px"><div class="relative flex gap-4"><imgsrc="https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png"class="relative rounded-lg -top-8 -mb-4 bg-white border h-20 w-20"alt=""loading="lazy"/><div class="flex flex-col w-full"><div class="flex flex-row justify-between"><pclass="relative text-xl whitespace-nowrap truncate overflow-hidden">{{ getUser(subItem.userId) }} 回复<span style="color: cornflowerblue">@{{ getUser(subItem.replyUserId) }}</span></p><a class="text-gray-500 text-xl" href="#"><i class="fa-solid fa-trash"/></a></div><p class="text-gray-400 text-sm">{{ item.createTime }}</p></div></div><p class="-mt-4 text-gray-500">{{ subItem.content }}</p><!-- 回复按钮 --><div><el-popover placement="bottom-start" trigger="click" :width="200"><template #reference><el-button style="float: right" link type="primary">回复</el-button></template><el-input v-model="input" /><el-button@click="submit(item.userId, item.id)"style="width: 100%">确定</el-button></el-popover></div></div></div></div></div>
</template><style lang="scss" scoped></style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/411338.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端——框架——Vue

提示&#xff1a; 本文只是从宏观角度简要地梳理一遍vue3&#xff0c;不至于说学得乱七八糟、一头雾水、不知南北&#xff0c;如果要上手写代码、撸细节&#xff0c;可以根据文中的关键词去查找资料 简问简答&#xff1a; vue.js是指vue3还是vue2&#xff1f; Vue.js通常指的是…

回馈科教,非凸科技助力第48届ICPC亚洲区决赛

1月12日-14日&#xff0c;“华为杯”第48届国际大学生程序设计竞赛&#xff08;ICPC&#xff09;亚洲区决赛在上海大学成功举办。非凸科技作为此次赛事的支持方之一&#xff0c;希望携手各方共同推动计算机科学和技术的发展。 这是一场智慧的巅峰对决&#xff0c;320支优秀队伍…

K8s-架构

一、K8s节点划分 K8s集群包含Master(控制节点)和Node(工作节点)&#xff0c;应用部署在Node节点上。 集群架构图&#xff1a; 二、Master节点 Master节点分成四个组件&#xff1a;scheduler、ApiServer、Controller Manager、ETCD。类似三层结构&#xff0c;controller&#…

20240117-【UNITY 学习】增加墙跑功能和跳墙功能

替换脚本PlayerCam_01.cs using System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening;public class PlayerCam_02 : MonoBehaviour {// 视觉灵敏度参数public float sensX 400;public float sensY 400;// 视角垂直旋转角度限制publ…

redis安装-Linux为例

可以下载一个Shell或者MobaXterm工具&#xff0c;便于操作 在redis官网下载压缩包 开始安装 安装依赖 yum install -y gcc tcl切换目录 切换目录后直接把redis安装包拖到/user/local/src/下 cd /user/local/src/解压然后安装 #解压 tar -zxvf redis-7.2.4.tar.gz #安装 …

Kafka-消费者-KafkaConsumer分析-ConsumerNetworkClient

前面介绍过NetworkClient的实现&#xff0c;它依赖于KSelector、InFlightRequests、Metadata等组件&#xff0c;负责管理客户端与Kafka集群中各个Node节点之间的连接&#xff0c;通过KSelector法实现了发送请求的功能&#xff0c;并通过一系列handle*方法处理请求响应、超时请求…

阿里云云原生助力安永创新驱动力实践探索

云原生正在成为新质生产力变革的核心要素和企业创新的数字基础设施。2023 年 12 月 1 日&#xff0c;由中国信通院举办的“2023 云原生产业大会”在北京召开。在大会“阿里云云原生”专场&#xff0c;安永科技咨询合伙人王祺分享了对云原生市场的总览及趋势洞见&#xff0c;及安…

React的合成事件

合成事件&#xff1a;通过事件委托&#xff0c;利用事件传播机制&#xff0c;当事件传播到document时&#xff0c;再进行分发到对应的组件&#xff0c;从而触发对应所绑定的事件&#xff0c;然后事件开始在组件树DOM中走捕获冒泡流程。 原生事件 —— > React事件 —— >…

小白准备蓝桥杯之旅(c/c++b组)

前言&#xff1a;省赛获奖比例高达百分之60,只要比一半的人努力&#xff0c;你就能大概率获奖。 寒假做的3件事 1.稳基础 熟练掌握基础语法部分&#xff0c;c比c多个stl库优势&#xff0c;c语言的同学需要会实现c中stl库部分 2.刷真题 大概比赛前30天&#xff0c;坚持每天做…

QQ文档删除了怎么恢复?记住这3个方法!

我们有时会因为误操作或者需要清理空间而删除一些不需要的文件&#xff0c;包括我们在QQ平台上接收的文档。但是&#xff0c;一旦我们删除了这些文档&#xff0c;如果没有备份的情况下就难以找回。 那么&#xff0c;如果我们在删除QQ文档后想要恢复它们&#xff0c;应该怎么做…

HarmonyOS —— buildMode 设置(对比 Android Build Varient)

前言 在安卓中 Build Variant 主要依赖模块&#xff08;module&#xff09;中 build.gradle 的 BuildType 和 ProductFlavor 提供的属性和方法&#xff0c;我们可以使用 Build Type 可以配置不同的构建方式、ProductFlavor 主要用来进行多渠道打包。 在鸿蒙中要做到同样像效果…

聚观早报 | OpenAI组建新团队;Nexperia推出新款全新产品

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 1月18日消息 OpenAI组建新团队 Nexperia推出全新产品 苹果推出Vision Pro应用商店 华为nova 12 Pro心钥套装 蔚…