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


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 相关文章推荐
JavaScript CSS菜单功能 改进版
Dec 20 Javascript
jquery中ajax调用json数据的使用说明
Mar 17 Javascript
文字溢出实现溢出的部分再放入一个新生成的div中具体代码
May 17 Javascript
jQuery操作input值的各种方法总结
Nov 21 Javascript
js动态删除div元素基本思路及实现代码
May 08 Javascript
javascript函数自动执行常用方法汇总
Mar 28 Javascript
基于Bootstrap里面的Button dropdown打造自定义select
May 30 Javascript
jQuery插件FusionCharts实现的2D饼状图效果【附demo源码下载】
Mar 03 Javascript
js+html5生成自动排列对话框实例
Oct 09 Javascript
vue组件间的参数传递实例详解
Apr 26 Javascript
vue实现pdf文档在线预览功能
Nov 26 Javascript
vue-cli4.x创建企业级项目的方法步骤
Jun 18 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
星际争霸中的热键
2020/03/04 星际争霸
PHP 函数执行效率的小比较
2010/10/17 PHP
PHP5.5.15+Apache2.4.10+MySQL5.6.20配置方法分享
2016/05/06 PHP
PHP通过文件路径获取文件名的实例代码
2018/10/14 PHP
通过PHP实现用户注册后邮箱验证激活
2020/11/10 PHP
javascript中的一些注意事项 更新中
2010/12/06 Javascript
使用JS 清空File控件的路径值
2013/07/08 Javascript
javascript实用小函数使用介绍
2013/11/11 Javascript
jquery对ajax的支持介绍
2013/12/10 Javascript
JS中判断null、undefined与NaN的方法
2014/03/24 Javascript
jquery中获得元素尺寸和坐标的方法整理
2014/05/18 Javascript
jquery 3D 标签云示例代码
2014/06/12 Javascript
javascript中attachEvent用法实例分析
2015/05/14 Javascript
javascript实现不同颜色Tab标签切换效果
2016/04/27 Javascript
使用jQuery实现WordPress中的Ctrl+Enter和@评论回复
2016/05/21 Javascript
JavaScript DOM节点操作方法总结
2016/08/23 Javascript
原生JS实现幻灯片
2017/02/22 Javascript
浅谈AngularJS中使用$resource(已更新)
2017/09/14 Javascript
利用nginx + node在阿里云部署https的步骤详解
2017/12/19 Javascript
Vue实现导出excel表格功能
2018/03/30 Javascript
使用React手写一个对话框或模态框的方法示例
2019/04/25 Javascript
JavaScript实现PC端横向轮播图
2020/02/07 Javascript
Python的Django应用程序解决AJAX跨域访问问题的方法
2016/05/31 Python
python利用matplotlib库绘制饼图的方法示例
2016/12/18 Python
python+selenium实现163邮箱自动登陆的方法
2017/12/31 Python
对Python的交互模式和直接运行.py文件的区别详解
2019/06/29 Python
详解Python 中sys.stdin.readline()的用法
2019/09/12 Python
python中常见错误及解决方法
2020/06/21 Python
简述python&amp;pytorch 随机种子的实现
2020/10/07 Python
新秀丽拉杆箱美国官方网站:Samsonite美国
2016/07/25 全球购物
机关党员三严三实心得体会
2014/10/13 职场文书
国庆节慰问信
2015/02/15 职场文书
2016年小学生迎国庆广播稿
2015/12/18 职场文书
PL350与SW11的比较
2021/04/22 无线电
JavaScript原型链详解
2021/11/07 Javascript
Python OpenCV超详细讲解读取图像视频和网络摄像头
2022/04/02 Python