微信小程序仿知乎实现评论留言功能


Posted in Javascript onNovember 28, 2018

 最近沉迷学习无法自拔,太久没有码字,码一个小程序留言功能实现。先上一波最后效果图:

微信小程序仿知乎实现评论留言功能

微信小程序仿知乎实现评论留言功能

(删除按钮,是用户自己的留言时才会显示该按钮)

实现技术

后台:SSM框架

数据库:MySQL数据库

数据库设计

评论功能的实现主要涉及三个表

comment:存储留言评论信息,表结构如下:

微信小程序仿知乎实现评论留言功能

表中,必须的字段:id,user_id,reply_comment_id,comment,insert_time,source_id

添加了冗余字段username,reply_user_name,userphoto

主要用于存储微信名、回复的微信名、微信头像(这三个字段完全不应该冗余,当小程序用户更换用户名时,该表要跟着更新,可维护性差,不建议存储这些冗余信息,我就是懒得写SQL了)

source:存储你在小程序需要回复的内容。

user:存储小程序使用的用户信息,主要包括用户名、用户头像等微信用户信息。

小程序端

wxml

<scroll-view scroll-top="{{scrollTop}}" scroll-y="true" style="height:{{scrollHeight}}px;" class="list" bindscrolltolower="bindDownLoad" bindscrolltoupper="refresh">
 <view class="pro-con">
 <block wx:for="{{list}}" wx:key="{{index}}">
  <view class="pro-box">
  <view class="head">
   <image class="img" src="{{item.userPhoto}}" mode="aspectFit"></image>
   <view class="box">
   <view class="shead clear">
    <view class="names fl">{{item.userName}}
     <view wx:if="{{!item.replyUserName == \" \"}}">
     -> {{item.replyUserName}}
    </view>
    </view>
   </view>
   </view>
  </view>
  <view class="addr-info">
   <view class="addr-text">
   {{item.comment}}
   </view>
  </view>
  <view class="info">
   <view class="text">
   <text decode="true">{{item.insertTime}}</text>
   </view>
   <view class="text">
   <button class="sharebtn" data-commentId="{{item.id}}" data-commentUserName="{{item.userName}}" bindtap="bindReply">回复</button>
   </view>
    <view wx:if="{{item.userId == userId}}" class="status text fr">
    <text class="delete" decode="true" bindtap='deleteComment' data-CommentId="{{item.id}}">删除</text>
    </view>
  </view>
  </view>
 </block>
 </view>
</scroll-view>
<form bindsubmit="submitForm" report-submit="true">
 <view class="release">
 <view wx:if="{{reply}}" class="replyinfo1">
  回复<text class="text">{{replyUserName}}</text>
  <button class="cancel" bindtap="cancleReply">取消回复</button>
 </view>
 <view class="replyinfo2">
  <textarea placeholder-class="input_null" fixed="true" maxlength="-1" show-confirm-bar="false" cursor-spacing="15" auto-height="true" placeholder="请输入回复" name="comment"></textarea>
  <button form-type="submit" class="submit">发送</button>
 </view>
 </view>
</form>

css

.names {
 display: flex;
 font-size: 30rpx;
 line-height: 40rpx;
}
 
.input_null {
 color: #c9c9c9;
}
 
.replyAll {
 position:absolute;
}
 
.release {
 align-items: flex-end; /*底部对齐*/
 box-sizing: border-box;
 position: fixed;
 left: 0;
 bottom: 0;
 width: 100%;
 padding: 18rpx 0 18rpx 30rpx;
 background-color: #f7f8f7;
 font-size: 28rpx;
 z-index: 999;
}
 
.replyinfo1{ 
 display: flex;
 justify-content: space-between; /*两端对齐*/
 font-size: 35rpx;
}
.replyinfo2{ 
 display: flex;
 justify-content: space-between; /*两端对齐*/
}
 
.release textarea {
 width: 550rpx;
 min-height: 34rpx;
 max-height: 102rpx; /*最多显示三行*/
 border-width: 15rpx 20rpx; /*使用padding与预期留白不一致,故使用border*/
 border-style: solid;
 border-color: #fff;
 line-height: 34rpx;
 font-size: 28rpx;
 background-color: #fff;
 border-radius: 4rpx;
}
 
.release .text {
 font-size: 40rpx;
 color: #c9c9c9;
}
 
