23-练手案例
参考:http://tubie.gitee.io/wxapp/#/home
# 一,搭建项目
# 1,创建项目
项目的基本骨架,已创建好,已记录了对应的依赖,大家直接用骨架开发就OK,骨架如下:
大家拿到项目后,只需要解压,安装依赖,如下:
使用vscode打开myapp,如下:
把项目跑起来,如下:
效果如下:
# 2,配置vant组件库
所谓的组件库,就是别人已经封装好的组件,我们直接安装使用,这样的组件库有很多,我们学习一个移动端的组件库,叫vant,Vant文档地址:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/
vant也有很多的版本,针对不的vue版本,需要使用不同的vant版本,我们使用的版本是vant2.x。因为vant2.x是专门针对vue2的。提示如下:
要使用vant,你就按官方文档来就OK,如下:
安装vant,已经安装过了,上面我们使用npm i 时就把vant安装过了。安装过后,你要使用人家的组件,有几种方案:
- 自动按需引入(推荐) 项目中用到了什么组件就是引入什么组件
- 手动按需引入(麻烦)
- 直接引用所有组件(不推荐)
我们要使用vant,我们就自动的按需引入。步骤如下:
步骤一: 这个插件,也不需要大家安装,之前已安装过了
步骤二:也不需要我们配置,在骨架中已经配置好了
我们配置好了,如下:
步骤三:在代码中就可以使用vant提供的组件了
在Home组件中,使用之,如下:
浏览器测试之,如下:
由于我们这个案例中使用到的组件非常多,我们可以把使用组件的代码抽离到一个文件中,创建这个文件,如下:
在入口文件中,就需要引入这个js文件,如下:
在项目中,使用组件时,直接使用之,就OK,不需要再引入。如下:
效果如下:
我已经把项目中用到组件都找出来,直接copy到代码中,项目中使用到的Vant组件,如下:
上面的代码,大家直接copy,就OK了。如下:
import Vue from 'vue';
import {
Button,
Search,
Swipe,
SwipeItem,
Icon,
Tag,
DropdownMenu,
DropdownItem,
Empty,
List,
Cell,
Toast,
Tabbar,
TabbarItem,
Col,
Row,
Form,
Field,
Grid,
GridItem,
Dialog,
Lazyload,
Sticky,
GoodsAction,
GoodsActionIcon,
GoodsActionButton,
Sku,
Checkbox,
CheckboxGroup,
Card,
SubmitBar,
Stepper,
SwipeCell,
Tab,
Tabs,
Pagination,
Sidebar,
SidebarItem,
NoticeBar,
PullRefresh
} from 'vant';
Vue.use(Button)
Vue.use(Search)
Vue.use(Swipe)
Vue.use(SwipeItem)
Vue.use(Icon)
Vue.use(Tag)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Empty)
Vue.use(List)
Vue.use(Cell)
Vue.use(Toast)
Vue.use(Tabbar)
Vue.use(TabbarItem)
Vue.use(Col)
Vue.use(Row)
Vue.use(Form)
Vue.use(Field)
Vue.use(Grid)
Vue.use(GridItem)
Vue.use(Dialog)
Vue.use(Lazyload, {
loading: require('../assets/loading.gif')
})
Vue.use(Sticky)
Vue.use(GoodsAction)
Vue.use(GoodsActionIcon)
Vue.use(GoodsActionButton)
Vue.use(Sku)
Vue.use(Checkbox)
Vue.use(CheckboxGroup)
Vue.use(Card)
Vue.use(SubmitBar)
Vue.use(Stepper)
Vue.use(SwipeCell)
Vue.use(Tab)
Vue.use(Tabs)
Vue.use(Pagination)
Vue.use(Sidebar)
Vue.use(SidebarItem)
Vue.use(NoticeBar)
Vue.use(PullRefresh)
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
把Home中的代码修改之,如下:
浏览器效果如下:
# 3,实现tabbar
需求:
上面的5个按钮就对应了5个组件,就创建5个组件,如下:
配置应对路由,如下:
效果如下:
到此,路由就配置成功了。
然后,我们就需要配置tabbar了,在components下面创建一个组件,如下:
在App.vue中,引入,注册并使用之,如下:
浏览器中效果如下:
现在我要去写tabbar组件,这个组件在vant中提供好了,直接找到,copy之,tabbar对应的文档
地址:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/tabbar
使用一个路由模式的tabbar,如下:
配置代码如下,并且测试OK:
# 二,首页搜索框与轮播图
# 1,axios二次封装与API接口封装
在真实开发中,一般我们都会对axios进行二次封装,在此案例中,我已封装好,大家直接使用之,看一下封装的代码,如下:
上面的是对axios的二次封装,在真实开发中,我们一般会把调用接口封装成一个个方法,在组件中发ajax请求,就是去调用方法,这些方法,封装如下:
上面的封装的比较,用到时候,再去详细的解释。上面的代码大家也是直接使用之。
# 2,实现搜索框
需求:
上面的搜索框,在vant组件库中,是有现成的,直接找到,使用之。
地址:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/search
上面的搜索框,是一个假的搜索框,说白了,就是一个按钮,当点击按钮时,到达搜索界面。如下:
直接copy代码,如下:
# 3,实现轮播图
定义一个状态,表示轮播图数据,如下:
在Home组件中,引入GetHome接口,如下:
发送ajax请求,获轮播图数据,如下:
把轮播图数据,赋值给banner这个状态,如下:
在控制台,看一下,有没有值,如下:
实现轮播图,在vant组件库,也有现在的组件,直接copy之,如下:
到此,轮播图就实现了。
# 三,实现popup页面相关功能
# 1,创建popup页面
当我们点击搜索框时,就要去popup页面,就需要创建出这个一个页面,如下:
配置对应的路由,如下:
还需要指定路由的出口,如下:
当点击假的搜索框时,就需要跳到popup页面,如下:
测试之,如下:
# 2,绘制popup页面并创建popup页面的三个组件
在popup页面下面有三个组件,如下:
创建出上面的三个组件,如下:
在popup页面中引入三个组件,注册,使用之,如下:
效果如下:
上面的三个组件,只能显示一个,定义一个状态,控制显示哪一个组件,如下:
效果如下:
# 3,完成历史记录和热门搜索的结构和样式
分析一下popup页面如下:
先去把搜索框组件实现了,如下:
效果如下:
然后,我们去实现历史记录和热门搜索,在父组件中,获取数据,传递给子组件,在父中引入对应的接口,如下:
定义两个状态,用来装历史记录和热门搜索数据,如下(热门的单词写错了,应该是hotKeywordList):
发起ajax请求,获取数据,如下:
就可以给状态赋值了,如下:
使用defautlKeyworkd数据,并且给子组件传值,如下:
子组件接收之,如下:
在控制面板中,看一下,子组件有没有得到数据,如果得到了,再去渲染,查看如下:
一定是查看有数据了,再去渲染数据,开始渲染数据,在渲染数据之前,看一样整体的样式,
设置popup页面整体的样式,如下:
然后,就需要把接收的数据渲染出来,如下:
写一些样式,如下:
效果如下:
# 4,清空历史记录
当点击历史记录后面的垃圾桶图标时,就需要清空历史记录,给图标绑定点击事件,如下:
实现上面的方法,如下:
使用上面的状态,如下:
测试如下:
# 5,完成搜索页的提示组件数据渲染
需求:
只要输入框数据发生了变化,都会触发一个事件,叫input事件,绑定input事件,如下:
在对应的方法中,也需要调用一个接口,获取数据,导入接口,如下:
再去定义一个状态,这个状态,就是装我们搜索出来的数据,如下:
如果没有思路,你就点人家写,看人家是ajax请求,传递了什么参数,分析如下:
在input事件对应的方法,发起ajax请求,获取数据,给上面的状态赋值,如下:
不要着急去渲染数据,你先看一下,数据有还是没有,如下:
当我们实时搜索时,需要让历史记录和热门搜索隐藏掉,改变状态,如下:
测试之,如下:
数据也获取到了,组件也显示了,父把数据传递给子,子渲染数据,如下:
子进行接收,如下:
子渲染之,如下:
效果如下:
# 6,点击热门搜索或历史记录标签去产品列表(子传父)
需求:
历史记录和热门搜索中的tag是在子组件中,在子组件中,我们是可以知道你点击了哪一个tag,我们需要把tag传递给父组件,此时就是所谓的子传父,说到子传父,你必须知道有两种方式:1)父传递一个方法,子去调用方法 2)子去触发一个自定义事件,我们使用第2种方式。此时给tag绑定点击事件,如下:
实现上面的方法,如下:
在父中绑定自定义事件,如下:
实现方法,如下:
测试之,如下:
父就得到数据了,父需要把数据放到输入框上,如下:
测试之,如下:
不只需要让输入框显示你点击的那个tag,还需要实现搜索,如下:
搜索也需要调用接口,获取后端响应的数据,导入接口,如下:
不着急,你去看一下人空调用接口时,传递了哪些参数,如下:
上面是我们调用接口时,给后端传递的参数,准备对应的状态,如下:
搜索时,调用接口,传递数据,如下:
浏览器测试之,如下:
给状态赋值,如下:
查看状态,如下:
到此,就有数据了。然后的任务就是把数据渲染出来。
# 7,渲染商品分类排序模块
在父中,已经有数据了,如下:
然后把数据传递给商品列表组件,如下:
子接收之,如下:
看一下,商品列表组件长什么样,如下:
先实现如下模块,如下:
在vant中有现成的组件,使用之,地址如下:
https://vant-contrib.gitee.io/vant/v2/#/zh-CN/dropdown-menu
实现代码如下:
效果如下:
分类的options是没有的,我们已经把分类的option传递给子组件了,按理说,你子组件直接使用之,就OK了,我们为什么没有写,原因是传递的数据格式和options数据格式不一样,如下:
我们需要把我们得到的数据格式,转化成人家需要的数据格式,你可以在子中转化,也可以在父中转化,我就直接在父中转化好,再传递给子,代码如下:
在控制台查看如下:
在子组件中直接使用之,如下:
浏览器测试之,如下:
# 8,渲染商品列表数据
需求:
数据已经有了,我们把商品列表封装成一个组件,如下:
在MyProduct中引入,注册,使用之如下:
在子组件中,接收之,如下:
查看子组件是否获取数据,如下:
然后,子组件中写HTML,和CSS,渲染数据就OK了。
渲染数据的代码,不写,如下:
对应的样式如下:
效果如下:
如果没有商品,给出一个提示组件,如下:
效果如下:
# 9,排序实现
需求:
再看一下分类的数据结构,如下:
为什么默认选中了全部,如下:
因为,使用v-model绑定了如下的数据:
categoryVal的值是0,在options中选项中,全部选项的value也是0,所以说,默认就选中了全部选项。如果说categoryVal不是0,我们就需要处理,如下:
效果如下:
当点击了价格排序,或分类排序,说白了,还需要发送ajax请求,获取新的数据,你点击了谁,数据是在子组件中,你发送ajax请求,是在父组件中,我们点击谁,需要把点击的那个数据传递给父,父得到数据后,重新发送ajax,现在又是所谓的子传父。我们使用自定义事件方案,使用子传父。给价格和分类绑定change事件,如下:
实现上面的方法,如下:
有了数据,就需要传递给父,如下:
在父的身上绑定自定义事件,如下:
当上面的自定义事件发生,触发下面的方法:
测试一下:
# 10,其它一些小功能补充
补充一些小的功能,下拉刷新,如下:
也是vant中封装好的一个组件,直接使用之,如下:
默认进入的是首页面,点击了首页面上面的假的搜索框就进入到了popup页面,如果在popup页面中点击了取消,就需要返回到首页面,如下:
给取消绑定事件,发下:
实现对应的方法,如下:
还有一个小功能,给列表提示组件绑定点击事件,一点击要去商品列表页面,如下:
绑定事件如下:
在父上绑定自定义事件,如下:
测试OK。
# 11,过渡动画实现
什么是过滤动画:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
/* 默认样式 */
width: 200px;
height: 200px;
background-color: gold;
/* CSS3中的过渡动画 */
/* all表示指对所有的突变样式都要产生过渡 */
/* 2s表示过渡时间 */
/* ease 是缓冲曲线 */
transition: all 2s ease;
}
.box:hover {
/* 突变样式 */
width: 400px;
height: 400px;
background-color: pink;
}
</style>
<!--
Vue中的过渡动画
条件渲染:
v-if v-show 控制一个DOM元素或组件显示隐藏
如果没有动画,也非常生硬
动态组件:
组件高级中讲
也可以添加过渡动画
根组件:
也可以添加过渡动画
Vue外向暴露了一些类,利用这些class就可以完成过渡动画。
v-enter-from:进入过渡开始的状态
v-enter-active:进入过渡生效时状态
v-enter-to:进入过渡结束的状态
v-leave-from: 离开过渡的开始状态
v-leave-active: 离开过渡生效时状态
v-leave-to: 离开过渡结束的状态
-->
</head>
<body>
<div class="box">
</div>
</body>
</html>
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
在Vue中也提供过动动画,使用一下,如下:
<template>
<div>
<button @click="show = !show">点击显示或隐藏下面的DIV</button>
<!-- transition是vue中内置的全局组件,在所有有其它组件中都可以使用 -->
<transition name="ml">
<div class="box" v-show="show">
<h3>我是H3标题</h3>
</div>
</transition>
</div>
</template>
<script>
export default {
name: "Topic",
props: [],
data() {
return {
show: false,
};
},
methods: {},
};
</script>
<style lang="less" scoped>
.box {
width: 400px;
height: 200px;
background-color: pink;
}
h3 {
color: white;
}
/* ------------- vue的过渡动画类 */
/* --------- 进入阶段 */
/* ---开始进入 */
// .v-enter {
.ml-enter {
opacity: 0;
}
/* ---过渡 */
// .v-enter-active {
.ml-enter-active {
transition: all 5s ease;
}
/* ---进入结束 */
// .v-enter-to {
.ml-enter-to {
opacity: 1;
}
/* --------- 离开阶段 */
/* ---开始进入 */
// .v-leave {
.ml-leave {
opacity: 1;
}
/* ---过渡 */
// .v-leave-active {
.ml-leave-active {
transition: all 5s ease;
}
/* ---进入结束 */
// .v-leave-to {
.ml-leave-to {
opacity: 0;
}
</style>
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
效果如下:
然后给项目添加过渡动画。如下:
先实现遮罩层,给遮罩层,首页,popup页面添加transition,如下:
遮罩层样式,如下:
遮罩层本身看不见,点击按钮,遮罩层就显示,淡入,类名如下:
首页面的类名如下:
重点是popup页面的类名,如下:
参考代码如下(轮播图没有被遮罩层盖住,自己看一下问题):
.zhezhao {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background-color: #000;
}
/* ----------- 遮罩层的类名 */
.myfade1-enter,
.myfade1-leave-to {
opacity: 0;
}
.myfade1-enter-active,
.myfade1-leave-active {
transition: opacity 0.5s linear;
}
.myfade1-enter-to,
.myfade1-leave {
opacity: 1;
}
/* ----------- 首页面的类名 */
.myfade-leave {
opacity: 1;
}
.myfade-leave-active {
transition: opacity 0.5s linear;
}
.myfade-leave-to {
opacity: 0;
}
/* ----------- popup页面的类名 */
.slide-enter,
.slide-leave-to {
/* %是相对于popup页面的宽度来说的 */
right: -100%;
}
.slide-enter-active,
.slide-leave-active {
transition: right 0.5s linear;
}
.slide-enter-to,
.slide-leave {
right: 0;
}
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
效果如下:
给popup页面也添加一个层级,如下:
# 12,回到顶部
需求:
把它封装成一个组件,如下:
在popup页面中,引入,注册,使用之,如下:
效果如下:
默认情况下,这个按钮是不能显示,把状态变成false,如下:
什么时候让它显示呢?肯定是你向下滚动,滚动一定的距离,就可以显示了,直接给window中绑定scroll事件,如下:
浏览器测试之,如下:
使用防抖函数进行降频,如下:
防抖函数实现(自行研究):
debounce(func, wait = 0) {
if (typeof func !== "function") {
throw new TypeError("need a function arguments");
}
let timeid = null;
let result;
return function() {
let context = this;
let args = arguments;
if (timeid) {
clearTimeout(timeid);
}
timeid = setTimeout(function() {
result = func.apply(context, args);
}, wait);
return result;
}
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
效果如下 :
假如滚动了500px, 就需要按钮显示,低于500px,隐藏,如下:
效果如下:
当点击按钮,需要回到顶部,如下:
我们上面的回到顶部,是没有动画效果的,你可以相办法,添加一个动画效果。
测试之,OK。
# 四,用户模块实现
# 1,页面绘制
需求:
先绘制头部,如下:
结构如下:
对应的样式,如下:
.van-row {
padding: 0.2rem 4%;
background-color: #333;
// background: #410303;
color: #fff;
.van-col {
line-height: 0.7rem;
font-size: 0.15rem;
img {
width: 0.7rem;
border-radius: 50%;
display: block;
}
&:last-child {
text-align: right;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
效果如下:
然后,需要绘制9宫格,准备对应的数据:
数据大家直接copy,如下:
gridArr: [{
icon: "label-o",
text: "我的订单"
},
{
icon: "bill-o",
text: "优惠劵"
},
{
icon: "goods-collect-o",
text: "礼品卡"
},
{
icon: "location-o",
text: "我的收藏"
},
{
icon: "flag-o",
text: "我的足迹"
},
{
icon: "contact",
text: "会员福利"
},
{
icon: "aim",
text: "地址管理"
},
{
icon: "warn-o",
text: "账号安全"
},
{
icon: "service-o",
text: "联系客服"
},
{
icon: "question-o",
text: "帮助中心"
},
{
icon: "smile-comment-o",
text: "意见反馈"
},
],
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
绘制9宫格,在Vant中有现在的组件,如下:
# 2,登录模态框绘制
需求:
从vant官网中,copy对应的组件,如下:
准备了两个状态,一个方法,如下:
效果如下:
书写样式,把模态框定位到中间位置,如下:
对应的样式,如下:
.login_modal {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 99;
section {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.van-form {
width: 90%;
height: 205px;
position: absolute;
top: 35vh;
left: 50%;
margin-left: -45%;
padding: .1rem;
box-sizing: border-box;
background-color: #fff;
border-radius: 3%;
.van-button {
background-color: darkred;
border: 0;
}
}
}
.form_bottom {
text-align: center;
color: #ccc;
}
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
效果如下:
添加一个遮罩层,如下:
效果如下:
默认情况下,模态框是不能显示的,点击了登录,才能显示,定义一个状态来控制它,如下:
使用状态,如下:
效果如下:
点击登录,就需要让模态框,显示出来,给顶部绑定点击事件,如下:
实现对应的方法,如下:
效果如下:
点击遮罩层,关闭模态框,给遮罩层绑定事件,实现对应的方法,如下:
# 3,实现登录功能
肯定需要调用接口,导入进来,如下:
调用接口,需要传递参数如下:
在onSubmit中调用接口,如下:
服务器响应如下:
服务器响应的内容中有userInfo,userInfo是用户信息,用户信息中有头像,有生日,有性别,有ID,有昵称,有用户名。除了用户信息之外,还有token,token是令牌,后面调用其它接口时,需要带上令牌,没有令牌,有些接口是不能调用的。把令牌和用户令牌存储到本地,如下:
测试之,如下:
头像之所以不显示,是由于后端接口给的链接不对。
刚才明明登录上,当刷新时,头部又变成如下的样子了,如下:
之前登录了,有token,把token存储到本地了,如果本地中有token表示你已登录,再去定义一个状态,如果登录了,这个状态就是true,如下:
再刷新时,数据就从本地获取了,如下:
之前登录成功了,也需要把标识变成true,如下:
根据isLogined状态,决定显示什么样的图标,如下:
当点击x时表示退出登录。
# 4,退出登录
当点击x时表示退出登录,给小x绑定一个点击事件,如下:
实现方法,如下:
效果如下:
把alert换成confirm,如下:
点击确认,肯定是要退出登录,你点击了确认,你要干什么,你把localStroage中的数据清除掉,代码如下:
测试之如下:
给宫格也绑定点击事件,如下:
实现对应的方法,如下:
测试如下:
# 5,导航守卫
所谓的导航守卫,就是指有些路由,只能在登录的情况下才能访问,如你没有登录,你是不能访问购物车的。此时就需要实现守卫。守卫的代码和路由相关,肯定要写在路由模块中,如下:
测试如下:
上面,我们去购物车,由于没有登录,人家拦截了,提示需要登录,放行到登录页,开始登录,登录成功后,去哪????
答:为了更好的体验,登录成功后,最好去购物车。这个过程就是返回上一页。
# 6,返回上一页面
记录是否返回上一页,很多地方,都需要使用,我们就把这个状态,放到vuex中,如下:
在导航守卫中,去改变状态,如下:
不要忘了,导入仓库,如下:
控制台中测试之,如下:
开始登录,登录成功后,要根据仓库中的状态,来确定是否需要返回上一页面,如下:
测试,完美,so easy~,测试如下:
解决一个警告,如下:
解决之,如下:
# 五,首页面其它内容渲染(自学)
# 1,首页面分析
首页面模块,如下:
ajax获取到的数据,如下:
定义不同模块中的中的状态,
ajax,获取到数据,给状态赋值:
查看状态,确保有数据,如下:
# 2,宫格数据渲染
创建宫格组件,如下:
在首页面中,引入,注册,使用之,并传递数据,如下:
效果如下:
宫格组件接收数据,并渲染之,如下:
# 3,品牌制造商实现
创建品牌机制商组件,如下:
在首页面中,引入,注册,使用之,并传递数据,如下:
组件接收数据,并渲染之,如下:
参考样式如下:
<style lang="less"scoped>.brandlist {
background: #fff;
margin-top: 20px;
margin-bottom: 20px;
}
.title {
text-align: center;
height: 50px;
line-height: 50px;
font-size: 0.2rem;
}
ul {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
li {
width: 49%;
margin-bottom: 2px;
position: relative;
h4 {
position: absolute;
left: 0.1rem;
top: 10px;
font-weight: normal;
}
p {
position: absolute;
left: 0.1rem;
top: 40px;
color: darkred;
}
}
}
</style>
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
效果如下:
# 4,新品首发实现
创建新品首发组件,如下:
在首页面中,引入,注册,使用之,并传递数据,如下:
内容被tabbar盖住了,添加一个盒子撑起来,如下:
效果如下:
子组件接收数据,并渲染之,如下:
效果如下:
# 5,人气推荐
创建人气推荐组件,如下:
在首页面中,引入,注册,使用之,并传递数据,如下:
效果如下:
子组件接收数据,并渲染之,如下:参考样式如下:
<style lang="less" scoped>
.personrec {
background: #fff;
margin-top: 20px;
margin-bottom: 20px;
}
.title {
text-align: center;
height: 50px;
line-height: 50px;
font-size: 0.2rem;
}
ul {
li {
background: #fafafa;
margin-bottom: 5px;
overflow: hidden;
.image {
float: left;
width: 30%;
}
.text {
float: left;
width: 70%;
height: 100%;
h4 {
height: 40px;
line-height: 40px;
font-weight: normal;
font-size: 0.18rem;
}
h5 {
height: 30px;
line-height: 30px;
font-weight: normal;
}
P {
margin-top: 10px;
color: darkred;
font-size: 0.13rem;
}
}
}
}
</style>
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
效果如下:
# 6,专题推荐
创建专题推荐组件,如下:
在首页面中,引入,注册,使用之,并传递数据,如下:
效果如下:
子组件接收数据,并渲染之,如下:
样式参考如下:
<style lang="less" scoped>
.spetopic {
margin-top: 20px;
background-color: #fff;
margin-bottom: 20px;
padding: 0 8px;
.title {
text-align: center;
height: 50px;
line-height: 50px;
font-size: 0.2rem;
}
.van-swipe-item {
margin-right: 15px;
height: auto;
padding-bottom: 10px;
background-color: #fff;
h2 {
line-height: 40px;
font-size: 0.16rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 90%;
font-weight: normal;
}
p {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 90%;
font-size: 0.14rem;
}
img {
width: 100%;
height: 200px;
}
}
}
</style>
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
效果如下:
# 7,分类模块实现
在首页面中,直接使用之前封装的ProductHome组件,如下:
效果如下:
# 8,回到顶部
在着页面中引入回到顶部组件,如下:
效果如下:
# 六,专题页实现(自学)
# 1,专题页实现
需求:
我们调用接口,都是带着token去请求,我们是在axios的二次封装中,还着token去请求的,如下:
分析传参,如下:
定义出四个状态,如下:
先去调用接口,获取专题页的数据,如下:
状态就有数据,如下:
然后,写HTML,把数据渲染出来,如下:
效果如下:
最后面还需要分页,分页组件中vant中是现在的,直接copy之,如下:
效果如下:
当我们点击了下一页,触发change事件,如下:
实现上面方法,点击查看状态,如下:
点击下一页,只需要重新发送ajax请求,如下:
测试分页OK,完美。
点击下一页,发送完请求后,需要返回顶部,如下:
测试OK。如下:
然后,我们需要写一点样式,如下:
效果如下:
对应的样式,如下:
<style lang="less"scoped>.van-pagination__item {
color: darkred;
}
.van-pagination__item--disabled {
color: #333;
}
.van-pagination__page-desc {
display: none;
}
.zhuanti {
padding-bottom: 100px;
.box {
background-color: #fff;
text-align: center;
padding-bottom: 10px;
margin-bottom: 20px;
.pic {
width: 100%;
}
.title {
font-size: 18px;
margin: 10px 0;
}
.tip {
font-size: 16px;
margin: 20px 0;
color: rgb(31, 31, 31);
overflow: hidden;
text-overflow: ellipsis;
/* 弹性伸缩盒子模型显示 */
display: -webkit-box;
/* 限制在一个块元素显示的文本的行数 */
-webkit-line-clamp: 1;
/* 设置或检索伸缩盒对象的子元素的排列方式 */
-webkit-box-orient: vertical;
}
.price {
color: rgb(155, 0, 0);
margin-bottom: 10px;
}
}
}
/deep/.van-pagination__page-desc {
display: none;
}
/deep/.van-pagination__item {
color: darkred;
}
</style>
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
# 七,分类页面数据渲染(自学)
# 1,分类页面数据渲染
需求:
# 八,点击首页宫格进入二级分类(自学)
# 1,实现二级分类模块
需求:
# 九,详情页实现(重点)
# 1,点击跳转到详情页面
看一下,点击哪些地方,可以去详情页面,如下:
居家下面的所有模块,点击某个商品,都是去商品详情页面了。除了首页面可以去详情之外,popup页面,也可以去详情,如下:
详情页面如下:
# 2,创建详情页面并配置路由
创建对应的页面组件,如下:
配置一下路由,如下:
测试一下,路由通不通,如下:
给第1小节中说的商品,绑定点击事件,点击时,去详情页面,如下:
测试OK,点击商品确实可以去商品详情页面。
# 3,控制Tabbar显示与隐藏
tabbar在商品详情页面是不显示的,如下:
但是我们的商品详情页面是有tabbar的,如下:
我们要想一下,为什么我们的商品详情页面有tabbar?答:原因是我们tabbar是在App组件中配置如下:
有些页面是有tabbar的,有些页面是没有tabbar的,控制tabbar显示与隐藏,可以在路由规则中通过设置元信息,通过元信息进行控制,如下:
现在的我的路由规则如下:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Store from '../store'
const Home = () => import('../view/Home.vue')
Vue.use(VueRouter)
// 下列三行代码是方式路由跳转时点击多次报出错误,进行错误抛出
//获取原型对象上的push函数
const originalPush = VueRouter.prototype.push
//修改原型对象中的push方法
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
const routes = [{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
// meta表示路由的元信息
meta: {
ifShowTabbar: true
},
component: Home,
children: [{
path: '/home/popup',
name: "Popup",
component: () => import("../view/Popup"),
}]
},
{
path: '/topic',
name: 'Topic',
// meta表示路由的元信息
meta: {
ifShowTabbar: true
},
component: () => import("../view/Topic"),
},
{
path: '/category',
name: 'Category',
// meta表示路由的元信息
meta: {
ifShowTabbar: true
},
component: () => import("../view/Category"),
},
{
path: '/cart',
name: 'Cart',
// meta表示路由的元信息
meta: {
ifShowTabbar: true
},
component: () => import("../view/Cart"),
},
{
path: '/user',
name: 'User',
// meta表示路由的元信息
meta: {
ifShowTabbar: true
},
component: () => import("../view/User"),
},
{
// 动态路由
path: '/productdetail/:id',
name: 'ProductDetail',
// meta表示路由的元信息
meta: {
ifShowTabbar: false
},
component: () => import("../view/ProductDetail"),
},
]
const router = new VueRouter({
routes,
})
// 前置守卫
// to去哪
// from来自哪
// next是放行 不调用next不会放行
router.beforeEach((to, from, next) => {
let token = localStorage.getItem("usertoken");
// 现在只守卫购物车
// console.log("to:", to);
if (to.path === "/cart") {
if (token) {
next(); // 放行
} else {
Vue.prototype.$toast.loading({
message: "请先登录"
})
setTimeout(() => {
next("/user")
// 修改仓库中的状态
Store.commit("changeIfgoBack", true)
}, 500)
}
}
// 不是去购物车
next();
})
export default router
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
如何获取元信息呢?
答:$route.meta.ifShowTabbar
在App组件中,使用元信息,如下:
测试之,如下:
# 4,获取详情页面数据
上面,点击某个商品,去商品详情页面,你是把ID传递给了商品详情页面,在商品详情页面中可以获取ID,如下:
测试之,如下:
然后,可以发送ajax请求,获取商品详情页面的数据,如下:
测试之,如下:
分析详情页面需要定义哪些状来接收数据,如下:
在控制台中,查看一下,如下:
接下来的任务,就是写HTML和CSS,渲染数据。
# 5,Navbar和Tips组件实现
Navbar就间商品的头部,如下:
创建NavBar组件如下:
参考代码如下:
<template>
<van-sticky>
<div class="navbar">
<van-icon class="navcross" name="arrow-left" @click="goBack" />
<span class="nav-title">商品详情</span>
</div>
</van-sticky>
</template>
<script>
export default {
name: 'Navbar',
methods: {
async goBack() {
await this.$router.go(-1)
}
}
}
</script>
<style scoped>
.navbar {
position: relative;
width: 100%;
background-color: #fff;
border-bottom: 1px solid #ccc;
height: .45rem;
line-height: .45rem;
}
.navcross {
position: absolute;
padding-left: .2rem;
margin-top: .13rem;
color: darkred;
font-size: .2rem;
}
.nav-title {
display: block;
font-size: .18rem;
text-align: center;
}
</style>
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
在商品详情页面中,引入,注册,使用之,如下:
效果如下:
Tips组件如下:
创建Tips组件如下:
参考代码如下:
<template>
<van-row gutter="20">
<van-col span="8">
<van-icon color="darkred" name="checked" />7天无理由退货
</van-col>
<van-col span="8">
<van-icon color="darkred" name="checked" />24小时快速退款
</van-col>
<van-col span="8">
<van-icon color="darkred" name="checked" />满88元免邮费
</van-col>
</van-row>
</template>
<script>
export default {
name: 'Tips'
}
</script>
<style lang="less" scoped>
.van-col {
font-size: .13rem;
height: .3rem;
align-items: center;
display: flex;
justify-content: center;
align-items: center;
.van-icon {
margin-right: .04rem;
}
}
</style>
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
在商品详情页面中,引入,注册,使用之,如下:
效果如下:
# 6,实现详情页面中的轮播图
需求:
详情页面的数据,已经有了,如下:
使用vant中轮播图组件,渲染数据,如下:
效果如下:
# 7,详情页面其它数据渲染
先渲染商品基本信息,如下:
结构如下:
效果如下:
绘制选择规格,如下:
也是vant中,现在的组件,直接使用之,如下:
效果如下:
然后,绘制商品的参数,如下:
商品参数的数据,也是已经获取了,如下:
结构如下:
效果如下:
再去渲染商品的富文本如下:
数据也是现成的,如下:
对应的结构如下:
效果如下:
然后,渲染常见问题,如下:
数据也是现成的,如下:
结构如下:
效果如下:
最后渲染相关的产品,如下:
获取某个商品相关的商品,也需要调用接口,如下:
查看状态,如下:
相关商品之前已经封装成了对应的组件,引入,注册,使用之,如下:
效果如下:
把回到顶部组件,引入,注册,使用之,如下:
效果如下:
再看一下,还有什么,如下:
这个组件,在vant中,也是封封装现成的,我们不直接使用,我们再单独封装一下,创建一个组件,如下:
参考代码如下:
<template>
<van-goods-action>
<van-goods-action-icon :icon="ifSave ? 'star' : 'star-o'" :text="ifSave ? '已收藏' : '收藏'" :color="ifSave ? 'darkred' : '#666'" @click="ifSave=!ifSave" />
<van-goods-action-icon icon="cart-o" text="购物车" :badge="badge" @click="gotoCart" />
<van-goods-action-button type="danger" text="立即购买" @click="buyNow" />
<van-goods-action-button type="warning" text="加入购物车" @click="addToCart"/>
</van-goods-action>
</template>
<script>
export default {
name: "MyGoodsAction",
props: ['badge'],
data() {
return {
ifSave: false
}
},
methods: {
buyNow() {
this.$toast('您所在地区尚不支持购买')
},
// 点击加入购物车
addToCart() {
this.$emit('addToCart')
},
// 跳转到购物车
gotoCart() {
this.$router.push('/cart')
}
}
}
</script>
<style>
</style>
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
在商品详情页面中,引入,注册,使用之,如下:
效果如下:
到此,商品详情页面的数据就渲染完毕了,详情页面的样式,不写,直接copy了,如下:
详情页面的样式如下(大家直接copy):
<style lang="less" scoped>
.context {
padding-bottom: 50px;
}
.info {
background: #fff;
text-align: center;
padding: 15px 0;
h3 {
font-weight: normal;
font-size: .2rem;
line-height: 30px;
}
p {
color: #999;
font-size: .14rem;
line-height: 30px;
}
div {
color: darkred;
font-size: .14rem;
line-height: 30px;
}
}
.showSku {
margin-bottom: 20px;
border-top: 1px solid #ccc;
}
.attribute {
background: #fff;
padding: 5px 2%;
overflow: hidden;
h4 {
font-weight: normal;
font-size: .2rem;
margin-top: 20px;
margin-bottom: 15px;
padding-left: .1rem;
}
ul {
padding: 0 .1rem;
li {
background: #efefef;
height: 40px;
line-height: 40px;
margin-bottom: 10px;
border-radius: 4px;
display: flex;
justify-content: space-between;
span {
width: 24%;
color: #999;
text-align: right;
}
section {
flex: 1;
padding-left: .1rem;
// 最多显示一行的文字,多余的内容会被省略
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
// 这里要深度修改样式,如果不加/deep/,样式在控制台上看不到,也就是没有给这个div加上样式,或者把style中的scoped去掉,也可加上样式。
/deep/.product_image {
img {
width: 100%;
display: block;
}
}
.line_title {
width: 100%;
height: 40px;
position: relative;
font-size: .18rem;
background: #fff;
span {
width: 50%;
height: 2px;
background: #ccc;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
h3 {
width: 30%;
height: 40px;
font-weight: normal;
font-size: .18rem;
background: #fff;
position: absolute;
text-align: center;
line-height: 40px;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
}
.issue {
background: #fff;
.issue_content {
h3 {
font-weight: normal;
height: 40px;
padding-left: .2rem;
line-height: 40px;
font-size: .16rem;
position: relative;
padding-right: .1rem;
&::before {
content: "";
width: 4px;
height: 4px;
background: darkred;
border-radius: 50%;
position: absolute;
left: 8px;
top: 50%;
margin-top: -2px;
}
}
p {
font-size: .14rem;
padding-left: .2rem;
padding-right: .1rem;
color: #666;
}
}
}
.van-goods-action {
z-index: 9999;
}
.van-sku-container {
min-height: auto;
}
// 轮播图右下加角标
.custom-indicator {
position: absolute;
color: #fff;
right: 6px;
bottom: 6px;
padding: 6px 12px;
font-size: 12px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.3);
}
</style>
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
效果如下:
# 8,SKU弹出层实现
什么时候弹出sku弹出层?
需要定义一个状态,控制弹出层的显示与否,如下:
写弹出层,如下:
对应的状态,需要注意sku的数据结构,大家可以查看有文档,如下:
给选择规格绑定点击事件,点击时,需要让sku弹出层显示,如下:
测试之,如下:
点击加入购物车,也是需要显示的,找到加入购物车按钮如下:
在父中绑定对应的自定义事件,如下:
实现方法,在方法,就需要显示sku弹出层,如下:
测试之,如下:
到此,控制弹出层的显示与否就实现了。
然后,渲染弹出层中的数据,准备数据如下:
效果如下:
# 9,渲染购物车中商品数量
需求:
定义一个状态,保存购物车中商品的数量,如下:
在登录条件下,获取当前商品中购物车的数量,如下:
查看状态,如下:
把状态传递给子组件,子组件显示之,如下:
子需要需要接收之,如下:
效果如下:
# 10,商品加入购物车
需求:
添加购物车,也需要调用接口,引入接口,如下:
看一下,你需要给服务器传递什么参数,如下:
当弹出层显示了,去点击加入购物车,代码如下:
服务器响应的数据如下:
也就是说,我们添加商品到购物车成功了,需要把sku关闭,给出提示,还需要获取新的购物车商品数量,代码如下:
测试之,如下:
点击购物车按钮,就要去购物车模块了,如下:
找到这个按钮,如下:
测试之,如下:
# 十,购物车模块实现(重点)
# 1,分析
# 2,购物车数据的渲染
获取购物车数据,如下:
得到结果如下:
定义状态,给状态赋值,如下:
控制台看一下状态,如下:
渲染出购物车的静态页面,如下:
<template>
<div>
<Tips></Tips>
<van-checkbox-group v-model="result" v-if="cartList.length > 0">
<van-checkbox
class="chx"
:name="item.product_id"
v-for="item in cartList"
:key="item.id"
>
<van-swipe-cell>
<van-card
:num="item.number"
:price="item.retail_price.toFixed(2)"
:title="item.goods_name"
:thumb="item.list_pic_url"
/>
<template #right>
<van-button
square
text="删除"
type="danger"
class="delete-button"
/>
</template>
</van-swipe-cell>
<!-- 步进器 -->
<van-stepper
min="1"
max="100"
v-show="isEditClicked"
v-model="item.number"
/>
</van-checkbox>
</van-checkbox-group>
<!-- 购物车为空时 -->
<div v-else>
<img src="../assets/custom-empty-image.jpg" alt="" />
<h3>购物车竟然是空的</h3>
<p>再忙,也要记得买点什么犒赏自己哟</p>
</div>
<!-- 底部结算区域 -->
<van-submit-bar
:price="cartTotal.checkedGoodsAmount * 100"
button-text="提交订单"
@submit="onSubmit"
>
<van-checkbox v-model="checkedAll">全选</van-checkbox>
<template #tip v-if="cartList.length > 0">
累计共<span>{{ cartTotal.checkedGoodsCount }}</span
>件商品,可点击<van-button
type="default"
size="small"
round
@click="isEditClicked = !isEditClicked"
>{{ isEditClicked ? "完成编辑" : "编辑" }}</van-button
>按钮进行商品数量修改
</template>
</van-submit-bar>
</div>
</template>
<script>
import Tips from "../components/Tips.vue";
import { GetCartData } from "../request/api";
export default {
name: "Cart",
components: {
Tips,
},
props: [],
created() {
GetCartData().then((res) => {
console.log("res:", res);
// 调用方法,给状态赋值
if (res.errno == 0) {
this.totalFn(res.data);
}
});
},
computed: {
checkedAll() {},
},
data() {
return {
cartList: [], // 购物车列表数据
result: [], // 选中商品
cartTotal: {
// 商品信息
checkedGoodsAmount: 0,
checkedGoodsCount: 0,
},
isEditClicked: false, // 是否处于编辑模式
};
},
methods: {
// 提交订单
onSubmit() {},
totalFn(data) {
let { cartList, cartTotal } = data;
this.cartList = cartList;
this.cartTotal = cartTotal;
// 清空result数组
this.result = [];
// 判断checked == 1,就把product_id加入到result数组中
this.cartList.map((val) => {
if (val.checked == 1) {
this.result.push(val.product_id);
}
});
},
},
};
</script>
<style lang="less" scoped>
</style>
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
效果如下:
样式直接copy,如下:
<style lang="less" scoped>
.van-submit-bar {
margin-bottom: 50px;
.van-submit-bar__tip {
display: flex;
align-items: center;
}
}
.chx {
padding: 10px 4%;
background: #fff;
border-bottom: 1px solid #ccc;
/deep/.van-checkbox__label {
flex: 1;
}
}
.van-card__price {
color: darkred;
}
.van-stepper {
text-align: right;
}
.delete-button {
height: 100%;
}
.custom-image {
margin-top: 20vh;
}
.empty {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 220px;
height: 300px;
overflow: hidden;
text-align: center;
img {
width: 90%;
border-radius: 50%;
}
h3 {
margin: 10px 0;
}
}
.van-card__title {
font-size: .16rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
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
效果如下:
到此,购物车列表数据就渲染完毕了。
# 3,选中与取消选中
需求:
换到商品,绑定点击事件,如下:
实现上面的方法,如下:
看一下,购物车中的一个商品数据结构,如下:
然后,就需要调用接口,改变一个商品是否选中,如下:
调用接口,如下:
服务器给出响应如下:
得到数据,需要重新渲染购物列表,如下:
测试如下:
需要再强调一下,当前状态如下:
# 4,全选或取消全选实现
需求:
当所有商品都选中了,需要让全选按钮也选中,如下:
checkedAll使用计算属性,如下:
测试之,如下 :
然后,我们需要点击全选和反选,如下:
测试OK,如下:
# 5,加1减1实现
如果处于编辑模式,选中一个或选就不能使用了,如下:
处理之,如下:
测试之,如下:
实现步进器,给步进器,绑定事件,如下:
实现上面方法,如下:
浏览器中测试之,如下:
也需要调用接口,引入接口,调用之,如下:
测试之,如下:
# 6,删除实现
需求:
给删除按钮,绑定点击事件,如下:
实现对应的方法,如下:
测试之,如下:
给空的购物车添加一个类,如下:
效果如下: