微信小程序-木樨shop
博客班级 | https://edu.cnblogs.com/campus/zjcsxy/SE2020 |
---|---|
作业要求 | https://edu.cnblogs.com/campus/zjcsxy/SE2020/homework/11334 |
作业目标 | 1.编写一个小程序,可以全新编写,也可以学习别人的小程序进行修改 2.熟悉git代码管理流程,将源代码上传到到github 3.在博客园班级中写一篇相应的博文 |
作业源代码 | https://github.com/863252054/miniProgram_first |
学号 | 31801140 |
姓名 | 徐洋 |
前言
第一次学习做微信小程序,我的学习方法是看视频,跟着bilibili上的教学视频一步步做。也曾试过直接下载他人github上的代码一点点琢磨其中语句的意思,但由于自己对小程序一无所知,在自己的电脑上根本运行不了,所以这条路直接被我否决。最终还是跟着教学视频一步一个脚印的去学习小程序的写法。该项目为跟着b站上的黑马程序员自己纯手打的,期间遇到好多小问题,但最终解决了其中的大部分,让作业以一个较为完整的方式展现出来。尽管仍有很多疑惑的地方,但作者的编程思路给了我极大的启发
此程序用的是教学视频提供的接口写的,其中数据若想成功获取必须勾选 微信开发工具=>详情=>本地设置=>不校验合法域名、web-view(业务域名) 才能正常加载页面
界面展示
文件结构
- components存放自定义组件
- icons存放tabbar的图标
- pages存放主要界面,其中cart为购物车,category为商品分类,goods_detail存放商品详情,goods_list存放某一商品的各类,index存放首页,login显示登录按钮,pay存放支付页面,user存放个人主页
- request中只有一个js文件,用来存放promise方法
- utils存放封装的各种方法
个人经验总结
- js用来编辑功能代码,存放数据,加载时需要执行的命令,事件触发时执行的事件都放在这里
- json决定是否使用自定义组件,设置页面样式,能否下拉等动作,其中app.json还可以定义tabbar
- wxml和html功能类似
- wxss和css功能类似,我用的vscode编写,在编辑样式时使用了插件,只要编写less文件就能自动生成wxss,使编辑方便了很多
设计逻辑
首页
本小程序中最核心的部分是对于教学视频提供的接口的使用,拿首页举例,要获取轮播图数组,楼层以及分类数组,调用了以下函数(用轮播图数组距离)
onLoad: function (options) {
...
this.getSwiperList();
this.getCatesList();
this.getFloorList();
},
// 获取轮播图数据
getSwiperList() {
request({ url: "/home/swiperdata" })
.then(result => {
this.setData({
swiperList: result
})
})
}
其中的reuqest为了避免回调地狱使用了promise函数在request.js文件中进行了如下封装
export const request=(params)=>{
ajaxTimes++;
// 显示加载中效果
wx.showLoading({
title: "加载中",
mask: true,
});
// 定义公共的url
const baseUrl="https://api-hmugo-web.itheima.net/api/public/v1"
return new Promise((resolve,reject)=>{
wx.request({
...params,
url:baseUrl+params.url,
success:(result)=>{
resolve(result.data.message);
},
fail:(err)=>{
reject(err);
},
complete:()=>{
ajaxTimes--;
if(ajaxTimes===0){
// 关闭正在等待的图标
wx.hideLoading()
}
}
});
})
}
商品分类
分类界面分为左右两条:左侧菜单和右侧商品内容。通过调用接口,拿请求到的数据值来进行构造。左右都用到了循环和scroll-view实现上下滑动。
期间有个小问题就是不一样的菜单下,右侧商品内容有多有少,这就导致了切换菜单时,右侧滚动条不能自动回到顶部,可能卡在中间的位置,造成观感不佳,因此在右侧的scroll-view中添加了scroll-top属性,在data中将其设置初始化为0,每次切换菜单,将其重置为零,这样就实现了切换菜单,商品内容自动回到顶部。
获取接口数据使用了es7 的async await来发送请求,request还是上面贴出来的代码,而调用方式用了新的写法:
// 获取分类数据
async getCates() {
const res = await request({ url: "/categories" });
this.Cates = res;
//把接口数据存入本地存储中
wx.setStorageSync("cates", { time: Date.now(), data: this.Cates })
// 构造左侧的大菜单数据
let leftMenuList = this.Cates.map(v => v.cat_name);
// 构造右侧的商品数据
let rightContent = this.Cates[0].children;
this.setData({
leftMenuList,
rightContent
})
},
由于接口数据量比较大,为了优化性能,在onLoad时进行了对本地是否有存储的判断,逻辑在注释中已写出
onLoad: function (options) {
/**
*
* 1 先判断本地存储中是否有旧数据
* {time:Date.now(),data:[...]}
* 2 没有旧数据 直接发送新请求
* 3 有旧数据 同时没过期 则直接使用
*/
// 1 获取本地存储中的数据 (小程序中也存在本地存储技术)
const Cates = wx.getStorageSync("cates");
// 2 判断
if (!Cates) {
// 不存在 发送请求获取数据
this.getCates();
} else {
// 有旧数据 定义过期时间 10s
if (Date.now() - Cates.time > 1000 * 10) {
// 重新发请求
this.getCates();
} else {
// 可以使用旧数据
this.Cates = Cates.data;
let leftMenuList = this.Cates.map(v => v.cat_name);
// 构造右侧的商品数据
let rightContent = this.Cates[0].children;
this.setData({
leftMenuList,
rightContent,
})
}
}
}
商品列表
在商品列表中与之前一样正常的获取接口数据,不过此处碰到了两个问题:
-
一页10条信息显示不完,解决办法如下:
用户上滑页面 滚动条触底 开始加载下一页数据 1. 找到滚动条触底事件 2. 判断还有没有下一页数据 a. 获取到总页数 总条数:total 总页数 = Math.ceil(总条数 / 页容量 pagesize) b. 获取到当前的页码 pagenum c. 判断 当前的页码是否大于等于 总页数 表示没有下一页数据 3. 假如没有下一页数据 弹出提示 4. 有的话继续加载
- 接口图片部分缺失,解决办法如下:
三元表达式,若检测有图片,直接显示,否则显示自己替换图片
<!-- 当接口中的图片丢失时 -->
<image mode="widthFix" src="{{item.goods_small_logo?item.goods_small_logo:'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1234272396,3380570950&fm=26&gp=0.jpg'}}" ></image>
商品详情
设计思路如下:
1 发送请求数据
2 点击轮播图 预览大图
a 给轮播图绑定点击事件
b 调用小程序的api previewImage
3 点击 加入购物车
a 先绑定点击事件
b 获取缓存中的购物车数据 数组格式
c 先判断当前商品是否已经存在于 购物车
d 已经存在 执行购物车数量++ 重新把购物车数组 填充回缓冲中
e 不存在于购物车的数组中 直接给购物车数组添加新元素
f 弹出提示
问题:iphone部分手机,不识别 webp图片格式,需要自己修改,并保证后台存在 1.webp=>1.jpg
goods_introduce: goodsObj.goods_introduce.replace(/\.webp/g, '.jpg'),
其中十分重要的是这一步和下一步的购物车之间有联系,因此得将涉及到的购物车数据写入缓存 中,因此将添加至购物车的代码贴出
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防止用户手抖,1.5s后才能继续响应
mask: true
});
}
购物车
购物车是此小程序中代码量最大也是逻辑最复杂的部分,设计思路如下:
1 获取用户的收货地址
1 绑定点击事件
2 调用小程序内置 api 获取用户的收货地址 wx.chooseAddress
3 把获取到的信息 存入本地缓存中
2 页面加载完毕
0 onload onshow
1 获取本地存储中的地址数据
2 把数据 设置给data中的一个变量
3 onShow
0 回到了商品详情页面 第一次添加商品的时候 手动添加了属性
1 num = 1;
2 checked = true;
1 获取缓存中的购物车数组
2 把购物车数据 填充到data中
4 全选的实现 数据的展示
1 onShow 获取缓存中的购物车数组
2 根据购物车中的商品数据 所有商品都被选中 全选就被选中
5 总价格和总数量
1 都需要商品被选中 我们才拿它来计算
2 获取购物车数组
3 遍历
4 判断商品是否被选中
5 总价格 += 商品的单价 * 商品的数量
总数量 += 商品的数量
6 把计算后的价格和数量 设置回data中即可
6 商品的选中
1 绑定change事件
2 获取到被修改的商品对象
3 商品对象的选中状态 取反
4 重新填充回data中和缓存中
5 重新计算全选,总价格,总数量。。。
7 全选和反选
1 全选 复选框绑定事件 change
2 获取 data中的全选变量 allChecked
3 直接取反 allChecked = !allChecked
4 遍历购物车数组 让里面 商品 选中状态跟随 allChecked 改变而改变
5 把购物车数组 和 allChecked 重新设置回data 把购物车 重新设置回缓存中
8 商品数量的编辑
1 "+" "-" 按钮 绑定同一个点击事件 区分的关键 自定义属性
1 "+" "+1"
2 "-" "-1"
2 传递被点击的商品id goods_id
3 获取data中的购物车数组 来获取需要被修改的商品对象
4 直接修改商品对象的数量 num
当购物车的数量 =1 同时用户点击 "-"
弹窗提示(showModal) 询问用户 是否要删除
1 确定 直接执行删除
2 取消 什么都不做
5 把cart数组 重新设置回 缓存中 和 data中 this.setCart
9 点击结算
1 判断有无收货地址
2 判断有无商品信息
3 以上都满足就跳转 支付页面
在这块代码中,我觉得最精妙的是对重新计算底部工具栏价格这一事件的封装,代码如下:
// 设置购物车状态 同时重新计算底部工具栏的数据
setCart(cart){
let allChecked = true;
// 1 总价格 总数量
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 ? allChecked : false;
this.setData({
allChecked,
totalPrice,
totalNum,
cart
});
wx.setStorageSync("cart", cart);
},
将购物车重新写入缓存中,这样的话,不管是在商品详情页面点击加入购物车事件,还是在购物车页面进行全选、选择、添加、减少,抑或是在之后的结算页面计算价格并进行购物车删除,都直接调用此函数,十分方便,且思路清晰,在我今后的代码中,也会多采用封装函数的形式
个人主页
个人主页由于和订单相关,但由于我本人没有自己的企业号,且无法发送获取支付请求,因此只写了一个静态布局,其中主页是设计了逻辑的,在登陆前只显示登录按钮,登陆后显示头像以及头像的高斯模糊背景,这块代码在wxml中实现
<view class="user_info_wrap">
<view wx:if="{{userinfo.avatarUrl}}" class="user_img_wrap">
<image class="user_bg" src="{{userinfo.avatarUrl}}"></image>
<view class="user_info">
<image class="user_icon" src="{{userinfo.avatarUrl}}"></image>
<view class="user_name">{{userinfo.nickName}}</view>
</view>
</view>
<view wx:else class="user_btn">
<navigator url="/pages/login/index">登录</navigator>
</view>
</view>
布局
在跟着视频敲代码的过程中,我觉得收益颇深的就是老师对各个class的命名方式,非常的规范,如此一来,搭配less来设置样式,整体就十分的清晰,如下图
包括其中对wxml注释的应用,整体的代码写下来之后再回顾,我都能清楚的明白这块代码实现了什么功能,我要改这个样式应该到哪里去找,应该如何修改。
此处列出两个在学习的过程中印象很深的两个布局方法:
- 多用flex伸缩盒子布局,设置垂直水平的居中很方便,同时flex:n可以轻松的控制元素占比多少大小。padding和margin也很好的控制了元素的四周留空,体内留空,让界面看着特别舒服
- 如果想让容器固定在一个位置,其父容器的position属性须是relative,之后在选择该元素的position为absolute;
总结
尽管这次作业,我自己的想法和构思占比并不多,但由于大家都是初学,我认为打下基础是十分重要的,因此我把大量的时间放在了跟视频敲代码上,有些视频我看完之后,自己的效果并不能做到和视频中的一样,我会反复去看,去研究代码究竟是哪里出了问题。
而恰是在这一过程中,我学习并弄懂了好多函数的使用,比如说wx:for={{xxx}}是for语句循环,其中的item和key,index的含义了;bindtap="xxx",如何去js文件中定义xxx,如何用console.log()去找到自己需要获取的一大堆数据的信息;什么是回调地狱,promise函数应该如何去写,这样写的好处是避免代码看起来冗余复杂,诸如此类的知识还有好多,其中还是有好多我没完全弄懂的知识,但我相信在日后的学习中,这些问题也会慢慢揭开它们的面纱。
跟着敲代码,不断调试,这样对于我一个初学者去了解如何写好小程序的帮助是十分大的,之后的作业中我会加入自己的想法,自己去设计一个实用的小程序。
参考资料
https://www.bilibili.com/video/BV1nE41117BQ?t=121&p=110 bilibili黑马程序员_小程序教学视频
https://www.showdoc.com.cn/128719739414963?page_id=2513235043485226 教学视频中提供的接口文档
https://blog.csdn.net/iefreer/article/details/50775887 弹性内容布局方式
https://blog.csdn.net/weixin_38289699/article/details/78964143 弹性布局,flex:n的含义
https://www.cnblogs.com/goloving/p/9275776.html 绝对定位需要注意的点
https://www.cnblogs.com/luxiaoxing/p/7544375.html vw、vh究竟是什么意思
https://blog.csdn.net/qq_21602341/article/details/87820778 回调地狱的的解决方法,promise ,es7 async