.cancel {
 width: 240rpx;
 height: 64rpx;
 line-height: 64rpx;
 text-align: center;
 color: #6c0;
 margin: 0 3px;
 padding: 0;
}
 
.release .submit {
 width: 120rpx;
 height: 64rpx;
 line-height: 64rpx;
 text-align: center;
 color: #6c0;
 margin: 0 3px;
 padding: 0;
}
 
.pro-box .info .text .delete {
 color: #f68135;
 border-radius: 50rpx;
 border: 1px solid #f68135;
 font-size: 28 rpx;
 width: 150rpx;
 height: 48rpx;
 text-align: center;
}

js

// pages/comment/comment.js
const model = require('../cityChoose/cityChoose.js')
const config = require('../../utils/config.js')
const util = require('../../utils/util.js')
const app = getApp()
var mydata = {
 end: 0,
 replyUserName: ""
}
Page({
 
 /**
 * 页面的初始数据
 */
 data: {
 list: [],
 },
 
 /**
 * 生命周期函数--监听页面加载
 */
 onLoad: function(options) {
 var that = this;
 mydata.sourceId = options.sourceId
 mydata.commentId = "";
 mydata.replyUserName = "";
 //设置scroll的高度
 wx.getSystemInfo({
  success: function(res) {
  that.setData({
   scrollHeight: res.windowHeight,
   userId:app.globalData.haulUserInfo.id
  });
  }
 });
 mydata.page = 1;
 that.getPageInfo(mydata.page);
 },
 /**
 * 页面下拉刷新事件的处理函数
 */
 refresh: function() {
 console.log('refresh');
 mydata.page = 1
 this.getPageInfo(mydata.page, function() {
  this.setData({
  list: []
  })
 });
 mydata.end = 0;
 },
 /**
 * 页面上拉触底事件的处理函数
 */
 bindDownLoad: function() {
 console.log("onReachBottom");
 var that = this;
 if (mydata.end == 0) {
  mydata.page++;
  that.getPageInfo(mydata.page);
 }
 },
 bindReply: function(e) {
 console.log(e);
 mydata.commentId = e.target.dataset.commentid;
 mydata.replyUserName = e.target.dataset.commentusername;
 this.setData({
  replyUserName: mydata.replyUserName,
  reply: true
 })
 },
 // 合并数组
 addArr(arr1, arr2) {
 for (var i = 0; i < arr2.length; i++) {
  arr1.push(arr2[i]);
 }
 return arr1;
 },
 deleteComment:function(e){
 console.log(e);
 var that = this;
 var commentId = e.target.dataset.commentid;
 
 wx.showModal({
  title: '删除评论',
  content: '请确认是否删除该评论?',
  success: function (res) {
  if (res.confirm) {
   wx.request({
   url: config.deleteComment,
   method: "POST",
   data: {
    commentId: commentId
   },
   header: {
    "content-type": "application/x-www-form-urlencoded;charset=utf-8",
   },
   success: res => {
    that.refresh();
    wx.showToast({
    title: "删除成功"
    })
   }
   })
  } else if (res.cancel) {
   console.log('用户点击取消')
  }
  }
 })
 },
 cancleReply: function(e) {
 mydata.commentId = "";
 mydata.replyUserName = "";
 this.setData({
  replyUserName: mydata.replyUserName,
  reply: false
 })
 },
 // 更新页面信息
 // 此处的回调函数在 传入新值之前执行 主要用来清除页面信息
 getPageInfo(page, callback) {
 var that = this;
 util.showLoading();
 console.log("getPageInfo");
 console.log("page" + page);
 var limited = 6;
 var offset = (page - 1) * 6;
 wx.request({
  url: config.getComments,
  method: "POST",
  data: {
  sourceId: mydata.sourceId,
  limited: limited,
  offset: offset
  },
  header: {
  "content-type": "application/x-www-form-urlencoded;charset=utf-8",
  },
  success: res => {
  console.log(res);
  if (page == 1) {
   that.data.list = res.data;
   that.setData({
   list: that.data.list
   })
   mydata.end = 0;
  } else {
   // 当前页为其他页
   var list = that.data.list;
   if (res.data.length != 0) {
   list = that.addArr(list, res.data);
   that.setData({
    list: list
   })
   mydata.end = 0;
   } else {
   mydata.end = 1;
   }
  }
  wx.hideLoading();
  }
 })
 },
 submitForm(e) {
 var form = e.detail.value;
 var that = this;
 console.log(app.globalData.haulUserInfo);
 if(form.comment == ""){
  util.showLog('请输入评论');
  return;
 }
 // 提交评论
 wx.request({
  url: config.insertComment,
  method: "POST",
  data: {
  sourceId: mydata.sourceId,
  comment: form.comment,
  userId: app.globalData.haulUserInfo.id,
  userName: app.globalData.haulUserInfo.userName,
  replyCommentId: mydata.commentId,
  replyUserName: mydata.replyUserName,
  userPhoto: app.globalData.haulUserInfo.userPhoto
  },
  header: {
  "content-type": "application/x-www-form-urlencoded;charset=utf-8",
  //token: app.globalData.token
  },
  success: res => {
  console.log(res)
  if (res.data.success) {
   wx.showToast({
   title: "回复成功"
   })
   that.refresh();
   mydata.commentId = "";
   mydata.replyUserName = "";
   this.setData({
   replyUserName: mydata.replyUserName,
   reply: false
   })
  } else {
   wx.showToast({
   title: '回复失败,请检查您的网络',
   })
  }
  }
 })
 }
})

