目录和分层:
client和server端
虽然小程序几乎全部使用的是nodejs来实现的,但是还是存在CS两端;client端对应的是前后端分离的前端,server则对应着后段;
手淘端主要的工作在client端,server端很少涉及,但是会有很多C端会经常调用S端写好的云函数,这时候就需要前后端的配合来;
下面着重介绍下手淘端的目录接口和作用;
common 文件夹 存放的都是公共的调用云函数的服务类;
components 文件夹 存放的是一些基础组件,如果要想封装更多的组件也可以在这里编写;
mixins 文件夹 存放的公共逻辑可以混入的代码,大部分的活动逻辑实现,几乎都在这里实现
mock 文件夹 存放的是我们的测试的mock数据
node_modules文件夹 存放的是依赖库,不用关系;
pages 文件夹 存放的是具体的页面,我们大部分页面实现都在这里进行;
util 文件夹 存放的是公共的工具库
框架逻辑和页面流程
app.js中的主要逻辑:
所有的页面和活动首先进入的是app.js进行全局的逻辑处理:区分模拟还是正式活动,授权鉴权,活动获取数据,并根据数据中的路径进行路由;
区分模拟还是正式活动(敲重点):
在测试环境中appid是我们定义的,如果是用户的活动则是用户的appid;我们通过这个特性来区分当前是测试环境还是用户真实场景;如果是测试环境我们就走mock数据,如果是用户的真实环境就走真实数据进行正常逻辑;
const sourceId = '3000000002060192'; //我们自己拥有的appkey
async onLaunch(options) {
logService.log('app onLaunch options',options);
try {
const context = await cloud.function.invoke('app'); // 获取context
let appId = context.sourceMiniAppId; // 此参数应该只有真机环境中才可以获得,本地只会获得开发者的appid
console.log('appId',appId);
if (`${appId}` == sourceId) {
console.log('进入mock环境');
this.activity = collectPrize3;
this.routerActivity(); // 路由活动
} else { // 正常路程
if (appId) { // 如果没能成功获得实例化的应用id,进行错误汇报
await this.authorize();
this.routerApp(appId,options.path);
} else {
this.errorReport('appId 获取失败');
}
}
} catch (e) {
logService.log('on launch error', e);
}
},
授权和鉴权:
真实环境中要走授权和鉴权:await this.authorize();
/**
* 用户授权
*/
authorize() {
return new Promise(resolve => {
my.authorize({
scopes: 'scope.userInfo',
success: resolve,
fail: (err) => this.errorReport(err.errorMessage)
});
})
},
获取活动数据:
/**
* 根据appid路由到具体商家的应用
*/
async routerApp(app_id,path) {
try {
logService.log('start router app');
const result = await cloud.db.collection("activity").find({ app_id }); // 跟进appid 获取活动数据
if (result && result.length >= 1) {
this.activity = result[0]; // 保存活动为全局活动
if(path){ // 如果有path 说明是分享过来的
if('pages/index/index' === path ){
logService.log('index 命中,进行进行跳转');
this.routerActivity(); // 路由活动
}else{
this.routerActivity('share'); // 路由活动
}
}else{
this.routerActivity(); // 路由活动
}
} else {
this.errorReport('活动信息获取失败'); // 如果没有获得具体的活动数据,进行错误报告
}
} catch (e) {
logService.log('routerApp error', e);
}
},
路由到具体活动:
/**
* 路由到具体活动
*/
async routerActivity(share) {
try {
logService.log('start router activity');
const { _id,template_path, begin, end } = this.activity;
const beginTime = + new Date(begin); // 活动开始时间
const endTime = + new Date(end); // 活动结束时间
const now = + new Date(); // 当前时间
if (beginTime > now) { // 未开始
const result = /^\d+\-(\d+)\-(\d+)\.*/.exec(begin);
this.errorReport(`活动尚未开始,请于${result[1]}月${result[2]}号后再来看看吧!`, 1);
} else if (endTime < now) { // 已结束
this.errorReport('活动已经结束');
} else {
logService.log('targetPath', template_path);
my.redirectTo({ url: template_path });
}
} catch (e) {
logService.log('routerActivity error', e);
}
},
统一的初始化界面统一的错误处理和错误页面
所有的业务逻辑:
首先都同时展示/pages/index/index页面;
然后活动逻辑正常的话,就进入对应的活动页面,活动数据中定义的:template_path;
如果发生来错误,则进入统一的错误页面:/pages/error/error;
/**
* 错误处理页面
*/
errorReport(msg, flag = 0) {
if (this.waiting) { // waiting实例已生成,更新waiting页面
this.waiting.update({ msg, flag });
} else { // 跳转到waiting页面
const url = `/pages/error/error?flag=${flag}&msg=${msg}`;
my.redirectTo({ url });
}
},
一般的活动开发流程和逻辑
一个活动可能存在多个模版,逻辑基本相同,所以我们的活动首先从模版开始;
首先构建模版页面;
主要是编写pages下面的axml,acss,js代码
然后编写公共的活动逻辑;
import {openDetail } from '/mixins/common'
import logic from '/mixins/collect-logic'
Page({
mixins: [ openDetail, logic],
});
倒入可以复用的逻辑和代码块,然后在代码中引用即可
最后根据活动的不同进行特定逻辑的添加和修改;
不同活动的触发机制和活动流程不一样,中间的事件不同,需要在公共方法中留好切面,并在具体的活动中进行回调;
公共方法中的切面编程见注视;
// 编程的回调切入点,处理完公共逻辑后,一些活动更特异性的逻辑交给具体的活动进行实现和处理
async checkAllGoodsCollectStatus(items){
let self = this;
let isAllCollect = true;
if(items){
for(let item of items){
const result = await self.checkGoodsStatus( item )
if(result === false){
isAllCollect = result;
}
}
}
this.setData({
items : items,
isAllCollect
})
if(isAllCollect){
this.setData({
showButton:false
})
if(!this.prizeSuccess){
if(this.onAllChecked){ // 编程的回调切入点,处理完公共逻辑后,一些活动更特异性的逻辑交给具体的活动进行实现和处理
this.onAllChecked()
}
}
}
},
具体活动的回调进行更具有针对性的逻辑处理
async onAllChecked(){
//判断是否已经参加了活动,并领取了奖品
const record = await activityService.getRecord(getApp().activity._id);
if (record) { // 如果已经领奖
this.tip('亲,已经领取奖品,进店看看');
this.setData({
showButton:false,
prizeSuccess:true
})
}else{
this.tip('亲,您已经收藏了所有商品,获得一次抽奖机会;');
const result = await activityService.probability(getApp().activity.extra.prize)
this.setData({
showPrizeButton:true,
showButton:false,
prize:result.data // 奖品信息
})
}
},
onStart() {
setTimeout(() => {
this.setData({
awardImg: 'https://gw.alicdn.com/tfs/TB1JsqGbHPpK1RjSZFFXXa5PpXa-289-298.png',
awardName: this.data.prize.name
})
this.gift(this.data.prize.name);
}, 2000);
},
async onFinish() {
this.setData({
showButton:false,
prizeSuccess:true
})
const param = {
activity_id: getApp().activity._id, //当前的活动id
prize: this.data.prize,
extra: { isAllCollect: true }
}
const result = await activityService.provide(param);
if (result.success) { // 发奖成功
this.tip('获得了奖品:'+this.data.prize.name) // 提交获奖
} else {
this.tip(result.msg || result.code) // 失败
}
this.gift();
},
手淘端常用函数介绍
抽奖函数:
const result = await activityService.probability(getApp().activity.extra.prize)
this.setData({
showPrizeButton:true,
showButton:false,
prize:result.data // 奖品信息
})
发奖函数:
const param = {
activity_id: getApp().activity._id, //当前的活动id
prize: this.data.prize,
extra: { isAllCollect: true }
}
const result = await activityService.provide(param);
if (result.success) { // 发奖成功
this.tip('获得了奖品:'+this.data.prize.name) // 提交获奖
} else {
this.tip(result.msg || result.code) // 失败
}
活动是否参与判断函数:
onst record = await activityService.getRecord(getApp().activity._id);
if (record) { // 如果已经领奖
this.tip('亲,已经领取奖品,进店看看');
this.setData({
showButton:false,
prizeSuccess:true
})
}