1. 程式人生 > >微信小程式 評論留言功能實現 仿知乎

微信小程式 評論留言功能實現 仿知乎

  最近沉迷學習無法自拔,太久沒有碼字,碼一個小程式留言功能實現。先上一波最後效果圖:

(刪除按鈕,是使用者自己的留言時才會顯示該按鈕)

實現技術

  後臺:SSM框架

  資料庫:MySQL資料庫

資料庫設計

評論功能的實現主要涉及三個表

comment:儲存留言評論資訊,表結構如下:

表中,必須的欄位:id,user_id,reply_comment_id,comment,insert_time,source_id

添加了冗餘欄位username,reply_user_name,userphoto

主要用於儲存微信名、回覆的微信名、微信頭像(這三個欄位完全不應該冗餘,當小程式使用者更換使用者名稱時,該表要跟著更新,可維護性差,不建議儲存這些冗餘資訊,我就是懶得寫SQL了)

source:儲存你在小程式需要回復的內容。

user:儲存小程式使用的使用者資訊,主要包括使用者名稱、使用者頭像等微信使用者資訊。

小程式端

wxml

<scroll-view scroll-top="{{scrollTop}}" scroll-y="true" style="height:{{scrollHeight}}px;" class="list" bindscrolltolower="bindDownLoad" bindscrolltoupper="refresh">
  <view class="pro-con">
    <block wx:for="{{list}}" wx:key="{{index}}">
      <view class="pro-box">
        <view class="head">
          <image class="img" src="{{item.userPhoto}}" mode="aspectFit"></image>
          <view class="box">
            <view class="shead clear">
              <view class="names fl">{{item.userName}}
                  <view wx:if="{{!item.replyUserName == \" \"}}">
                  -> {{item.replyUserName}}
                </view>
              </view>
            </view>
          </view>
        </view>
        <view class="addr-info">
          <view class="addr-text">
            {{item.comment}}
          </view>
        </view>
        <view class="info">
          <view class="text">
            <text decode="true">{{item.insertTime}}</text>
          </view>
          <view class="text">
            <button class="sharebtn" data-commentId="{{item.id}}" data-commentUserName="{{item.userName}}" bindtap="bindReply">回覆</button>
          </view>
              <view wx:if="{{item.userId == userId}}" class="status text fr">
                <text class="delete" decode="true" bindtap='deleteComment' data-CommentId="{{item.id}}">刪除</text>
              </view>
        </view>
      </view>
    </block>
  </view>
</scroll-view>
<form bindsubmit="submitForm" report-submit="true">
  <view class="release">
    <view  wx:if="{{reply}}" class="replyinfo1">
      回覆<text class="text">{{replyUserName}}</text>
      <button class="cancel" bindtap="cancleReply">取消回覆</button>
    </view>
    <view class="replyinfo2">
      <textarea placeholder-class="input_null" fixed="true" maxlength="-1" show-confirm-bar="false" cursor-spacing="15" auto-height="true" placeholder="請輸入回覆" name="comment"></textarea>
      <button form-type="submit" class="submit">傳送</button>
    </view>
  </view>
</form>

css

.names {
  display: flex;
  font-size: 30rpx;
  line-height: 40rpx;
}

.input_null {
  color: #c9c9c9;
}

.replyAll {
  position:absolute;
}

.release {
  align-items: flex-end; /*底部對齊*/
  box-sizing: border-box;
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  padding: 18rpx 0 18rpx 30rpx;
  background-color: #f7f8f7;
  font-size: 28rpx;
  z-index: 999;
}

.replyinfo1{ 
  display: flex;
  justify-content: space-between; /*兩端對齊*/
  font-size: 35rpx;
}
.replyinfo2{ 
  display: flex;
  justify-content: space-between; /*兩端對齊*/
}