后台

后台功能:获取评论、删除评论、插入评论,都是简单的数据库操作,放在一个controller类中实现即可

package com.melon.haul.web;
 
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
 
import net.sf.json.JSONObject;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import com.melon.haul.dto.DataUtil;
import com.melon.haul.dto.GetLocation;
import com.melon.haul.dto.Result;
import com.melon.haul.entity.Comment;
import com.melon.haul.entity.District;
import com.melon.haul.entity.Source;
import com.melon.haul.service.CommentService;
import com.melon.haul.service.DistrictService;
import com.melon.haul.service.SourceService;
 
@Controller
@WebAppConfiguration
@RequestMapping("/Comment")
public class CommentController {
 private Logger logger = LoggerFactory.getLogger(this.getClass());
 
 @Autowired
 private CommentService commentService;
 
 @RequestMapping(value = "/getComments", method = RequestMethod.POST)
 private @ResponseBody List<Comment> getComments(@RequestParam("sourceId") int sourceId,
 @RequestParam("limited") int limited,@RequestParam("offset") int offset) {
 logger.info("getComments");
 List<Comment> list = new ArrayList<Comment>();
 try{
 list = commentService.getComment(sourceId, limited, offset);
 }catch(Exception e){
 
 }
 return list;
 }
 
 @RequestMapping(value = "/insertComment", method = RequestMethod.POST)
 private @ResponseBody
 Result<Map<String,String>>insertComment(@RequestParam("sourceId") String sourceId,
 @RequestParam("comment") String comment,@RequestParam("userId") int userId,
 @RequestParam("userName") String userName,@RequestParam("replyCommentId") String replyCommentId,
 @RequestParam("replyUserName") String replyUserName,@RequestParam("userPhoto")String userPhoto) {
 logger.info("insertComment");
 Map<String, String> resultMap = new HashMap<String, String>();
 try{
 Integer rCId = -1;
 if(!replyCommentId.equals(""))
 rCId = Integer.parseInt(replyCommentId);
 commentService.insertComment(Integer.parseInt(sourceId), comment, userId,userName,rCId,replyUserName,userPhoto);
 resultMap.put("msg", "insertComment success");
 }catch(Exception e){
 System.out.print(e);
 resultMap.put("msg", "insertComment error");
 }
 return new Result<Map<String, String>>(true, resultMap);
 }
 
 @RequestMapping(value = "/deleteComment", method = RequestMethod.POST)
 private @ResponseBody
 Result<Map<String,String>>deleteComment(@RequestParam("commentId") String commentId) {
 logger.info("deleteComment");
 Map<String, String> resultMap = new HashMap<String, String>();
 try{
 commentService.deleteComment(commentId);
 resultMap.put("msg", "deleteComment success");
 }catch(Exception e){
 System.out.print(e);
 resultMap.put("msg", "deleteComment error");
 }
 return new Result<Map<String, String>>(true, resultMap);
 }
}

公共CSS(app.wxss)

