前言
作为一个刚入门学前端的小白我来说,写这个小程序花了很多时间,遇到了很多问题,也学到了很多,非常感谢那些帮我解决问题的老师和同学。这个小程序主要实现的是购物车功能,有些许功能未得以实现,比如搜索功能,由于小米有品没有开源的api,搜索的很多数据需要自己来写,觉得这是一项巨大的工程,然后你懂得(就没写搜索功能了)。项目地址。希望能给初学者一些帮助。
开发前的准备
- Vscode代码编辑器和微信开发者工具(开发工具)
- Easy-mock(一个可视化,并且能快速生成 模拟数据 的持久化服务)
- Icon-font(阿里巴巴矢量图标库)
- 小米有品官网(图片来源)
- 微信官方文档
项目部分截图(gif)
项目主要页面及功能实现
首页
- 商品详情页功能实现
1.分享功能 利用button组件,把catchtap="shares"来点击分享实现分享给微信好友的弹框(同时出现遮罩层),在此之前要先给包容弹框的盒子设置一个遮罩层,点击页面的任何一个地方遮罩层消失。
<view class="share-mask {{active ? 'active' : ''}}" bindtap="hideMask">
<view class="shares">
<view class="share">分享</view>
<view class="share_content">
<view class="share_left">
<button catchtap="shares" class="shares-btn" open-type="share" hover-class="none">
<image class="shareImg" src="../../../images/wechat.png"/>
<text>发送给朋友</text>
</button>
</view>
<view class="share_right">
<image class="shareImg" src="../../../images/poster.png"/>
<text>生成图库</text>
</view>
</view>
<view class="cancel">取消</view>
</view>
</view>
// 分享
toShare() {
this.setData({
active: true
})
},
// 隐藏遮罩层
hideMask() {
this.setData({
active: false
})
},
// 分享给微信好友
onShareAppMessage (options) {
return {
title:this.data.detail[0].detailOne
}
}
分类页面
<view class="category-left">
<view wx:for="{{category}}" wx:key="{{item.id}}"
data-id="{{item.id}}" data-index="{{index}}"
class="cate-list {{ curIndex === index ? 'on' : '' }}" bindtap="switchTab" >{{item.name}}</view>
</view>
<scroll-view class="categroy-right" scroll-y scroll-into-view="{{toView}}" scroll-with-animation="{{true}}">
<view class="cate-box" id="{{detailList.id}}">
<view class="cate-banner">
<image src="{{detailList.banner}}"></image>
</view>
<view class="cate-title">
<text>{{detailList.cate}}</text>
</view>
<view class="product">
<view class="product-list" wx:for="{{detailList.detail}}" wx:key="index" wx:for-item="product">
<navigator url="#">
<image src="{{product.thumb}}" />
<view class="classname">
<text>{{product.name}}</text>
</view>
</navigator>
</view>
</view>
</view>
</scroll-view>
Page({
data: {
category:[],
detail: [],
detailList: {},
curIndex:0,
toView:"youpin",
},
switchTab(e){
let currentId = e.currentTarget.dataset.id
let currentIndex = e.currentTarget.dataset.index
this.setData({
detailList: this.data.detail[currentIndex],
curIndex: currentIndex
})
}
购物车页面
此页面简单的实现了点击首页的商品,购物车会显示相关加入的商品,以及加减、全选、取消全选(点击任意一个商品,全选消失)的功能。
selectAll() {
let selectAllStatus = this.data.selectAllStatus
selectAllStatus = !selectAllStatus
let carts = this.data.carts
for (let i = 0; i < carts.length; i++) {
carts[i].selected = selectAllStatus
}
this.setData({
selectAllStatus,
carts
})
this.getTotalPrice()
},
selectList (e) {
console.log(e.currentTarget.dataset.index)
let oIndex = e.currentTarget.dataset.index
let carts = this.data.carts
let test = carts[oIndex].selected
test = !test
let cartSelect = `carts[${oIndex}].selected`
this.setData({
[cartSelect]: test
})
if (this.data.carts.find(function(e) {return e.selected === false})) {
this.setData({
selectAllStatus: false
})
} else {
this.setData({
selectAllStatus: true
})
}
this.getTotalPrice()
},
getTotalPrice() {
let carts = this.data.carts
let total = 0
let totalNum = 0
for (let i = 0; i < carts.length; i++) {
if (carts[i].selected) {
total += carts[i].num * carts[i].price
totalNum += carts[i].num
}
}
this.setData({
totalPrice: total.toFixed(2),
totalNum
})
},
minusCount(e) {
// console.log(e)
const index = e.target.id
let carts = this.data.carts
// console.log(index,carts)
let num = carts[index].num
if (num <= 1) {
return
}
num = num - 1
carts[index].num = num
this.setData({
carts: carts
})
this.getTotalPrice()
},
addCount(e) {
const index = e.target.id
let carts = this.data.carts
let num = carts[index].num
num = num + 1
carts[index].num = num
this.setData({
carts: carts
})
this.getTotalPrice()
},
个人页面
该页面没怎么写功能,大部分都是静态的,就不过多介绍了。
页面传值
使用全局变量解决页面的传值问题
主要是点击商品详情页(goods.js)的加入购物车,购物车页面(cart.js)会显示被加入的商品,通过把数据存储在全局变量里来传值。
1.首先在app.js全局变量里定义一个carts[]数组
globalData: { cart:[] }
2.其次在goods.js页面和cart.js页面头部定义const app = getApp();
在goods.js的addCart()方法里app.globalData.cart = [...app.globalData.cart, this.data.detail]
用es6的解构方法,把从easy-mock里获取到的detail[]传到全局变量的cart[]里。这种写法与push()方法不同在于push()方法会修改数组里的值,而不是创建一个新的数组。
3.最后在cart.js的onShow生命周期里设置购物车的数据源,即获取goods.js在app.js里的cart[]
this.setData({
hasGoods: true,
carts: app.globalData.cart
})
点击首页的商品列表跳到相应的商品详情页
首页跳转到商品详情页时,通过每个商品的id来跳转
// pages/index/index.js
// 去到商品详情页
toLists:function(e) {
// console.log(e)
let id = e.currentTarget.dataset.id
wx.navigateTo({
url: `/pages/goods/goods/goods?id=${id}`
})
}
在商品详情页把商品的id赋值给商品的index来设置数据源
// pages/goods/goods.js
onLoad: function (options) {
const id = options.id
this.setData({
index: id
})
}
使用路径传参
如:/pages/goods/goods/goods?id=${id}
,这里应注意下,如果参数要传的值是一条对象的话,我们都知道小程序里是不支持传对象的,此时把值传过去的时候应该用JSON.stringify()把对象转为字符串,拿到值的时候应该用JSON.parse()把字符串转为对象。
页面其它功能
商品结算时的合计
onShow () {
let self = this
wx.request({
url:'https://www.easy-mock.com/mock/5d077b333780f05f8385d2c1/xiaoMi_youPin/goods_detail',
success(res){
console.log(res)
self.setData({
goods:res.data.data.goods
})
self.setData({
sum: self.data.goods[0].goodsPrice + self.data.goods[0].carriage - self.data.goods[0].discounts
})
}
})
}
这里的合计是写在onShow声明周期里的,我想说的是应该注意计算合计与请求接口数据会产生异步问题,
应该把计算合计另外放在self.setData({})里,并放在请求接口数据的self.setData({})下面,因为请求接口数据的时间大于计算合计的时间,必须要拿到数据后才能计算合计。这两个不能放在同一个self.setData({})里,否则得不到合计的值,如下代码是不正确的
self.setData({
goods:res.data.data.goods,
sum: self.data.goods[0].goodsPrice + self.data.goods[0].carriage - self.data.goods[0].discounts
})
收获地址管理
//pages/goods/addressList/addressList.js
Page({
data: {
addressList:[]
},
onLoad: function (options) {
var arr = wx.getStorageSync('addressList') || [];
console.info("缓存数据:" + arr);
// 更新数据
this.setData({
addressList: arr
});
},
onShow: function () {
this.onLoad();
},
addAddress:function(){
wx.navigateTo({
url: '../../goods/address/address'
});
},
/* 删除item */
delAddress: function (e) {
this.data.addressList.splice(e.target.id.substring(3), 1);
// 更新data数据对象
if (this.data.addressList.length > 0) {
this.setData({
addressList: this.data.addressList
})
wx.setStorageSync('addressList', this.data.addressList);
} else {
this.setData({
addressList: this.data.addressList
})
wx.setStorageSync('addressList', []);
}
}
})
//pages/goods/address/address.js
var area = require('../../../utils/area.js');
var areaInfo = []; //所有省市区县数据
var provinces = []; //省
var provinceNames = []; //省名称
var citys = []; //城市
var cityNames = []; //城市名称
var countys = []; //区县
var countyNames = []; //区县名称
var value = [0, 0, 0]; //数据位置下标
var addressList = null;
Page({
data: {
transportValues: ["收货时间不限", "周六日/节假日收货", "周一至周五收货"],
transportIndex: 0,
provinceIndex: 0, //省份
cityIndex: 0, //城市
countyIndex: 0, //区县
provinceNames: ["江西省","北京省"]
},
onShow: function() {
var that = this;
area.getAreaInfo(function(arr) {
areaInfo = arr;
//获取省份数据
that.getProvinceData();
});
},
// 获取省份数据
getProvinceData: function() {
var that = this;
var s;
provinces = [];
provinceNames = [];
var num = 0;
for (var i = 0; i < areaInfo.length; i++) {
s = areaInfo[i];
if (s.di == "00" && s.xian == "00") {
provinces[num] = s;
provinceNames[num] = s.name;
num++;
}
}
that.setData({
provinceNames: provinceNames
})
that.getCityArr();
that.getCountyInfo();
},
// 获取城市数据
getCityArr: function(count = 0) {
var c;
citys = [];
cityNames = [];
var num = 0;
for (var i = 0; i < areaInfo.length; i++) {
c = areaInfo[i];
if (c.xian == "00" && c.sheng == provinces[count].sheng && c.di != "00") {
citys[num] = c;
cityNames[num] = c.name;
num++;
}
}
if (citys.length == 0) {
citys[0] = {
name: ''
};
cityNames[0] = {
name: ''
};
}
var that = this;
that.setData({
citys: citys,
cityNames: cityNames
})
console.log('cityNames:' + cityNames);
that.getCountyInfo(count, 0);
},
// 获取区县数据
getCountyInfo: function(column0 = 0, column1 = 0) {
var c;
countys = [];
countyNames = [];
var num = 0;
for (var i = 0; i < areaInfo.length; i++) {
c = areaInfo[i];
if (c.xian != "00" && c.sheng == provinces[column0].sheng && c.di == citys[column1].di) {
countys[num] = c;
countyNames[num] = c.name;
num++;
}
}
if (countys.length == 0) {
countys[0] = {
name: ''
};
countyNames[0] = {
name: ''
};
}
console.log('countyNames:' + countyNames);
var that = this;
that.setData({
countys: countys,
countyNames: countyNames
})
},
bindTransportDayChange: function(e) {
console.log('picker country 发生选择改变,携带值为', e.detail.value);
this.setData({
transportIndex: e.detail.value
})
},
bindProvinceNameChange: function(e) {
var that = this;
console.log('picker province 发生选择改变,携带值为', e.detail.value);
var val = e.detail.value
that.getCityArr(val); //获取地级市数据
that.getCountyInfo(val, 0); //获取区县数据
value = [val, 0, 0];
this.setData({
provinceIndex: e.detail.value,
cityIndex: 0,
countyIndex: 0,
value: value
})
},
bindCityNameChange: function(e) {
var that = this;
console.log('picker city 发生选择改变,携带值为', e.detail.value);
var val = e.detail.value
that.getCountyInfo(value[0], val); //获取区县数据
value = [value[0], val, 0];
this.setData({
cityIndex: e.detail.value,
countyIndex: 0,
value: value
})
},
bindCountyNameChange: function(e) {
var that = this;
console.log('picker county 发生选择改变,携带值为', e.detail.value);
this.setData({
countyIndex: e.detail.value
})
},
saveAddress: function(e) {
var consignee = e.detail.value.consignee;
var mobile = e.detail.value.mobile;
var transportDay = e.detail.value.transportDay;
var provinceName = e.detail.value.provinceName;
var cityName = e.detail.value.cityName;
var countyName = e.detail.value.countyName;
var address = e.detail.value.address;
console.log(transportDay + "," + provinceName + "," + cityName + "," + countyName + "," + address); //输出该文本
var arr = wx.getStorageSync('addressList') || [];
console.log("arr,{}", arr);
addressList = {
consignee: consignee,
mobile: mobile,
address: provinceName + cityName + countyName+address,
transportDay: transportDay
}
arr.push(addressList);
wx.setStorageSync('addressList', arr);
wx.navigateBack({ })
}
})
结语
这个小程序作为我学前端以来写的第一个项目,写的过程是漫长的,同时也是痛并快乐着的。希望能给那些初写小程序商城的同学们一些帮助,毕竟我刚开始写这个小程序的时候是迷茫的,不知如何下手。觉得写得还ok的话,可不可以动动你们的小拇指给我点个赞呢!若有哪写得不足得地方可以在下面的评论区附上你们的建议。最后再加一句,我是2020届的毕业生,即将面临实习的压力,有哪位大佬可以稍微关照下嘛。