.release textarea {
  width: 550rpx;
  min-height: 34rpx;
  max-height: 102rpx; /*最多顯示三行*/
  border-width: 15rpx 20rpx; /*使用padding與預期留白不一致,故使用border*/
  border-style: solid;
  border-color: #fff;
  line-height: 34rpx;
  font-size: 28rpx;
  background-color: #fff;
  border-radius: 4rpx;
}

.release .text {
  font-size: 40rpx;
  color: #c9c9c9;
}

.cancel {
  width: 240rpx;
  height: 64rpx;
  line-height: 64rpx;
  text-align: center;
  color: #6c0;
  margin: 0 3px;
  padding: 0;
}

.release .submit {
  width: 120rpx;
  height: 64rpx;
  line-height: 64rpx;
  text-align: center;
  color: #6c0;
  margin: 0 3px;
  padding: 0;
}

.pro-box .info .text .delete {
  color: #f68135;
  border-radius: 50rpx;
  border: 1px solid #f68135;
  font-size: 28 rpx;
  width: 150rpx;
  height: 48rpx;
  text-align: center;
}

js

// pages/comment/comment.js
const model = require('../cityChoose/cityChoose.js')
const config = require('../../utils/config.js')
const util = require('../../utils/util.js')
const app = getApp()
var mydata = {
  end: 0,
  replyUserName: ""
}
Page({

  /**
   * 頁面的初始資料
   */
  data: {
    list: [],
  },

  /**
   * 生命週期函式--監聽頁面載入
   */
  onLoad: function(options) {
    var that = this;
    mydata.sourceId = options.sourceId
    mydata.commentId = "";
    mydata.replyUserName = "";
    //設定scroll的高度
    wx.getSystemInfo({
      success: function(res) {
        that.setData({
          scrollHeight: res.windowHeight,
          userId:app.globalData.haulUserInfo.id
        });
      }
    });
    mydata.page = 1;
    that.getPageInfo(mydata.page);
  },
  /**
   * 頁面下拉重新整理事件的處理函式
   */
  refresh: function() {
    console.log('refresh');
    mydata.page = 1
    this.getPageInfo(mydata.page, function() {
      this.setData({
        list: []
      })
    });
    mydata.end = 0;
  },
  /**
   * 頁面上拉觸底事件的處理函式
   */
  bindDownLoad: function() {
    console.log("onReachBottom");
    var that = this;
    if (mydata.end == 0) {
      mydata.page++;
      that.getPageInfo(mydata.page);
    }
  },
  bindReply: function(e) {
    console.log(e);
    mydata.commentId = e.target.dataset.commentid;
    mydata.replyUserName = e.target.dataset.commentusername;
    this.setData({
      replyUserName: mydata.replyUserName,
      reply: true
    })
  },
  // 合併陣列
  addArr(arr1, arr2) {
    for (var i = 0; i < arr2.length; i++) {
      arr1.push(arr2[i]);
    }
    return arr1;
  },
  deleteComment:function(e){
    console.log(e);
    var that = this;
    var commentId = e.target.dataset.commentid;

    wx.showModal({
      title: '刪除評論',
      content: '請確認是否刪除該評論?',
      success: function (res) {
        if (res.confirm) {
          wx.request({
            url: config.deleteComment,
            method: "POST",
            data: {
              commentId: commentId
            },
            header: {
              "content-type": "application/x-www-form-urlencoded;charset=utf-8",
            },
            success: res => {
              that.refresh();
              wx.showToast({
                title: "刪除成功"
              })
            }
          })
        } else if (res.cancel) {
          console.log('使用者點選取消')
        }
      }
    })
  },
  cancleReply: function(e) {
    mydata.commentId = "";
    mydata.replyUserName = "";
    this.setData({
      replyUserName: mydata.replyUserName,
      reply: false
    })
  },
  // 更新頁面資訊
  // 此處的回撥函式在 傳入新值之前執行 主要用來清除頁面資訊
  getPageInfo(page, callback) {
    var that = this;
    util.showLoading();
    console.log("getPageInfo");
    console.log("page" + page);
    var limited = 6;
    var offset = (page - 1) * 6;
    wx.request({
      url: config.getComments,
      method: "POST",
      data: {
        sourceId: mydata.sourceId,
        limited: limited,
        offset: offset
      },
      header: {
        "content-type": "application/x-www-form-urlencoded;charset=utf-8",
      },
      success: res => {
        console.log(res);
        if (page == 1) {
          that.data.list = res.data;
          that.setData({
            list: that.data.list
          })
          mydata.end = 0;
        } else {
          // 當前頁為其他頁
          var list = that.data.list;
          if (res.data.length != 0) {
            list = that.addArr(list, res.data);
            that.setData({
              list: list
            })
            mydata.end = 0;
          } else {
            mydata.end = 1;
          }
        }
        wx.hideLoading();
      }
    })
  },
  submitForm(e) {
    var form = e.detail.value;
    var that = this;
    console.log(app.globalData.haulUserInfo);
    if(form.comment == ""){
      util.showLog('請輸入評論');
      return;
    }
    // 提交評論
    wx.request({
      url: config.insertComment,
      method: "POST",
      data: {
        sourceId: mydata.sourceId,
        comment: form.comment,
        userId: app.globalData.haulUserInfo.id,
        userName: app.globalData.haulUserInfo.userName,
        replyCommentId: mydata.commentId,
        replyUserName: mydata.replyUserName,
        userPhoto: app.globalData.haulUserInfo.userPhoto
      },
      header: {
        "content-type": "application/x-www-form-urlencoded;charset=utf-8",
        //token: app.globalData.token
      },
      success: res => {
        console.log(res)
        if (res.data.success) {
          wx.showToast({
            title: "回覆成功"
          })
          that.refresh();
          mydata.commentId = "";
          mydata.replyUserName = "";
          this.setData({
            replyUserName: mydata.replyUserName,
            reply: false
          })
        } else {
          wx.showToast({
            title: '回覆失敗,請檢查您的網路',
          })
        }
      }
    })
  }
})

