微信小程序实战练习(仿五洲到家微信版) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
bili
V2EX    微信

微信小程序实战练习(仿五洲到家微信版)

  •  
  •   bili 2017-08-28 17:07:40 +08:00 2786 次点击
    这是一个创建于 3038 天前的主题,其中的信息可能已经有所发展或是发生改变。

    微信程序实战

    github 地址(欢迎 star ): https://github.com/xiaobinwu/dj

    版本:0.17.172600 ( wxss 背景图设置会不成功)

    图片描述图片描述

    目录结构

    图片描述

    • css => 放置公用 wxss,目前只有一个 font.wcss
    • image => 静态资源目录
    • lib => 第三方库(如:qqmap-wx-jssdk.min.js )
    • pages => 小程序页面(包括四个文件,.wxml/.wxss/.js/.json )
    • template => 抽离出来的 template,具有复用性
    • utils => 工具类
    • app.js/app.json/app.wxss => 配置

    踩过的坑

    1. promise 封装

    官方 request 代码:

    wx.request({ url: 'test.php', //仅为示例,并非真实的接口地址 data: { x: '' , y: '' }, header: { 'content-type': 'application/json' }, success: function(res) { console.log(res.data) } }) 

    但是有很多场景需要 promise 化的,所以使用第三方 promise 库( es6-promise.min.js ),对 request 进行了一层包装:

    /* utils/util.js */ /* api 接口 promise 柯里化*/ var Promise = require('../lib/es6-promise.min.js'); function wxPromisify(fn, scope) { return function (obj = {}) { return new Promise((resolve, reject) => { obj.success = function (res) { resolve(res); } obj.fail = function (res) { reject(res); } if(scope){ //改变 this 指向 var newFn = fn.bind(scope); newFn(obj); }else{ fn(obj); } }) } } /* request 封装*/ var wxrequest = wxPromisify(wx.request); function wxRequest(options, tokenNotRequired){ return wxrequest(options).then(res => { var data = res.data; if(data.status === 404404) { if(tokenNotRequired){ delete options.headers; return wxRequest(options); }else{ return updateToken().then(token => { return wxRequest(object.assignIn(options, { headers: { 'X-Auth-Token': token } })); }); } }else { return Promise.resolve(data); } }).catch(err => { return Promise.reject(err); }); } 

    2. Javascript 作用域问题

    由于小程序默认给的微信地图 api 有些需求达不到要求,于是使用第三方库( qqmap-wx-jssdk.min.js ,这是绝配),这样定位功能也比较好做,以及后续要做的地址管理模块也比较好下手,但是有个问题,对微信地图 jdk 接口进行 promise 化后,使用过程会报错,导致定位失败,所以需要改变其执行作用,于是对 wxPromisify()方法做了些改造,重新绑定作用域至 qqmapsdk,调用如下:

    //address.js // 引入 SDK 核心类 var QQMapWX = require('../lib/qqmap-wx-jssdk.min.js'); // 实例化 API 核心类(需要配置安全域名 https://apis.map.qq.com) var qqmapsdk = new QQMapWX({ key: 'xxxxx' //需要到腾地图上申请 key }); ... ... // 请求用户授权定位 //逆地址解析 var ReverseGeocoder = util.wxPromisify(qqmapsdk.reverseGeocoder, qqmapsdk); //需改变作用域 

    对于小程序是需要配置对应的安全域名的,这样才能执行 request

    3. 如何衍生出组件模板

    模板页( template )没有天生配对 js,但是也可以实现,实现面向对象的思想,对模板所需要的 js 进行一层类的封装,保证构造函数需要接受父页面的上下文对象,然后可以把声明好的类方法绑定到父页面上面去,对于模板页 js 方法,以_FUN()方式命名。下面是为图片懒加载优化而做的 swiper 模板组件,可以参考一下。

    /** * 图片预加载组件 * * @author xiaobin_wu * template/silder/silder.js */ class Slider { constructor(pageContext, optiOns= { picList: [], showArr:[] }){ this.page = pageContext; //获取页面上下文 this.page.data.slider = { picList: options.picList, showArr: options.showArr }; //初始化 data this.page._sliderChange = this._sliderChange.bind(this); } //监听滑动事件,实现图片懒加载 _sliderChange(e){ if(this.page.data.slider.showArr){ let showArr = this.page.data.slider.showArr; for(let i = 0; i < showArr.length; i++){ if(i === e.detail.current){ showArr[i] = true; } } this.page.setData({ 'slider.showArr': showArr }); } } initData(imgs){ const arr = new Array(imgs.length).fill(false); this.page.setData({ 'slider.picList': imgs, 'slider.showArr': arr.fill(true, 0 , 1) }); } } module.exports = Slider 

    以类形式 module.exports 出去,Page 页面,以var Slider = require('../../template/slider/slider.js');形式引入,然后new操作,模板 wxml 也参考template/silder/silder.wxml,也可以对应写 wxss,这样做模板页复用性高,类似组件的模式。

    4. scroll-view 使用 scroll-x 失效问题

    刚开始使用 scroll-view,scroll-x 一直失效,不能水平 scroll,折腾了好多时间,结果这样就成了,大概如下结构( home.wxml ):

     <scroll-view scroll-x="{{true}}" scroll-left="{{scrollLeft}}" class="scroll-bar" style="width:100%;" > <view style="width: {{idxData.navbar.length * 168}}rpx"> <view wx:for="{{idxData.navbar}}" wx:for-item="cate" class="cate-item {{index == currentIndex ? 'active' : ''}}" data-id="{{cate.nav_id}}" data-index="{{index}}" bindtap="cateClick">{{cate.nav_name}}</view> </view> </scroll-view> 

    忽略其他乱起八糟的代码,主要是这个<view style="width: {{idxData.navbar.length * 168}}rpx">,需要保证 scroll-view 下面的 view 的 width 必须要大于 100%,充满整个 scroll-view

    4. swiper 高度无法自动撑开,暂时不支持

    图片描述 于是对于红线部分的产品分类 swiper,就只能手动计算 swiper 高度,来实现 swiper 的效果,但是由于对应每个 swiper-item 还会有个下拉加载,所以产品数目会一直变化,所以计算起来相当于耗性能,希望官方能尽快让 swiper 高度允许自动撑开

    5. template 模板

    template 模板,对象传递方式=>data={{a: x1,b: x2}},x1、x2 对应 data 绑定的变量

    6. setData 设置动态数据

    可能你会遇到这种情况(设置动态数据):

    this.setData({ 'array[0]': 1 }); /* 上面这样设置是没问题的,但是是动态的,那该怎么办?这样... */ this.setData({ 'array['+ index +']': 1 }); /* 很遗憾,无法怎么做 */ 

    解决办法,声明中间量,如下:

    /* utils/util.js */ //动态 setData function dynamicSetData(field, index, value, suffix, type='object'){ var param = {}; var string = field + '[' + index + ']' + (typeof suffix !== 'undefined' ? type === 'object' ? '.' + suffix : '[' + suffix + ']' : ''); param[string] = value; return param; } 

    这样最后就可以这样,this.setData(util.dynamicSetData('firstLoadDataFlag', index, true));,即可用于对象的改变,也可以用于数组的改变。

    7. 设置顶层标签 Page 的样式,处理安卓机的背景色问题

    8 px 与 rpx 之间转化

    对于小程序中,也有一些组件需要传递变量单位为 px 的,如果这个变量是需要计算出来的,但是我们使用的确是 rpx 单位,那么他们之间的转化比例是有必要知道的

    /* utils/util.js */ //获取 px 与 rpx 之间的比列 function getRpx(){ var winWidth = wx.getSystemInfoSync().windowWidth; return 750/winWidth; } 

    8. image 问题

    image 组件,其实对于 src 图片路径,是以背景图展示的,并不是真的类似 img,auto 是不生效的。

    9. wx.navigateBack 返回无法传参数通知

    wx.navigateBack 返回通知上一页执行指定函数的作用,可以使用 getCurrentPages()来获取上一页 page 对象,事先执行,如下:

    /* pages/order-detail/order-detail.js */ //返回执行上一个页面的函数,good navigateBackFun: function(){ var pages = getCurrentPages(); var prevPage = pages[pages.length - 2]; if(prevPage.__route__.indexOf("pages/order/order") != -1) { prevPage.actionCallback(this.data.btnAction,this.data.page); } } 

    10. class 可以多组操作

     <view class="status-item {{index == orderData.progress.last_index? 'active' : ''}} {{index === orderData.progress.info.length - 1 ? 'last-status-item' : ''}}"></view> 

    11. 对于字体文件的使用

    对于下面的字体文件的引用会导致报错,微信小程序似乎不支持怎么使用

    @font-face { font-family: 'Glyphicons Halflings'; src: url('/assets/fonts/glyphicons-halflings-regular.eot'); src: url('/assets/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('/assets/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('/assets/fonts/glyphicons-halflings-regular.woff') format('woff'), url('/assets/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('/assets/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); } 

    解决办法,将 ttf 文件拿出,转化成 base64,以 wxss 引入。base64 转化

    12. 购物车功能

    对于购车功能也是相当折腾的,通过在 app.js 定义全局变量:

    cartData:{ list:[], totalCount:1, totalPrice:0, // 起送价 floorPrice:0, // 总价达到此价免配送费 freeShipPrice:0, // 运费 deliveryFee:0, storeId:0, storeName:'' } 

    然后每次加减产品,清空购物车来操作 cartData 的变化,list 存储购物车产品数据,在首页和产品详情页,可以来获取购物车的数据,当然也会把购物车数据的商品 id 和门店 id 存储到 Storage,可以用来异步更新最新的购物车数据,在首页和产品详情页的来回切换,对于购物车需要时刻去检查,映射到对应分类的 swiper 产品的加减变化,这里有没有像 vue 中 vuex 的状态管理能对数据集中管理,(对于 vuex 的使用 点击),导致监听变化变得很复杂,有把加减部件 cart-ctrl 和购物车 cart 提取成 template 模板组件,结果处理起来,这里一万个省略号,很悲催!

    13. require 原理

    微信小程序类似浏览器一样,借助一个 HTML 页面来引用加载所有的 JS 文件,但是并不会马上去执行,代理服务部分代码又怎么两个全局函数 define 和 require,类似 amd,使用 define 函数对请求回来的 js 文件内容进行包装,但是不去执行,然后可以使用 require 函数按需递归式进行初始化,这个时候 js 就会执行了,并且只执行一次,那么对于 Page 页面为什么 js 能一开始不使用 require 函数就能加载呢?因为默认 page 页面 js 文件,会自动添加上 require 自己,加载后立即初始化。这样是不需要担心多次 require 会执行多次 js 的。 这篇文章写得很详细 微信小程序的 require 机制浅析,顺便贴一下几段代码(帮助理解):

    代理服务部分代码: (projectManager.js) function getScripts(projInfo, callback) { ... fs.readFile(fname, 'utf8', function(err, scripts) { .... scripts = 'define("' + moduleName + '", function(require, module, exports, ' + noBrowserStr + '){ ' + scripts + '\n});', needRequire && (scripts += 'require("' + moduleName + '")'), //page 页面 js 文件,会添加上 require 自己,加载后立即初始化。 ..... callback(null, scripts) //scripts 串内容作为 HTTP GET 的返回 
     var ...... moduleList = {}; define = function(moduleName, factory) { //define 是全局函数,每个 JS 文件都默认会调用. moduleList[moduleName] = { status: status1, factory: factory } }; 
    .... require = function(moduleName) { .... var module = moduleList[moduleName]; //define 函数调用时为 moduleList 赋的值 ..... if (module.status === status1) { //如果未初始化,则初始化 var factory = module.factory, //这个 factory 就是这个 JS 文件的脚本. obj = { exports: {} }, u = void 0; factory && (u = factory(o(moduleName), obj, obj.exports)), module.exports = obj.exports || u, module.status = status2 } return module.exports } 
    2 条回复    2017-08-28 18:09:56 +08:00
    q409195961
        1
    q409195961  
       2017-08-28 17:38:17 +08:00
    Star + Fork
    yangg
        2
    yangg  
       2017-08-28 18:09:56 +08:00
    setData({
    ["a" + i]: "val"
    }) // 不需要变量
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3410 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 38ms UTC 10:39 PVG 18:39 LAX 02:39 JFK 05:39
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86