这篇文章主要为大家详细介绍了微信小程序仿知乎实现评论留言功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
最近沉迷学习无法自拔,太久没有码字,码一个小程序留言功能实现。先上一波最后效果图:
(删除按钮,是用户自己的留言时才会显示该按钮)
实现技术
后台: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);
}
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;
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。 |