後臺

後臺功能:獲取評論、刪除評論、插入評論,都是簡單的資料庫操作,放在一個controller類中實現即可

package com.melon.haul.web;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONObject;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.melon.haul.dto.DataUtil;
import com.melon.haul.dto.GetLocation;
import com.melon.haul.dto.Result;
import com.melon.haul.entity.Comment;
import com.melon.haul.entity.District;
import com.melon.haul.entity.Source;
import com.melon.haul.service.CommentService;
import com.melon.haul.service.DistrictService;
import com.melon.haul.service.SourceService;

@Controller
@WebAppConfiguration
@RequestMapping("/Comment")
public class CommentController {
	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private CommentService commentService;
	
	@RequestMapping(value = "/getComments", method = RequestMethod.POST)
	private @ResponseBody List<Comment>  getComments(@RequestParam("sourceId") int sourceId,
			@RequestParam("limited") int limited,@RequestParam("offset") int offset) {
		logger.info("getComments");
		List<Comment> list = new ArrayList<Comment>();
		try{
			list = commentService.getComment(sourceId, limited, offset);
		}catch(Exception e){
			
		}
		return list;
	}
	
	@RequestMapping(value = "/insertComment", method = RequestMethod.POST)
	private @ResponseBody
	Result<Map<String,String>>insertComment(@RequestParam("sourceId") String sourceId,
			@RequestParam("comment") String comment,@RequestParam("userId") int userId,
			@RequestParam("userName") String userName,@RequestParam("replyCommentId") String replyCommentId,
			@RequestParam("replyUserName") String replyUserName,@RequestParam("userPhoto")String userPhoto) {
		logger.info("insertComment");
		Map<String, String> resultMap = new HashMap<String, String>();
		try{
			Integer rCId = -1;
			if(!replyCommentId.equals(""))
				rCId = Integer.parseInt(replyCommentId);
			commentService.insertComment(Integer.parseInt(sourceId), comment, userId,userName,rCId,replyUserName,userPhoto);
			resultMap.put("msg", "insertComment success");
		}catch(Exception e){
			System.out.print(e);
			resultMap.put("msg", "insertComment error");
		}
		return new Result<Map<String, String>>(true, resultMap);
	}
	