/**app.wxss**/
.container {
 height: 100%;
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: space-between;
 padding: 200rpx 0;
 box-sizing: border-box;
} 
/* large button style */
.large-btn{
 background: #f68135;
 border-radius: 50rpx;
 border: 1px solid #f68135;
 color: #fff;
 height: 100rpx;
 line-height: 100rpx;
 margin: 0 auto;
 width: 96%;
 text-align: center;
}
.large-btn.empty{
 background: transparent;
 color: #f68135;
 margin-top: 50rpx;
}
.large-btn.disabled{
 border-color: #ccc;
 background: #ccc;
 color: #fff;
}
/* public style to clear default styles */
.fl{
 float: left;
}
.fr{
 float: right;
}
.fc{
 float:none;
}
.col-gray{
 color: #999!important;
}
 
 
/* the message of auction about goods & cars */
.pro-con{
 padding: 20rpx;
 background: #f1f1f1;
}
.pro-box{
 background: #fff;
 padding: 20rpx;
 box-sizing: border-box;
 border-radius: 10rpx;
 margin-bottom: 20rpx;
}
.pro-box .img{
 display: inline-block;
 vertical-align: top;
 width: 80rpx;
 height: 80rpx;
 border-radius: 50%;
 overflow: hidden;
 margin-right: 10rpx;
}
.pro-box .box{
 display: inline-block;
 vertical-align: top;
 width: calc(98% - 80rpx);
}
.pro-box .shead{
 padding-bottom: 20rpx;
}
.pro-box .shead .name{
 font-size: 30rpx;
 line-height: 40rpx;
}
.pro-box .shead .stxt{
 font-size: 26rpx;
 color: #999;
}
.pro-box .shead .fr{
 padding-top: 10rpx;
}
.pro-box .shead .fr navigator{
 font-size: 0;
}
.pro-box .shead .fr image{
 width: 48rpx;
 height: 48rpx;
}
 .pro-box .sharebtn{
 height:48rpx;
 background: #f68135;
 border-radius: 50rpx;
 border: 1px solid #f68135;
 color: #fff;
 text-align: center;
 line-height: 50rpx;
 font-size:30rpx;
} 
 
.pro-box .addr-info{
 align-items: center;
 justify-content: space-between;
 border-bottom: 1px dashed #ccc;
 margin: 0 -20rpx;
 margin-bottom: 20rpx;
 padding-bottom: 20rpx;
 padding-left: 20rpx;
 padding-right: 20rpx;
 display: inline-block;
}
 
.pro-box .addr-info .addr-text{
 font-size: 35rpx;
 line-height: 40rpx;
 width:100%;
}
 .pro-box .addr-info .addr-text .color1{
 color:lightskyblue;
 border-color: #ccc;
 border: 1px solid lightskyblue;
 border-radius:15px;
 margin-right: 5px;
 padding: 0rpx,2rpx,0rpx,2rpx;
} 
.pro-box .addr-info .addr-text .color2{
 color: #f68135;
 border-color: #ccc;
 border: 1px solid #f68135;
 border-radius:10px;
 margin-right: 5px;
 margin-left: 5px;
 padding: 0rpx,2rpx,0rpx,2rpx;
} 
 
.pro-box .position{
 width: 48rpx;
 height: 48rpx;
} 
 
.pro-box .comment{
 width: 55rpx;
 height: 48rpx;
} 
 
.pro-box .addr{
 align-items: center;
 justify-content: space-between;
 border-bottom: 1px dashed #ccc;
 margin: 0 -20rpx;
 margin-bottom: 20rpx;
 padding-bottom: 20rpx;
 padding-left: 20rpx;
 padding-right: 20rpx;
 display: flex;
}
 
