目录和分层:
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'; //我们自己拥有的appkeyasync onLaunch(options) {logService.log('app onLaunch options',options);try {const context = await cloud.function.invoke('app'); // 获取contextlet appId = context.sourceMiniAppId; // 此参数应该只有真机环境中才可以获得,本地只会获得开发者的appidconsole.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, //当前的活动idprize: 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
})
}
