前言

小程序发布以来,凭借无需安装、用完即走、触手可及、无需注册、无需登录、以及社交裂变等多个优势,一路高歌,变得愈来愈火爆,它革命性的降低了移动应用的开发成本,也正好迎合了用户的使用应用的习惯。小程序魅力如此之大,作为一枚程序猿,我想怎么不自己做一个呢?话不多说,咱撸起袖子就是干

准备工作

  • 前端开发利器:VSCode
  • 调试:微信开发者工具
  • 自己Mock的一些数据
  • 微信开发文档

项目介绍:小米商城实战

项目目录结构

├── assets                   用到的一些图标文件
├── lib├── weui.wxss            引用了weui
├── modules                    ├── showDetail.js        跳转展示商品详情的公共js文件  ├── showcDetail.js
├── pages                    项目的各个页面├── index                商城首页├── categories           商品分类页├── discovery            发现页├── channel              商品频道目录├── phone            手机频道├── tv               电视频道├── computer         电脑频道├── cart                 购物车├── mine                 个人信息页├── goods                商品详情页├── selectGoods          商品属性选择页├── search               商品搜索页├── addr                 收货地址页
├── template                 使用到的模版文件               ├── slide                轮播图模版  ├── goods_list           商品展示模版├── cover                商品展示模版
├── util                     使用到的工具类               ├── mock.js              项目中使用到的一些数据
├── app.js                   项目逻辑
├── app.wxss                 项目公共样式表
└── app.json                 项目公共设置

功能的展示与实现

一、商城首页

页面结构分析:

  • 顶部搜索条

这里看上去是一个搜索框,但其实,它要实现的仅仅是个页面跳转功能,只要把它的disabled设置为true就可以了,另外要想让它placeholder占位符居中显示的话,微信小程序提供了一个placeholder-class的属性,通过它可以改变placeholder的样式。

  • 轮播图区域

这里微信小程序给我们提供了swiper组件,直接用就可以了。但是轮播图在各个页面都可能存在,只是其中所显示的图片不一样而已,所以使用组件化思想,把它写成一个模版,哪里要使用,就引入这个模版即可。

<template name="slide"><view class="section section-swiper"><swiper class="slide" indicator-dots="{{true}}" autoplay="{{true}}" interval="2000" duration="1000"><block wx:for="{{slides}}" wx:key="{{index}}"><swiper-item><image src="{{item.slide_url}}" mode="widthFix" class="slide-image" data-id="{{item.id}}" /></swiper-item></block></swiper></view>
</template>

使用时,这样引入

<import src="../../../templates/slide/slide.wxml" />
<view class="container"><template is="slide" data="{{slides}}"></template>
</view>
  • 商城导航区、活动区

这里只是个简单的布局,就不赘述了。但是需要注意的是在微信小程序里,强烈推荐使用弹性布局

  • 首页商品展示区

这里的商品都是分块展示,很有规律,因此整个商品展示都可以直接用wx:for遍历出来。
wxml:

<!-- 首页商品版块 --><view class="section block"><block wx:for="{{index_block}}" wx:key="{{item.id}}"><view class="section cover"><image class="cover-img" src="{{item.img_url}}" data-cid="{{item.id}}" bindtap="showcDetail"/></view><view class="section goods-list"><block wx:for="{{item.section}}" wx:key="index" wx:for-item="product"><view class="goods-item"><image class="goods-img {{product.is_new?'new':''}} {{product.on_sale?'on-sale':''}}" src="{{product.goods_cover}}" data-pid="{{product.id}}" mode="aspectFill" bindtap="showDetail"/><text class="title">{{product.header}}</text><text class="desp">{{product.description}}</text><text class="meta">{{product.meta}}</text><text class="discount">{{product.discount}}</text></view></block></view></block></view><!-- end-section block -->

