首先要感谢@Jianbo提供的开源代码。我的MiniBBS就是在此基础上开发完成的。
我把这个项目叫做MiniBBS是因为它还有很大的改进空间,我目前只实现了帖子预览和发帖功能。如果继续开发下去,应该还可以实现帖子上传图片附件,针对帖子的评论等更高级的功能。那样就更像一个完善的社交圈子了。
我做的这个项目是一个招聘信息发布中心,招聘信息传播的特点是对回评基本没有要求,对图片也没有什么要求,只要信息更新快速,能够让更多的人看到即可。所以,做出来的效果如下图所示:
我省略了帖子详情,所以单个帖子是不可以点开浏览详情的。用户看到的就是信息流(可以按条件加塞广告哦)。右上角悬浮按钮是发帖,采用的是表单提交方式实现帖子发布的功能。
好了,我们来看看完成这个项目我们需要具备什么条件:
- 需要安装json-api插件,我们用这个插件实现了wordpress评论的功能。为什么不用wordpress原生的评论API呢?因为貌似最近一段时间,安卓用户采用最初的方式在小程序里对wordpress文章进行评论总是发生评论无法创建的问题。安装好这个插件之后,打开插件设置界面,在插件提供的api地址框中填写api这三个字母,设置好网站api调用的网址。提交评论的api地址格式应该是这样的: https://www.domain.com/api/respond/submit_comment/?post_id=id
- MiniBBS是在wordpress评论基础上创建的,我们需要挑选一篇文章出来,将这篇文章的评论模拟成帖子。@Jianbo在新版的开源项目里,对wordpress的评论做了升级,要求获取用户的openid,在这里,我还是保留了最初的提交方式,不要求用户提供openid,简化评论步骤。
- 在项目里建立两个页面 pages/quanzi/quanzi 和 pages/fatie/fatie,到app.jspon里,注册这两个页面,然后我建议分配一个tab按钮给pages/quanzi/quanzi,方便调试。
- 在utils/api.js里新增以下代码: 顶部加上定义:
var JSON_API = 'https://www.domain.com/api/';
并在代码中添加以下几个函数:
//获取特定文章的评论 getCommentsSpecial: function (obj) { return HOST_URI + 'comments?parent=0&per_page=100&orderby=date&order=desc&post=' + '1234' + '&page=' + obj.page + '&per_page=10' }, //获取特定文章下评论的回复 getChildrenCommentsSpecial: function (obj) { var url= HOST_URI + 'comments?parent_exclude=0&per_page=100&orderby=date&order=desc&post=' + '1234' return url; }, //新增一个发表评论的函数 postCommentSpecial: function (id) { return JSON_API + 'respond/submit_comment/?post_id=' + id },
代码的第3行和第7行,设定文章ID为1234,这个请根据自己的实际情况设置。你可以到wordpress后台发一篇空内容或者随便敲点什么文字的文章(因为等一下我们不需要在前端展示文章本身的内容。),找到它的ID,然后填入这段代码里。好了,api.js就改到这里。
- 将以下代码复制粘贴到pages/quanzi/quanzi.js
- pages/quanzi/quanzi.wxml代码如下:
- 打开quanzi.json,在{}里加上一句
- 下面提供quanzi.wxss样式代码:
- 圈子(帖子列表)这个页面弄好了,现在,来到发帖界面,这其实就是一个表单,表单数据按照wordpress评论的json数据格式提供给fatie.js,再用wx.request的方法post到数据库里,然后在再用quanzi.js里的wx.request的方法get出来,输出到quanzi.wxml展示,嗯,原理就是这么个意思。
- 下面是fatie.wxml,这个简单,做一个表单就行了:
- fatie.wxss样式表如下:
- fatie.json无需设置,留空即可。
var Api = require('../../utils/api.js'); var util = require('../../utils/util.js'); var WxParse = require('../../wxParse/wxParse.js'); var app = getApp() Page({ data: { detail: {}, commentsList:{}, ChildrenCommentsList: [], commentCount:'', detailDate:'', commentValue:'', wxParseData:[], display:'none', isLastPage:false, page: 1, isLastPage:false, showerror: "none", showallDisplay: "block", postID:null, scrollHeight: 0, link: '', isGetUserInfo:false, dialog: { title: '', content: '', hidden: true }, content:'', userInfo: {}, // 存放用户信息 page:1, // 页码值 animationData: {}, animationData1: {}, // 发布按钮下滑动画 animationData2: {}, // 位置按钮下滑动画 }, onLoad: function (e,options) { this.fetchDetailData(options); var self = this; wx.getSystemInfo({ success: function (res) { self.setData({ scrollHeight: res.windowHeight, }); } }); wx.showToast({ title: '加载中', icon: 'loading', duration: 6000 }); var dataTop = {'detail':{'scrollTop':0}} //获取用户信息 app.getUserInfo(function (userInfo) { self.setData({ userInfo: userInfo, isGetUserInfo:true }) }); }, onShareAppMessage: function () { return { title: this.data.detail.title.rendered, path: 'pages/quanzi/quanzi', success: function (res) { // 转发成功 }, fail: function (res) { // 转发失败 } } }, //获取文章内容 fetchDetailData: function (id) { var self = this; wx.request({ url: Api.getPostByID(1234, { mdrender: false }), success: function (response) { if (response.data.total_comments != null && response.data.total_comments != '') { self.setData({ commentCount: "有" + response.data.total_comments + "个话题" }); } self.setData({ detail: response.data, postID: id, link: response.data.link, detailDate: util.cutstr(response.data.date, 10, 1), wxParseData: WxParse.wxParse('article', 'html', response.data.content.rendered, self, 5), display: 'block' }); wx.setNavigationBarTitle({ title: response.data.title.rendered, success: function (res) { // success } }); // 调用API从本地缓存中获取阅读记录并记录 var logs = wx.getStorageSync('readLogs') || []; // 过滤重复值 if (logs.length > 0) { logs = logs.filter(function (log) { return log[0] !== id; }); } // 如果超过指定数量 if (logs.length > 19) { logs.pop();//去除最后一个 } logs.unshift([id, response.data.title.rendered]); wx.setStorageSync('readLogs', logs); //end self.fetchCommentData(self.data); } }); }, //给a标签添加跳转和复制链接事件 wxParseTagATap: function (e) { var self = this; var href = e.currentTarget.dataset.src; console.log(href); var domain = Api.getDomain(); //我们可以在这里进行一些路由处理 if (href.indexOf(domain) == -1) { wx.setClipboardData({ data: href, success: function (res) { wx.getClipboardData({ success: function (res) { wx.showToast({ title: '链接已复制', //icon: 'success', image: '../../images/link.png', duration: 2000 }) } }) } }) } else { var slug = util.GetUrlFileName(href, domain); if (slug == 'index') { wx.switchTab({ url: '../index/index' }) } else { wx.request({ url: Api.getPostBySlug(slug), success: function (res) { var postID = res.data[0].id; var openLinkCount = wx.getStorageSync('openLinkCount') || 0; if (openLinkCount > 4) { wx.redirectTo({ url: '../detail/detail?id=' + postID }) } else { wx.navigateTo({ url: '../detail/detail?id=' + postID }) openLinkCount++; wx.setStorageSync('openLinkCount', openLinkCount); } } }); } } }, //获取评论 fetchCommentData: function (data) { var self = this; if (!data) data = {}; if (!data.page) data.page = 1; if (data.page === 1) { self.setData({ commentsList: [] }); }; wx.request({ url: Api.getCommentsSpecial(data), success: function (response) { if (response.data.length < 6) { self.setData({ isLastPage: true }); } self.setData({ commentsList: self.data.commentsList.concat(response.data.map(function (item) { var strSummary = util.removeHTML(item.content.rendered); var strdate = item.date item.summary = strSummary; item.date = util.formatDateTime(strdate); if (item.author_url.indexOf('wx.qlogo.cn') !=-1 ) { if (item.author_url.indexOf('https') ==-1 ) { item.author_url = item.author_url.replace("http", "https"); } } else { item.author_url ="../../images/gravatar.png"; } return item; })) }); wx.showToast({ title: '加载中', icon: 'loading', mask: true, duration: 1000 }) } }); }, //底部刷新 loadMore: function (e) { var self = this; if (!self.data.isLastPage) { self.setData({ page: self.data.page + 1 }); console.log('当前页' + self.data.page); this.fetchCommentData(self.data); } else { wx.showToast({ title: '没有更多内容', mask: false, duration: 1000 }); } }, onPullDownRefresh:function(){ //下拉刷新 var self = this self.fetchCommentData() wx.showToast({ title: '刷新数据中', icon: 'loading', duration: 2000 }) self.setData({ page:1, commentsList: [] }) wx.stopPullDownRefresh() }, donghua: function(topNum) // 发帖按钮动画 { var self = this var animation = wx.createAnimation({ duration: 400, timingFunction: 'linear', delay:0, }) var animation1 = wx.createAnimation({ duration: 400, timingFunction: 'linear', delay:0, }) animation.opacity(0).translateY(topNum + 5).step() animation1.opacity(0).translateY(topNum + 5).step() self.setData({ animationData1:animation.export(), animationData2:animation1.export() }) setTimeout(function(){ animation.opacity(1).step() animation1.opacity(1).step() self.setData({ animationData1:animation.export(), animationData2:animation1.export() }) },1500) }, bindAdd: function(){// 跳转发帖界面 var self = this // 发帖 wx.showActionSheet({ itemList: ['发布'], success: function(res) { if (!res.cancel) { console.log(res.tapIndex) if(res.tapIndex == 0) { wx.redirectTo({ url: '../../pages/fatie/fatie' }) } } } }) }, })
代码的第92行,我直接设置了当前文章的ID为1234,与api.js里设置保持一致。代码实现了下拉刷新和点击尾部链接加载更多帖子的功能。
<import src="../../wxParse/wxParse.wxml" /> <view> <!--右上角发布小图标--> <view class="float-action" bindtap="bindAdd" style="opacity: {{ballOpacity}};bottom:{{ballBottom}}px;right:{{ballRight}}px;" style="display:{{floatDisplay}}"> <image src="../../images/add.png"></image> </view> <!--背景小图标--> <view class="bg"> <image class="baseimg" src="../../images/bg.jpg" mode="scaleToFill"></image> </view> <!--背景图底部大头像--> <view class="headimg"> <image class="baseimg" wx:if="{{userInfo['avatarUrl']}}" src="{{userInfo['avatarUrl']}}" mode="scaleToFill"></image> </view> <!--背景图底部昵称--> <view class="nickname"> <text>{{userInfo['nickName']}}</text> </view> <!--占位行--> <view class="lie"></view> <view class="showerror" style="display:{{showerror}}"> <image src="../../images/cry80.png" style="height:100rpx;width:100rpx"></image> <view class="errortext"> 暂时无法访问网络,刷新重试... </view> </view> <view style="display:{{showallDisplay}}"> <!--循环展示/Start--> <view class="content" wx:key="id" wx:for="{{commentsList}}"> <!--头像--> <view class="head"> <image class="baseimg" src="{{item.author_url}}" mode="scaleToFill"></image> </view> <!--昵称--> <text class="wz">{{item.author_name}} </text> <!--内容--> <rich-text class="desc" nodes="{{item.content.rendered}}"></rich-text> <!--发布时间--> <text class="time">{{item.date}}</text> </view> </view> <view style="display:{{floatDisplay}}"> <view class="more-comment" bindtap="loadMore" hidden="{{isLastPage}}"> 点击加载更多信息... </view> <view class="no-more-comment" hidden="{{!isLastPage}}">无更多信息...</view> </view> <view class="copyright"> © www.domain.com </view> </view>
代码第39行,我用了微信原生的rich-text来解析发帖者提交的信息,并未采用wxParse,这样做简单些。待会儿在fatie.js里你会看到我直接把帖子内容固定了一段html语句,提交给quanzi.js的rich-text来解析。
"enablePullDownRefresh":true
让页面下拉刷新生效。
@import "../../wxParse/wxParse.wxss"; .box { /*border:2px solid #576b95;*/ /*height:100%;*/ } .float-action { position: fixed; top: 70rpx; right: 50rpx; width: 70rpx; height: 70rpx; border-radius: 50%; box-shadow: 2px 2px 10px #aaa; z-index: 100; /*background-color:transparent;*/ background-color:#04be02; } .float-action image{ width:320px; height:240px; display:inline; } .entry-summary image { width: 100% !important; } .textNoEmpty { color: red; } .gravatarImg { margin-top: 4px; border-radius: 50%; opacity: 1; transform: scale(1); perspective-origin: top center; transition: all ease-in-out 0.3s; height: 48rpx; width: 48rpx; } .baseimg { width:100%; height:100%; } .oneImg { width:auto; height:auto; /*margin-left:100rpx;*/ overflow:hidden; } .images-image { width: 150rpx; height: 150rpx; margin: 10rpx; } .bg { height: 400rpx; /*background: red;*/ overflow:hidden; } .headimg { width: 150rpx; height: 150rpx; position:absolute; border: 2px solid #fff; margin-top: -100rpx; margin-left: 570rpx; overflow:hidden; } .nickname { width:400rpx; text-align: right; position:absolute; color:#fff; margin-top: -70rpx; margin-left: 150rpx; font-size:14pt; font-weight:600; /*border:1px solid red;*/ } .lie { /*border: 1px solid #ccc;*/ margin-top: 100rpx; flex-direction: column; } .content { border-bottom:1px solid #F5F5F5; display:flex; flex-direction: column; padding-bottom:20rpx; } .head { width: 80rpx; height: 80rpx; /*background: darkcyan;*/ margin-top:20rpx; margin-left:20rpx; overflow:hidden; } .wz { margin-top:-80rpx; margin-left:120rpx; font-size:10pt; color:#576b95; } .desc { margin-top:20rpx; margin-left:120rpx; margin-right:20rpx; font-size:10pt; } .descrich{ margin-top:20rpx; margin-left:120rpx; margin-right:20rpx; font-size:10pt; } .descrich image{ height: 150rpx; width: 30%; max-height: 150rpx; max-width: 150rpx; padding-right: 5px; object-fit: cover; } .address { margin-top:20rpx; height:30rpx; font-size:10pt; color:#576b95; margin-left:120rpx; margin-bottom:20rpx; width:600rpx; } .time { margin-top:20rpx; height:30rpx; font-size:8pt; color:#ccc; margin-left:120rpx; } .dele { margin-top:20rpx; width:80rpx; height:30rpx; font-size:10pt; color:#576b95; margin-top:-28rpx; margin-left:270rpx; } .biao { width: 50rpx; height:40rpx; overflow:hidden; margin-top:-32rpx; margin-left:680rpx; margin-bottom:20rpx; } .more-comment { color: #04be02; font-size: 30rpx; line-height: 1.8rem; margin-bottom: 50rpx; text-align: center; margin-top: 30rpx } .no-more-comment { color: #757575; font-size: 30rpx; line-height: 1.8rem; margin-bottom: 100rpx; text-align: center; margin-top: 30rpx } .copyright{ text-align: center; margin: 30px; font-size: 28rpx; color: #ccc; margin-bottom: 120rpx; }
下面,上代码,fatie.js
var Api = require('../../utils/api.js'); var util = require('../../utils/util.js'); var WxParse = require('../../wxParse/wxParse.js'); var bmap = require('../../libs/bmap-wx/bmap-wx.min.js'); var wxMarkerData = []; //定位成功回调对象 var app = getApp() Page({ data: { ak: "", //填写从百度申请到的ak markers: [], longitude: '', //经度 latitude: '', //纬度 address: '', //地址 cityInfo: {}, //城市信息 date: '请点击选择服务时间', pickerHidden: true, chosen: '', title: '文章内容', detail: {}, commentsList:{}, commentCount:'', detailDate:'', wxParseData:[], display:'none', page: 1, isLastPage:false, postID:null, scrollHeight: 0, isGetUserInfo:false, dialog: { title: '', content: '', hidden: true }, content:'', userInfo:[], imageObject:[], imagescontents:[], tempFilePaths:[], }, //页面载入 onLoad: function (options) { this.fetchDetailData(options.id); var self = this; wx.getSystemInfo({ success: function (res) { //console.info(res.windowHeight); self.setData({ scrollHeight: res.windowHeight, }); } }); //获取用户信息 app.getUserInfo(function (userInfo) { //更新数据 self.setData({ userInfo: userInfo, isGetUserInfo:true }) }); /* 获取定位地理位置 */ // 新建bmap对象 var BMap = new bmap.BMapWX({ ak: self.data.ak }); var fail = function (data) { console.log(data); }; var success = function (data) { //返回数据内,已经包含经纬度 console.log(data); //使用wxMarkerData获取数据 wxMarkerData = data.wxMarkerData; //把所有数据放在初始化data内 self.setData({ markers: wxMarkerData, latitude: wxMarkerData[0].latitude, longitude: wxMarkerData[0].longitude, address: wxMarkerData[0].address, cityInfo: data.originalData.result.addressComponent }); } // 发起regeocoding检索请求 BMap.regeocoding({ fail: fail, success: success }); }, //获取页面内容 fetchDetailData: function () { var self = this; }, //底部刷新 loadMore: function (e) { var self = this; if (!self.data.isLastPage) { self.setData({ page: self.data.page + 1 }); console.log('当前页' + self.data.page); this.fetchCommentData(self.data); } else { wx.showToast({ title: '没有更多内容', mask: false, duration: 1000 }); } }, //发布话题 formSubmit: function (e) { var address = this.data.address; var self = this; var name = self.data.userInfo.nickName; var email = "test@test.com"; var comment = e.detail.value.inputComment; var author_url = self.data.userInfo.avatarUrl; var images = new Array(); //console.log(auth); wx.hideLoading(); //var postID = e.detail.value.inputPostID; if (comment.length===0 ) { self.setData({ 'dialog.hidden': false, 'dialog.title': '提示', 'dialog.content': '没有填写内容。' }); } else { //检测授权 //self.checkSettingStatu(); if (self.data.isGetUserInfo) { wx.request({ url: 'https://www.domain.com/api/respond/submit_comment/?post_id=1234', method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT data: { name: name, email: email, content: comment+'<blockquote>本信息发自:</blockquote><blockquote>@'+address+'</blockquote>', url: author_url, }, header: { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' }, success: function (res) { wx.showToast({ title: '已发布', icon: 'success', duration: 2000 }); wx.switchTab({ url: '../quanzi/quanzi' }); }, fail: function (res) { //console.log(res.data) self.setData({ 'dialog.hidden': false, 'dialog.title': '提示', 'dialog.content': '发布失败,' + res.data.message }); } }); } } }, // 检测授权状态 checkSettingStatu: function (cb) { var that = this; // 判断是否是第一次授权,非第一次授权且授权失败则进行提醒 wx.getSetting({ success: function success(res) { console.log(res.authSetting); var authSetting = res.authSetting; if (util.isEmptyObject(authSetting)) { console.log('首次授权'); } else { console.log('不是第一次授权', authSetting); // 没有授权的提醒 if (authSetting['scope.userInfo'] === false) { wx.showModal({ title: '用户未授权', content: '如需正常使用评论的功能,请授权管理中选中“用户信息”,然后点按确定后再次提交评论。', showCancel: false, success: function (res) { if (res.confirm) { console.log('用户点击确定') wx.openSetting({ success: function success(res) { console.log('openSetting success', res.authSetting); var as = res.authSetting; for (var i in as) { if(as[i]) { //获取用户信息 app.getUserInfo(function (userInfo) { //更新数据 that.setData({ userInfo: userInfo, isGetUserInfo: true }) }); } } } }); } } }) } } } }); }, confirm: function () { this.setData({ 'dialog.hidden': true, 'dialog.title': '', 'dialog.content': '' }) }, modalChange: function(e) { this.setData({ modalHidden: true }) } })
关于调用百度地图API进行地址定位的js代码点击这里下载:http://lbsyun.baidu.com/index.php?title=wxjsapi/wxjs-download,按照代码第4行设定的路径放置好就可以了。然后,你需要到百度开放平台http://lbsyun.baidu.com/index.php?title=wxjsapi/guide/key申请一个应用,并为这个应用获取一个ak,填入上述代码第10行,代码的第171行设置了文章的ID,这也需要与utils/app.js中的设置保持一致。
<view class="container"> <scroll-view style="height:{{scrollHeight}}px;" scroll-y="true" lower-threshold="100rpx"> <view class="page__hd"> <text class="page__title">发布信息</text> </view> <form class="page__bd" report-submit="true" catchsubmit="formSubmit" catchreset="formReset"> <view class="section"> <textarea name="inputComment" placeholder-style="color:#1199ed;" placeholder="请在这里输入内容..." /> </view> <view class="on-line-20"></view> <view class="location"> <image src="../../images/fujin.png"></image> <text>{{address}}</text> </view> <view class="on-line-1"></view> <view class="btn-area"> <button hover-class="submitbutton-hover" class="submitbutton" formType="submit">发布</button> </view> <view class="on-line-1"></view> <view class="btn-area"> <navigator url="/pages/quanzi/quanzi" open-type ="switchTab"> <button>返回</button> </navigator> </view> </form> <view class="copyright">© www.domain.com</view> </scroll-view> </view>
wx-label { display: block; margin-top: 10rpx; margin-left: 15rpx; } .section__title{ font-size: 30rpx; margin-bottom: 30rpx; font-weight: bold; } .page { min-height: 100%; flex: 1; background-color: #FBF9FE; font-size: 32rpx; font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif; overflow: hidden; } .page__hd{ padding: 20rpx 50rpx 50rpx 50rpx; text-align: center; } .page__title{ display: inline-block; padding: 20rpx 40rpx; font-size: 48rpx; color: #AAAAAA; border-bottom: 1px solid #CCCCCC; } .page__desc{ display: none; margin-top: 20rpx; font-size: 26rpx; color: #BBBBBB; } .section{ margin-bottom: 80rpx; } .section_gap{ padding: 0 30rpx; } .section__title{ margin-bottom: 16rpx; padding-left: 30rpx; padding-right: 30rpx; } .section_gap .section__title{ padding-left: 0; padding-right: 0; } .text-view{ padding: 20rpx; height: 300rpx; } .on-line-20{ height: 20rpx; background: #f4f4f4; } .on-line-50{ height: 50rpx; background: #f4f4f4; } .on-line-1{ margin-top: 20rpx; height: 1px; background: #eee; } .location image{ width:40rpx; height:40rpx; padding-left: 20rpx; padding-top:20rpx; } .btn-area{ padding: 0 30px; } .btn-area button{ margin-top: 20rpx; margin-bottom: 20rpx; } .submitbutton-hover { background-color: #81CBFA; opacity: 0.7; } .submitbutton { flex: 2rpx; border: none !important; color: #fff !important; background-color: #1199ed } .copyright{ text-align: center; margin: 30px; font-size: 28rpx; color: #ccc; }
最后,把代码中引用的图片放置在images文件夹下,打开微信小程序调试工具,你就可以开始测试发帖了,图片点击下方链接下载:
add.png https://wx1.sinaimg.cn/large/006mIDfnly1fl6ad5ykp3j30ad0addfl.jpg 透明图,不要打开后以为不存在哦,点屏幕左上角下载,下载后还原文件名为 add.png(扩展名也要还原)
fujin.png https://wx1.sinaimg.cn/large/006mIDfnly1fl6aeelwbkj303k03kmwy.jpg 下载后还原文件名为 fujin.png(扩展名也要还原)
bg.jpg https://wx1.sinaimg.cn/large/006mIDfnly1fl6af2jx0nj30ax065410.jpg 下载后还原文件名为 bg.jpg