	@RequestMapping(value = "/deleteComment", method = RequestMethod.POST)
	private @ResponseBody
	Result<Map<String,String>>deleteComment(@RequestParam("commentId") String commentId) {
		logger.info("deleteComment");
		Map<String, String> resultMap = new HashMap<String, String>();
		try{
			commentService.deleteComment(commentId);
			resultMap.put("msg", "deleteComment success");
		}catch(Exception e){
			System.out.print(e);
			resultMap.put("msg", "deleteComment error");
		}
		return new Result<Map<String, String>>(true, resultMap);
	}
}

公共CSS(app.wxss)


/**app.wxss**/
.container {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: 200rpx 0;
  box-sizing: border-box;
} 
/* large button style */
.large-btn{
	background: #f68135;
	border-radius: 50rpx;
	border: 1px solid #f68135;
	color: #fff;
	height: 100rpx;
	line-height: 100rpx;
	margin: 0 auto;
	width: 96%;
	text-align: center;
}
.large-btn.empty{
	background: transparent;
	color: #f68135;
  margin-top: 50rpx;
}
.large-btn.disabled{
	border-color: #ccc;
	background: #ccc;
	color: #fff;
}
/* public style to clear default styles */
.fl{
	float: left;
}
.fr{
	float: right;
}
.fc{
  float:none;
}
.col-gray{
	color: #999!important;
}


/* the message of auction about goods & cars */
.pro-con{
	padding: 20rpx;
	background: #f1f1f1;
}
.pro-box{
	background: #fff;
	padding: 20rpx;
	box-sizing: border-box;
	border-radius: 10rpx;
	margin-bottom: 20rpx;
}
.pro-box .img{
	display: inline-block;
	vertical-align: top;
	width: 80rpx;
	height: 80rpx;
	border-radius: 50%;
	overflow: hidden;
	margin-right: 10rpx;
}
.pro-box .box{
	display: inline-block;
	vertical-align: top;
	width: calc(98% - 80rpx);
}
.pro-box .shead{
	padding-bottom: 20rpx;
}
.pro-box .shead .name{
	font-size: 30rpx;
	line-height: 40rpx;
}
.pro-box .shead .stxt{
	font-size: 26rpx;
	color: #999;
}
.pro-box .shead .fr{
	padding-top: 10rpx;
}
.pro-box .shead .fr navigator{
	font-size: 0;
}
.pro-box .shead .fr image{
	width: 48rpx;
	height: 48rpx;
}
 .pro-box .sharebtn{
   height:48rpx;
	 background: #f68135;
   border-radius: 50rpx;
   border: 1px solid #f68135;
   color: #fff;
   text-align: center;
   line-height: 50rpx;
   font-size:30rpx;
} 

.pro-box .addr-info{
	align-items: center;
	justify-content: space-between;
	border-bottom: 1px dashed #ccc;
	margin: 0 -20rpx;
	margin-bottom: 20rpx;
	padding-bottom: 20rpx;
	padding-left: 20rpx;
	padding-right: 20rpx;
  display: inline-block;
}

.pro-box .addr-info .addr-text{
	font-size: 35rpx;
	line-height: 40rpx;
  width:100%;
}
 .pro-box .addr-info .addr-text .color1{
  color:lightskyblue;
  border-color: #ccc;
  border: 1px solid lightskyblue;
  border-radius:15px;
  margin-right: 5px;
  padding: 0rpx,2rpx,0rpx,2rpx;
} 
.pro-box .addr-info .addr-text .color2{
  color: #f68135;
  border-color: #ccc;
  border: 1px solid #f68135;
  border-radius:10px;
  margin-right: 5px;
  margin-left: 5px;
  padding: 0rpx,2rpx,0rpx,2rpx;
} 

