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


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 乱码问题
Aug 06 Javascript
js实现checkbox全选、不选与反选的方法
Feb 09 Javascript
jQuery实现下滑菜单导航效果代码
Aug 25 Javascript
angular.js分页代码的实例
Jul 27 Javascript
html中鼠标滚轮事件onmousewheel的处理方法
Nov 11 Javascript
angular2倒计时组件使用详解
Jan 12 Javascript
vue.js开发环境安装教程
Mar 17 Javascript
js实现单张图片平移切换效果
Oct 11 Javascript
mockjs,json-server一起搭建前端通用的数据模拟框架教程
Dec 18 Javascript
vue配置请求本地json数据的方法
Apr 11 Javascript
详解vue添加删除元素的方法
Jun 30 Javascript
微信小程序自定义带价格显示日历效果
Dec 29 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 计算两个时间戳相隔的时间的函数(小时)
2009/12/18 PHP
PHPWind与Discuz截取字符函数substrs与cutstr性能比较
2011/12/05 PHP
PHPEXCEL 使用小记
2013/01/06 PHP
php 修改上传文件大小限制实例详解
2016/10/23 PHP
基于php双引号中访问数组元素报错的解决方法
2018/02/01 PHP
PHP设计模式(三)建造者模式Builder实例详解【创建型】
2020/05/02 PHP
Javascript-Mozilla和IE中的一个函数直接量的问题分析
2007/08/12 Javascript
使Ext的Template可以解析二层的json数据的方法
2007/12/22 Javascript
深入理解Javascript闭包 新手版
2010/12/28 Javascript
Javascript级联下拉菜单以及AJAX数据验证核心代码
2013/05/10 Javascript
JS增加行复制行删除行的实现代码
2013/11/09 Javascript
使用javascript实现有效时间的控制,并显示将要过期的时间
2014/01/02 Javascript
JS中JSON对象和String之间的互转及处理技巧
2016/04/06 Javascript
使用Angular.js开发的注意事项
2016/10/19 Javascript
jQuery源码分析之init的详细介绍
2017/02/13 Javascript
微信小程序中时间戳和日期的相互转换问题
2018/07/09 Javascript
在layui框架中select下拉框监听更改事件的例子
2019/09/20 Javascript
js实现列表向上无限滚动
2020/01/13 Javascript
vue内置组件component--通过is属性动态渲染组件操作
2020/07/28 Javascript
js实现表格单列按字母排序
2020/08/12 Javascript
Python编程实现输入某年某月某日计算出这一天是该年第几天的方法
2017/04/18 Python
浅谈Python中带_的变量或函数命名
2017/12/04 Python
Tensorflow之构建自己的图片数据集TFrecords的方法
2018/02/07 Python
HTML5移动端开发遇见的东西
2019/10/11 HTML / CSS
天美时手表加拿大官网:Timex加拿大
2016/09/01 全球购物
美国复古街头服饰精品店:Need Supply Co.
2017/02/22 全球购物
日语专业毕业生自荐信
2013/11/11 职场文书
优秀辅导员事迹材料
2014/02/16 职场文书
房地产财务部员工岗位职责
2014/03/12 职场文书
企业员工集体活动方案
2014/08/17 职场文书
ktv周年庆活动方案
2014/08/18 职场文书
暑期培训班招生方案
2014/08/26 职场文书
自我检讨报告
2015/01/28 职场文书
教师节班会开场白
2015/06/01 职场文书
Redis中缓存穿透/击穿/雪崩问题和解决方法
2021/12/04 Redis
Python语言中的数据类型-序列
2022/02/24 Python