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

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

需求描述

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

实现要点分析

  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 = this
		if(!_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.dataset
			if(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, // 默认9
			sizeType: ['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;	//去除bom
							let 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 = this
		let index = e.target.dataset.index
		console.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 = this
		var tempFilePaths = that.data.tempFilePaths
		var imgs1 = that.data.imgs1
		var 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 = this
		e.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.dataset
		let 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.commentInfo
		commentInfo.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].children
					if(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].children
								if(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].children
											if(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.data
				that.setData({
					commentInfo: wx.getStorageSync('commentInfo')
				})
			}, "GET");
		//	获取评论+++++++++++++++++++完整方式==结束======================		
	},
	
	/*************************评论/点赞组件->开始***************************/
	//点击打开弹窗
	replayAct: function(e){
		var that = this
		util.commentAction.replayAct(that,e)
	},
	//点击打开弹窗
	inputs: function(e){
		var that = this
		util.commentAction.inputs(that,e)
	},	
	//上传文件
	upload: function(){
		var that = this
		util.commentAction.upload(that)
	},
	//图片预览
	listenerButtonPreviewImage: function(e){
		var that = this
		util.commentAction.listenerButtonPreviewImage(that,e)
	},
	//长按删除文件
	deleteImage: function(e){
		var that = this
		util.commentAction.deleteImage(that,e)
	},
	//表单提交
	formSubmit: function(e){
		var that = this
		util.commentAction.formSubmit(that,e)
	},
	//表单提交
	formReset: function(e){
		var that = this
		util.commentAction.formReset(that,e)
	},
	
	thumbsupAct: function(e){
		var that = this
		util.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

字段名称字段类型字段属性字段说明是否主键是否自增
idint11自增ID
usernamechar20用户名
unionIDchar50用openID也可以
avatarvarchar200用户头像
nicknamechar20用户昵称
register_timechar10注册时间
statustinyint1用户状态

tp_c_thumbsup表结构

字段名称字段类型字段属性字段说明是否主键是否自增
idint11自增ID
unionIDchar50用openID也可以
cmidint10被点赞的评论ID
aidint10文章ID
nav_idint10文章栏目
typetinyint1文档属性
statustinyint1用户状态

tp_new_comment表结构

字段名称字段类型字段属性字段说明是否主键是否自增
idint11自增ID
pidint20被回复的评论ID
unionIDchar50用openID也可以
aidint10文章ID
nav_idint10文章栏目
typetinyint1文档属性
picvarchar900评论图片
avatarvarchar200用户头像
contentvarchar240评论内容
thumbsUpint10点赞数量
create_timechar10创建时间
statustinyint1用户状态

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

效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

未解决的问题

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

  • 9
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是一个简单的微信小程序实现点赞评论和发布的示例代码: wxml文件: ``` <!-- 点赞 --> <view class="like" bindtap="onLikeTap"> <image class="like-icon" src="{{isLiked ? '/images/liked.png' : '/images/like.png'}}"></image> <text class="like-text">{{isLiked ? '已赞' : '点赞'}}</text> </view> <!-- 评论 --> <view class="comment"> <input class="comment-input" placeholder="说点什么吧..." value="{{comment}}" bindinput="onCommentInput"></input> <button class="comment-button" disabled="{{comment.length === 0}}" bindtap="onCommentTap">发布</button> </view> <!-- 评论列表 --> <view class="comment-list"> <block wx:for="{{comments}}" wx:key="index"> <view class="comment-item"> <text class="comment-username">{{item.username}}</text> <text class="comment-content">{{item.content}}</text> </view> </block> </view> ``` js文件: ``` Page({ data: { isLiked: false, // 是否已点赞 comment: '', // 评论内容 comments: [ // 评论列表 { username: '张三', content: '这个小程序真棒!' }, { username: '李四', content: '点赞支持!' } ] }, // 点赞 onLikeTap: function () { this.setData({ isLiked: !this.data.isLiked }) }, // 输入评论 onCommentInput: function (e) { this.setData({ comment: e.detail.value }) }, // 发布评论 onCommentTap: function () { var comment = { username: '匿名用户', content: this.data.comment } var comments = this.data.comments.concat(comment) this.setData({ comments: comments, comment: '' }) } }) ``` 其中,点赞功能通过 `isLiked` 变量实现,评论功能通过 `comment` 变量表示当前输入的文本内容,并通过 `comments` 变量存储评论列表。在发布评论时,将新评论追加到评论列表中,并清空输入框内容。需要注意的是,发布评论时没有实现用户登录和身份验证,因此暂时使用匿名用户来表示评论者。如果需要实现更完整的功能,需要进一步完善代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值