.pro-box .position{
	width: 48rpx;
	height: 48rpx;
} 

.pro-box .comment{
	width: 55rpx;
	height: 48rpx;
} 

.pro-box .addr{
	align-items: center;
	justify-content: space-between;
	border-bottom: 1px dashed #ccc;
	margin: 0 -20rpx;
	margin-bottom: 20rpx;
	padding-bottom: 20rpx;
	padding-left: 20rpx;
	padding-right: 20rpx;
  display: flex;
}

.pro-box .addr .addr-text{
	font-size: 34rpx;
	line-height: 40rpx;
	max-width: 240rpx;
	min-width:200rpx;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.pro-box .addr .addr-text .color-text{
  color: #f68135;
}
.pro-box .addr .time{
	font-size: 26rpx;
	line-height: 36rpx;
	text-align: center;
}
.pro-box .addr .line{
	background: #ccc;
	height: 1px;
	margin: 6rpx -20rpx;
	position: relative;
}
.pro-box .info{
	display: flex;
	align-items: center;
	justify-content: space-between;
}
.pro-box .info .text{
  vertical-align:text-top;
	font-size: 26rpx;
}
.pro-box .info .text .delete{
  color: #f68135;
	border-radius: 50rpx;
	border: 1px solid #f68135;
	width: 100rpx;
	height: 48rpx;
	text-align: center;
}

相關推薦

程式 評論留言功能實現 仿

  最近沉迷學習無法自拔,太久沒有碼字,碼一個小程式留言功能實現。先上一波最後效果圖: (刪除按鈕,是使用者自己的留言時才會顯示該按鈕) 實現技術   後臺:SSM框架   資料庫:MySQL資料庫 資料庫設計 評論功能的實現主要涉及三個表 comment:

程式評論/留言功能,附:前端+後端程式碼+視訊講解!

前端介面: 演示: <!-- 表單 --> <form bindsubmit="formSubmit"> <input type="text" name="liuyantext" placeholder='輸入留言內容' class

程式 蒐藏功能實現(八)

搜尋功能用到了小程式的快取功能:wx.setStorage 如果沒有向用戶提供removeStorageSync或clearStorageSync,小程式的快取永久存在,沒有失效期,快取的最大不超過10MB 四類操作,八種方法: setStorage,getStorage

程式發紅包功能實現,附效果圖加講解。

 有問題可以掃碼加我微信,有償解決問題。承接小程式開發。 微信小程式開發交流qq群   173683895  、 526474645 ; 正文: 目前此功能尚在內測,無法申請。此博文僅示例。 流

程式五星評價功能實現

實現五星評價功能,效果圖如下: .wxml檔案: <view class="star-title">1、品質效果</view> <view class="star

程式與vue的區別,回答!!!

明顯不是嘛,資料屬性更新是這樣的 小程式: Page({ data: { items: [] }, onLoad: function(options) { this.setData({ items: [1,2,3] }) } }) Vue:

程式評論功能實現原始碼,複製貼上

wxml: 傳送 js: var ComContent = ‘’ var CommentList = ‘[]’ var app = getApp() Page({ /** * */ data: { CommentList: [{}], bindContent: null, Co

程式 評論功能實現

前端 <textarea class='the_prw_in' bindinput='bindblur' cursor-spacing="130" placehold

程式評論功能原始碼

  wxml:   <textarea class="input" bindinput='bindContent' value="" placeholder="請填寫評論內容 "></textarea>

程式——評論點贊功能

實現的最終效果圖 1.點贊功能 前端頁面結構 1 <view class='talk-item-zan'> 2 <view bindtap='favorclick' data-id='{{item.id}}' data-islike="{{

程式+WEB使用JS實現註冊【60s】倒計時功能

微信小程式+WEB使用JS實現註冊【60s】倒計時功能開發步驟: 1、效果圖:   2、頁面僅僅利用了JS的相關功能,包含:wxml、js、wxss  2.1wxml頁面程式碼: <text

程式 右上角分享功能實現

微信小程式前段時間開放了小程式右上角的分享功能,    可以分享任意一個頁面到好友或者群聊,    但是目前小程式不可以分享到朋友圈onShareAppMessage(options)在 Page 中定義 onShareAppMessage 函式,設定該頁面的轉發資訊。只有定

程式 簡訊驗證 功能實現(附案例程式碼/前後端/直接用)

模組效果展示(小程式介面) 實現的功能 小程式端: 請求獲取簡訊驗證碼 兩次請求之間間隔至少一分鐘 填寫必填內容後,才能提交表單 手機號合法性檢驗 後臺: 接前臺請求後,

程式開發之radio實現顯示和隱藏功能

我們在開發微信小程式的時候,經常會用到顯示和隱藏,但是我們知道在微信小程式裡面是不是能 使用dom操作的,話不多說,直接上程式碼 第一步.直接在wxml,首先在要選擇的按鈕上註冊一個bindtap事件如下圖 第二步.在js中的pages下的data中新增 showView

使用程式自定義元件實現的tabs選項卡功能

一個自定義元件由 json wxml wxss js 4個檔案組成。要編寫一個自定義元件,首先需要在 json 檔案中進行自定義元件宣告(將 component 欄位設為 true 可這一組檔案設為自定義元件) components/navigator/i

程式-template使用:實現購物車商品數量加減功能

前言上一篇我們實現了購物車功能,裡面有用到template模板功能來實現購物車商品數量加減和價格計算功能,可能篇幅過長介紹的並不清楚,本篇將詳細介紹一下template模板來減少冗餘程式碼。模板WXML提供模板(template),可以在模板中定義程式碼片段,然後在不同的地方

程式(看文件寫例項十)程式課堂寶APP實現我的模組相關介面及邏輯

繼上篇博文,這篇完成最後一個模組,即我的模組。 一、頁面效果 這個模組是和使用者型別相關的,因此老師賬號和學生賬號能看的功能不一樣,老師端效果如下: 點選頭像到達個人資訊如下: 點選後可以做相應的修改。學生端的介面如下: 修改密碼的頁面如下: &nbs

程式(看文件寫例項八)程式課堂寶APP實現練習模組前臺

接上篇博文,這篇主要描述練習模組的前臺顯示,其中包括test頁面,答題detail頁面以及提交答題後答卷answer頁面。 一、練習模組test頁面 練習頁面主要展示的是當前使用者的頭像,暱稱以及學校資訊,另外還有答題資訊,以及每個章節的練習資訊,先來看看效果: grid用的是樣式

程式(看文件寫例項七)程式課堂寶APP實現線上課堂測試

接著上篇博文已經完成簽到功能,這篇來完成課堂測試功能。 一、需求描述 1、在後臺選擇題、主觀題表中上傳測試題 2、客戶端獲取題目資訊 3、把題目資訊格式化載入顯示 4、客戶端答題,主觀題每題能上傳一張答題圖片 5、客戶端答題結束提交到伺服器 二、前臺頁面 提交大量資料

程式(看文件寫例項六)程式課堂寶APP實現簽到邏輯

繼上篇博文,這篇寫下籤到實現的邏輯。 一、實現邏輯 發起簽到 1、先上傳當前自己的定位經緯度 2、學生查詢老師的最後一次簽到記錄,如果發現簽到記錄signComplete為false說明有新的簽到 3、得到簽到的第幾次課 4、系統獲得學生的定位經緯度 5、判斷兩點經緯度轉