这里有个细节,每个版块里的商品会分成“新品”、“立减”(即有折扣)、“无折扣”三种,着该怎么去做呢?这里我用了一个巧妙的方法:给每个商品的class里绑定布尔值is_newon_sale通过三元运算符判断是否给该商品挂载一个类名,再使用伪元素给该商品打上“新品”或“立减”的标签如下:

wxml:

<image class="goods-img {{product.is_new?'new':''}} {{product.on_sale?'on-sale':''}}" src="{{product.goods_cover}}" data-pid="{{product.id}}" mode="aspectFill" bindtap="showDetail"/>

wxss

.goods-img.new:before{      /*新品标签样式*/position: absolute;left: 0;top: 0;width: 100rpx;height: 40rpx;line-height: 40rpx;content: "新品";color: #fff;font-size: 9pt;text-align: center;background: #8CC64A;
}
.goods-img.on-sale:before{   /*立减标签样式*/position: absolute;left: 0;top: 0;width: 100rpx;height: 40rpx;line-height: 40rpx;content: "立减";font-size: 9pt;color: #fff;text-align: center;background: #ec6055;
}

逻辑分析:
首页只是些商品,所以逻辑层只要根据每个商品的id来跳到对应商品的详情页即可,很显然这个方法在多个页面都要使用的,所以使用模块化思想,创建一个modules文件夹,把方法写在单独的js文件里,并向外输出

const showDetail=(e)=>{const id=e.currentTarget.dataset.pid; //获取每个商品的idwx.navigateTo({url: `/pages/goods/show?id=${id}`})
};
export default showDetail;

哪里要使用,就用import引入

import showDetail from "../../modules/showDetail";

二、商品分类页


页面结构分析:
商品分类页分为左侧的商品分类菜单和右边的商品分类展示区,
用两个scroll-view就可以了,左右两边都设置scroll-y让它们垂直方向滚动,此外,scroll-view还有一个scroll-into-view属性能让我们实现类似a标签的锚点功能,scroll-into-view的值是某个子元素的id,但是此处有一个小坑这个id不能以数字开头

当时查了一下文档就开做了,于是乎给左侧菜单取了些数字id,现在想起来当时我太自以为然了 ,此外如果内容太多,是会产生滚动条的,如图:

这样看起来也太丑了。。

**解决办法:给全局样式加入下面的样式

//隐藏滚动条
::-webkit-scrollbar{  height: 0;width: 0;color: transparent;
}

嗯,beautiful !!

商品分类功能

逻辑分析:给页面注册个curIndex(当前选中菜单的下标),如果当前下标和选中的菜单下标相同,则处于激活状态
部分代码:
wxml:

<view class="main"><scroll-view scroll-y class="category-left"><view class="cate-nav-list" wx:for="{{cate_nav_list}}" wx:key="{{item.id}}" data-id="{{item.id}}" data-index="{{index}}"bindtap="switchCategory"><text class="cate-name {{curIndex===index?'on':''}}">{{item.name}}</text></view></scroll-view><scroll-view class="category-right" scroll-y="{{true}}" scroll-into-view="{{toView}}" scroll-with-animation="true"><view class="cate-content"><view class="cate-list-content" wx:for="{{detail}}" wx:key="{{item.id}}" id="{{item.id}}"><view class="banner"><image src="{{item.banner}}"/></view><view class="header">{{item.cate_name}}</view><view class="cate-list"><view class="cate-item"  wx:for="{{item.cate_list}}" wx:key="{{index}}" wx:for-item="cateList"><image src="{{cateList.item_img}}" /><text>{{cateList.item_name}}</text></view></view></view></view></scroll-view>
</view>

js:

