基于jquery 的评论小插件
github项目地址:https://github.com/fancaixia/jquery-comment
案例思路:
[后台建立两个表]
- comment_list 评论表 id,userID,userName,articleID,content
- reply_list 回复表 id,talkID,content,fromID,fromUser,toID,toUser
[查询评论]
- 后台获取 comment_list 表数据
- 检索reply_list表中item.talkID == comment_list中 item.id, 并存入comment_list中对应item.child
[发送评论]
- 前台发送用户id,用户名称, 文章id, 评论内容到后台
- 后台将评论存储到comment_list中
[删除评论]
- 前台发送此条评论ID到后台,
- 后台reply_list判断 talkId == id 进行删除
- 同时查询comment_list判断 id 相等的数据并进行删除
[回复评论]
- 前台发送评论id, 内容, fromID, fromUser, toID, toUser 到后台
- 后台将消息存储到reply_list中
[删除回复]
- 前台发送此条回复ID到后台,
- 后台reply_list判断对应 id 进行删除
[前台判断]
- 根据当前登录用户id判断评论或回复用户是否一致 (不可给自己评论)
- 划过某条评论时判断是不是当前用户的评论,(只能删除自己的评论)
代码结构
comment_static
config.js 接口文档
common.js 封装 ajax 请求及提示信息
index.html 主文件
index.js 业务逻辑
模拟多个用户可复制index.html,并修改index.js 中currentUserID和currentUserName
comment_mysql_server
app.js 主文件 连接mysql配置
routeactivity_talk 后台逻辑处理
项目启动
- cd / comment_mysql_server
- app.js 更改数据库连接信息
- npm / cnpm install
- npm run dev
代码片段:
server / route / activity_talk.js
const express = require('express');
const uuid = require('uuid')
const route_talk = express.Router();
route_talk.use((req,res,next)=>{
//解决跨域请求
res.header("Access-Control-Allow-Origin","*");
res.header("Access-Control-Allow-Headers", "X-Requested-With,Origin,Content-Type,Accept");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
next()
})
// 查询评论数据
route_talk.post('/get',(req,res)=>{
let articleID = req.body.articleID;
let talkID = req.body.talkID;
let replyCount = req.body.replyCount; //查询评论对应回复的条数 默认3 查询所有时设置为10000
req.pool.query(`SELECT * FROM comment_list WHERE articleID=${articleID}`,(err,data)=>{
if(err){
res.send({code:1,msg:"服务端错误"}).end();
}else{
//文章列表查询评论成功后 遍历数组 回复表根根talkID查 回复数据
function makePromise(item, index) {
return new Promise((resolve) => {
let newitem = JSON.parse(JSON.stringify(item));
//如果传入 talkID 那么此评论对应回复查询所有
if(talkID && talkID == newitem.id){
replyCount = 10000;
}else{
replyCount = 3;
}
req.pool.query(`SELECT * FROM reply_list WHERE talkID="${newitem.id}" LIMIT 0,${replyCount}`,(err,replydata)=>{
if(err){
res.send({code:1,msg:"服务端错误"}).end();
}else{
//模拟查询用户表数据
newitem.img="./images/reply.jpg";
newitem.time = "2017-07-06 12:45";
newitem.child = JSON.parse(JSON.stringify(replydata));
//遍历回复数据 模拟查询用户表 添加用户信息
newitem.child.forEach((item,index) => {
item.fromImg = './images/reply_01.jpg'; //模拟查询用户表数据
item.time = "2017-07-06 12:45";
});
req.pool.query(`SELECT COUNT(*) AS count FROM reply_list WHERE talkID="${newitem.id}"`,(err,num)=>{
if(err){
res.send({code:1,msg:"服务端错误"}).end();
}else{
newitem.replytotal = num[0].count;
resolve(newitem)
}
})
}
})
});
}
Promise.all(data.map((v, k) => makePromise(v, k))).then(data => {
res.send({code:0,data,}).end();
});
}
})
})
//添加评论
route_talk.post('/add',(req,res)=>{
const uuid_str = uuid.v4().replace(/-/g,'');
req.pool.query(`insert into comment_list (id,userID,userName,articleID,content) VALUES("${uuid_str}","${req.body.userID}","${req.body.userName}","${req.body.articleID}","${req.body.content}")`,(err,state)=>{
if(err){
res.send({code:1,msg:"添加失败"}).end();
}else{
//添加成功 返回评论数据
res.send({code:0,msg:"添加成功"}).end();
}
})
})
//删除评论 首先删除评论对应回复 DELETE FROM reply_list WHERE talkID=""
route_talk.post('/remove',(req,res)=>{
req.pool.query(`delete from reply_list where talkID="${req.body.id}"`,(err,state)=>{
if(err){
res.send({code:1,msg:"删除回复失败"}).end();
}else{
req.pool.query(`delete from comment_list where id="${req.body.id}"`,(err,data)=>{
if(err){
res.send({code:1,msg:"删除评论失败"}).end();
}else{
res.send({code:0,msg:"ok"}).end();
}
})
}
})
})
//添加回复
route_talk.post('/addreply',(req,res)=>{
const uuid_str = uuid.v4().replace(/-/g,'');
req.pool.query(`insert into reply_list (id,talkID,content,fromID,fromUser,toID,toUser) VALUES("${uuid_str}","${req.body.talkID}","${req.body.content}","${req.body.fromID}","${req.body.fromUser}","${req.body.toID}","${req.body.toUser}")`,(err,state)=>{
if(err){
res.send({code:1,msg:"回复失败"}).end();
}else{
res.send({code:1,msg:"ok"}).end();
}
})
})
//删除回复
route_talk.post('/removereply',(req,res)=>{
req.pool.query(`delete from reply_list where id="${req.body.id}"`,(err,data)=>{
if(err){
res.send({code:1,msg:"删除回复失败"}).end();
}else{
res.send({code:1,msg:"删除回复成功"}).end();
}
})
})
module.exports = route_talk;
static / index.js
let replySize = 3; //回复默认显示3条 当点击更多 则为评论相关回复total总数
const replyCount = 3; //回复条数大于3 隐藏
const currentUserID = '1549bd4753424d88a4f0d5839da33f96';
let currentUserName = '小财神';
// const currentUserID = '25b92f4e04d54ca5a159b46309255f7e';
// let currentUserName = '小迷糊';
let replyUser = ''; //回复人XXX
let replyUserID = ''; //回复人ID
let seeAlltalkID = ''; //点击查看按钮时 储存点击的评论ID
let articleID = '0' // 假设此篇文章ID 为 0 (根据文章ID 请求对应评论数据)
$('#current_user').html('当前用户:'+currentUserName)
//初始化渲染评论数据
getTalkData();
//初始化渲染评论数据
function getTalkData(id){
let talkID = id || '';
let data = {
articleID,
talkID:talkID,
replyCount:replyCount
};
commonfun.request(serverApi.getTalk,data,renderData,renderFail)
}
//获取数据成功回调
function renderData(data){
if(data.code != 0){
tipObj.setErrmsg(data.msg,1);
return;
}
let talkStr = ''; //整个评论信息
let moreStr = '';
data.data.forEach((talkitem,index)=>{
//判断此评论是否有回复
let replyStr = ''; //回复文本
if(talkitem.child.length > 0){
replyStr = '<div class="comment_reply">';
talkitem.child.forEach((item,index)=>{
//渲染评论数据
replyStr +=
'<div data-fromID='+item.fromID+' data-id='+item.id+' class="one_reply '+(index==talkitem.child.length-1?'one_reply_noborder':'')+'">'+
'<div class="comment_people_info">'+
'<img class="fl_left" src="'+item.fromImg+'" alt="">'+
'<span class="first_reply fl_left"><span class="first_reply_aa">'+item.fromUser+'</span>'+
'<span class="first_reply_text"> 回复: </span>'+
'<span class="first_reply_aa">'+item.toUser+'</span></span>'+
'<span class="date fl_right">'+item.time+'</span></div>'+
'<div class="comment_people_content">'+
'<span class="fl_left">'+item.content+'</span>'+
'<span data-userID='+item.fromID+' data-userName='+item.fromUser+' class=" reply_fn reply_btn fl_right">回复</span>'+
'<span id="reply_remove" class="reply_remove reply_btn fl_right reply_btn_cont none">删除</span>'+
'</div></div>'
})
moreStr = '<div class="comment_more '+(talkitem.replytotal>replyCount?'':'none')+'">'+
'<span>剩余'+(seeAlltalkID== talkitem.id? talkitem.replytotal-replySize : talkitem.replytotal-replyCount)+'条评论</span> <span id="see_reply" data-total='+talkitem.replytotal+' class="green see">'+(talkitem.replytotal-replySize == 0?'收起':"点击查看")+'</span></div>';
replyStr += moreStr+'</div>'
}
talkStr += '<div data-id='+talkitem.id+' data-userID='+talkitem.userID+' class="comment">'+
'<div class="comment_people">'+
'<div class="comment_people_titleBox">'+
'<div class="comment_people_info">'+
'<img class="fl_left" src="'+talkitem.img+'" alt="">'+
'<span class="first_reply fl_left">'+talkitem.userName+'</span>'+
'<span class="date fl_left">'+talkitem.time+'</span>'+
'<span data-userID='+talkitem.userID+' data-userName='+talkitem.userName+' class="reply_fn fl_right reply_btn">回复</span></div>'+
'<div class="comment_people_content">'+
'<span class="fl_left">'+talkitem.content+'</span>'+
'<span id="talk_remove" class="reply_btn fl_right none reply_btn_cont">删除</span>'+
'</div></div>'+replyStr+'</div></div>';
})
$('#talk_content').html(talkStr);
}
// 渲染数据失败
function renderFail(err){
tipObj.setErrmsg("请求失败,请重试",1);
}
//对文章评论 ++++++++++++++++++++++
$('#textareaBox').on('focus',function () {
$('#cancelbtn').removeClass('none')
})
//发布评论
$('#talk_submit').on('click',function(){
//提交请求
let content = $.trim($('#textareaBox').val())
if(content.length > 0){
let data = {
userID:currentUserID,
userName:currentUserName,
articleID,
content,
};
commonfun.request(serverApi.addTalk,data,addTalkfun,addTalkFail)
}else{
//提示层
tipObj.setErrmsg('请输入评论内容',1);
}
})
//添加评论成功
function addTalkfun(data){
$('#textareaBox').val('')
$('#cancelbtn').addClass('none')
getTalkData();
}
//添加评论失败
function addTalkFail(err){
tipObj.setErrmsg("请求失败,请重试",1);
}
//发布评论回车事件处理
$('#textareaBox').keypress(function(e){
if(e.ctrlKey && e.which == 13 || e.which == 10 || e.which == 13) {
$('#talk_submit').trigger('click')
e.preventDefault(); //屏蔽enter对系统作用。按后增加\r\n等换行
}
});
//取消评论
$('#cancelbtn').on('click',function(){
$('#textareaBox').val('')
$(this).addClass('none')
})
//滑过评论控件 显示删除按钮
$('#talk_content').on('mouseover','.comment_people_titleBox ',function(){
if(currentUserID == $(this).parents('.comment').attr('data-userID')){
$(this).find('#talk_remove').removeClass('none')
}
});
//移出评论控件 隐藏删除按钮
$('#talk_content').on('mouseout','.comment_people_titleBox',function(){
$(this).find('#talk_remove').addClass('none')
})
//删除评论
$('#talk_content').on('click','#talk_remove',function(){
let id = $(this).parents('.comment').attr('data-id')
let data = {
id,
}
commonfun.request(serverApi.removeTalk,data,removeTalkfun,removeTalkFail)
})
//删除评论成功回调
function removeTalkfun(){
//初始化查询评论数据
getTalkData();
}
//删除评论失败回调
function removeTalkFail(){
tipObj.setErrmsg("请求失败,请重试",1);
}
//滑过回复控件 显示删除
$('#talk_content').on('mouseover','.one_reply ',function(){
if(currentUserID == $(this).attr('data-fromid')){
$(this).find('#reply_remove').removeClass('none')
}
});
//移出回复控件 隐藏删除
$('#talk_content').on('mouseout','.one_reply',function(){
$(this).find('#reply_remove').addClass('none')
})
//点击回复
// 判断是否对自己回复 对自己回复则 return
// 判断是否存在评论框 不存在创建 同时设置文本域值 @xxxx ++++++++++++++++++++++
$('#talk_content').on('click','.reply_fn',function(){
if($(this).attr('data-userid') == currentUserID){
return;
}
let areachild = $('#talk_content').find('.talk_box_s')
if($(areachild).length == 0){
let textBox = setchildTextarea();
$(this).parents('.comment').append(textBox)
}else{
$('#talk_content').find('.talk_box_s').remove();
let textBox = setchildTextarea();
$(this).parents('.comment').append(textBox)
}
let val = $(this).attr('data-username');
$('#talk_content').find('#textareaBox_s').val('@'+val+' ')
replyUser = val;
replyUserID = $(this).attr('data-userid');
})
//回复发布
$('#talk_content').on('click','#talk_submit_s',function(){
// 替换 @ xxx 为空
let content = ($.trim($('#textareaBox_s').val())).replace((replyUser),'')
content = content.replace('@','')
let talkID = $(this).parents('.comment').attr('data-id')
if(content.length > 0){
let data = {
talkID,
content,
fromID:currentUserID,
fromUser:currentUserName,
toID:replyUserID,
toUser:replyUser,
}
commonfun.request(serverApi.addreply,data,replyfun,replyfail)
}else{
tipObj.setErrmsg('请输入评论内容',1);
}
})
//回复发布成功
function replyfun(){
getTalkData();
$(this).parents('.talk_box_s').remove();
}
//回复发布失败
function replyfail(){
tipObj.setErrmsg('请求失败,请重试',1)
}
//回复发布 回车事件处理
$('#talk_content').keypress('#textareaBox_s',function(e){
if(e.ctrlKey && e.which == 13 || e.which == 10 || e.which == 13) {
$('#talk_content').find('#talk_submit_s').trigger('click')
e.preventDefault(); //屏蔽enter对系统作用。按后增加\r\n等换行
}
});
//删除回复
$('#talk_content').on('click','#reply_remove',function(){
let id = $(this).parents('.one_reply').attr('data-id')
let data = {
id:id
}
commonfun.request(serverApi.removereply,data,removeReplyfun,removeReplyfail)
})
//删除回复成功
function removeReplyfun(){
//初始化查询评论数据
getTalkData();
}
//删除回复失败
function removeReplyfail(){
tipObj.setErrmsg('请求失败,请重试',1)
}
//点击查看 获取评论并且根据talkID获取全部评论
$('#talk_content').on('click','#see_reply',function(){
seeAlltalkID = $(this).parents('.comment').attr('data-id')
let replyTotal = $(this).attr('data-total')
if(replySize == replyTotal){
replySize = replyCount;
getTalkData();
}else{
replySize = replyTotal;
//初始化查询评论数据
getTalkData(seeAlltalkID);
}
})
//回复取消
$('#talk_content').on('click','#cancelbtn_s',function(){
$(this).parents('.talk_box_s').remove();
})
//回复评论框
function setchildTextarea(){
return '<div class="talk_box talk_box_s">'+
'<textarea name="talk" id="textareaBox_s" placeholder="写下你的评论..."></textarea>'+
'<button id="talk_submit_s">发布</button><button id="cancelbtn_s" class="cancelbtn">取消</button></div>'
}