.pro-box .addr .addr-text{
 font-size: 34rpx;
 line-height: 40rpx;
 max-width: 240rpx;
 min-width:200rpx;
 overflow: hidden;
 text-overflow: ellipsis;
 white-space: nowrap;
}
.pro-box .addr .addr-text .color-text{
 color: #f68135;
}
.pro-box .addr .time{
 font-size: 26rpx;
 line-height: 36rpx;
 text-align: center;
}
.pro-box .addr .line{
 background: #ccc;
 height: 1px;
 margin: 6rpx -20rpx;
 position: relative;
}
.pro-box .info{
 display: flex;
 align-items: center;
 justify-content: space-between;
}
.pro-box .info .text{
 vertical-align:text-top;
 font-size: 26rpx;
}
.pro-box .info .text .delete{
 color: #f68135;
 border-radius: 50rpx;
 border: 1px solid #f68135;
 width: 100rpx;
 height: 48rpx;
 text-align: center;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
背景音乐每次刷新都可以自动更换
Feb 01 Javascript
复制小说文本时出现的随机乱码的去除方法
Sep 07 Javascript
JS不间断向上滚动效果代码
Dec 25 Javascript
jquery序列化form表单使用ajax提交后处理返回的json数据
Mar 03 Javascript
jquery分割字符串的方法
Jun 24 Javascript
浅谈JS正则表达式的RegExp对象和括号的使用
Jul 28 Javascript
使用json来定义函数,在里面可以定义多个函数的实现方法
Oct 28 Javascript
JS实现json对象数组按对象属性排序操作示例
May 18 Javascript
vuex直接赋值的三种方法总结
Sep 16 Javascript
Vue一次性简洁明了引入所有公共组件的方法
Nov 28 Javascript
微信小程序自定义带价格显示日历效果
Dec 29 Javascript
vue项目里面引用svg文件并给svg里面的元素赋值
Aug 17 Javascript
微信小程序实现评论功能
Nov 28 #Javascript
在 Angular-cli 中使用 simple-mock 实现前端开发 API Mock 接口数据模拟功能的方法
Nov 28 #Javascript
小程序点击图片实现自动播放视频
May 29 #Javascript
django使用channels2.x实现实时通讯
Nov 28 #Javascript
在 Vue-CLI 中引入 simple-mock实现简易的 API Mock 接口数据模拟
Nov 28 #Javascript
详解Vue中watch的详细用法
Nov 28 #Javascript
vscode下的vue文件格式化问题
Nov 28 #Javascript
You might like
php self,$this,const,static,-&amp;gt;的使用
2009/10/22 PHP
PHP curl模拟浏览器采集阿里巴巴的实现代码
2011/04/20 PHP
16个最流行的JavaScript框架[推荐]
2011/05/29 Javascript
jquery遍历数组与筛选数组的方法
2013/11/05 Javascript
JS操作CSS随机改变网页背景实现思路
2014/03/10 Javascript
在Google 地图上实现做的标记相连接
2015/01/05 Javascript
javascript中的正则表达式使用指南
2015/03/01 Javascript
jquery中JSON的解析方式
2015/03/16 Javascript
jQuery使用slideUp方法实现控制元素缓慢收起
2015/03/27 Javascript
js上传图片及预览功能实例分析
2015/04/24 Javascript
jQuery动态生成不规则表格(前后端)
2017/02/21 Javascript
JS实现简单短信验证码界面
2017/08/07 Javascript
JS数组Object.keys()方法的使用示例
2019/06/05 Javascript
Swiper.js实现移动端元素左右滑动
2019/09/08 Javascript
JS三级联动代码格式实例详解
2019/12/30 Javascript
返回上一个url并刷新界面的js代码
2020/09/12 Javascript
[50:50]完美世界DOTA2联赛PWL S3 INK ICE vs DLG 第一场 12.20
2020/12/23 DOTA
python3.5绘制随机漫步图
2018/08/27 Python
对PyQt5基本窗口控件 QMainWindow的使用详解
2019/06/19 Python
pyqt5移动鼠标显示坐标的方法
2019/06/21 Python
Python 的AES加密与解密实现
2019/07/09 Python
Python爬虫图片懒加载技术 selenium和PhantomJS解析
2019/09/18 Python
浅析python中的del用法
2020/09/02 Python
css3的@media属性实现页面响应式布局示例代码
2014/02/10 HTML / CSS
美国生日蛋糕店:Bake Me A Wish!
2017/02/08 全球购物
HomeAway澳大利亚:预订你的度假屋,公寓、度假村、别墅等
2019/02/20 全球购物
UNIX文件系统常用命令
2012/05/25 面试题
小区门卫工作职责
2013/12/14 职场文书
致跳高运动员广播稿
2014/01/13 职场文书
高一新生军训感言
2014/03/02 职场文书
初中英语课后反思
2014/04/25 职场文书
交通事故一次性赔偿协议书范本
2014/11/02 职场文书
经典祝酒词大全
2015/08/12 职场文书
详细聊聊关于Mysql联合查询的那些事儿
2021/10/24 MySQL
Mysql中有关Datetime和Timestamp的使用总结
2021/12/06 MySQL
Nginx虚拟主机的搭建的实现步骤
2022/01/18 Servers