黑马电商品优购小程序
几日前完成了该项目,整理了下大概的逻辑思路,希望和大家一起交流学习,文档中不足之处希望各位不吝赐教。
该项目使用小程序原生mina框架
项目页面的搭建
| 页面名称 | 文件 |
|---|---|
| 首页 | index |
| 分类 | category |
| 商品列表 | goods_list |
| 商品详情 | goods_detail |
| 购物车 | cart |
| 收藏 | collect |
| 订单 | order |
| 搜索 | search |
| 个人中心 | user |
| 意见反馈 | user |
| 登录 | login |
| 授权 | auth |
| 结算 | pay |
- 项目文件目录

首页

分类页面

1.点击左侧菜单切换内容
获取被点击的标题身上的索引
给data中的currentIndex赋值
根据不同的索引来渲染右侧的商品内容
handleItemTap (e) { const { index } = e.currentTarget.dataset; let rightContent = this.Cates[index].children; this.setData({ currentIndex: index, rightContent, scrollTop: 0 }) }
商品列表

Tab栏

1.自定义组件传值
- ⽗组件通过属性的⽅式给⼦组件传递参数、⼦组件通过事件的⽅式向⽗组件传递参数
- ⽗组件 把数据 {{tabs}} 传递到 ⼦组件的 tabItems 属性中
- ⽗组件 监听 onMyTab 事件
- ⼦组件 触发 bindmytap 中的事件
- ⾃定义组件触发事件时,需要使⽤ triggerEvent ⽅法,指定 事件名 、 detail 对象
- ⽗ -> ⼦ 动态传值 this.selectComponent("#tabs");
2.Tab栏切换
获取被点击的标题索引
修改源数组
赋值到data中
const { index } = e.detail; let { tabs } = this.data; tabs.forEach((v, i) => i === index ? v.isActive = true : v.isActive = false); this.setData({ tabs })
3.下拉页面功能
⻚⾯的json⽂件中开启设置 enablePullDownRefresh:true

- ⻚⾯的JS中,绑定事件 onPullDownRefresh
// 下拉刷新事件 onPullDownRefresh () { // 1 重置数组 this.setData({ goodsList: [] }) // 2 重置页码 this.QueryParams.pagenum = 1; // 3 发送请求 this.getGoodsList(); }
启⽤上拉⻚⾯功能 onReachBottom ⻚⾯触底事件
加载下⼀⻚功能
// 页面上滑 滚动条触底事件 onReachBottom () { // 1 判断还有没有下一页数据 if (this.QueryParams.pagenum >= this.totalPages) { // 没有下一页数据 wx.showToast({ title: '没有下一页数据' }); } else { // 还有下一页数据 this.QueryParams.pagenum++; this.getGoodsList(); } },
商品详情

1.点击图片预览

- 实现效果

2.加入购物车 逻辑
- 先绑定点击事件
- 获取缓存中的购物车数据 数组格式
- 先判断 当前的商品是否已经存在于 购物车
- 已经存在 修改商品数据 执行购物车数量++ 重新把购物车数组 填充回缓存中
- 不存在于购物车的数组中 直接给购物车数组添加一个新元素 新元素 带上 购买数量属性 num 重新把购物车数组 填充回缓存中
- 弹出提示
// 点击 加入购物车
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',
// true 防止用户 手抖 疯狂点击按钮
mask: true
});
},
3.商品收藏
页面onShow的时候 加载缓存中的商品收藏的数据
判断当前商品是不是被收藏
是 改变页面的图标
不是
点击商品收藏按钮
- 判断该商品是否存在于缓存数组中
- 已经存在 把该商品删除
- 没有存在 把商品添加到收藏数组中 存入到缓存中即可
// 点击 商品收藏图标
handleCollect () {
let isCollect = false;
// 1 获取缓存中的商品收藏数组
let collect = wx.getStorageSync("collect") || [];
// 2 判断该商品是否被收藏过
let index = collect.findIndex(v => v.goods_id === this.GoodsInfo.goods_id);
// 3 当index!=-1表示 已经收藏过
if (index !== -1) {
// 能找到 已经收藏过了 在数组中删除该商品
collect.splice(index, 1);
isCollect = false;
wx.showToast({
title: '取消成功',
icon: 'success',
mask: true
});
} else {
// 没有收藏过
collect.push(this.GoodsInfo);
isCollect = true;
wx.showToast({
title: '收藏成功',
icon: 'success',
mask: true
});
}
// 4 把数组存入到缓存中
wx.setStorageSync("collect", collect);
// 5 修改data中的属性 isCollect
this.setData({
isCollect
})
}
购物车