const app=getApp();Page({/*** 页面的初始数据*/data: {cate_nav_list:[{name:"新品",id:"new"},{name:"手机",id:"phone"},{name:"电视",id:"tv"},{name:"电脑",id:"laptop"},{name:"家电",id:"appliance"},{name:"路由",id:"router"},{name:"智能",id:"smart"},{name:"儿童",id:"kids"},{name:"灯具",id:"lignts"},{name:"电源",id:"adapter"},{name:"耳机",id:"headset"},{name:"音箱",id:"voicebox"},{name:"生活",id:"life"},{name:"服务",id:"service"},{name:"米粉卡",id:"card"}],curIndex:0,  //初始化当前下标为0toView:"new", //默认显示“新品展示”detail:[]},switchCategory(e){const curIndex=e.currentTarget.dataset.index?e.currentTarget.dataset.index:0;  //获取每个菜单的id//更新数据this.setData({toView:e.currentTarget.dataset.id,curIndex});},onLoad: function (options) {const detail=app.globalData.category; //获取分类展示数据this.setData({detail});}
})

三、发现页

页面结构分析:

里面展示了一些商品宣传视频(当时还是不太想切太多的页面?)这里用弹性布局+video组件就搞定了。这里是文档

四、商品详情页


页面结构分析:
商品详情顶部由一个swiper组成,中间部分由一些cells组成(weui)点击这里快速查看
底部是商品的概述及参数配置两部分组成,这个还是比较简单的, 给两个元素分别绑定一个布尔值来控制谁显示谁隐藏。

当然,要使用weui必须引入它的样式文件,我们在app.wxss里引入,然后全局都可以使用了

@import "./lib/weui.wxss";

嗯,weui的官网和github地址自然少不了。weui官网 、weui github官方文档,在github上阅读代码是一个非常有效的学习方式,但是文件夹嵌套太深找个文件也不容易,这里奉上github阅读神器

使用到的weui:
请容许我贴一段weui抄来的结构

<view class="weui-cells"><view class="weui-cell"><view class="weui-cell__bd"><view class="title">{{goods.header}}</view><view class="desp">{{goods.description}}</view><view class="meta">{{goods.meta}}</view></view></view>
</view>

商品详情页的显示

逻辑分析:
每个商品通过id来跳转到相应的详情页,但是这个id会在哪里呢因为先这个详情页是通过一个个商品来打开的,所以在商品详情页加载时,可以在 onLoad 中获取打开当前页面所调用的 query 参数(是条json数据),因为在showDetail里只用了id来跳转,所以options只有id属性

onLoad: function (options) {console.log(options); //{id:"4"}const id=options.id; //获取options里的idconst goods=app.globalData.goodsDetail.filter(item=>{return item.id==id;  //通过id来筛选显示对应的商品});this.setData({goods:goods[0] //因为id是唯一的,所以上面筛选出来的数组只有一条数据,这条数据就是要显示的商品数据});}

商品图片预览实现

微信小程序为我们提供了wx.previewImage()方法来实现图片的预览,实现方法如下:

previewImage(e){const index=e.currentTarget.dataset.index;    //获取swiper里的图片的下标const slide=this.data.goods.goods_slides; //获取商品轮播图const imgList=[]; //定义一个数组来存放轮播图的urlslide.map(item=>{imgList.push(item.slide_url); //用js的map方法把图片的url地址取出来放到数组里});wx.previewImage({current: imgList[index], // 当前显示图片的链接,不填则默认为 urls 的第一张urls: imgList})}

五、商品属性选择


页面结构:整个商品属性页的结构是由一些cells组成,所以用weui最合适(插一段:原生的radio真的是很丑,不过weui对它做了改进)
一开始想有weui就简单多了,直接用form表单的bindsubmit处理就可以了,奈何在weui里使用bindsubmit,取不到radio的值,折腾了很久,还是取不到,但是换成原生的radio竟可以取到!!

这个不行,那就换一种吧,用weui radiogroup里的bindchange事件吧,虽然繁琐了些,但是还是能解决问题的。。
话不多说,看代码(感觉写了一些很累赘的代码。。)

selectVersion(e) {const version = e.detail.value;const memory = version.split(",")[0];const price = version.split(",")[1];wx.setStorageSync('memory', memory);wx.setStorageSync('price', price);this.setData({memory,price});},selectColor(e) {let color = e.detail.value;let cover_img=this.data.goods_attrSelect[0].goods_slides[0].slide_url;wx.setStorageSync('color', color);wx.setStorageSync('cover', cover_img);this.setData({color,cover_img});},colorHasSelected(e) {const curcIndex = e.currentTarget.dataset.index ? e.currentTarget.dataset.index : 0;console.log(curcIndex);this.setData({curcIndex});},versionHasSelected(e) {const curvIndex = e.currentTarget.dataset.index ? e.currentTarget.dataset.index : 0;console.log(curvIndex);this.setData({curvIndex});}

对应商品的属性页的跳转

逻辑分析:在商品详情页中,通过当前商品的id跳转

toSelect(e){const id=e.currentTarget.dataset.id;wx.navigateTo({url:`../selectGoods/selectGoods?id=${id}`});}

在商品的属性选择页里,同样是通过id筛选显示不同商品的属性选择信息

onLoad: function (options) {const id = options.id;console.log(id);const goods_attrSelect = app.globalData.goodsDetail.filter(item => {return item.id == id;});
}

商品属性的联动选择

既然是商品,没有属性选择怎么行呢?兴致一来二话不说就是写。但是这里有一个难点:用户的选择多样性,难道你会把所有用户可能做出的选择都列出来吗?天哪,商品这么多,这是要累死我吗?no,no,no,这种方法不可取。哪还有什么解决办法呢?当时一想到的就是用数据缓存,嗯,这种方法可行,但是用缓存存取用户选择的值的话,这里有一个很大的问题:微信小程序里的数据缓存将data存储在本地缓存中指定的key中,会覆盖掉原来该key对应的内容,无论是wx.setStorage(异步接口)还是wx.setStorageSync(同步接口),这样的话,无论用户选择了多少个商品,只要key值一样,缓存数据永远只有一条!!!我此时的内心是痛苦的。

有什么方法能让数据缓存不覆盖原来的值,而是加上去?说实话,这里卡了我好久。废话不多说,直接上代码

submit(e) {const pre_item = wx.getStorageSync('attr_item');const temp = {'goods_name': wx.getStorageSync('goods_name'),'memory': wx.getStorageSync('memory'),'price': wx.getStorageSync('price'),'color': wx.getStorageSync('color'),'select_num': wx.getStorageSync('select_num'),'cover': wx.getStorageSync('cover'),'selected': false,'isTouchMove': false}wx.setStorageSync('attr_item', [temp, ...pre_item]); //把获取到的pre_item和temp的数据缓存存入attr_itemwx.showToast({title: '已加入购物车',icon: 'success',duration: 3000,success() {setTimeout(() => {wx.navigateBack({url: "../goods/show"});}, 1000)}});}

商品数量的加减操作

实现思路:分别绑定一个事件来操作商品数量的加减,最后将用户选择的数量设置到数据缓存里。
实现代码:

 data: {select_num: 1  //默认选择为数量为1},
minusCount(e) {    //用户减操作let select_num = this.data.select_num;select_num--;if (select_num < 1) {return;}this.setData({select_num});wx.setStorageSync('select_num', select_num);},addCount(e) {     //用户加操作let select_num = this.data.select_num;select_num++;if (select_num > 5) {return;}this.setData({select_num});wx.setStorageSync('select_num', select_num);}

六、购物车操作


页面结构分析:购物车列表用一个弹性布局就搞定。底部的操作栏用固定定位 + 弹性布局就搞定。

<view wx:if="{{cart_list==''}}"><view class="empty-cart"><view class="cart-icon"><image src="../../assets/icons/cart_empty.png" mode="aspectFill" /></view><view class="prompt">购物车还是空的</view><button type="warn" class="btn-warn" style="background: #ff6700;" bindtap="goIndex">到小米商城逛逛</button></view>
</view>
<view wx:else><view class="cart-box"><view class="cart-list" wx:for="{{cart_list}}" wx:key="{{index}}"><view class="cart-item {{item.isTouchMove? 'touch-move-active': ''}}" bindtouchstart="touchstart" bindtouchmove="touchmove" data-index="{{index}}"><view class="cart-content"><icon type="{{item.selected?'success':'circle'}}" class="" color="#ff6700" size="20" bindtap="selectList" data-index="{{index}}"/><image src="{{item.cover}}"mode="aspectFill" /><text class="item-title">{{item.goods_name}} {{item.memory}}</text><text class="item-num">{{item.color}}</text><text class="item-price">{{item.select_num}}×</text><text class="item-price">{{item.price}}</text><view class="del-cart-item" catchtap="delCartItem">删除</view></view></view></view></view><view class="user-operation"><view class="select-all"><icon wx:if="{{selectAllStatus}}" type="success" class="total-select" color="#ff6700" bindtap="selectAll" /><icon wx:else type="circle" class="total-select" color="#ff6700" size="20" bindtap="selectAll" /><text>全选</text></view><view class="total-price">合计:<text>{{totalPrice}}元</text></view><view class="btn-primary pay" bindtap="checkOut">结算</view></view>
</view>

底部操作栏样式

.user-operation{width: 100%;height: 100rpx;line-height: 100rpx;display: flex;flex-direction: row;justify-content: space-between;align-items: center;position: fixed;left: 0;bottom: 0;
}
.select-all,.total-price,.btn-primary.pay{flex: 1;  //三个盒子等分所有设备宽度font-size: 12pt;text-align: center;
}

加入购物车操作

逻辑分析:之前解决数据缓存问题,是为加入购物车功能做铺垫。在商品属性的级联选择中,已经获得了用户的所有要加入购物车的数据,这时候,把数据取出来在绑定到购物车页面上就可以了。

实现代码:

data: {cart_list: [],   //初始化一个空数组用来存放购物车列表},goIndex() {  //如果购物车为空,则让用户去首页wx.switchTab({url: "../index/index"})},
onShow: function () {const attr_item = wx.getStorageSync('attr_item'); //获取数据缓存里将要加入购物车的数据let cart_list = this.data.cart_list;cart_list = [...attr_item]; //把缓存里的数据加到购物车列表里const select_num = cart_list.map(item => { //获取用户每次选择的数量return item.select_num;})let goods_sum=select_num.reduce(function(prev,cur){return prev+cur;  //用es6的reduce()方法把用户每次选择的数量相加});wx.setStorageSync('goods_sum', goods_sum);  //再次存入缓存this.setData({   //更新购物车列表cart_list});}

购物车全选、反选、计算总价功能

这是一个很经典的问题,凡事涉及到购物车的操作,这个功能都是少不了的。

data: {cart_list: [],totalPrice: 0,},
selectList(e) {let selectAllStatus = this.data.selectAllStatus;const index = e.currentTarget.dataset.index;let cart_list = this.data.cart_list;// console.log(cart_list[index].selected);const selected = cart_list[index].selected;cart_list[index].selected = !selected;console.log(selected);//购物车列表里的条目只要有一个取消,全选就取消const symbol = cart_list.some(cart => {  //这里用es6的some()函数return cart.selected === false;});if (symbol) {  //如果找到false,全选就取消this.data.selectAllStatus = false;} else {this.data.selectAllStatus = true;}this.setData({  //更新数据cart_list,selectAllStatus: this.data.selectAllStatus});this.getTotalPrice();},getTotalPrice() {  //定义一个计算总价的方法let cart_list = this.data.cart_list;let totalPrice = 0;for (let i = 0; i < cart_list.length; i++) {if (cart_list[i].selected) {totalPrice += parseInt(cart_list[i].select_num) * parseInt(cart_list[i].price);  //注意这里要用parseInt()把数量和价格取出来}}//更新总价this.setData({totalPrice});},selectAll(e) {let selectAllStatus = this.data.selectAllStatus;selectAllStatus = !selectAllStatus;let cart_list = this.data.cart_list;for (let i = 0; i < cart_list.length; i++) {cart_list[i].selected = selectAllStatus; //全选为true,则所有购物车列表为true,全选为false,则所有购物车列表为false}//更新数据this.setData({cart_list,selectAllStatus});this.getTotalPrice();}

删除购物车操作

data: {startX: 0, //开始坐标startY: 0,},
//滑动事件处理touchmove(e) {letindex = e.currentTarget.dataset.index, //获取当前索引startX = this.data.startX, //获取开始X坐标startY = this.data.startY, //获取开始Y坐标touchMoveX = e.changedTouches[0].clientX, //滑动变化坐标touchMoveY = e.changedTouches[0].clientY, //滑动变化坐标//获取滑动角度angle = this.getAngle({X: startX,Y: startY}, {X: touchMoveX,Y: touchMoveY});this.data.cart_list.forEach(function (v, i) {v.isTouchMove = falseif (Math.abs(angle) > 30) return;//用户滑动超过30度,删除按钮就不出来 if (i == index) {if (touchMoveX > startX) //右滑v.isTouchMove = falseelse //左滑v.isTouchMove = true}})//更新数据this.setData({cart_list: this.data.cart_list})},getAngle(start, end) {let X = end.X - start.X,Y = end.Y - start.Y//返回角度 /Math.atan()返回数字的反正切值return 360 * Math.atan(Y / X) / (2 * Math.PI);},delCartItem(e) {const index=e.currentTarget.dataset.index;  //获取购物车要删除商品的下标this.data.cart_list.splice(index, 1);wx.clearStorageSync("select_num");this.setData({cart_list: this.data.cart_list});}

七、商品的匹配及搜索功能实现


页面结构分析:先把搜索提示框固定定位到搜索栏下方,如果搜索到商品,则用商品展示模版输出该数据

<import src="../../templates/goods_list/goods_list.wxml" />
<view class="weui-search-bar"><view class="weui-search-bar__form"><view class="weui-search-bar__box"><icon class="weui-icon-search_in-box" type="search" size="14"></icon><input type="text" class="weui-search-bar__input" placeholder="搜索" placeholder-class="plac" bindinput="searchInput" /></view></view><view class="weui-search-bar__cancel-btn" bindtap="search">搜索</view>
</view>
<view class="search-list {{is_hidden?'hidden':''}}"><block wx:for="{{search_list}}" wx:key="{{item.id}}"><text class="search-item" bindtap="showItemDetail" data-header="{{item.header}}">{{item.header}}</text></block>
</view>
<template is="goods_list" data="{{goods_list}}"></template>

逻辑分析:
我的实现思路是:

  • 如果匹配到商品,搜索框下方就要让搜索提示框显示;
  • 当用户输入搜索的内容为空,搜索提示框隐藏
  • 用户点击搜索按钮,则把所有匹配到的商品列表显示出来,注意要模糊搜索不区分大小写,提高用户体验;
  • 用户点击匹配到的商品条目,则搜索该商品

实现方法:

  • filter()+indexOf()+toLowerCase();

代码如下:

import showDetail from "../../modules/showDetail";
const app=getApp();Page({/*** 页面的初始数据*/data: {goods_list:[],search_list:[],is_hidden:true},searchInput(e){let search_list=this.getList(e.detail.value); //获取用户的输入值if(e.detail.value==""){ //如果用户没输入,搜索提示列表置空,并且让搜索提示框隐藏search_list=[]; this.data.is_hidden=true;}else{this.data.is_hidden=false;} //更新数据this.setData({search_list,is_hidden:this.data.is_hidden});},search(e){//按关键字筛选商品,如果关键字匹配到商品名称,则返回该商品列表const keywords=wx.getStorageSync('keywords');wx.showLoading({title: '请稍等',});setTimeout(()=>{this.setData({goods_list:this.getList(keywords),is_hidden:true  //如果搜索到了商品,就让搜索框隐藏});wx.hideLoading();},500);},showDetail,showItemDetail(e){//按关键字筛选商品,如果关键字匹配到商品名称,则返回该商品列表const header=e.currentTarget.dataset.header.toLowerCase(); console.log(header);wx.showLoading({title: '请稍等',})setTimeout(()=>{wx.hideLoading()this.setData({goods_list:this.getList(header),is_hidden:true});},500)},/*** attr:需要匹配筛选的数据*/getList(attr){  //定义一个获取商品标题的方法return app.globalData.phone.goods_list.filter(item=>{return item.header.toString().toLowerCase().indexOf(attr)>-1;});}
})

八、收货地址页


实现思路:用数据缓存和数据绑定来控制input的disabled实现实时编辑
代码如下:

// pages/address/address.js
Page({data: {receiverName: "",mobile: "",addressDetail: "",postCode: "",isDisabled: false,isComplete: false,buttonTitle: "保存"},formSubmit(e) {const addrInfo = e.detail.value;let {receiverName,mobile,addressDetail,postCode}=addrInfo; //把data里的数据结构出来if(receiverName==""||mobile==""||addressDetail==""||postCode==""){this.data.isComplete=false;wx.showModal({title:"提示",content:"请完善信息",showCancel:false}); }else if(!/^[1][3,4,5,7,8]\d{9}$/.test(mobile)){ //判断手机号格式wx.showModal({title:"提示",content:"手机号格式不规范",showCancel:false}); }else if(!/^[0-9]{6}$/.test(postCode)){wx.showModal({title:"提示",content:"邮政编码不规范",showCancel:false}); }else{this.data.isComplete=true;wx.setStorageSync('addrInfo', addrInfo);}this.setData({isDisabled: true,isComplete: this.data.isComplete});},updateAddr(e){this.setData({isDisabled:false,isComplete:false,buttonTitle: "保存"});},/*** 生命周期函数--监听页面加载*/onLoad: function (options) {let addrInfo=wx.getStorageSync("addrInfo");console.log(addrInfo);let {receiverName,mobile,addressDetail,postCode}=addrInfo;this.setData({receiverName,mobile,addressDetail,postCode,isDisabled: true,isComplete:true,buttonTitle: "修改"});}
})

总结:

在做这个项目的时候遇到了很多问题,中途因为一个问题都要卡很久,但是现在想想,经历过肯定是能学到东西的,之前对于一些东西想都不敢想,但是没关系,只要你想做,那就去做吧,去网上查资料,多去社区问问大牛,你总是能学到东西的。对于这个项目,很多功能都还没完善,比如商品评论,生成订单等等,但是我会不定时的更新?,有时间的话,我会用mpvue或者wepy改写,又能学到新技术?

最后

码字不易,如果大家喜欢的话,就star一下吧,项目地址,我会不定时更新的哦。
项目还存在很多缺陷,希望大家提出宝贵建议?

小爱童鞋@你,一起来撸个小程序吧相关推荐

  1. 小程序工程化实践(上篇)-- 手把手教你撸一个小程序 webpack 插件,一个例子带你熟悉 webpack 工作流程...

    本文基于 webpack 4 和 babel 7,Mac OS,VS Code 小程序开发现状: 小程序开发者工具不好用,官方对 npm 的支持有限,缺少对 webpack, babel 等前端常用工 ...

  2. 给ofo共享单车撸一个微信小程序

    想学一下微信小程序,发现文档这东西,干看真没啥意思.所以打算自己先动手撸一个.摩拜单车有自己的小程序,基本功能都有,方便又小巧,甚是喜爱.于是我就萌生了一个给ofo共享单车撸一个小程序(不知道为啥of ...

  3. 微信小程序学习-app.json分析

    使用app.json文件来对微信小程序进行全局配置,包括页面文件的路径.窗口表现,tab菜单,设置网络超时时间等: 以下是一个简单配置: {"pages": ["page ...

  4. 微信小程序联盟:官方文档+精品教程+demo集合(未完待续,持续更新中……)

    1:官方工具:https://mp.weixin.qq.com/debug/w ... tml?t=1476434678461 2:简易教程:https://mp.weixin.qq.com/debu ...

  5. 我收集的小程序干货汇总-前后台都有,不用谢!

    小程序开发资源! 「 起因 」 最近小程序生态基本已经趋于稳定,而且各大.小.创业公司都已经全面转化到小程序产品线,很多童鞋的公司主产品线都已经被小程序占领,不得不说现在 web前端和后端又要多一门基 ...

  6. 微信小程序联盟:官方文档+精品教程+demo集合(5月31日更新,持续更新中……)...

    2019独角兽企业重金招聘Python工程师标准>>> 微信小程序联盟 1:官方工具: https://mp.weixin.qq.com/debug/w ... tml?t=1476 ...

  7. 微信小程序导航:官方文档+精品教程+demo集合(5月31日更新)

    官方文档 1:官方工具:https://mp.weixin.qq.com/debug/w - tml?t=1476434678461 2:简易教程:https://mp.weixin.qq.com/d ...

  8. 微信小程序自动化测试的研究过程

    [复制自:原文链接:https://www.cnblogs.com/Test-xiaobai/p/9066331.html ] [注:文中提到的Xtest已下线] 山雨欲来风满楼,最近微信小程序相关开 ...

  9. 【干货】微信小程序之自动化技术

    使用XTest录制从体验上确实简单便捷,简单到不用插线不用PC,可以躺着录走着录,即使撩妹都不耽误测试,跟平时操作App无异.对比早期录制脚本又抓控件又摸路径受的罪,幸福感大增.录制很容易上手,就是在 ...

最新文章

  1. nginx和pcre错误问题
  2. wxpython多线程 假死_wxpython中利用线程防止假死的实现方法
  3. python box2d模拟平抛运动_论述如何基于Box2D模拟星球重力效果
  4. 西安交大送大一新生这本书,你读过吗?12本有趣有料的科普书盘点
  5. VS2010 转VS2008
  6. Cisco Nexus-1000v授权
  7. react-native 自定义view向js暴露接口方法
  8. 伍德里奇计量经济学第四章计算机答案,计量经济学中文答案 伍德里奇
  9. 【OpenCV 例程200篇】201. 图像的颜色空间转换
  10. java中将url下载并转换为MultipartFile文件
  11. 手机浏览器一键跳转微信加好友的方法
  12. 微信、企业微信分享按钮隐藏问题
  13. 【机器学习算法笔记】5. 自组织映射SOM
  14. 华为21级程序员月薪27万,你怎么看?
  15. Bootstrap登录页面带验证码
  16. Android闹钟制作过程图,小学闹钟手工制作步骤详解(配图)
  17. JAVA面向对象编程(2)
  18. 结合泛函极值_(二) 泛函的极值
  19. 迪博·中国上市公司内部控制指数(2000-2020年)
  20. Android service几个问题

热门文章

  1. JS函数的定义与调用方法
  2. MS UC 2013-0-虚拟机-标准化-部署-2-模板机-制作-2-设置-虚拟机
  3. 西卡 你要浮出水面啦
  4. Oracle认证成功获取方案
  5. Lua脚本语言快速入门手册
  6. 前端:JS中JSON对象和String转换
  7. mysql-master/slave同步问题:Slave_IO_Running: No
  8. 利用单片机快速实现家庭智能控制平台
  9. Linux bash 漏洞修补
  10. 修改ubuntu崩溃转储的设置