依尘优购
一、项目总览
二、项⽬搭建
1、首页
页面
业务逻辑
-
使⽤tabbar 实现底部导航功能
-
使⽤⾃定义组件的⽅式 实现 头部搜索框
-
加载 轮播图 数据
-
加载 导航 数据
-
加载 楼层 数据
关键技术
-
⼩程序内置 request API
-
es6的 promise
-
⼩程序 swiper 组件
-
⾃定义组件 实现 搜索框
代码亮点
// 获取轮播图数据
getSwiperList() {
request({
url: "/home/swiperdata",
}).then((result) => {
this.setData({
swiperList: result.data.message,
});
});
},
小结
import{request}提交url,返回后保存数据。
2、分类⻚⾯
页面
业务逻辑
-
加载分类⻚⾯数据
-
点击左侧菜单,右侧数据动态渲染
关键技术
-
scroll--view 组件
-
es7的 async 和 await
代码亮点
// 1 获取本地存储中的数据 (小程序也是存在本地存储技术)
const Cates = wx.getStorageSync("cates");
// 2 判断
if (!Cates) {
// 不存在 发送请求获取数据
this.getCates();
} else {
// 有旧的数据了
if (Date.now() - Cates.time > 1000 * 10) {
// 重新发送请求
this.getCates();
} else {
// 可以使用旧的数据
// console.log("可以使用旧的数据");
this.Cates = Cates.data;
let leftMenuList = this.Cates.map(v => v.cat_name);
let rightContent = this.Cates[0].children;
this.setData({
leftMenuList,
rightContent
})
}
}
// 左侧菜单的点击事件
handleItemTap(e) {
// console.log(e.currentTarget.dataset);
/*
1 获取被点击的标题身上的索引
2 给data中的currentIndex赋值就可以了
3 根据不同的索引来渲染右侧的商品内容
*/
const {
index
} = e.currentTarget.dataset;
// 根据index不同构造不同的右侧大菜单数据
let rightContent = this.Cates[index].children;
this.setData({
currentIndex: index,
rightContent,
// 从新设置内容的scroll-view标签距离顶部的距离
scrollTop: 0
})
}
小结
用wx.setStorageSync和wx.getStorageSync来存取Cates数据,然后渲染到页面上,如果选择左侧菜单不同的标签会改变index,右侧菜单会重新渲染新的内容
3、商品列表⻚⾯
页面
业务逻辑
-
用户上滑页面滚动条触底开始加载下页数据
-
找到滚动条触底事件微信 小程序官方开发文档寻找
-
判断还有没有下一页数据
1 获取到总页数 只有总条数 总页数 = Math.ceil(总条数/页容量 pagesize)
2 获取到当前的页码 pagenum
3 判断一下当前的页码是否大于等于总页数 表示没有下一页数据
-
假如没有下一页数据弹出一个提示
-
假如还有下一页数据来加载下一页数据
1 当前的页码++
2 重新发送请求
3 数据请求回来 要对data中的数组进行拼接而不是全部替换! ! !
-
-
下拉刷新页面
1 触发下拉刷新事件 需要在页面的json文件中开启一个配置项
2 重置数据数组
3 重置页码设置为1
4 重新发送请求
5 数据请求回来需要手动的关闭等待效果
关键技术
-
⼩程序配置⽂件中 启⽤上拉 和下拉功能
-
搜索框和tab栏是⼩程序的⾃定义组件(有组件事件和参数交互)
代码亮点
// 标题点击事件从子组件传递过来
handleTabsItemChange(e) {
// 1 获取被点击的标题索引
const { index } = e.detail;
// 2 修改源数组
let { tabs } = this.data;
tabs.forEach((v, i) =>
i === index ? (v.isActive = true) : (v.isActive = false)
);
// 3 赋值到data中
this.setData({
tabs,
});
},
// 页面上拉触底事件
onReachBottom() {
// 判断还有没有下一页数据
if (this.QueryParams.pagesize >= this.totalPages) {
// 没有下一页数据
wx.showToast({
title: "没有下一页数据",
});
} else {
// 有下一页数据
this.QueryParams.pagenum++;
this.getGoodsList();
}
},
// 下拉刷新事件
onPullDownRefresh() {
// 1 重置数据数组
this.setData({
goodsList: [],
}),
// 2 重置页数
(this.QueryParams.pagenum = 1);
// 3 重新发送请求
this.getGoodsList();
},
小结
给tab栏一个index,切换不同标签改变index控制改变css,对比当前页数和总页数来判断还有没有下一页数据,下拉刷新重置数据数组和页数并且重新发送请求
4、商品详情⻚⾯
页面
业务逻辑
-
发送获取数据
-
点击轮播图 预览大图
1 给轮播图绑定点击事件
2 调用小程序的api previewImage
-
点击 加入购物车
1 先绑定点击事件
2 获取缓存中的购物车数据数组格式
3 先判断当前的商品是否已经存在于购物车
4 已经存在修改商品数据 执行购物车数量++重新把购物车数组填充回缓存中
5 不存在于购物车的数组中直接给购物车数组添加一个新元素新元素带上购买数量属性num 重新把购物车数组填充回缓存中
6 弹出提示
-
商品收藏
1 页面onShow的时候加载缓存中的商品收藏的数据
2 判断当前商品是不是被收藏
1 是 改变页面的图标
2 不是 不变
3 点击商品收藏按钮
1 判断该商品是否存在于缓存数组中
2 已经存在把该商品删除
3 没有存在把商品添加到收藏数组中存入到缓存中即可
关键技术
-
swiper组件
-
本地存储实现 收藏功能
-
联系客服 ⼩程序管理后台中 直接添加即可
-
富⽂本标签 渲染 富⽂本
-
⼩程序 预览图⽚接⼝
代码亮点
// 点击轮播图放大预览
handlePreviewImage(e) {
// 1 先构造要预览的图片数组
console.log(e.currentTarget.dataset.url);
const urls = this.GoodsInFo.pics.map((v) => v.pics_mid);
// 2 接收传递过来的图片url
const current = e.currentTarget.dataset.url;
wx.previewImage({
current: current,
urls: urls,
});
},
// 点击加入购物车
handleCartAdd() {
// 1 获取缓存中的购物车数据数组格式
let cart = wx.getStorageSync("cart") || [];
// 2 判断当前的商品是否已经存在于购物车
let index = cart.findIndex((v) => v.goods_id === this.GoodsInFo.goods_id);
if (index === -1) {
// 3 不存在 第一次添加
this.GoodsInFo.num = 1;
this.GoodsInFo.checked = true;
cart.push(this.GoodsInFo);
} else {
// 4 已经存在购物车数据 执行 num++
cart[index].num++;
}
// 5 把购物车重新添加到缓存中
wx.setStorageSync("cart", cart);
// 6 弹窗提醒
wx.showToast({
title: "加入成功",
icon: "success",
mask: true,
});
},
小结
轮播图绑定放大预览事件,传递当前图片的url,收藏和加入购物车都是差不多的检查是否当前存在数据再做处理
5、收藏⻚
页面
业务逻辑
-
获取本地存储中的数据进⾏渲染
-
点击商品可以跳转到商品详情⻚⾯
关键技术
-
⼩程序 ⾃定义组件
-
本地存储 加载收藏数据
代码亮点
onShow() {
const collect = wx.getStorageSync("collect") || [];
this.setData({
collect,
});
},
小结
没什么好说的,再存储中找到collect,保存在data中渲染就行。
6、购物⻋⻚⾯
页面
业务逻辑
-
获取用户的收货地址
1 绑定点击事件
2 调用小程序内置api 获取用户的收货地址
3 把获取到的收货地址存入到本地存储中
-
页面加载完毕
0 onLoad onShow
1 获取本地存储中的地址数据
2 把数据设置给data中的一个变量
-
onshow
0 回到了商品详情页面第一次添加商品的时候手动添加了属性
1 num=1 ;
2 checked=true;1 获取缓存中的购物车数组
2 把购物车数据填充到data中
-
全选的实现数据的展示
1 onShow 获取缓存中的购物车数组
2 根据购物车中的商品数据所有的商品都被选中checked=true 全选就被选中
-
总价格和总数量
1 都需要商品被选中我们才拿它来计算
2 获取购物车数组
3 遍历
4 判断商品是否被选中
5 总价格+=商品的单价*商品的数量
6 把计算后的价格和数量设置回data中即可
-
商品的选中
1 绑定change事件
2 获取到被修改的商品对象
3 商品对象的选中状态取反
4 重新填充回data中和缓存中
5 重新计算全选 总价格总数量。。。
-
全选和反选
1 全选复选框绑定事件change
2 获取data中的全选变量allChecked
3 直接取反allChecked= !allChecked
4 遍历购物车数组让里面商品选中状态跟随allChecked改变而改变
5 把购物车数组和allChecked 重新设置回data 把购物车重新设置回缓存中
-
商品数量的编辑
"+" "-" 按钮绑定同一个点击事件区分的关键自定义属性
1 “+”"+1"
2 "”"1”
2 传递被点击的商品id goods_id
3 获取data中的购物车数组来获取需要被修改的商品对象
4 当购物车的数量=1同时用户点击"-" (showModal)
弹窗提示询问用户是否要删除
1 确定 直接执行删除
2 取消 什么都不做5 直接修改商品对象的数量num
6 把cart数组重新设置回缓存中和data中
-
点击结算
1判断有没有收货地址信息
2判断用户有没有选购商品
3经过以上的验证跳转到支付页面!
关键技术
-
获取购物⻋数据 本地存储实现
-
调⽤微信的收货地址
代码亮点
// 设置购物车状态同时重新计算底部工具栏的数据全选总价格购买的数量
setCart(cart) {
wx.setStorageSync("cart", cart);
let allChecked = true;
let totalPrice = 0;
let totalNum = 0;
cart.forEach((v) => {
if (v.checked) {
totalPrice += v.num * v.goods_price;
totalNum += v.num;
} else {
allChecked = false;
}
});
// 判断数组是否为空
allChecked = cart.length != 0 ? allChecked : false;
this.setData({
cart,
allChecked,
totalPrice,
totalNum,
});
},
// 商品数量编辑
handleItemNumEdit(e) {
// 1获取传递过来的参数
const { operation, id } = e.currentTarget.dataset;
// 2 获取购物车数组
let { cart } = this.data;
// 3 找到需要修改的商品的索引
const index = cart.findIndex((v) => v.goods_id === id);
// 判断是否要执行删除
if (cart[index].num === 1 && operation === -1) {
// 弹窗提示
wx.showModal({
title: "提示",
content: "您是否要删除该商品?",
success: (res) => {
if (res.confirm) {
cart.splice(index, 1);
this.setCart(cart);
} else if (res.cancel) {
console.log("用户点击取消");
}
},
});
} else {
// 4 进行修改数量
cart[index].num += operation;
// 5 设置回缓存和data中
this.setCart(cart);
}
},
小结
最难的一个页面,拿到购物车数据渲染,如果选中了就计算总价格和数量,如果点击全选就会将所有购物车数据里的选中值改变为全选的值,如果商品数量改变就会重新渲染和计算数据,最后要有地址数据和商品数据才能进行结算。
7、⽀付⻚⾯
页面
业务逻辑
-
页面加载的时候
1 从缓存中获取购物车数据渲染到页面中
这些数据 checked=true
-
微信支付
1 哪些人哪些帐号可以实现微信支付
1 企业帐号
2 企业帐号的小程序后台中必须给开发者添加上白名单1 一个appid 可以同时绑定多个开发者
2 这些开发者就可以公用这个appid 和它的开发权限
-
支付按钮
1 先判断缓存中有没有token
2 没有跳转到授权页面进行获取token
3 有token
4 创建订单获取订单编号
5 已经完成了微信支付
6 手动删除缓存中已经被选中了的商品
7 删除后的购物车数据填充回缓存
8 再跳转页面
关键技术
⼩程序 ⽀付 api
代码亮点
// 点击 支付
async handleOrderPay() {
try {
// 1 先判断缓存中有没有token
const token = wx.getStorageSync("token");
// 2 判断
if (!token) {
wx.navigateTo({
url: "/pages/auth/index",
});
return;
}
// 创建订单
// const header = { Authorization: token };
const order_price = this.data.totalPrice;
const consignee_addr = this.data.address.all;
const cart = this.data.cart;
let goods = [];
cart.forEach((v) =>
goods.push({
goods_id: v.goods_id,
goods_number: v.num,
goods_price: v.goods_price,
})
);
const orderParams = { order_price, consignee_addr, goods };
// 4准备发送请求创建订单获取订单编号
const { order_number } = await request({
url: "/my/orders/creat",
method: "POST",
data: orderParams,
});
// 5 发起预支付接
const { pay } = await request({
url: "/my/orders/req_unifiedorder",
method: "POST",
data: { order_number },
});
// 6 发起微信支付
wx.requestPayment({
timeStamp: "pay.timeStamp",
nonceStr: "pay.nonceStr",
package: "pay.package",
signType: "pay.signType",
paySign: "pay.paySign",
success: (result) => {
console.log(result);
},
fail: () => {},
complete: () => {},
});
// 7 查询后台 订单状态
const res = await request({
url: "/my/orders/chkOrder",
method: "POST",
data: { order_number },
});
await wx.showToast({
title: "支付成功",
});
// 8手动删除缓存中已经支付了的商品
let newCart = wx.getStorageSync("cart");
newCart = newCart.filter((v) => !v.checked);
wx.setStorageSync("cart", newCart);
// 8 跳转订单页面
wx.navigateTo({
url: "/pages/order/index",
});
} catch (error) {
console.log(error);
}
},
小结
将购物车被选中的数据传过来,调起微信支付,付款成功后在确定后台订单状态后,手动删除缓存中已经支付了的商品,跳回订单页面
8、授权⻚⾯
页面
业务逻辑
-
获取⽤⼾信息 返回 encryptedData,rawData,iv,signature
-
⼩程序登录 返回 code
-
提交数据到⾃⼰ 的后台 执⾏post请求 提交数据
encryptedData, rawData, iv, signature, code
-
将 token 和⽤⼾数据 rawData 存⼊本地存储
关键技术
-
微信的getUserInfo和login
代码亮点
async handleGetUserInfo(e) {
// 1 获取用户信息
const { encryptedData, rawData, iv, signature } = e.detail;
// 2 获取小程序登录成功后的code
let code = {};
wx.login({
timeout: 10000,
success: (result) => {
code = result.code;
},
});
const loginParams = { encryptedData, rawData, iv, signature, code };
// 3 发送请求 获取用户的token
let { token } = await request({
url: "/users/wxlogin",
data: loginParams,
method: "post",
});
// 没有企业微信 token自定义
token =
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIzLCJpYXQiOjE1NjQ3MzAwNzksImV4cCI6MTAwMTU2NDczMDA3OH0.YPt-XeLnjV-_1ITaXGY2FhxmCe4NvXuRnRB8OMCfnPo";
console.log(token);
// 4把token存入缓存中同时跳转回上一个页面
wx.setStorageSync("token", token);
wx.navigateBack({
delta: 1,
});
},
小结
获取用户信息,上传到后台获得token值保存到存储中
9、订单列表⻚⾯
页面
业务逻辑
-
页面被打开的时候onShow
0 onShow不同于onLoad无法在形参上接收options参 数
1获取ur1上的参数type
2根据type去发送请求获取订单数据
3渲染页面
-
点击不同的标题重新发送请求来获取和渲染数据
关键技术
⼩程序 ⾃定义组件的 传参 ⽗向⼦动态传参 this.selectComponent("#tabs"); 时间戳 格式化处理
代码亮点
onShow(options) {
// 验证token
const token = wx.getStorageSync("token");
if (!token) {
wx.navigateTo({
url: "/pages/auth/index",
});
}
// 1 获取当前的小程序的页面栈-数组长度最大是10页面
let pages = getCurrentPages();
// 2 数组中索引最大的页面就是当前页面
let currentPage = pages[pages.length - 1];
// 3 获取url上的type参数
const { type } = currentPage.options;
// 4 激活选中页面标题
this.changeTitleByIndex(type - 1);
},
小结
onShow没有onLaod的options,可以使用getCurrentPages等一系列方法来实现相同的功能。
10、搜索⻚⾯
页面
业务逻辑
-
输入框绑定值改变事件input事件
1获取到输入框的值
2合法性判断
3检验通过把输入框的值发送到后台
4返回的数据打印到页面上
-
防抖(防止抖动)定时器节流
0防抖一般输入框中防止重复输入重复发送请求
1节流 一般是用在页面下拉和上拉
1定义全局的定时器id
关键技术
-
⼩程序 输⼊框组件
-
输⼊值改变时,为了提⾼性能,使⽤ 防抖 技术
代码亮点
// 1获取到输入框的值
const { value } = e.detail;
// 2合法性判断
if (!value.trim()) {
this.setData({
goods: [],
isFocus: false,
});
// 值不合法
return;
}
// 3准备发送请求获得数据
this.setData({
isFocus: true,
});
clearTimeout(this.timeId);
// 延时 防抖
this.timeId = setTimeout(() => {
this.qsearch(value);
}, 1000);
},
小结
在搜索中延时1s让用户输完,再进行上传到后台拿到数据渲染。
11、个⼈中⼼⻚⾯
页面
业务逻辑
-
获取登录信息
-
加载收藏信息
-
查询订单状态
关键技术
-
css属性filter的使⽤
代码亮点
onShow() {
const userinfo = wx.getStorageSync("userinfo");
// console.log(userinfo);
const collect = wx.getStorageSync("collect");
this.setData({
userinfo,
collectNums: collect.length,
});
},
小结
这里最重要的是css样式,js中只有读取数据
12、意⻅反馈⻚⾯
页面
业务逻辑
-
点击“+”触发tap点击事件
1调用小程序内置的选择图片的api
2获取到图片的路径 数组
3把图片路径存到data的变量中
4页面就可以根据图片数组进行循环显示自定义组件
-
点击自定义图片组件
1获取被点击的元素的索引
2获取data中的图片数组
3根据索引数组中删除对应的元素
4把数组重新设置回data中
-
点击“提交”
1获取文本域的内容
2对这些内容合法性验证
3验证通过用户选择的图片上传到专门的图片的服务器返回图片外网的链接
4文本域和外网的图片的路径一起提交到服务器
5清空当前页面
6返回上一页
关键技术
-
⾃定义组件 tab
-
⾃定义组件 图⽚删除组件
-
⼩程序 上传⽂件 api
代码亮点
// 点击“+”触发tap点击事件
handleChooseImg(e) {
// 调用小程序内置的选择图片的api
wx.chooseImage({
count: 9,
sizeType: ["original", "compressed"],
sourceType: ["album", "camera"],
success: (res) => {
console.log(res);
this.setData({
// 图片数组 进行拼接
chooseImage: [
小结
引用自定义组件,再绑定点击事件调用小程序内置的选择图片的api,发送数据给子组件显示。
三、其他补充
⼩程序中⽀持es7的async语法
ES7的 async 号称是解决回调的最终⽅案
-
在⼩程序的开发⼯具中,勾选 es6转es5语法
-
下载 facebook的regenerator库中的 regenerator/packages/regenerator-runtime/runtime.js
-
在⼩程序⽬录下新建⽂件夹 lib/runtime/runtime.js ,将代码拷⻉进去
-
在每⼀个需要使⽤async语法的⻚⾯js⽂件中,都引⼊(不能全局引⼊)
源代码
https://gitee.com/LYC3214026782/code-warehouse/tree/master