目录和分层:

image.png

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数据,如果是用户的真实环境就走真实数据进行正常逻辑;

  1. const sourceId = '3000000002060192'; //我们自己拥有的appkey
  2. async onLaunch(options) {
  3. logService.log('app onLaunch options',options);
  4. try {
  5. const context = await cloud.function.invoke('app'); // 获取context
  6. let appId = context.sourceMiniAppId; // 此参数应该只有真机环境中才可以获得,本地只会获得开发者的appid
  7. console.log('appId',appId);
  8. if (`${appId}` == sourceId) {
  9. console.log('进入mock环境');
  10. this.activity = collectPrize3;
  11. this.routerActivity(); // 路由活动
  12. } else { // 正常路程
  13. if (appId) { // 如果没能成功获得实例化的应用id,进行错误汇报
  14. await this.authorize();
  15. this.routerApp(appId,options.path);
  16. } else {
  17. this.errorReport('appId 获取失败');
  18. }
  19. }
  20. } catch (e) {
  21. logService.log('on launch error', e);
  22. }
  23. },

授权和鉴权:

真实环境中要走授权和鉴权:await this.authorize();

  1. /**
  2. * 用户授权
  3. */
  4. authorize() {
  5. return new Promise(resolve => {
  6. my.authorize({
  7. scopes: 'scope.userInfo',
  8. success: resolve,
  9. fail: (err) => this.errorReport(err.errorMessage)
  10. });
  11. })
  12. },

获取活动数据:

  1. /**
  2. * 根据appid路由到具体商家的应用
  3. */
  4. async routerApp(app_id,path) {
  5. try {
  6. logService.log('start router app');
  7. const result = await cloud.db.collection("activity").find({ app_id }); // 跟进appid 获取活动数据
  8. if (result && result.length >= 1) {
  9. this.activity = result[0]; // 保存活动为全局活动
  10. if(path){ // 如果有path 说明是分享过来的
  11. if('pages/index/index' === path ){
  12. logService.log('index 命中,进行进行跳转');
  13. this.routerActivity(); // 路由活动
  14. }else{
  15. this.routerActivity('share'); // 路由活动
  16. }
  17. }else{
  18. this.routerActivity(); // 路由活动
  19. }
  20. } else {
  21. this.errorReport('活动信息获取失败'); // 如果没有获得具体的活动数据,进行错误报告
  22. }
  23. } catch (e) {
  24. logService.log('routerApp error', e);
  25. }
  26. },

路由到具体活动:

  1. /**
  2. * 路由到具体活动
  3. */
  4. async routerActivity(share) {
  5. try {
  6. logService.log('start router activity');
  7. const { _id,template_path, begin, end } = this.activity;
  8. const beginTime = + new Date(begin); // 活动开始时间
  9. const endTime = + new Date(end); // 活动结束时间
  10. const now = + new Date(); // 当前时间
  11. if (beginTime > now) { // 未开始
  12. const result = /^\d+\-(\d+)\-(\d+)\.*/.exec(begin);
  13. this.errorReport(`活动尚未开始,请于${result[1]}月${result[2]}号后再来看看吧!`, 1);
  14. } else if (endTime < now) { // 已结束
  15. this.errorReport('活动已经结束');
  16. } else {
  17. logService.log('targetPath', template_path);
  18. my.redirectTo({ url: template_path });
  19. }
  20. } catch (e) {
  21. logService.log('routerActivity error', e);
  22. }
  23. },

统一的初始化界面统一的错误处理和错误页面

所有的业务逻辑:
首先都同时展示/pages/index/index页面;
然后活动逻辑正常的话,就进入对应的活动页面,活动数据中定义的:template_path;
如果发生来错误,则进入统一的错误页面:/pages/error/error;

  1. /**
  2. * 错误处理页面
  3. */
  4. errorReport(msg, flag = 0) {
  5. if (this.waiting) { // waiting实例已生成,更新waiting页面
  6. this.waiting.update({ msg, flag });
  7. } else { // 跳转到waiting页面
  8. const url = `/pages/error/error?flag=${flag}&msg=${msg}`;
  9. my.redirectTo({ url });
  10. }
  11. },

一般的活动开发流程和逻辑

一个活动可能存在多个模版,逻辑基本相同,所以我们的活动首先从模版开始;

首先构建模版页面;

image.png
主要是编写pages下面的axml,acss,js代码

然后编写公共的活动逻辑;

  1. import {openDetail } from '/mixins/common'
  2. import logic from '/mixins/collect-logic'
  3. Page({
  4. mixins: [ openDetail, logic],
  5. });

倒入可以复用的逻辑和代码块,然后在代码中引用即可

最后根据活动的不同进行特定逻辑的添加和修改;

不同活动的触发机制和活动流程不一样,中间的事件不同,需要在公共方法中留好切面,并在具体的活动中进行回调;
公共方法中的切面编程见注视;
// 编程的回调切入点,处理完公共逻辑后,一些活动更特异性的逻辑交给具体的活动进行实现和处理

  1. async checkAllGoodsCollectStatus(items){
  2. let self = this;
  3. let isAllCollect = true;
  4. if(items){
  5. for(let item of items){
  6. const result = await self.checkGoodsStatus( item )
  7. if(result === false){
  8. isAllCollect = result;
  9. }
  10. }
  11. }
  12. this.setData({
  13. items : items,
  14. isAllCollect
  15. })
  16. if(isAllCollect){
  17. this.setData({
  18. showButton:false
  19. })
  20. if(!this.prizeSuccess){
  21. if(this.onAllChecked){ // 编程的回调切入点,处理完公共逻辑后,一些活动更特异性的逻辑交给具体的活动进行实现和处理
  22. this.onAllChecked()
  23. }
  24. }
  25. }
  26. },

具体活动的回调进行更具有针对性的逻辑处理

  1. async onAllChecked(){
  2. //判断是否已经参加了活动,并领取了奖品
  3. const record = await activityService.getRecord(getApp().activity._id);
  4. if (record) { // 如果已经领奖
  5. this.tip('亲,已经领取奖品,进店看看');
  6. this.setData({
  7. showButton:false,
  8. prizeSuccess:true
  9. })
  10. }else{
  11. this.tip('亲,您已经收藏了所有商品,获得一次抽奖机会;');
  12. const result = await activityService.probability(getApp().activity.extra.prize)
  13. this.setData({
  14. showPrizeButton:true,
  15. showButton:false,
  16. prize:result.data // 奖品信息
  17. })
  18. }
  19. },
  20. onStart() {
  21. setTimeout(() => {
  22. this.setData({
  23. awardImg: 'https://gw.alicdn.com/tfs/TB1JsqGbHPpK1RjSZFFXXa5PpXa-289-298.png',
  24. awardName: this.data.prize.name
  25. })
  26. this.gift(this.data.prize.name);
  27. }, 2000);
  28. },
  29. async onFinish() {
  30. this.setData({
  31. showButton:false,
  32. prizeSuccess:true
  33. })
  34. const param = {
  35. activity_id: getApp().activity._id, //当前的活动id
  36. prize: this.data.prize,
  37. extra: { isAllCollect: true }
  38. }
  39. const result = await activityService.provide(param);
  40. if (result.success) { // 发奖成功
  41. this.tip('获得了奖品:'+this.data.prize.name) // 提交获奖
  42. } else {
  43. this.tip(result.msg || result.code) // 失败
  44. }
  45. this.gift();
  46. },

手淘端常用函数介绍

抽奖函数:

 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
        })
}