1.获取用户的收货地址
绑定点击事件
调用小程序内置 api 获取用户的收货地址 wx.chooseAddress
获取 用户权限 状态 scope
假设 用户 点击获取收货地址的提示框 确定 scope 值 true 直接调用 获取收货地址
假设 用户 从来没有调用过 收货地址的api scope undefined 直接调用 获取收货地址
假设 用户 点击获取收货地址的提示框 取消
- scope 值 false
- 诱导用户 自己 打开 授权设置页面(wx.openSetting) 当用户重新给与 获取地址权限的时候
- 获取收货地址
- 把获取到的收货地址 存入到 本地存储中
// 点击 收货地址
async handleChooseAddress() {
try {
// 1 获取 权限状态
const res1 = await getSetting();
const scopeAddress = res1.authSetting["scope.address"];
// 2 判断 权限状态
if (scopeAddress === false) {
await openSetting();
}
// 4 调用获取收货地址的 api
let address = await chooseAddress();
address.all = address.provinceName + address.cityName + address.countyName + address.detailInfo;
// 5 存入到缓存中
wx.setStorageSync("address", address);
} catch (error) {
console.log(error);
}
},
2.全选的实现 数据的展示
- onShow 获取缓存中的购物车数组
- 根据购物车中的商品数据 所有的商品都被选中 checked=true 全选就被选中
// 商品全选功能
handleItemAllCheck() {
// 1 获取data中的数据
let { cart, allChecked } = this.data;
// 2 修改值
allChecked = !allChecked;
// 3 循环修改cart数组 中的商品选中状态
cart.forEach(v => v.checked = allChecked);
// 4 把修改后的值 填充回data或者缓存中
this.setCart(cart);
},
3.商品数量编辑
- 获取传递过来的参数
- 获取购物车数组
- 找到需要修改的商品的索引
- 判断是否要执行删除
- 进行修改数量
- 设置回缓存和data中
// 商品数量的编辑功能
async handleItemNumEdit (e) {
// 1 获取传递过来的参数
const { operation, id } = e.currentTarget.dataset;
// 2 获取购物车数组
let { cart } = this.data;
// 3 找到需要修改的商品的索引
const index = cart.findIndex(v => v.goods_id === id);
// 4 判断是否要执行删除
if (cart[index].num === 1 && operation === -1) {
// 4.1 弹窗提示
const res = await showModal({ content: "您是否要删除?" });
if (res.confirm) {
cart.splice(index, 1);
this.setCart(cart);
}
} else {
// 4 进行修改数量
cart[index].num += operation;
// 5 设置回缓存和data中
this.setCart(cart);
}
},
个人中心

收藏

订单

搜索

1.输入框绑定 值改变事件 input事件
- 获取到输入框的值
- 合法性判断 (非空)
- 检验通过 把输入框的值 发送到后台
- 返回的数据打印到页面上
2.防抖 (防止抖动) 定时器 节流
- 防抖 一般 输入框中 防止重复输入 重复发送请求
- 节流 一般是用在页面下拉和上拉
- 定义全局的定时器id
// 输入框的值改变 就会触发的事件
handleInput(e){
// 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);
},
// 发送请求获取搜索建议 数据
async qsearch(query){
const res=await request({url:"/goods/qsearch",data:{query}});
console.log(res);
this.setData({
goods:res
})
},
意见反馈

1 点击 “+” 触发tap点击事件
1 调用小程序内置的 选择图片的 api
2 获取到 图片的路径 数组
3 把图片路径 存到 data的变量中
4 页面就可以根据 图片数组 进行循环显示 自定义组件
// 点击 “+” 选择图片
handleChooseImg() {
// 2 调用小程序内置的选择图片api
wx.chooseImage({
// 同时选中的图片的数量
count: 9,
// 图片的格式 原图 压缩
sizeType: ['original', 'compressed'],
// 图片的来源 相册 照相机
sourceType: ['album', 'camera'],
success: (result) => {
this.setData({
// 图片数组 进行拼接
chooseImgs: [...this.data.chooseImgs, ...result.tempFilePaths]
})
}
});
},
2 点击 自定义图片 组件
1 获取被点击的元素的索引
2 获取 data中的图片数组
3 根据索引 数组中删除对应的元素
4 把数组重新设置回data中
// 点击 自定义图片组件
handleRemoveImg(e) {
// 2 获取被点击的组件的索引
const { index } = e.currentTarget.dataset;
// 3 获取data中的图片数组
let { chooseImgs } = this.data;
// 4 删除元素
chooseImgs.splice(index, 1);
this.setData({
chooseImgs
})
},
3 提交
获取文本域的内容 类似 输入框的获取
- data中定义变量 表示 输入框内容
- 文本域 绑定 输入事件 事件触发的时候 把输入框的值 存入到变量中
对这些内容 合法性验证
验证通过 用户选择的图片 上传到专门的图片的服务器 返回图片外网的链接
遍历图片数组
挨个上传
自己再维护图片数组 存放 图片上传后的外网的链接
文本域 和 外网的图片的路径 一起提交到服务器 前端的模拟 不会发送请求到后台
清空当前页面
返回上一页
// 提交按钮的点击
handleFormSubmit() {
// 1 获取文本域的内容 图片数组
const { textVal, chooseImgs } = this.data;
// 2 合法性的验证
if (!textVal.trim()) {
// 不合法
wx.showToast({
title: '输入不合法',
icon: 'none',
mask: true
});
return;
}
// 3 准备上传图片 到专门的图片服务器
// 上传文件的 api 不支持 多个文件同时上传 遍历数组 挨个上传
// 显示正在等待的图片
wx.showLoading({
title: "正在上传中",
mask: true
});
// 判断有没有需要上传的图片数组
if (chooseImgs.length != 0) {
chooseImgs.forEach((v, i) => {
wx.uploadFile({
// 图片要上传到哪里
url: 'http://img.coolcr.cn/index/api.html',
// 被上传的文件的路径
filePath: v,
// 上传的文件的名称 后台来获取文件 file
name: "image",
// 顺带的文本信息
formData: {},
success: (result) => {
console.log(result);
let url = JSON.parse(result.data).url;
this.UpLoadImgs.push(url);
// 所有的图片都上传完毕了才触发
if (i === chooseImgs.length - 1) {
wx.hideLoading();
console.log("把文本的内容和外网的图片数组 提交到后台中");
// 提交都成功了
// 重置页面
this.setData({
textVal: "",
chooseImgs: []
})
// 返回上一个页面
wx.navigateBack({
delta: 1
});
}
}
});
})
}else{
wx.hideLoading();
console.log("只是提交了文本");
wx.navigateBack({
delta: 1
});
}
}
登录


结算

因为支付功能需要权限,所以这部分没有做,除了支付其他的部分是都可以做的,大家加油!