上一周概念和相关的配置讲的比较多,实际涉及到小程序业务的很少;接下来的这周是概念比较多,业务比较少,整体都是在铺垫一个坚实的基础,毕竟—-万丈高楼平地起!
优惠券的一些基本概念
前端:涉及到核算,不仅仅是满100减20,比如会有以下情况:
- 优惠券类型:满减、折扣、使用条件(全场券,品类券)
- 过期,比如领取之后多少天过期
两大概念:领取和使用
- 领取:通过活动的方式领取,活动的领取时间,结束时间和在线时间;优惠券使用的范围也就是品类券,会和优惠券的Coupon绑定一起,而不是和商品的分类绑定一起
优惠券入口
新建优惠券模型:
model\activity.js
1 2 3 4 5 6 7 8 9 10 11
| import { Http } from "../utils/http"; class Activity { static LocationD = "a-2"; static getHomeLocationD() { return Http.request({ url: `activity/name/${Activity.LocationD}`, }); } }
export { Activity };
|
pages\home\home.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
data: { themeA: null, bannerB: null, grid: [], activity: null, },
async onLoad(options) { this.initAllData(); }, async initAllData() { const themeA = await Theme.getHomeLocationA(); const bannerB = await Banner.getHomeLocationB(); const grid = await Category.getHomeLocationC(); const activity = await Activity.getHomeLocationD();
this.setData({ themeA: themeA[0], bannerB: bannerB, grid: grid, activity: activity, }); },
|
pages\home\home.wxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <view> <image class="top-theme" src="{{themeA.entrance_img}}" /> <swiper class="swiper" indicator-dots indicator-active-color="#157658" autoplay circular > <block wx:for="{{bannerB.items}}" wx:key="index"> <swiper-item> <image class="swiper" src="{{item.img}}"></image> </swiper-item> </block> </swiper> <s-category-grid grid="{{grid}}"></s-category-grid> <image class="activity" src="{{activity.entrance_img}}"></image> </view>
|
pages\home\home.wxss
1 2 3 4 5
| .activity{ margin-top: 20rpx; width: 100%; height: 310rpx; }
|
背景颜色到底怎么设置?
根据设计图可以看到每个业务对象之间是有间隔的,并且是灰色背景,当然最简单的方式就是加入view,然后设置背景颜色,但是这样的代码很冗余,这节中将设置页面的背景颜色;
官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/page.html
设置窗口的背景色:backgroundColor:#ffffff;
pages\home\home.json
1 2 3 4 5 6 7
| { "usingComponents": { "s-category-grid": "/components/category-grid/index" }, "enablePullDownRefresh": true, "backgroundColor": "#000000" }
|
会发现添加了之后没用,这是因为配置的是窗体背景色,只会在下拉刷新或上拉加载时露出,而不是页面的常规背景色(enablePullDownRefresh是设置刷新)
真机预览的时候,调用的API需要是https的,如果不是,需要点击预览,扫描二维码,然后在真机小程序的三个点,打开调试模式,就可以访问http的API了
既然这种方式不行,试下直接的方式,直接在页面最外面的view设置backgroundColor,这种方式最直接简单,但是有以下问题需要解决:有两个缺点,一个是所有页面都需要这样写,另外背景颜色只作用于页面内容,我们需要实现的是全屏背景色
pages\home\home.wxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <view class="container"> <image class="top-theme" src="{{themeA.entrance_img}}" /> <swiper class="swiper" indicator-dots indicator-active-color="#157658" autoplay circular > <block wx:for="{{bannerB.items}}" wx:key="index"> <swiper-item> <image class="swiper" src="{{item.img}}"></image> </swiper-item> </block> </swiper> <s-category-grid grid="{{grid}}"></s-category-grid> <image class="activity" src="{{activity.entrance_img}}"></image> </view>
|
pages\home\home.wxss
1 2 3
| .container{ background-color: #000000; }
|
- 顶部凸显一个黑块,这是因为在
app.wxss中也有一个container类,把他删除即可;
- 删除之后又会发现图片与图片之间有间距,这是图片自带的间距,可以在
.top-theme中加入display: flex;
- 六宫格没有设置背景色,默认是透明的,所以背景色是黑色就会变黑色,修改方式就是给六宫格设置一个白色背景色:
components\category-grid\index.wxss文件中的.container加入 background-color: #ffffff;
- 优惠券底部也有黑色背景,解决方式一样,在
.activity中加入display: flex;
最终方式:
app.wxss
1 2 3
| page{ background-color: #f5f5f5; }
|
这样整个小程序页面的背景色都是设置为 #f5f5f5,也不用单独每个页面设置,而且不仅只作用于页面内容,而是作用与整个页面
页面是否合并HTTP请求
上面已经完成了优惠券入口,接下来完成每周上新这个主题;这里涉及到很多知识点,单这个每周上新的样式这周都没写完…..
首先来个最正常的写法:
model\theme.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
import { Http } from "../utils/http"; class Theme { static LocationA = "t-1"; static LocationE = "t-2"; static getHomeLocationA() { return Http.request({ url: "theme/by/names", data: { names: Theme.LocationA, }, }); } static getHomeLocationE() { return Http.request({ url: "theme/by/names", data: { names: Theme.LocationE, }, }); } }
export { Theme };
|
pages\home\home.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| async onLoad(options) { this.initAllData(); }, async initAllData() { const themeA = await Theme.getHomeLocationA(); const bannerB = await Banner.getHomeLocationB(); const grid = await Category.getHomeLocationC(); const activity = await Activity.getHomeLocationD(); const newweek = await Theme.getHomeLocationE(); this.setData({ themeA: themeA[0], bannerB: bannerB, grid: grid, activity: activity, newweek, }); },
|
改变的地方:创建getHomeLocationE的请求,在首页调用接口;
思考:在theme这个对象,getHomeLocationA和getHomeLocationE去请求的接口都是一样的,只是参数不一样,这里完全可以写成拼接的方式去合并请求数据,而不是分2次请求,多次请求会导致服务器性能下降;这里就引出了本节的重点,页面是否合并HTTP请求
页面是否合并HTTP请求可以从三个方面讨论:
衡量的依据:Http的请求数量;Http请求有多少次数据库查询;接口灵活性、维护性;
- 全部合并:Http的请求数量是最少的,这种方式Http请求的数据库查询次数适中(每一个http都会去查询数据库,哪怕是重复的查询,但是发送一次不会重复查询,比如我3个请求都是查询A和B的数据库,因为发送3此请求所以查询了3次,但是我一个请求查询A和B的数据库,实际上我只查询了2次,把A和B的查询结果一次性返回),灵活性和维护性最低;
- 每一个数据都发送一次请求:这种方式Http的请求数量最多,Http请求的数据库查询次数最多;灵活性和维护性高;
- 部分合并,部分单独:上面的2种方式都有极端的操作和严重的缺点,灵活性和维护性高亦或者低;接下来这种方式是汲2种方式之所长,有选择的合并接口,比如一个业务完全可以一次得到的,就一次接口得到,业务完全不同的,就分开,这样可以提升维护性,也可以提升性能。
说点另外的:每一次http请求并不是都会进行数据库查询,所以这部分请求影响服务器性能很小,但是会影响一定的带宽,比如成千上万的用户像该接口发起请求,有些没有权限的都会直接返回404等,没有查询数据库;影响性能的主要分为IO密集型和CPU密集型,比如数据库查询就是典型的IO操作,视频解码是CPU密集型,通常CPU密集型会放到另外服务器
优化:(这里添加了另外2个主题)
model\theme.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static LocationA = "t-1"; static LocationE = "t-2"; static LocationF = "t-3"; static LocationH = "t-4";
static getThemes() { const names = `${Theme.LocationA},${Theme.LocationE},${Theme.LocationF},${Theme.LocationH}`; return Http.request({ url: "theme/by/names", data: { names: names, }, }); }
|
pages\home\home.js(发送请求)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| async initAllData() { const bannerB = await Banner.getHomeLocationB(); const grid = await Category.getHomeLocationC(); const activity = await Activity.getHomeLocationD(); const themes = await Theme.getThemes(); this.setData({ bannerB: bannerB, grid: grid, activity: activity, }); }
|
至此,接口合并完成,但是查看接口返回的数据可以发现,返回来是一个数组,显然这不能直接使用,下一小节中将解决这个问题;
函数式编程概述
在上一节中,接口返回的是一个数组,通过分析数组可以发现,每个数组都有一个names,names分别是t-1、t-2、t-3、t-4;这正好对应每一个主题,所以可以通过判断names来设置这个主题应该放在哪个位置上
通过find进行函数式编程
1 2 3 4 5 6 7
| const themes = await Theme.getThemes();
const themeA = themes.find((t) => t.name === "t-1"); const themeE = themes.find((t) => t.name === "t-2"); const themeF = themes.find((t) => t.name === "t-3"); const themeH = themes.find((t) => t.name === "t-4");
|
themes是一个数组,find去查询每一个数组,t这个参数代表数组项,判断这个数组项里面的name是不是t-1,是的话就返回;
当然,这里还不是最终态,这里写的t-1、t-2、t-3和t-4这些初看完全不知道用意,完全凭空出来的,这在后面难以维护,其实这些在theme这个类里面已经声明过了,可以直接在类里面取值,这个在类保存数据,对象保存状态这节会讲解到。
第三次作业(动态获取图片宽高)
最讨厌的作业….
图片有时候不是一尘不变的,比如在CMS传任意一张图片,都可以在前端保持比较好的展现
微信小程序媒体组件图片组件:https://developers.weixin.qq.com/miniprogram/dev/component/image.html
类可以保存数据,对象可以保存状态
思考过程:在函数式编程概述这节中,使用函数式编程的方式解决了for循环这种繁琐的写法,但是却又引出了新的问题,类似t-1、t-2、t-3和t-4这种硬编码,像是随意凭空出现的,而且这个硬编码已经在themes这个类已经定义了,所以直接取值就可以了,否则后期难以维护,那现在优化后的代码是这样的:
1 2 3 4 5 6 7
| const themes = await Theme.getThemes();
const themeA = themes.find((t) => t.name === Theme.LocationA); const themeE = themes.find((t) => t.name === Theme.LocationE); const themeF = themes.find((t) => t.name === Theme.LocationF); const themeH = themes.find((t) => t.name === Theme.LocationH);
|
好的,硬编码的问题解决了,现在有新的问题:如果我page这个页面下有一个新的方法,也需要用的themes这个接口返回来的数组,怎么办?第一时间想到的代码是这样:
1 2 3 4 5 6 7
| async test () { const themes = await Theme.getThemes(); const themeA = themes.find((t) => t.name === Theme.LocationA); const themeE = themes.find((t) => t.name === Theme.LocationE); const themeF = themes.find((t) => t.name === Theme.LocationF); const themeH = themes.find((t) => t.name === Theme.LocationH); }
|
这样写第一个是代码冗余,第二个是这样多请求了一次接口;那直接把themes存到页面page下的data,不就可以节省发送请求又可以解决冗余问题,这时候代码是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| async initAllData() { const bannerB = await Banner.getHomeLocationB(); const grid = await Category.getHomeLocationC(); const activity = await Activity.getHomeLocationD(); const themes = await Theme.getThemes(); this.data.themes= themes const themeA = themes.find((t) => t.name === Theme.LocationA); const themeE = themes.find((t) => t.name === Theme.LocationE); const themeF = themes.find((t) => t.name === Theme.LocationF); const themeH = themes.find((t) => t.name === Theme.LocationH); this.setData({ themeA, themeE, themeF, themeH, bannerB, grid, activity, }); }, async test() { if (this.data.themes.lenght === 0) { const themeA = this.data.themes.find((t) => t.name === Theme.LocationA); const themeE = this.data.themes.find((t) => t.name === Theme.LocationE); const themeF = this.data.themes.find((t) => t.name === Theme.LocationF); const themeH = this.data.themes.find((t) => t.name === Theme.LocationH); } },
|
可以看到这种方式只是解决了http请求多次的问题,但是没有解决冗余问题,还是得把themeA、themeE等这些找出来,那除了这种方式外,还有其他办法吗,答案是有的,比如可以使用缓存的方式(复杂,并且缓存数据是永久的)、app全局变量(大材小用),这些方式都只是解决了一个减少http请求的问题,并没有解决代码冗余问题;接下来就会用到一个知识点:类可以保存数据,不可以保存状态,但是对象可以保存状态;
1 2 3 4 5 6 7
| const t = new Theme(); t.a = 1; const t2 = new Theme(); t2.a = 1; Theme.a = 1; Theme.a = 2;
|
通过这段代码可以知道,t和t2的a是不同的,但是类Theme第一次赋值1,后面又复制2,最终输出的a肯定是2;
重构Theme获取
先来最终代码:
model\theme.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
import { Http } from "../utils/http"; class Theme { static LocationA = "t-1"; static LocationE = "t-2"; static LocationF = "t-3"; static LocationH = "t-4"; themes = []; async getThemes() { const names = `${Theme.LocationA},${Theme.LocationE},${Theme.LocationF},${Theme.LocationH}`; this.themes = await Http.request({ url: "theme/by/names", data: { names: names, }, }); } getHomeLocationA() { return this.themes.find((t) => t.name === Theme.LocationA); } getHomeLocationE() { return this.themes.find((t) => t.name === Theme.LocationE); } getHomeLocationF() { return this.themes.find((t) => t.name === Theme.LocationF); } getHomeLocationH() { return this.themes.find((t) => t.name === Theme.LocationH); } }
export { Theme };
|
pages\home\home.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
|
import { Activity } from "../../model/activity"; import { Banner } from "../../model/banner"; import { Category } from "../../model/category"; import { Theme } from "../../model/theme";
Page({
data: { themeA: null, themeE: null, bannerB: null, grid: [], activity: null, },
async onLoad(options) { this.initAllData(); }, async initAllData() { const bannerB = await Banner.getHomeLocationB(); const grid = await Category.getHomeLocationC(); const activity = await Activity.getHomeLocationD(); const themes = new Theme(); await themes.getThemes(); const themeA = themes.getHomeLocationA(); const themeE = themes.getHomeLocationE(); console.log(themeA);
this.setData({ themeA, themeE, bannerB, grid, activity, }); }, async test() { },
onPullDownRefresh() {},
onReachBottom() {},
onShareAppMessage() {}, });
|
思路:
- 在theme这个类中创建themes来存放主题数组,也属于对象值,存放状态,
- 然后getThemes不直接返回,而是存放在这个themes数组中;
- 这个时候getHomeLocationA和getHomeLocationE就可以直接在themes数组寻找对应的数组项并返回调用方;
- 在home.js中就需要实例化theme,并且调用getThemes()发送请求,
- 这个时候themes数组已经有数据了,那使用这个实例化的对象去调用getHomeLocationA和getHomeLocationE就能得到最终数据;
这里再去调用getHomeLocationA和getHomeLocationE就不会去发送请求了,这两个方法会自动去themes数组拿数据
但是这个也不是最终的方式,这只解决代码冗余这个问题,把实例化写到initAllData方法,在其他方法是不能使用的,所以需要把实例化直接写到onLoad这个生命周期函数上,这样其他的方法也可以一起调用;
pages\home\home.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
import { Activity } from "../../model/activity"; import { Banner } from "../../model/banner"; import { Category } from "../../model/category"; import { Theme } from "../../model/theme";
Page({
data: { themeA: null, themeE: null, themeF: null, themeH: null, bannerB: null, grid: [], activity: null, },
async onLoad(options) { this.theme = new Theme(); await this.theme.getThemes(); this.initAllData(); this.test(); }, async initAllData() { const bannerB = await Banner.getHomeLocationB(); const grid = await Category.getHomeLocationC(); const activity = await Activity.getHomeLocationD(); const themeA = this.theme.getHomeLocationA(); const themeE = this.theme.getHomeLocationE(); const themeF = this.theme.getHomeLocationF(); const themeH = this.theme.getHomeLocationH(); this.setData({ themeA, themeE, themeF, themeH, bannerB, grid, activity, }); }, async test() { const themeA1 = this.theme.getHomeLocationA(); console.log(themeA1); },
onPullDownRefresh() {},
onReachBottom() {},
onShareAppMessage() {}, });
|
主要改动:
- 统一Theme实例管理:在
onLoad中只创建一次Theme实例并获取数据
- 消除冗余实例化:
initAllData和test方法都使用同一个this.theme实例
- 避免重复HTTP请求:
getThemes()只在onLoad中调用一次
- 保持原有逻辑:其他功能保持不变
这样修改后,Theme实例只会创建一次,getThemes()方法也只会调用一次,完全消除了原来的代码冗余和重复HTTP请求问题。
第四次作业(SKU的概念和实现)
又是作业…..
- SPU:Standard Product Unit,标准产品单元,可以理解为一个产品型号,比如上面图片看到的iPhone 14 (A2884) 就是一个标准的产品单元,它属于生产制造过程的一个标准品,标准品在缺乏具体规格信息的时候是不能直接售卖的(除非这个产品系列只有一个规格)。
- SKU:Stock Keeping Unit,最小库存单元,也就是对应仓库中的一件商品,这个商品的规格信息在入库的时候就已经确定了的,因此是可以直接售卖的。
- SPU 和 SKU 的关系:SPU 是一个相对抽象的概念,而SKU 是具象化的 SPU,也就是在 SPU 基础上添加了一个可售卖完整的规格信息,从而能够让顾客明确知道拿到手的商品是什么样。以服装为例,服装的一个款式是一个 SPU,只有加上了尺码、颜色后才能成为一个 SKU
接下来继续完善每周上新模块,在上一节中,接口只是获取了每周上新的封面数据,接下来需要增加一个获取带有spu数据的每周上新,因为这个模块需要展示部分的spu,对于每周上新这个模块,我们单独抽象出一个组件。
- 新建spu-scroll组件
- 新建获取带有SPU的theme数据的接口
model\theme.js
1 2 3 4 5 6 7 8 9 10
| static async getThemesSpuByName(name) { return await Http.request({ url: `theme/name/${name}/with_spu`, }); } static async getHomeLocationESpu() { return Theme.getThemesSpuByName(Theme.LocationE); }
|
pages\home\home.js
1
| const themeESpuList = await Theme.getHomeLocationESpu();
|
是不是方法都需要加async和await
总结一句话:需要等待数据返回的时候就写async和await,不需要等待的时候就不加;
大白话:当你请求了接口数据,你是直接return回去的,没有后面需要写业务逻辑的,可以不写,后面有业务逻辑需要用到的,就需要写;
例子:像上面的获取带有SPU的theme数据的接口就可以优化成:
1 2 3 4 5 6 7 8 9 10
| static getThemesSpuByName(name) { return Http.request({ url: `theme/name/${name}/with_spu`, }); }
static getHomeLocationESpu() { return Theme.getThemesSpuByName(Theme.LocationE); }
|
把async和await去掉,因为他们本质上都是直接return回去数据,但是在homejs中就需要async和await接受数据,因为后面还需要setData;
同样的theme类中的getThemes方法为什么需要添加async和await,因为他 其实没有返回数据,还是赋值给了themes这个数组,所以需要加上。
接下来完善home的接口调用:
pages\home\home.js
1 2 3 4 5 6 7 8 9 10
| const themeE = this.theme.getHomeLocationE(); let themeESpu = []; if (themeE.online) { const data = await Theme.getHomeLocationESpu(); if (data) { themeESpu = data.spu_list.slice(0, 8); } }
|
这里先判断每周上新这个模块有没有商品数据,没有的话不请求接口数据,有的话,就只提取spu_list数组中8个数据
pages\home\home.wxml
1 2 3 4 5
| <s-scroll theme="{{themeE}}" spuList="{{themeESpu}}" wx:if="{{themeE.online}}" ></s-scroll>
|
第五次作业(SKU和SPU的实现思路)
又又是作业…..
一句玩笑话:我花钱了是来学习的,不是来写作业的,我懂了干嘛还需要学习…..
上面已经请求到带有SPU的theme数据的接口的数据,接下来完成spu-scroll这个自定义组件的骨架:
首先需要子组件接受数据:
components\spu-scroll\index.js
1 2 3 4
| properties: { theme: Object, spuList: Array, },
|
components\spu-scroll\index.wxml
1 2 3 4 5 6 7 8 9 10 11 12
| <view class="container"> <image src="{{theme.title_img}}" class="title"></image> <scroll-view> <block wx:for="{{spuList}}" wx:key="index"> <view> <image src="{{item.img}}"></image> <text>{{item.title}}</text> </view> </block> </scroll-view> </view>
|
components\spu-scroll\index.wxss
1 2 3 4 5
| .title{ width: 694rpx; height: 90rpx; }
|
至此,后端数据可以正常显示到前端了;但是这里少了价格,由于价格这里涉及到很多种情况,所以这里直接使用LinUI的Price组件,下节课介绍使用
LinUI Price价格组件应用
引入l-price组件:app.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { "pages": [ "pages/home/home" ], "window": { "navigationBarTextStyle": "black", "navigationBarTitleText": "Weixin", "navigationBarBackgroundColor": "#ffffff" }, "style": "v2", "componentFramework": "glass-easel", "sitemapLocation": "sitemap.json", "usingComponents": { "l-grid": "/miniprogram_npm/lin-ui/grid/index", "l-grid-item": "/miniprogram_npm/lin-ui/grid-item/index", "l-price": "/miniprogram_npm/lin-ui/price/index" }, "lazyCodeLoading": "requiredComponents" }
|
使用:components\spu-scroll\index.wxml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <view class="container"> <image src="{{theme.title_img}}" class="title"></image> <scroll-view> <block wx:for="{{spuList}}" wx:key="index"> <view> <image src="{{item.img}}"></image> <l-price color="#157658" value="{{item.price}}" l-unit-class="price-unit" l-value-class="price-value" ></l-price> <text>{{item.title}}</text> </view> </block> </scroll-view> </view>
|
外部样式类:components\spu-scroll\index.wxss
1 2 3 4 5 6 7 8 9 10 11 12
| .title{ width: 694rpx; height: 90rpx; } .price-unit{ font-size: 24rpx !important; } .price-value{ font-size: 48rpx !important; font-weight: 800 !important; }
|
小符号大价格的特点
周总结
无…..