微信小程序仿知乎实现评论留言功能
程序员文章站
2022-06-22 15:40:22
最近沉迷学习无法自拔,太久没有码字,码一个小程序留言功能实现。先上一波最后效果图:
(删除按钮,是用户自己的留言时才会显示该按钮)
实现技术...
最近沉迷学习无法自拔,太久没有码字,码一个小程序留言功能实现。先上一波最后效果图:
(删除按钮,是用户自己的留言时才会显示该按钮)
实现技术
后台: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; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 新版小程序登录授权的方法