前不久,小程序推出了云开发的功能,使开发者们无需搭建服务器,用云端能力直接迈入全栈开发。正巧用着网易云课堂充电,界面精致、细节到位,因而决定用云开发来仿一仿网易云课堂。来,先看一波效果图html
|-study163 项目名node
|-cloudfunctions 云函数 |-getMyCourse |-getCourseInfo |-getCart |-miniprogram 项目模块 |-components 自定义组件 |-box-module 盒子 |-special-module 专题 |-utils 工具 |-indexMock 获取主页数据 |-viewContent 文本处理 |-pages 页面 |-account 帐号 |-cart 购物车 |-confirm 确认订单 |-courseInfo 课程信息 |-myStudy 个人学习 |-index 首页 |-vant-weapp 有赞vant框架组件库 |-··· app.js 全局js app.json 全局json配置 app.wxss 全局wxss
在新版的开发者工具中,具备了云开发的功能,且在启动模板中添加了云开发快速启动模板git
云开发的三个核心github
数据库 文档(JSON)数据库 相似MongoDB
存储管理 存储空间,相似百度云盘
云函数 独立的node项目sql
数据库数据库
课程的数据项是多出了个人预料的,这里我只定义了我所使用了的部分。每一个页面显示不一样的课程。这里要注意的一点是,数据滚动栏里的、课程盒子里的、课程信息里的图片虽然看起来类似,但并非同一张,因此储存的连接不一样。小tips:设计好数据库后记得设置相对应的权限管理,不然可能出现读取不到数据的状况。_id是数据库自动生成的属性(惟一),而我定义了一个id属性,串联个个数据表(collection),相似传统sql数据库中的主键。json
存储管理小程序
当文件储存在此后,会生成一个下载地址,能够直接拿出来用。后端
固然也能够用相应的API直接进行上传或下载数组
云函数
云函数就是部署在云端的独立运行的node后端项目,根据自身的业务具体实现。在生成云函数后,首先记得安装依赖,在终端中yarn。写完后不光得保存还得记得上传并部署。云函数(小demo)的门槛不高,能够轻松助你实现全栈开发。这里用最简单的查询举个例子。
// 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database(); const course_cart = db.collection('course_cart'); // 云函数入口函数 exports.main = async (event, context) => { let info = course_cart.where({ id: event.id }).get(); return info }
拿到数据后,下一步就是在页面上显示出来,众所周知js是个单线程语言,而数据交互是个异步过程。这一段没有处理好的话,可能出现即便获取到了数据也没显示出来的问题。因此啊,异步问题仍是得交给promise处理!而当有多处数据交互时,用promise .all()
const promiseArr = cart_coursesId.map(course => new Promise((resolve, reject) =>{ wx.cloud.callFunction({ name: 'getCart', data: { id: course} }) .then(res =>{ resolve(res.result.data[0]); }) })) Promise .all(promiseArr) .then(data => { console.log(data); wx.hideLoading() this.setData({ courses: data }) })
主页
关于主页的课程盒子,我想用一个模板兼容全部的表现效果,因此这也是坑我最多的地方。为了兼容大图的展示,我给图片加上了一个isBig属性(bool)。当isBig为false时,图片宽度为48.7%,margin-right为 2.6%;,并经过父类的ntd-child(even)将偶数子类(处于右边)的margin-right改成0%,造成 (48.7%+2.6%)+48.7%的结构分割。但当isBig为true,第一行就只有一张大图,因此从第二行开始偶数子元素出如今左边,结构变成48.7%+(48.7%+2.6%)原本在中间的间隙到了右边,两张图片贴在了一块儿,很不美观。因此我在每处理到一张大图的同时,再生成一个display为none的小盒子。这样第一行依然只显示一张大图,但有了两个子元素。因此下面的行列,依然是奇数子类在左边,偶数子类在右边。效果实现了,可是当我想把它封装成组件时,天坑出现了 component不支持ntd-child选择器,而且组件的slot也并不仅是直白的整段插入。不过往后仍是会从新封装~
<view class="courses"> <block wx:for="{{mainPage.sellWell.content}}" wx:key="index" > <block wx:if="{{item.isBig}}"> <view class="course_big" > <navigator url="../courseInfo/courseInfo?id={{item.id}}" > <image class="course-image_big " src="{{item.image}}" /> <text class="course-title">{{item.title}}</text> <text class="course-price" wx:if="{{item.price > 0}}">¥{{item.price}}</text> </navigator> </view> <view style="display:none"></view> </block> <block wx:else> <view class="course" > <navigator url="../courseInfo/courseInfo?id={{item.id}}" > <image class="course-image " src="{{item.image}}" /> <text class="course-title">{{item.title}}</text> <text class="course-price" wx:if="{{item.price > 0}}">¥{{item.price}}</text> </navigator> </view> </block> </block> </view>
购物车
相关数据项有
totalPrice:0, //总价 selectedId:[], //被选中的课程ID selectAllStatus: false //全选状态
经过对列表中数据的isSelected属性判断,来计算总价
getTotalPrice: function(){ let courses = this.data.courses; let total = 0; let selectedId = []; for(let i=0;i<courses.length;i++){ if(courses[i].isSelected){ total += courses[i].price; selectedId.push(courses[i].id) } } this.setData({ totalPrice:total, selectedId }) console.log('[total]',total); console.log('[selected]',selectedId); return total; }
选中课程,仍是MVVM数据绑定,且选中后从新计算总价
selectedList:function(e){ const index = e.currentTarget.dataset.index; const courses = this.data.courses; courses[index].isSelected = !courses[index].isSelected; this.setData({ courses }) this.getTotalPrice(); }
全选,每次进行selectAll操做,先将selectAllStatus改成!selectAllStatus,(全选=>全不选 || 全不选=>全选 ),以后将全部数据的isSelected属性统一为selectAllStatus的当前状态。
selectAll:function(e){ let courses = this.data.courses; let selectAllStatus = this.data.selectAllStatus; selectAllStatus = !selectAllStatus; courses.forEach((item, index) => { item.isSelected = selectAllStatus }) this.setData({ courses, selectAllStatus }) this.getTotalPrice(); }
数据传递到下一个界面 将选中的数组selectedId做为对象经过navigator传入下一个页面。
<block wx:if="{{totalPrice > 0}}"> <navigator url="../confirm/confirm?ids={{selectedId}}"> <view class="pay-btn active" bindtap="confirm">去结算</view> </navigator> </block>
订单
先获取购物车传递的数据
let orderIds = options.ids.split(","); console.log(orderIds) this.setData({ userInfo : app.globalData.userInfo, orderIds: orderIds })
完成订单后,将购买的课程存入数据库内
wx.cloud.init() const db = wx.cloud.database(); const my_courses = db.collection('my_courses'); addMyCourse(){ const orders = this.data.orders; for(let order of orders){ let myCourse = { title:order.title, image:order.image, id:order.id } console.log(myCourse); my_courses.add({data:myCourse}) } }
提交订单以后 my_courses更新,相应的,打开被购买的课程页面也会更新,查询当前课程是否在购买的课程中,在则将isPaid改成true。
wx.cloud.callFunction({ name: 'getMyCourse', }).then(res=>{ for(const my_course of res.result.data){ if(options.id === my_course.id){ let isPaid = true; this.setData({ isPaid }) return ; } } }) <block wx:if="{{isPaid}}"> <view class="foot">打开网易云课堂APP学习 支持倍速播放<view class="arrow"></view></view> </block> <block wx:else> </block>
indexMock 此处是用来请求数据的,数据的请求不要放在页面的js中,将它封装出去,import到须要用的页面便可。
let indexMock = function(url){ return new Promise((resolve,reject) => { wx.request({ url:url, success(res){ resolve(res.data) }, fail(err){ reject(err) } }) })
}
module.exports = { indexMock: indexMock }
viewContent
在显示课程介绍时,遇到了一个头疼的问题,原来介绍是html格式的,有各类<span><p>
之类的标签,而小程序内是wxml文件。最初我想到的是用wxparse来解析。但此处我只须要解析轻量的文章,有点杀鸡焉用牛刀的感受。其实呢,wxparse的实现就是经过生成node数组来转换。因此我干脆直接用正则写一个转换的数组工具。
const viewContent = (data) => { if(data && (typeof data === "string")){ return data.replace(/<\/?span>/g, "").replace(/<\/?p>/g, "").replace(/ /g, " ").split("<br>"); } else{ return } } module.exports = { viewContent: viewContent }
数据库中存的是html文档
这里要注意的一点是要给txt-item设置一个高度,实现空行的效果。
写项目时老是,思考不全老是遇到各类各样的坑坑坑,算是意识到结构健壮、逻辑一致的重要性。wxml是结构,首先得保证结构的稳定性,至于样式就是体力活了,至于数据那就是MVVM大显身手的地方了。路漫漫其修远兮,吾将上下而求索。
感谢阅读,文中有错误的地方或者有建议欢迎提出!
网易云课堂 确实是个好地方鸭,没事多去充充电
项目地址:https://github.com/MarchYuanx...
欢迎 ☆☆☆star☆☆☆!