微信小程序 点赞+评论(无限级评论回复)/带图评论解决方案

  • 需求描述
  • 实现要点分析
  • 目录结构
  • 前端功能方法集成
  • 后端方法
  • 数据库结构
  • 效果展示
  • 未解决的问题

需求描述

最近在思考一个需求:文章可以评论,并且是无限级带图评论。初次看到这个需求疑惑了很久,由于自己接触小程序时间很短,对小程序机制也了解的很肤浅,所以就按照自己的想法做了一个基础的框架版本出来,里面有些内容还希望有高人指点一下,如果有大能能把它封装为插件,那就再好不过有了。废话不说了,直接上内容:

实现要点分析

  1. 微信小程序前端界面如何展现:在参考了众多的评论回复展示模块后,决定采用简书的评论回复展示方式
  2. 评论回复功能如何实现:本例借助于php无限级分类的思路来处理无限级评论
  3. 评论数据递归如何处理:本例采用的思路是当 评论的主体是文章(即评论的pid为0,这点很重要)时,评论展示为最外层评论,其余的基于本评论主体下的衍生回复评论展示为最内层。基于此思路,我在递归时做出判断,让所有的子项全部置于pid为0的字段children数组中,这样子就行实现前段所需要的数据格式
  4. 图片上传如何处理:本例前端采用小程序自带的接口,并且在传图时,将上传成功的图片置于临时数据集合中(后台有对应的上传接口),等点击提交按钮时,一起传递给服务器
  5. 点赞功能如何实现:本例中,点赞的思路是先在本地集合中处理点赞数据标记,利用wx.setStorageSync系列函数实现不同作用于数据的存储、交换,等后台返回执行结果后,在根据结果判断是否要在本地执行本地数据修改结果保存
  6. json数据查找、递归修改如何处理:这一块我真的没有什么好的方法,来实现递归查找数据并修改数据,如果有朋友有更好的方法,烦请浏览指导

目录结构

|-./app
|-------|–app.js
|-------|–app.json
|-------|–app.wxss
|-------|–project.config.json
|-------|–utils (扩展资源目录)
|-------|------|–util.js
|-------|–pages (页面资源目录)
|-------|-------|–index
|-------|-------|-------|–index.js
|-------|-------|-------|–index.json
|-------|-------|-------|–index.wxml
|-------|-------|-------|–index.wxss
|-------|–static (静态资源目录)
|-------|-------|–img
|-------|–template (模板资源目录)
|-------|-------|–template.js
|-------|-------|–template.wxml
|-------|-------|–template.wxss

准备好了目录结构,接下里就切入正题:

前端功能方法集成

app.js

