前不久,小程序推出了云开发的功能,使开发者们无需搭建服务器,用云端能力直接迈入全栈开发。正巧用着网易云课堂充电,界面精致、细节到位,因而决定用云开发来仿一仿网易云课堂。javascript
来,先看一波效果图html
购物车java
直接购买node
在项目开发中选用好的工具使得工做事半功倍git
|-study163 项目名
|-cloudfunctions 云函数
|-getMyCourse 获取个人课程
|-getCourseInfo 获取课程信息
|-getCart 获取购物车
|-miniprogram 项目模块
|-components 自定义组件
|-box-module 盒子
|-myCourse-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
复制代码
在新版的开发者工具中,具备了云开发的功能,且在启动模板中添加了云开发快速启动模板github
云开发的三个核心
sql
课程的数据项是多出了个人预料的,这里我只定义了我所使用了的部分。每一个页面显示不一样的课程。这里要注意的一点是,数据滚动栏里的、课程盒子里的、课程信息里的图片虽然看起来类似,但并非同一张,因此储存的连接不一样。小tips:设计好数据库后记得设置相对应的权限管理,不然可能出现读取不到数据的状况。数据库
_id是数据库自动生成的属性(惟一),而我定义了一个id属性,串联个个数据表(collection),相似传统sql数据库中的主键。json
course_info小程序
course_cart
my_course
当文件储存在此后,会生成一个下载地址,能够直接拿出来用。
固然也能够用相应的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处理!
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为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>复制代码
虽然简单,但这是典型的数据驱动界面效果,数据绑定,也就是MVVM。页面经过对数据status的判断来决定显示对应的部分。
<ul class="status">
<li>
<text class="{{status == '1' ? 'active':''}}" bindtap="showStatus" data-status="1">个人课程</text>
</li>
<li>
<text class="{{status == '2' ? 'active':''}}" bindtap="showStatus" data-status="2">个人微专业</text>
</li>
</ul>
<block wx:if="{{status == '1'}}">
</block>
<block wx:else>
</block>复制代码
给相应的标签设置对应的data-status,再将修改的函数绑定到bindtap上,一个最简单的MVVM例子就实现了。
showStatus: function(e){
let status = e.currentTarget.dataset.status;
this.setData({
status:status
})
}复制代码
关于课程的获取就是云函数的用武之地了
onShow:function(){
wx.cloud.callFunction({
name: 'getMyCourse',
}).then(res=>{
this.setData({
my_courses: res.result.data
})
})
}复制代码
在你须要使用云函数的地方调用wx.cloud.callFunction(),把要调用的云函数的函数名做为参数传入,如此处{name: 'getMyCourse'}。接着在wxml内经过一个wx:for把数据输出便可。此处myCourse-module为封装好了的自定义组件。
<view wx:for="{{my_courses}}" wx:key="index">
<myCourse-module image="{{item.image}}" title="{{item.title}}" cid="{{item.id}}">
</myCourse-module>
</view>复制代码
相关数据项有
totalPrice:0,
selectedId:[],
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>复制代码
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}复制代码
import { indexMock } from '../../utils/indexMock.js';
//先引入indexMock
Page({
data: {
mainPage:{},
url:"https://www.easy-mock.com/mock/5bda9d1a58caf84108172bab/study163/mainPage",
},
onLoad() {
const url = this.data.url;
indexMock(url) //indexMock返回的是一个Promise 后面用then()处理就
.then(res => {
console.log(res)
this.setData({
mainPage:res
})
})
.then(res=>{
console.log("mainpage",this.data.mainPage)
})
} //其余函数....
});
复制代码
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/study163
欢迎 ☆☆☆star☆☆☆!