App({onLaunch: function () {var that = this//调用登录接口(此处内容自行处理)},globalData: {userInfo: {},swiperInfo: {},template:template,baseUrl:'https://xx.xxxx.cn',}
})

app.wxss

/**app.wxss**/
@import "/template/template.wxss";
page{background:#f8f8f8; maegin:0; padding:0;}
.container {height: 100%;display: flex;flex-direction: column;align-items: center;justify-content: space-between;padding: 200rpx 0;box-sizing: border-box;
}
button {position:relative;display:block;margin-left:auto;margin-right:auto;padding-left:0;padding-right:0;box-sizing:border-box;font-size:18px;text-align:left;text-decoration:none;line-height:2.55555556;border-radius:5px;-webkit-tap-highlight-color:transparent;overflow:hidden;color:#000000;background-color:#F8F8F8;
}.btn-area button{display:block; text-align:center; width:80%; line-height:2;}
view{overflow:hidden;font-size:30rpx;color:#555;}
.swiperBox{width:100%; margin:0 auto; margin:0; background:#fff; border-radius:0rpx;}
.mainBox{width:97.5%; margin:0 auto; margin-top:15rpx; background:#fff; border-radius:8rpx;}
.widyBox{width:100%; margin:0 auto; margin:0; background:#fff; border-radius:8rpx;}
.examBox{width:97.5%; margin:0 auto; background:#fff; border-radius:8rpx;}
.blank{width:100%; height:0rpx; clear:both;}
.blank10{width:100%; height:10rpx; clear:both;}
.blank20{width:100%; height:20rpx; clear:both;}
.blank30{width:100%; height:30rpx; clear:both;}
.blank40{width:100%; height:40rpx; clear:both;}
.blank50{width:100%; height:50rpx; clear:both;}
.blank60{width:100%; height:60rpx; clear:both;}
.blank70{width:100%; height:70rpx; clear:both;}
.blank80{width:100%; height:80rpx; clear:both;}
.blank90{width:100%; height:90rpx; clear:both;}
.blank100{width:100%; height:100rpx; clear:both;}
.blank110{width:100%; height:110rpx; clear:both;}
.blank120{width:100%; height:120rpx; clear:both;}
.blank130{width:100%; height:130rpx; clear:both;}.split10{width:100%; height:31rpx;position:relative;}
.split10_line{width:100%; height:14rpx;border-bottom:1px solid #ccc;}
.split10-line{width:100rpx; height:11rpx; display:block; position:absolute; background:#ccc; top:calc(50% - 8rpx); left:calc(50% - 50rpx);}.blockTit{width:100%; height:50rpx; line-height:50rpx; font-size:30rpx; padding-left:0rpx;}
.blockTit::before{width:10rpx; height:50rpx; background:#ff0000; content:" "; display:block; float:left;}
.blockTit::after{width:10rpx; height:50rpx; content:" "; display:block; float:left;}.commentSubmit{width:97.5%; margin:10rpx auto 30rpx;}
/* comment */
.commentSubmit .conts{ width:calc(90% - 2rpx); height: auto; position:relative; padding-bottom:0rpx; margin:15rpx auto 15rpx;}
.commentSubmit .conts textarea.areas{ width:99.2%; height:152rpx; font-size:30rpx; /*text-indent:28rpx;*/ border:1rpx solid gainsboro; padding-top:30rpx; margin:0 auto; overflow: hidden; position:relative;}
.commentSubmit .currentWordNumber{ font-size: 28rpx; color: gray; position: absolute; right:10rpx; bottom:10rpx;}
.commentSubmit .hint{ font-size: 28rpx; position: absolute; left:20rpx; bottom:10rpx; color: #FF6600;}
/* ͼƬ */
.commentSubmit .img_box{ width:100%; position:relative; display: flex; flex-wrap: wrap; margin:0 auto;}
.commentSubmit .imgs{ width:22%; /*display:flex;*/ justify-content: center; margin-right:4% ;margin-bottom:20rpx;}
.commentSubmit .imgs:last-child{ margin-right:0;}
.commentSubmit .imgs image{ width:100%; max-height:160rpx; border:1px solid rgba(214, 212, 212, 0.1); /* box-shadow: 5rpx 5rpx 1rpx 3rpx #e2e0e0; */}
.commentSubmit .imgs>image{ width:80%; max-height:160rpx;}
.commentSubmit .imgs .images{ position:relative;}
.commentSubmit .images button{ width:100%; height:100%; position:absolute; top:0; left:0; line-height:2;}
.commentSubmit .btn-area{margin-top:30rpx;}
.commentSubmit .img_box .images{ width:100%; max-width:125rpx; height: 125rpx; border:1px solid #ccc; border-radius:4rpx; display: flex; align-items: center; justify-content: center;}
.commentSubmit .img_box .images>image{ width:60rpx; height:60rpx;}

util.js

//获取应用实例
const app = getApp();
//Translate data method
function request(url, paramData, doSuccess, method="GET") {let host = app.globalData.baseUrl+'/api/';url = url || '';paramData = paramData||[];//console.log("++++++");wx.request({url: host + url,header: {"content-type": "application/json;charset=UTF-8"},method: method,data: paramData,success: function (res) {doSuccess(res);},fail: function () {wx.hideLoading();wx.showToast({title     :   '请求超时',icon      :   'loading',duration  :   2000})},})
}function parseParam(param, key, encode) {if (param==null) return '';var paramStr = '';var t = typeof (param);if (t == 'string' || t == 'number' || t == 'boolean') {if(paramStr == ''){paramStr += '&' + key + '='  + ((encode==null||encode) ? encodeURIComponent(param) : param); }else{paramStr += '&' + key + '='  + ((encode==null||encode) ? encodeURIComponent(param) : param);             }} else {for (var i in param) {var k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i)paramStr += parseParam(param[i], k, encode)}}return paramStr;
}let commentAction = {// 用户点击评论显示弹窗replayAct: function(_that,e) {//let _that = thisif(!_that.data.click) {_that.setData({click: true,})}if(_that.data.opt){_that.setData({opt: false,})// 关闭显示弹窗动画的内容,不设置的话会出现:点击任何地方都会出现弹窗,就不是指定位置点击出现弹窗了setTimeout(() => {_that.setData({click: false,})}, 500)}else{_that.setData({opt: true})}// 查验数据是否存在if(e!==undefined){//  传递数据到from表单var ed = e.target.datasetif(ed.aid!==undefined){this._ssWindow(_that, ed)}}},/***开关窗口***/_ssWindow: function(_that, ed) {let comment_bInfo = {aid:ed.aid,pid:ed.pid,type:ed.type||'',nav_id:ed.nav_id,min:_that.data.min,max:_that.data.max,unionID:wx.getStorageSync('openId'),avatar:wx.getStorageSync('avatarUrl'),}console.log(comment_bInfo)_that.setData({comment_bInfo:comment_bInfo})},/*** 字数限制*/ inputs: function (that,e) {//let that = this;// 获取输入框的内容var value = e.detail.value    // 获取输入框内容的长度var len = parseInt(value.length)//最少字数限制if (len <that.data.min){that.setData({texts: "加油,至少要输入5个字哦",disabled: true})}else if (len >= that.data.min){that.setData({texts: "",disabled: false})}//最多字数限制if (len > that.data.max) return// 当输入框内容的长度大于最大长度限制(max)时,终止setData()的执行that.setData({currentWordNumber: len //当前字数  })},/*** 上传图片方法*/upload: function (that) {//let that = this;wx.chooseImage({count: 3, // 默认9sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有success: res => {wx.showToast({title: '正在上传...',icon: 'loading',mask: true,duration: 500})// 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片let tempFilePaths = res.tempFilePaths;that.setData({tempFilePaths: tempFilePaths});console.log(that.data.tempFilePaths);/*** 上传完成后把文件上传到服务器**/var count = 0;for (var i = 0, h = tempFilePaths.length; i < h; i++){//上传文件wx.uploadFile({url: app.globalData.baseUrl+'/api/api/uploads?act=image',filePath: tempFilePaths[i],name: 'file',header: {"Content-Type":"multipart/form-data"},success: function (res) {let imgs1=that.data.imgs1;res=res.data.startsWith("\ufeff")?res.data.slice(1):res.data;    //去除bomlet newImgData=JSON.parse(res);                             imgs1.push(newImgData.data.imgurl);that.setData({imgs1: imgs1});count++;//如果是最后一张,则隐藏等待中  if (count == tempFilePaths.length) {wx.hideToast();}},fail: function (res) {wx.hideToast();wx.showToast({title: '图片上传失败...',icon: 'loading',mask: true,duration: 500})}});}}})},/*** 预览图片方法*/listenerButtonPreviewImage: function (that,e) {//let that = thislet index = e.target.dataset.indexconsole.log(that.data.tempFilePaths[index])console.log(that.data.tempFilePaths)wx.previewImage({current: that.data.tempFilePaths[index],urls: that.data.tempFilePaths,//这根本就不走success: function (res) {//console.log(res);},//也根本不走fail: function () {//console.log('fail')}})},/*** 长按删除图片*/deleteImage: function (that,e) {//let that = thisvar tempFilePaths = that.data.tempFilePathsvar imgs1 = that.data.imgs1var index = e.currentTarget.dataset.index  //获取当前长按图片下标wx.showModal({title: '提示',content: '确定要删除此图片吗?',success: function (res) {if (res.confirm) {console.log('点击确定了');tempFilePaths.splice(index, 1);imgs1.splice(index, 1);} else if (res.cancel) {console.log('点击取消了');return false;}that.setData({tempFilePaths:tempFilePaths,imgs1:imgs1});}})},//表单提交按钮formSubmit: function (that,e) {//let that = thise.detail.value.pic=that.data.imgs1.join(',')   //加入图片数据到form数据数组中let param = parseParam(e.detail.value)// 提交表单数据request('api/handleData', {'act': 'pushComment',   'param': param,},function(res) {console.log(res.data.data.commentInfo);if(res.data.error==0){//  关闭/打开窗口//   设置/清除所有的数据that.setData({commentInfo: res.data.data.commentInfo,})wx.setStorageSync('handleStatus',true)}}, "POST")console.log(wx.getStorageSync('handleStatus'))if(wx.getStorageSync('handleStatus')){this.replayAct(that)that.setData({form_value: '',allValue: ''})wx.removeStorageSync('handleStatus')}//console.log('form发生了submit事件,携带数据为:', e.detail.value)},//表单重置按钮formReset: function (that,e) {//console.log('form发生了reset事件,携带数据为:', e.detail.value)this.replayAct(that)that.setData({allValue: ''})},//---------------点赞--------------/***点赞***/thumbsupAct: function (that,e) {var ed = e.target.datasetlet thumbsupData = {cmid:ed.id,aid:ed.aid,type:ed.type,nav_id:ed.nav_id,unionID:wx.getStorageSync('openId'),isThumbsup:ed.isthumbsup,}console.log(thumbsupData)//  本地 检索json数组并 修改数据var commentInfo = that.data.commentInfocommentInfo.list=this.findle(that, ed.id)//   是否点赞request('api/handleData', {'act': 'thumbsUpDone',   'param': parseParam(thumbsupData),},function(res) {if(res.data.data.result==1){//修正数据that.setData({commentInfo: commentInfo})}}, "POST")},/***查找**多维数组***开始***/findle: function(that, str){if(JSON.stringify(str) == "" || typeof(str) == "object"){return;}else{var commentInfo = that.data.commentInfo, obj=commentInfo.list;return this.traverse(obj, str);}},/***查找json数组-最多四维数组***/  (这部分太臃肿,如果有哪位朋友有更好的无限级递归修改json数据的方法,希望留言指导一下,谢谢!)traverse: function (obj, str) {//obj 就是json对象if(typeof(obj)=="object" && obj.length){for(var a=0, le=obj.length; a<le; a++){if(obj[a].id==str){ //  第一层判断,成立则修改属性值obj[a].isThumbsup = obj[a].isThumbsup ? 0 : 1}else{let _arr0 = obj[a].childrenif(typeof(_arr0)==="object" && _arr0.length){for(var b=0,len=_arr0.length; b<len; b++){if(_arr0[b].id==str){ //  第二层判断,成立则修改属性值_arr0[b].isThumbsup = _arr0[b].isThumbsup ? 0 : 1}else{let _arr1 = _arr0[b].childrenif(typeof(_arr1)==="object" && _arr1.length){for(var c=0,leng=_arr1.length; c<leng; c++){if(_arr1[c].id==str){ //  第三层判断,成立则修改属性值_arr1[c].isThumbsup = _arr1[c].isThumbsup ? 0 : 1}else{let _arr2 = _arr2[c].childrenif(typeof(_arr2)==="object" && _arr2.length){for(var d=0,lengt=_arr2.length; d<lengt; d++){if(_arr2[d].id==str){   //  第四层判断,成立则修改属性值_arr2[d].isThumbsup = _arr2[d].isThumbsup ? 0 : 1}}}}}}}}}}}}return obj},/***查找**多维数组-最多为四维数组***结束***/
}
module.exports = {request: request,parseParam: parseParam,commentAction: commentAction,
}

template.wxml(这部分的样式是参考简书评论中的评论回复样式)

<!----replay-thumbsUp---->
<!----loading---->
<template name="replayThumbsup">
<block wx:if="{{commentInfo}}">
<view class="commentmut"><view class="note"><view><view class="note-graceful-button"><view class="line-container"><view class="line"></view><p class="content"><span>点赞赚钻</span><span class="special">最高日赚数百元</span></p></view><view class="this-is-graceful-btn"><i class="iconfont ic-H-like"></i></view><span class="graceful-words"> 赞 <span class="numbers">(0)</span></span></view></view></view><view id="comment-main"><view class="margin-top"></view><view class="top-title">评论({{commentInfo.num}})<view class="button write-comment" data-pid="0" data-aid="{{commentInfo.aid}}" data-nav_id="{{commentInfo.nav_id}}" data-type="{{commentInfo.type}}" catchtap="replayAct"><i class="iconfont ic-write"></i>写评论</view></view><view class="comments-wrap"><!--------comment-list-------><block wx:for="{{commentInfo.list}}" wx:for-item="co" wx:for-index="index" wx:key="*.this">         <view class="comment-item"><a href="javascript:void(0)" class="user-avatar avatar"><span class="avatar"><image src="{{co.avatar}}" /></span></a><view class="main"><view class="comment-user"><view class="nickname-wrap"><a href="javascript:void(0)" class="nickname oneline">{{co.nickname}}</a></view></view><view class="comment-content">{{co.content}}<block wx:if="{{co.pic}}" wx:for="{{co.pic}}" wx:key="*.this" wx:for-item="cop" wx:for-index="copindex"><image mode="widthFix" src="{{cop}}" /></block>                        </view><view class="comment-extra"><span class="floor">{{index+1}}楼</span>{{co.create_time}}<view class="social-wrap"><view class="button reply-btn" data-pid="{{co.id}}" data-aid="{{co.aid}}" data-nav_id="{{co.nav_id}}" data-type="{{co.type}}" catchtap="replayAct">回复</view><view class="button" data-id="{{co.id}}" data-aid="{{co.aid}}" data-pid="{{co.pid}}" data-nav_id="{{co.nav_id}}" data-isthumbsup="{{co.isThumbsup}}" data-type="{{co.type}}" catchtap="thumbsupAct">点赞</view></view></view><block wx:if="{{co.children}}"><view class="sub-comment-list"><!--------son-list-------><block wx:for="{{co.children}}" wx:key="*.this" wx:for-item="coc" wx:for-index="cocindex"><view class="sub-comment-item"><a href="javascript:void(0)" class="user-avatar"><span class="avatar son"><image src="{{coc.avatar}}" /></span></a><view class="sub-comment-wrap"><a href="javascript:void(0)" class="nickname oneline">{{coc.nickname}}</a><view class="sub-comment-text"><a href="javascript:void(0)" class="maleskine-author" target="_blank" data-user="1">@{{coc.replay_nickname}}</a>{{coc.content}}<block wx:if="{{coc.pic}}" wx:for="{{coc.pic}}" wx:key="*.this" wx:for-item="cocp" wx:for-index="cocpindex"><image mode="widthFix" src="{{cocp}}" /></block>                                 </view><view class="sub-comment-extra">{{coc.create_time}}<view class="button reply-btn"><view class="sbtn" data-aid="{{coc.aid}}" data-pid="{{coc.id}}" data-nav_id="{{coc.nav_id}}" data-type="{{coc.type}}" catchtap="replayAct">回复</view><view class="sbtn" data-id="{{coc.id}}" data-aid="{{coc.aid}}" data-pid="{{coc.pid}}" data-nav_id="{{coc.nav_id}}" data-isthumbsup="{{coc.isThumbsup}}" data-type="{{coc.type}}" catchtap="thumbsupAct">点赞</view></view>                                   </view></view></view></block><!--------son-list-------></view></block><!----></view></view></block><!--------comment-list-------></view><view class="no-more-comment"></view><!---------------comBoxIng-------------------><view class="comment-wrapper"><!-- 底部弹窗动画的内容 --><view class='pupContent {{click? "showContent": "hideContent"}} {{opt? "open": "close"}}' hover-stop-propagation='true'><view class="pupContent-top"></view><view class="commentSubmit"><form bindsubmit="formSubmit" bindreset="formReset"><view class="conts"><textarea name="content" class="areas" placeholder='留下点评,帮助更多人' minlength="{{comment_bInfo.min}}" maxlength="{{comment_bInfo.max}}" bindinput="inputs">{{form_value}}</textarea><text class="hint">{{texts}}</text><text class="currentWordNumber">{{currentWordNumber|0}}/{{comment_bInfo.max}}</text><input bindinput="bindReplaceInput" style="display:none" placeholder="连续的两个1会变成2" value="{{form_value}}" /></view><view class="img_box"><view class="imgs" wx:for="{{tempFilePaths}}" wx:key="index"><image src='{{item}}' bindlongpress="deleteImage" bindtap="listenerButtonPreviewImage" data-index="{{index}}" mode='widthFix' /></view><view class="imgs"><view class="images" bindtap="upload"><image src='/static/img/plus.png' mode='widthFix' /></view></view></view><view class="btn-area"><input name="aid" style="display:none" value="{{comment_bInfo.aid}}" /><input name="pid" style="display:none" value="{{comment_bInfo.pid}}" /><input name="type" style="display:none" value="{{comment_bInfo.type}}" /><input name="nav_id" style="display:none" value="{{comment_bInfo.nav_id}}" /><input name="unionID" style="display:none" value="{{comment_bInfo.unionID}}" /><input name="avatar" style="display:none" value="{{comment_bInfo.avatar}}" /><button type="warn" disabled="{{disabled}}" form-type="submit">提交</button><button type="default" form-type="reset">重置</button></view></form></view></view><!-- 固定的背景 --><view class='pupContentBG {{click?"showBG":"hideBG"}} {{opt?"openBG":"closeBG"}}' catchtap='replayAct'></view><!----></view><!---------------comBoxEnd-------------------></view>
</view>
</block>
</template>

template.wxss(这部分的样式是参考简书评论中的评论回复样式)

/**--------------------------------replay and thumbsUp Start----------------------------------**/
/***thumbsUp***/
.commentmut .note{background-color:#fff;}
.commentmut .note-graceful-button{margin:15px 0 26px;text-align:center}
.commentmut .note-graceful-button .line-container{position:relative;height:31px;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.commentmut .note-graceful-button .line-container .line{position:absolute;width:100%;top:15px;height:1px;background-color:#ddd;z-index:0}
.commentmut .note-graceful-button .line-container .content{display:inline-block;margin:0 auto;position:relative;z-index:1;font-size:14px;line-height:31px;padding:0 10px;background-color:#fff}
.commentmut .note-graceful-button .line-container .content{background-color:#fff}
.commentmut .note-graceful-button .line-container .content span{display:inline-block;vertical-align:middle;font-size:16px;margin-right:5px;font-weight:500;line-height:22px;color:#666}
.commentmut .note-graceful-button .line-container .content .special{color:#ea6f5a}
.commentmut .note-graceful-button .this-is-graceful-btn{width:60px;height:60px;line-height:60px;text-align:center;margin:12px auto 3px;border-radius:50%}
.commentmut .note-graceful-button .this-is-graceful-btn.is-active i{color:#ea6f5a}
.commentmut .note-graceful-button .this-is-graceful-btn i{color:#888;font-size:60px}
.commentmut .note-graceful-button .graceful-words{font-size:13px;color:#888}
.commentmut .note-graceful-button .graceful-words.is-active{color:#ea6f5a}
.commentmut .note-graceful-button .graceful-words .numbers{font-size:10px}
/***thumbsUp***/.commentmut #comment-main{position:relative;margin-left:0;margin-right:0;background-color:#fff}
.commentmut #comment-main .margin-top{height:10px;background-color:#f5f5f5;width:100%;}
.commentmut #comment-main .top-title {padding:15px 18px 10px; border-bottom:none; margin-left:0; margin-right:0; font-size:16px; font-weight:700; color:#545454;}
.commentmut #comment-main .top-title .write-comment{float:right;color:#ea6f5a;line-height:22px;font-size:14px}
.commentmut #comment-main .comments-wrap>.comment-item{border-bottom: 1px solid; border-color: #f0f0f0;}
.commentmut #comment-main .comments-wrap>.comment-item:last-child{border-bottom:none}
.commentmut #comment-main .comment-open-app-btn-wrap{text-align:center;padding-bottom:20px;margin-top:20px}
.commentmut #comment-main .comment-open-app-btn-wrap .comment-open-app-btn{border:2px solid #ea6f5a;color:#ea6f5a;background-color:transparent;border-radius:4px;text-align:center;width:250px;height:45px;padding:10px 30px;font-size:16px}
.commentmut #comment-main .emoji{vertical-align:sub}
.commentmut #comment-main .has-more-comment{padding:16px 0;font-size:15px;color:#969696;text-align:center;background-color:transparent}
.commentmut #comment-main .comment-reply-drawer{padding:15px}
.commentmut #comment-main .comment-reply-drawer .control{text-align:right}
.commentmut #comment-main .comment-reply-drawer .control .button{margin-left:15px;width:80px;height:35px;font-size:17px;line-height:35px}
.commentmut #comment-main .comment-reply-drawer .control .cancel{color:#999}
.commentmut #comment-main .comment-reply-drawer .control .submit{background-color:#42c02e;color:#fff;border-radius:4px}
.commentmut #comment-main .comment-wrapper .commentSubmit{width:100%;}
.commentmut #comment-main .comment-wrapper .commentSubmit .conts{width:100%;}
.commentmut #comment-main .comment-wrapper textarea{padding:15rpx; width:calc(100% - 36rpx); height:150px; font-size:14px; line-height:1.5; background-color:transparent; color:#333}.commentmut #comment-main .comment-wrapper .commentSubmit .btn-area button{width:44%; margin:0 3%; font-size:16px; float:left;}
.commentmut #comment-main .comment-wrapper .commentSubmit .btn-area button[type=warn]{color:#fff;}
.commentmut #comment-main .comment-wrapper .commentSubmit .btn-area button[type=default]{color:#555;}.commentmut #comment-main .comment-wrapper textarea{color:#555}
.commentmut #comment-main .comment-wrapper .control{position:absolute;bottom:15px;right:15px}
.commentmut #comment-main .no-content{padding:40px 0;font-size:14px;color:#969696;text-align:center;background-color:#fff}
.commentmut #comment-main .no-content{background-color:#3f3f3f}
.commentmut #comment-main .no-content img{padding-bottom:10px;width:140px}
.commentmut #comment-main .no-content .button{color:#3194d0}.commentmut .comment-item .main .comment-content{word-break:break-word!important;}
.commentmut .comment-item{padding-top:15px;padding-left:18px}
.commentmut .comment-item .user-avatar{float:left;margin-right:10px}
.commentmut .comment-item .main{overflow:hidden}
.commentmut .comment-item .main .comment-user{margin-bottom:6px}
.commentmut .comment-item .main .comment-user .nickname-wrap{overflow:hidden}
.commentmut .comment-item .main .comment-user .nickname-wrap .nickname{font-size:16px;font-weight:700;vertical-align:middle;color:#484848}
.commentmut .comment-item .main .comment-user .nickname-wrap .label{padding:1px 2px;font-size:12px;color:#ea6f5a;border:1px solid #ea6f5a;border-radius:3px;vertical-align:middle;margin-left:5px}
.commentmut .comment-item .main .comment-user .nickname-wrap img{width:18px;height:18px;vertical-align:middle;margin-left:5px}
.commentmut .comment-item .main .comment-content{font-size:16px;line-height:1.7;padding-right:18px;color:#484848}
.commentmut .comment-item .main .comment-content>image{width:100%;margin-bottom:10rpx;}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-text>image{width:100%;margin-bottom:10rpx;}
.commentmut .comment-item .main .comment-images{margin-top:8px;margin-bottom:8px;position:relative;font-size:0}
.commentmut .comment-item .main .comment-images .image{position:relative;border-radius:2px;background-position:50%;background-repeat:no-repeat;overflow:hidden}
.commentmut .comment-item .main .comment-images .image:not(:nth-child(3n)){margin-right:2%}
.commentmut .comment-item .main .comment-images .image:nth-child(n+4){margin-top:2%}
.commentmut .comment-item .main .comment-images .image.is-long:after{content:"\957F\56FE";position:absolute;bottom:0;margin-left:-22px;width:44px;height:28px;font-size:18px;text-align:center;line-height:28px;background-color:#2f2f2f;opacity:.5;color:#fff;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5);-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom}
.commentmut .comment-item .main .comment-images .one-image.is-long{height:0;padding-top:67%;min-width:33%;-webkit-background-size:33% 33%;background-size:33%;background-position:0}
.commentmut .comment-item .main .comment-images .one-image.is-long:after{left:33%}
.commentmut .comment-item .main .comment-images .one-image.is-normal{height:98px;width:100%;-webkit-background-size:contain;background-size:contain;background-position:0}
.commentmut .comment-item .main .comment-images .many-image{-webkit-background-size:cover;background-size:cover;display:inline-block;height:0;width:32%;padding-top:32%}
.commentmut .comment-item .main .comment-images .many-image:after{left:100%}
.commentmut .comment-item .main .comment-images .mask{position:absolute;right:0;top:0;height:100%;width:32%;border-radius:2px;background-color:rgba(47,47,47,.6);overflow:hidden;pointer-events:none}
.commentmut .comment-item .main .comment-images .mask span{font-size:15px;color:#fff;position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:100%;text-align:center}
.commentmut .comment-item .main .comment-extra{font-size:12px;height:20px;line-height:20px;color:#b1b1b1;margin-top:5px;margin-bottom:15px}
.commentmut .comment-item .main .comment-extra .floor{margin-right:4px}
.commentmut .comment-item .main .comment-extra .social-wrap{float:right;height:20px;margin-right:18px}
.commentmut .comment-item .main .comment-extra .social-wrap .button{margin-left:25px;color:#b1b1b1; /*width:45px;*/ float:left;}
.commentmut .comment-item .main .comment-extra .social-wrap .button .icoX{margin-right:5px;color:#b1b1b1; width:20px; float:left;}
.commentmut .comment-item .main .comment-extra .social-wrap .button .icoX image{width:100%; max-width:20px; height:100%;}
.commentmut .comment-item .main .comment-extra .social-wrap .button>image{width:100%; max-width:20px; height:100%;}
.commentmut .comment-item .main .comment-extra .social-wrap i{font-size:19px;vertical-align:middle}
.commentmut .comment-item .main .comment-extra .social-wrap span{vertical-align:middle;font-size:13px;margin-right:3px}
.commentmut .comment-item .main .comment-extra .social-wrap .ic-icon_comment_like_active,.comment-item .main .comment-extra .social-wrap span.active{color:#ea6f5a}
.commentmut .comment-item .main .more-sub-comment{display:block;font-size:14px;font-weight:500;color:#3194d0;border-top:1px solid;width:100%;padding:20px 0;text-align:left;border-color:#e6e6e6}
.commentmut .comment-item .main .more-sub-comment{border-color:#1f1f1f}
.commentmut .avatar{position:relative;background-color:transparent;-webkit-transition:.4s linear;-o-transition:.4s linear;transition:.4s linear;overflow:hidden;display:inline-block;border-radius:50%}
.commentmut .avatar image{width:100%;height:100%;display:block}
.commentmut .avatar{width:35px; height:35px;}
.commentmut .son{width: 22px; height: 22px;}.commentmut .sub-comment-item{padding:15px 0;border-top:1px solid;font-size:14px;color:#484848;border-color:#e6e6e6}
.commentmut .sub-comment-item .user-avatar{float:left;margin-right:8px}
.commentmut .sub-comment-item .sub-comment-wrap{overflow:hidden;padding-right:18px}
.commentmut .sub-comment-item .sub-comment-wrap .nickname{font-size:16px;font-weight:700;vertical-align:middle;color:#484848}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-text{font-size:15px}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-text a{color:#3194d0}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images{margin-top:8px;margin-bottom:8px;position:relative;font-size:0}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .image{position:relative;border-radius:2px;background-position:50%;background-repeat:no-repeat;-webkit-background-size:cover;background-size:cover;display:inline-block;height:0;width:32%;padding-top:32%;overflow:hidden}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .image:not(:nth-child(3n)){margin-right:2%}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .image:nth-child(n+4){margin-top:2%}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .image:after{left:100%}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .image.is-long:after{content:"\957F\56FE";position:absolute;bottom:0;margin-left:-22px;width:44px;height:28px;font-size:18px;text-align:center;line-height:28px;background-color:#2f2f2f;opacity:.5;color:#fff;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5);-webkit-transform-origin:left bottom;-ms-transform-origin:left bottom;transform-origin:left bottom}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .mask{position:absolute;right:0;top:0;height:100%;width:32%;border-radius:2px;background-color:rgba(47,47,47,.6);overflow:hidden;pointer-events:none}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .mask span{font-size:15px;color:#fff;position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:100%;text-align:center}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-images .collapsed-btn{color:#3194d0;font-size:13px;margin-top:11px}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-extra{padding-top:5px;font-size:12px;color:#b1b1b1}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-extra .reply-btn{padding-left:6px;width:190rpx;float:right;font-size:13px;color:#b1b1b1;}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-extra .reply-btn .sbtn{width:80rpx;margin-right:30rpx;float:left;color:#b1b1b1;}
.commentmut .sub-comment-item .sub-comment-wrap .sub-comment-extra .reply-btn .sbtn:last-child{margin-right:0;color:#b1b1b1;}
/**************bottom-toast*****************/
.commentmut .pupContentBG {width:100vw; height:100vh; position:fixed; top:0;}
.commentmut .pupContent {width:100%; background:white; position:fixed; top:0; box-shadow:0 0 10rpx #555; height:0; z-index:999;}/**设置显示的背景**/
.commentmut .showBG {display: block;}
.commentmut .hideBG {display: none;}/* 弹出或关闭动画来动态设置内容高度 */
@keyframes slideBGtUp {from {background: transparent;}to {background: rgba(0, 0, 0, 0.5);}
}
@keyframes slideBGDown {from {background: rgba(0, 0, 0, 0.5);}to {background: transparent;}
}/* 显示或关闭内容时动画 */
.commentmut .openBG {animation: slideBGtUp 0.5s ease-in both;/* animation-fill-mode: both 动画将会执行 forwards 和 backwards 执行的动作。 */
}
.commentmut .closeBG {animation: slideBGDown 0.5s ease-in both;/* animation-fill-mode: both 动画将会执行 forwards 和 backwards 执行的动作。 */
}/* 设置显示内容 */
.commentmut .showContent {display: block;width:calc(100% - 39rpx);padding: 20rpx;border-top:10px solid #f5f5f5;
}
.commentmut .hideContent {display: none;}/* 弹出或关闭动画来动态设置内容高度 */
@keyframes slideContentUp {from {height: 0;}to {height: 800rpx;}
}
@keyframes slideContentDown {from {height: 800rpx;}to {height: 0;}
}/* 显示或关闭内容时动画 */
.commentmut .open {animation: slideContentUp 0.5s ease-in both;/* animation-fill-mode: both 动画将会执行 forwards 和 backwards 执行的动作。 */
}
.commentmut .close {animation: slideContentDown 0.5s ease-in both;/* animation-fill-mode: both 动画将会执行 forwards 和 backwards 执行的动作。 */
}
/**--------------------------------replay and thumbsUp End----------------------------------**/

index.js

//index.js
//获取应用实例
const app = getApp();
let util = require('../../utils/util.js');Page({data: {userInfo: app.globalData.userInfo,click: false, //是否显示弹窗内容 + + + + + + + + + + + 评论点赞插件专属opt: false, //显示弹窗或关闭弹窗的操作动画 + + + + + + 评论点赞插件专属min:2, //输入框最少字数 + + + + + + + + + + + + + + + 评论点赞插件专属max:120, //输入框最多字数 + + + + + + + + + + + + + 评论点赞插件专属form_value:'',   //输入框中的内容 + + + + + + + + + + + 评论点赞插件专属texts: "", //当前输入的字数 + + + + + + + + + + + + + 评论点赞插件专属imgs1:[],  //当前图片资源 + + + + + + + + + + + + + + 评论点赞插件专属tempFilePaths: [],   //临时文件资源 + + + + + + + + + + 评论点赞插件专属disabled: true,      //按钮初始状态 + + + + + + + + + + 评论点赞插件专属},onLoad: function (options) {var that = this;      //  获取评论+++++++++++++++++++完整方式==开始======================var actParam = {aid: 1,nav_id: 2,type:1,}//    服务器段处理,并返回处理结果util.request('api/index', {'act': 'getCommentHub',   'param': util.parseParam(actParam),},function(res) {wx.setStorageSync('commentInfo', res.data.data)app.globalData.commentInfo = res.data.datathat.setData({commentInfo: wx.getStorageSync('commentInfo')})}, "GET");// 获取评论+++++++++++++++++++完整方式==结束======================        },/*************************评论/点赞组件->开始***************************///点击打开弹窗replayAct: function(e){var that = thisutil.commentAction.replayAct(that,e)},//点击打开弹窗inputs: function(e){var that = thisutil.commentAction.inputs(that,e)},  //上传文件upload: function(){var that = thisutil.commentAction.upload(that)},//图片预览listenerButtonPreviewImage: function(e){var that = thisutil.commentAction.listenerButtonPreviewImage(that,e)},//长按删除文件deleteImage: function(e){var that = thisutil.commentAction.deleteImage(that,e)},//表单提交formSubmit: function(e){var that = thisutil.commentAction.formSubmit(that,e)},//表单提交formReset: function(e){var that = thisutil.commentAction.formReset(that,e)},thumbsupAct: function(e){var that = thisutil.commentAction.thumbsupAct(that,e)},/*************************评论/点赞组件->结束***************************/
})

index.wxml

<!--index.wxml-->
<import src="/template/template.wxml"/>
<!----new-comment&thumbsup---->
<view class="widyBox"><template is="replayThumbsup" data="{{commentInfo, click, opt, currentWordNumber, disabled, tempFilePaths, imgs1, comment_bInfo}}"/>
</view>
<!----new-comment&thumbsup----><template is="copyright" data="{{copyright}}"/>

index.wxss和index.json是默认的内容,这里就不占用地方了。

后端方法

后端采用tp5.0.24开发,目录这里就不展示了,本次评论模块使用的是api模块下的api控制器下的各种方法
/app/application/api/controller/api.php

<?php
namespace app\api\controller;class Api extends Base
{public function _initialize(){$this->host=(isHTTPS() ? 'https://' : 'https://') . $_SERVER['HTTP_HOST']; //获取域名}public function index($act='getAllCousrseList',$param){$data = ['error' => 0,'msg'   => 'Connected!','data'  => [],];parse_str($param,$param);   //  主要处理自己拼接的字符串为数组switch($act){case 'getCommentHub':if(empty($param)){   //  获取指定栏目信息$data = ['error' => 0,'msg'    => 'Lost param!','data' => [],];}else{//    评价资料#   个人基础信息$aid = $param["aid"];$nav_id=$param['nav_id'];$return_data = $param;if(!$aid || !$nav_id) {$this->error("参数错误");}$model = db('newComment');$_mode_arr = $model->alias('c')->where(['c.aid'=>$aid,'c.nav_id'=>$nav_id,'c.status'=>1])->field('c.*,m.nickname,m.avatar,ct.status as isThumbsup')->join([['member m','m.unionID=c.unionID','LEFT'],['c_thumbsup ct','ct.aid=c.aid and ct.unionID=c.unionID and ct.cmid=c.id','LEFT']])->order('c.id DESC')->select();foreach($_mode_arr as $k=>&$v){if(!empty($v['pic'])){$_pic=[];foreach(explode(',',$v['pic']) as $vp){$_pic[]=$this->host.$vp;}$v['pic']=$_pic;}$v['isThumbsup']=$v['isThumbsup'] ? 1 : 0;$v['thumbsUp']=db('c_thumbsup')->where(['cmid'=>$v['id']])->count();$v['create_time']=date('Y.m.d',$v['create_time']);$v['update_time']=date('Y.m.d',$v['update_time']);}$return_data['num'] =  count($_mode_arr); //获取评论总数$return_data['list']=$this->listHub(0, 1, $_mode_arr);//获取评论列表$data = ['error' => 0,'msg'   => 'OK!','data' => $return_data,];}break;}return json($data);}/*图片上传*/public function uploads($act='image'){$data=['error' =>0,'msg'    =>'Connected!','data'   =>[],];$file = request()->file('file');switch($act){case 'image':$path = ROOT_PATH . 'public' . DS . 'static' . DS . 'uploads' . DS . 'comment' . DS . 'images';$attrPath='images';break;case 'file':$path = ROOT_PATH . 'public' . DS . 'static' . DS . 'uploads' . DS . 'comment' . DS . 'files';$attrPath='files';break;}if (!is_dir($path)) {mkdir($path,0777,true);}$info = $file->move($path);if($info){$filePath = DS .'static' . DS . 'uploads'. DS .'comment'. DS . $attrPath . DS .$info->getSaveName();$data=['error' =>0,'msg' =>'OK!','data'  =>['imgurl'=>$filePath,]];}else{$data=['error' =>1,'msg' =>$file->getError(),'data' =>[]];}return json($data);}#    前台API数据获取/*+   @param['unionid'] string(max[20],min[0]) 当前用户的unionid+  @param['uid'] intval(max[10],min[1]) 默认用户ID+    @param['token'] string (16) 默认用户密码substr(md5($str), 8, 16) 两次*/# 数据修改public function handleData($act='pushComment', $param){$ret=['error'=>0,'msg'=>'Connected!','data'=>[],];if(is_array($param)){$data = $param;}else{parse_str($param,$data);    //  处理自己拼接的字符串为数组}switch($act){case 'pushComment':    #   写入服务日志if(request()->isPost()){$data['create_time']=time();$data['status']=1;if(db('new_comment')->insert($data)){$ret['msg']='Ok';}else{$ret['msg']='Failed';}}$where=['c.aid'=>$data['aid'],'c.nav_id'=>$data['nav_id'],'c.type'=>$data['type'],];$_commentList = db('new_comment')->alias('c')->where($where)->field('c.*,m.nickname,m.avatar,ct.status as isThumbsup')->join([['member m','m.unionID=c.unionID','LEFT'],['c_thumbsup ct','ct.aid=c.aid and ct.unionID=c.unionID and ct.cmid=c.id','LEFT']])->order('c.id DESC')->select();if(!empty($_commentList)){foreach($_commentList as $k=>&$v){if(!empty($v['pic'])){$_pic=[];foreach(explode(',',$v['pic']) as $vp){$_pic[]=$this->host.$vp;}$v['pic']=$_pic;}$v['isThumbsup']=$v['isThumbsup'] ? 1 : 0;$v['thumbsUp']=db('c_thumbsup')->where(['cmid'=>$v['id']])->count();$v['create_time']=date('Y.m.d',$v['create_time']);$v['update_time']=date('Y.m.d',$v['update_time']);}$_resData = ['aid'=>$data['aid'],'nav_id'=>$data['nav_id'],'type'=>$data['type']];$_resData['num'] =  count($_commentList); //获取评论总数$_resData['list']=$this->listHub(0, 1, $_commentList);//获取评论列表}else{$_resData=[];}$ret['data']['commentInfo']=$_resData;break;case 'thumbsUpDone':    #   点赞if(request()->isPost()){$isup = $data['isThumbsup'];unset($data['isThumbsup']);if($isup==0){    //  添加点赞if(db('c_thumbsup')->insert($data)){db('new_comment')->where(['id'=>$data['cmid']])->setInc('thumbsUp');$ret=['error'=>0,'msg'=>'OK!','data'=>['result'=>1],];}else{                  $ret=['error'=>0,'msg'=>'Falied!','data'=>['result'=>0],];}}else{if(db('c_thumbsup')->where($data)->delete()){db('new_comment')->where(['id'=>$data['cmid']])->setDec('thumbsUp');$ret=['error'=>0,'msg'=>'OK!','data'=>['result'=>1],];}else{$ret=['error'=>0,'msg'=>'Falied!','data'=>['result'=>0],];}}$data['id'] = $data['cmid'];unset($data['unionID'],$data['cmid']);$ret['data']['thumbsupNum']=db('new_comment')->where($data)->value('thumbsUp');}break;default:$ret['msg']='Param lost!';}return json($ret);}//  受保护方法protected function listHub($pid=0, $level=1, $array=[]){  static $result = [];static $key = 0;if(!empty($array)){foreach($array as $k => $v){if($v['pid'] == $pid){$v['level']=$level;if($v['pid'] == 0){  //  当前是顶级回复$v['children']=[];$result[]=$v;$key = count($result) -1;    //获取当前顶级回复的索引}else{$v['replay_nickname'] = db('new_comment')->alias('nc')->where(['nc.status'=>1,'nc.id'=>$v['pid']])->join([['member m','m.unionID=nc.unionID','LEFT']])->value('m.nickname');$result[$key]['children'][]=$v;}unset($array[$k]);$this->listHub($v['id'], $v['level']+1, $array);}}}return $result;}
}

数据库结构

tp_member

字段名称 字段类型 字段属性 字段说明 是否主键 是否自增
id int 11 自增ID
username char 20 用户名
unionID char 50 用openID也可以
avatar varchar 200 用户头像
nickname char 20 用户昵称
register_time char 10 注册时间
status tinyint 1 用户状态

tp_c_thumbsup表结构

字段名称 字段类型 字段属性 字段说明 是否主键 是否自增
id int 11 自增ID
unionID char 50 用openID也可以
cmid int 10 被点赞的评论ID
aid int 10 文章ID
nav_id int 10 文章栏目
type tinyint 1 文档属性
status tinyint 1 用户状态

tp_new_comment表结构

字段名称 字段类型 字段属性 字段说明 是否主键 是否自增
id int 11 自增ID
pid int 20 被回复的评论ID
unionID char 50 用openID也可以
aid int 10 文章ID
nav_id int 10 文章栏目
type tinyint 1 文档属性
pic varchar 900 评论图片
avatar varchar 200 用户头像
content varchar 240 评论内容
thumbsUp int 10 点赞数量
create_time char 10 创建时间
status tinyint 1 用户状态

以上是所有的前后端+数据库内容,希望可以给有需要该功能的朋友一点思路,同时也希望谁可以把我写的不好的地方改写一下,要是可以做成小程序插件,还望指导一下,谢谢!!

效果展示






未解决的问题

虽然基础框架已经完成,但是复用效率较低,我真诚希望有哪位高手能够封装为插件,以方便多次调用,如果有人愿意施以援手,请留言指导,或者做成成品,邮箱 1755773846@qq.com。我会在第一时间发布出来,让知识与技能共享,谢谢!

微信小程序 点赞+评论(无限级评论回复)/带图评论解决方案相关推荐

  1. 微信小程序发布审核基本步骤(带图)

    本文作为烂笔头记录或小白感性认识或为客户做基础说明(复杂性时间等)大佬请勿费神. 微信小程序源码编辑与调试略.微信小程序注册略. 一.安装微信开发者工具 微信开发者工具下载地址与更新日志 | 微信开放 ...

  2. 微信小程序-点赞业务实现

    微信小程序-点赞业务实现 这里写目录标题 微信小程序-点赞业务实现 一.效果 二.实现 1.逻辑 2.wxml 3.js 一.效果 二.实现 1.逻辑 1.从登录界面时,用户数据已经缓存到本地,在on ...

  3. 微信小程序之自定义模态弹窗(带动画)实例

    代码地址如下: http://www.demodashi.com/demo/13991.html 一.前期准备工作 软件环境:微信开发者工具 官方下载地址:https://mp.weixin.qq.c ...

  4. 微信小程序获取手机号码第一次失败第二次成功的解决方案

    标题 微信小程序获取手机号码第一次失败第二次成功的解决方案 注意点: 1.千万记住在getphone之后不能login,否则session_key就会失效 我的解决方案是再onshow里面直接登录获取 ...

  5. 微信小程序10:WXML 组件- 轮播图 swiper

    微信小程序10:WXML 组件- 轮播图 swiper 官网地址https://developers.weixin.qq.com/miniprogram/dev/component/swiper.ht ...

  6. 微信小程序+公众号的流量主收入图在线制作微信小程序源码

    微信小程序+公众号的流量主收入图在线制作微信小程序源码,这是一款流量主制作生成小工具,支持小程序流量主图制作生成,也支持公众号的流量主制作生成. 每一种制作都包含了所有的 流量主 模式,用户自己填写选 ...

  7. 微信小程序点赞成功,取消点赞、评论。

    今天做了一个微信小程序的点赞和评论功能!人生都有第一次,没有做过是因为没有思路,如果思路明白了,自然就会了! 一.首先,我们点赞需要明白两点. 1.微信人的id,也就是本人点击成功. 2.我想要点赞哪 ...

  8. ssm+java+vue基于微信小程序的电影院票务系统(可选座评论等功能)#毕业设计

    随着移动应用技术的发展,越来越多的用户借助于移动手机.电脑完成生活中的事务,许多的传统行业也更加重视与互联网的结合,由于城镇人口的增加,人们去电影院总是排着长长的队伍,对于时间紧的人是一个非常头痛的事 ...

  9. 微信小程序——点赞和取消点赞功能的实现

    这个项目是一个有前后端的微信小程序,能够发布和查看论文,并可以给论文点赞.这篇文章主要介绍点赞和取消点赞功能的实现. 一.成果展示 用户没有点赞的状态(可能有点看不清,是一个空的心的图标). 点一下之 ...

最新文章

  1. ARKit从入门到精通-ARKit工作原理及流程介绍
  2. 图解Win7下set命令使用
  3. mysql1756_MySQL Error_code: 1756
  4. 博客园CodingLife模板样式优化
  5. 树的存储结构(树的二叉链表(孩子—兄弟))
  6. 位换记号、排列测试与状态图:杂耍中的数学
  7. 构建安全网络 比格云全系云产品30天内5折购
  8. 基于相干解调法和基于相位比较法的2DPSK数字通信系统 MATLAB Simulink仿真
  9. 苹果自研5G调制解调器将在2023年量产 采用台积电4nm工艺
  10. 向量化计算cell_吴恩达老师课程笔记系列第24节-Octave教程之向量化和作业(6)
  11. day 15 模块、起别名、from导入
  12. SharePoint服务器端对象模型 之 使用CAML进行数据查询(Part 4)
  13. 跟我学Kafka之zookeeper的存储结构
  14. 2017年前端该学些什么(译)
  15. FastStone Capture注册破解码
  16. SD卡数据被误删除该怎么恢复?
  17. 微信小程序选择图片更换背景
  18. 登录邮恰显示服务器登录失败,邮洽邮箱收不到邮件是什么原因?
  19. 不给移动一分钱!10个免费发短信的国外站点
  20. 输入学生的学习成绩,学习成绩>=90分的同学用A表示,60-89分之间的用B表示,60分以下的用C表示。

热门文章

  1. 最新开源视觉 SLAM 方案
  2. Oracle调优之看懂Oracle执行计划
  3. 浅谈对Python的理解和优缺点
  4. 力图搜集各种跟CV,AR相关的代码
  5. MCPcounter估计免疫浸润细胞丰度
  6. 电商平台-搜索模块的设计与架构
  7. 4、ArrayList的详细扩容过程
  8. MS2108 RGB to USB是一款数字视频和音频采集芯片,内部集成USB 2 0 Device控制器、数据收发模块、数字视频输入处理模块、I2S输入处理模块、音视频处理
  9. 配置linux开发板ssh登录
  10. PC微信v3.3.0.20测试版下载