09-Vue3.x 码路严选
# 一,项目的搭建
项目参考:http://tubie.gitee.io/maluyanxuan
仓库地址:https://gitee.com/tubie/20221222maluyanxuan.git
已经注册的用户名和密码:
用户名:18338812345
密码:123456
# 1,创建远程仓库和项目
创建远程仓库,如下:
创建一个项目,如下:
把项目初始化成一个仓库,如下:
本地仓库与远程仓库进行关联,如下:
看一下git忽略文件,如下:
先进行本地仓库管理,如下:
把代码推送到远程仓库,如下:
把仓库开源,如下:
# 2,安装项目的依赖
用到的依赖:
"dependencies": {
"vue": "^3.2.41",
"@vant/area-data": "^1.3.2",
"axios": "0.27.2",
"js-md5": "^0.7.3",
"pinia": "^2.0.27",
"vant": "^4.0.0",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@types/node": "^18.11.9",
"@vitejs/plugin-vue": "^3.2.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"typescript": "^4.6.4",
"unplugin-vue-components": "^0.22.11",
"vite": "^3.2.3",
"vue-tsc": "^1.0.9"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
直接把上面的依赖,copy一份,复制到package.json中,如下:
运行项目如下:
效果如下:
# 3,部署项目
配置如下:
打包如下:
生成dist文件夹,如下:
把代码推送到仓库中,如下:
部署如下:
效果如下:
要求,大家在写项目过程中,提交两个地址,一个是代码仓库的地址,一个是部署好项目的地址。
# 二,搭建五大组件,并配置路由
# 1,搭建5大组件,并配置路由
创建一个开发分支,如下:
接下来的开发都是在dev分支上进行的。
在views文件夹下,创建5大组件,如下:
代码如下:
配置路由如下:
对应的规则如下:
在main.js中注册路由,如下:
配置路由出口,如下:
测试之,如下:
其它的就不测试了。
# 2,配置重置样式,字体图标,Vant样式
vant的文档地址:https://vant-contrib.gitee.io/vant/#/zh-CN/
重置样式,字体图标,适配的JS文件,如下:
把准备好的资源,放到项目中,如下:
在main.js中引入之,如下:
测试一下vant组件是否可以使用,如下:
测试如下:
到此,vant也配置成功了。
# 3,创建tabbar组件
创建tabbar组件,如下:
对应的样式如下:
在App组件中,使用之,如下:
效果如下:
# 三,注册与登录
# 1,绘制注册和登录界面
需求:
绘制如下:
<template>
<div class="login">
<!-- 顶部导航栏 -->
<header>
<van-nav-bar :title="isTitle?'登录':'注册'" left-arrow @click-left="onClickLeft">
<template #right>
<van-icon name="ellipsis" size="18" />
</template>
</van-nav-bar>
</header>
<!-- logo -->
<img src="../../assets/logo.png" width="100" class="logo" alt="">
<!-- 登录表单 -->
<div class="login-form">
<van-form @submit="onSubmit">
<van-field
v-model="username"
name="用户名"
label="用户名"
placeholder="手机号"
:rules="[{ required: true, message: '请填写手机号' }]"
/>
<van-field
v-model="password"
type="password"
name="密码"
label="密码"
placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]"
/>
<!-- 验证码 -->
<div style="display:flex;width: 100%;margin-top: 10px;">
<div class="identify">验证码</div>
<input type="text" placeholder="验证码" v-model="identifyVal">
<div class="code" @click="refreshCode" style="width:112px">
<Identify></Identify>
</div>
</div>
<!-- 文本提示 -->
<div style="margin:16px;">
<p class="link-register" @click="isTitle = !isTitle">{{isTitle? '立即注册' : '已有账号,立即登录'}}</p>
<van-button round block type="primary">{{isTitle? '登录' : '注册'}}</van-button>
</div>
</van-form>
</div>
</div>
</template>
<script setup>
import {ref,reactive,toRefs} from "vue"
import Identify from "../../components/Identify.vue"
let userInfo = reactive({
username:"17001100999",
password:"123456",
isTitle:true, // 控制是登录还是注册
});
// 点击提交
let onSubmit = ()=>{}
// 点击返回
let onClickLeft = ()=>{}
let {username,password,isTitle} = toRefs(userInfo);
</script>
<style scoped lang="less">
.login {
width: 100%;
height: 100%;
header {
width: 100%;
padding: 0 0.26667rem;
box-sizing: border-box;
background: #fff;
border-bottom: 0.02667rem solid #dcdcdc;
}
.logo {
width: 3.2rem;
height: 3.2rem;
display: block;
margin: 36px auto 0;
}
.login-form {
padding: 0 0.53333rem;
.van-form {
display: block;
.identify {
margin: 10px 16px;
margin-right: 28px;
font-size: 15px;
}
input {
font-size: 16px;
padding-left: 26px;
width: 80px;
height: 34px;
margin-right: 20px;
border: none;
border-bottom: 1px solid rgb(226, 221, 221);
}
input::-webkit-input-placeholder {
color: #aab2bd;
font-size: 14px;
}
}
/deep/.cerify-code-panel {
display: flex;
margin-top: 0.42667rem;
height: 100%;
overflow: hidden;
.verify-code {
width: 40% !important;
height: 40px !important;
line-height: 40px !important;
font-size: 16px !important;
}
.verify-code-area {
width: 54%;
margin-left: 0.37333rem;
.verify-input-area .varify-input-code {
width: 2.4rem;
height: 1.01333rem;
border: 0.02667rem solid #e9e9e9;
padding-left: 0.26667rem;
font-size: 0.42667rem;
}
.verify-change-area {
line-height: 1.17333rem;
font-size: 12px;
}
}
}
:deep(.cerify-code-panel) {
display: flex;
margin-top: 0.42667rem;
height: 100%;
overflow: hidden;
.verify-code {
width: 40% !important;
height: 40px !important;
line-height: 40px !important;
font-size: 16px !important;
}
.verify-code-area {
width: 54%;
margin-left: 0.37333rem;
.verify-input-area .varify-input-code {
width: 2.4rem;
height: 1.01333rem;
border: 0.02667rem solid #e9e9e9;
padding-left: 0.26667rem;
font-size: 0.42667rem;
}
.verify-change-area {
line-height: 1.17333rem;
font-size: 12px;
}
}
}
}
.link-register {
float: left;
font-size: 0.37333rem;
margin-bottom: 0.53333rem;
color: #1989fa;
display: inline-block;
}
.van-button--round {
background: rgb(27, 174, 174) !important;
border-color: rgb(27, 174, 174) !important;
}
}
</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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
效果如下:
# 2,配置SSH
创建一个空的仓库,使用空的仓库来演示,如下:
我们就有了一个远程仓库,如下:
创建一个本地仓库,如下:
让本地仓库与远程仓库进行关联,如下:
我们使用https协议,如下:
我把电脑的凭据删除掉,如下:
开始写代码,进行本地仓库管理,如下:
点击确定,实现推送,如下:
电脑会记录凭据,如下:
除了使用用户名和密码记录凭据之外,还可以使用秘钥,我们再一次把用户名和密码凭据删除掉,如下:
现在我们要走ssh协议,就要生成秘钥,如下:
ssh-keygen -t rsa -C "717628672@qq.com"
打开cmd窗口,生成秘钥,如下:
把秘钥放在了如下的位置:
找到秘钥,如下:
打开gitee,把上面的字符串,放到gitee上,如下:
现在我们把之前关联的地址删除掉,如下:
现在我们关联远程仓库,使用ssh协议,如下:
现在我们推送代码,就不需要输入用户名和密码,如下:
# 3,axios二次封装
在项目,通常都会对axios进行二次封装,也是一个面试必问的。
面试官:你在项目中是如何对axios进行二次封装的?
答:......
在src目录下面,创建一个api文件夹,在api下面创建一个http.js文件,就是对axios进行的二次封装,如下:
开始写代码,如下:
// 对ajax进行二次封装
import axios from "axios"
import {
Notify
} from "vant"
// http://backend-api-01.newbee.ltd/api/v1/user/login
// process.env.NODE_ENV
// yarn dev 开发 process.env.NODE_ENV "development"
// yarn build 打包 process.env.NODE_ENV "production"
// 开发环境:
// 生产环境:
let http = axios.create({
//配置对象
//基础路径,发请求的时候,路径当中会出现api,不用你手写
baseURL: process.env.NODE_ENV === 'production' ? 'http://backend-api-01.newbee.ltd/api/v1/' : '/api/v1'
//请求时间超过5秒
timeout: 5000
})
// 添加请求拦截器
http.interceptors.request.use(function(config) {
config.headers.token = localStorage.getItem("mltoken")
return config;
}, function(error) {
return Promise.reject(error)
})
// 添加响应拦截器
http.intercetors.response.use((response) => {
//成功的回调函数:服务器响应数据回来以后,响应拦截器可以检测到,可以做一些事情。
let data = response.data;
// 在这里,可以判断服务器响应的不同的状态
// ..... Notify
return data;
}, (error) => {
Notify({
type: "danger",
message: "系统繁忙,稍后再试"
})
return promise.reject(new Error('faile'))
});
export default http;
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
# 4,配置代理,配置@别名,配置vant按需加载
什么跨域?
答:存在在浏览器中的ajax中,ajax是用来向服务器发请求的,默认情况下,如果不同源,ajax是拿不到数据的,如果得到数据,就是我们需要解决的跨域问题,在开发中,通常是使用代理解决跨域,在上线时,可以使用nginx来解决跨域,也可以让后端解决跨域。
代理解决跨域问题:
配置如下:
# 5,封装API接口
在api文件夹下面,创建一个index.js,如下:
分析一下注册接口,如下:
看请求头,如下:
看响应头,如下:
看通用头,如下:
给服务器传递的参数,如下:
看服务器给出的响应,如下:
如果服务器给出的业务状态不是200,数据可能就不是我们需要的,此时我们需要在响应拦截器中处理了,如下:
// 对ajax进行二次封装
import axios from "axios"
import {
showNotify
} from 'vant';
// http://backend-api-01.newbee.ltd/api/v1/user/login
// process.env.NODE_ENV
// yarn dev 开发 process.env.NODE_ENV "development"
// yarn build 打包 process.env.NODE_ENV "production"
// 开发环境:
// 生产环境:
let http = axios.create({
//配置对象
//基础路径,发请求的时候,路径当中会出现api,不用你手写
baseURL: process.env.NODE_ENV === 'production' ? 'http://backend-api-01.newbee.ltd/api/v1/' : '/api/v1',
//请求时间超过5秒
timeout: 5000
})
// 添加请求拦截器
http.interceptors.request.use(function(config) {
config.headers.token = localStorage.getItem("mltoken")
return config;
}, function(error) {
return Promise.reject(error)
})
// 添加响应拦截器
http.interceptors.response.use((response) => {
//成功的回调函数:服务器响应数据回来以后,响应拦截器可以检测到,可以做一些事情。
let data = response.data;
// 在这里,可以判断服务器响应的不同的状态
// ..... Notify
if (data.resultCode != 200) {
// 代码走到这里,说明数据不是我们需要的,给出提示
showNotify({
type: 'danger',
message: data.message || '系统繁忙'
});
}
return data;
}, (error) => {
Notify({
type: "danger",
message: "系统繁忙,稍后再试"
})
return promise.reject(new Error('faile'))
});
export default http;
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
开始封装接口,如下:
按这样的套路,再封装一个登录接口,如下:
# 6,实现注册
封装完毕后,在组件中,就可以调用接口,此时,你要知道,调用接口,就是调用方法,先实现注册,代码如下:
import {
register,
login
} from "@/api/index.js"
import {
showNotify
} from 'vant'
// 点击提交
let onSubmit = () => {
if (userInfo.isTitle) {
// 表示登录
// console.log("登录...");
} else {
// 表示注册
// console.log("注册...");
register(userInfo.username, userInfo.password).then(data => {
console.log(data);
if (data.resultCode == 200) {
showNotify({
type: 'success',
message: "注册成功"
});
userInfo.isTitle = true // 注册成功后开始登录
}
})
}
}
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
到此,就实现注册,如下:
# 7,实现登录
代码如下:
// 点击提交
let onSubmit = () => {
if (userInfo.isTitle) {
// 表示登录
// console.log("登录...");
login(userInfo.username, userInfo.password).then(data => {
console.log(data);
})
} else {
// 表示注册
// console.log("注册...");
register(userInfo.username, userInfo.password).then(data => {
if (data.resultCode == 200) {
showNotify({
type: 'success',
message: "注册成功"
});
userInfo.isTitle = true // 注册成功后开始登录
}
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
服务器响应的数据如下:
处理之,如下:
import {
useRoute,
useRouter
} from "vue-router"
let route = useRoute();
let router = useRouter();
// 点击提交
let onSubmit = () => {
if (userInfo.isTitle) {
// 表示登录
// console.log("登录...");
login(userInfo.username, userInfo.password).then(data => {
// console.log(data);
if (data.resultCode == 200) {
localStorage.setItem("mltoken", data.data)
router.replace("/home")
showNotify({
type: 'success',
message: "登录成功"
});
}
})
} else {
// 表示注册
// console.log("注册...");
register(userInfo.username, userInfo.password).then(data => {
if (data.resultCode == 200) {
showNotify({
type: 'success',
message: "注册成功"
});
userInfo.isTitle = true // 注册成功后开始登录
}
})
}
}
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
测试之,如下:
看一下localstorage中有没有token,如下:
后面再去调用其它接口,就会自动地带个token。
完整代码:
<template>
<div class="login">
<!-- 顶部导航栏 -->
<header>
<van-nav-bar :title="isTitle ? '登录' : '注册'" left-arrow @click-left="onClickLeft">
<template #right>
<van-icon name="ellipsis" size="18" />
</template>
</van-nav-bar>
</header>
<!-- logo -->
<img src="../../assets/logo.png" width="100" class="logo" alt="">
<!-- 登录表单 -->
<div class="login-form">
<van-form @submit="onSubmit">
<van-field v-model="username" name="用户名" label="用户名" placeholder="手机号"
:rules="[{ required: true, message: '请填写手机号' }]" />
<van-field v-model="password" type="password" name="密码" label="密码" placeholder="密码"
:rules="[{ required: true, message: '请填写密码' }]" />
<!-- 验证码 -->
<div style="display:flex;width: 100%;margin-top: 10px;">
<div class="identify">验证码</div>
<input type="text" placeholder="验证码" v-model="identifyVal">
<div class="code" @click="refreshCode" style="width:112px">
<Identify></Identify>
</div>
</div>
<!-- 文本提示 -->
<div style="margin:16px;">
<p class="link-register" @click="isTitle = !isTitle">{{ isTitle ? '立即注册' : '已有账号,立即登录' }}</p>
<van-button round block type="primary" native-type="submit">{{ isTitle ? '登录' : '注册' }}</van-button>
</div>
</van-form>
</div>
</div>
</template>
<script setup>
import { register, login } from "@/api/index.js"
import { ref, reactive, toRefs } from "vue"
import { useRoute, useRouter } from "vue-router"
import { showNotify } from 'vant'
import Identify from "../../components/Identify.vue"
let userInfo = reactive({
username: "17001100999",
password: "123456",
isTitle: true, // 控制是登录还是注册
});
let route = useRoute();
let router = useRouter();
// 点击提交
let onSubmit = () => {
if (userInfo.isTitle) {
// 表示登录
// console.log("登录...");
login(userInfo.username, userInfo.password).then(data => {
// console.log(data);
if (data.resultCode == 200) {
localStorage.setItem("mltoken", data.data)
router.replace("/home")
showNotify({
type: 'success',
message: "登录成功"
});
}
})
} else {
// 表示注册
// console.log("注册...");
register(userInfo.username, userInfo.password).then(data => {
if (data.resultCode == 200) {
showNotify({
type: 'success',
message: "注册成功"
});
userInfo.isTitle = true // 注册成功后开始登录
}
})
}
}
// 点击返回
let onClickLeft = () => { }
let { username, password, isTitle } = toRefs(userInfo);
</script>
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
# 8,验证码实现
只有在验证码正确的前提下才能实现登录 和 注册,大家自己实现。
# 9,github的使用
通常我们是不能直接访问github的,需要科学上网,如下:
访问github,如下:
需要注册一个账号,如下:
创建一个远程仓库,如下:
把远程仓库克隆下来,如下:
创建index.html,如下 :
代码如下:
进行本地仓库的管理,如下:
第一次,也需要输入用户名和密码,如下:
现在github不支持用户名和密码登录,参考如下 连接:
https://blog.csdn.net/lm_is_dc/article/details/120745701
现在就可以把代码推送到仓库中了,如下:
当然,我们也可以配置SSH,如下:
部署如下:
分享一个github仓库:https://github.com/GrowingGit/GitHub-Chinese-Top-Charts
# 10,freewha
可以免费部署静态页面。
网站:https://freewha.com/
使用步骤,如下:
此时它就给了你一片空间,直接访问之,如下:
你需要把代码扔到服务器上,通过一个工具来扔,如下:
安装此工具,安装完后,有如下图标:
双击打开此软件,如下:
连上去,如下:
可以把项目更换一个名字,如下:
访问之,如下:
# 四,首页面数据渲染
需求如下:
# 1,实现header背景颜色的切换
直接上代码,如下 :
<template>
<div class="home_box">
<header class="header_box" :class="{ active: !isTop }">
<div class="left_box">左</div>
<div class="middle_box">中</div>
<div class="right_box">右</div>
</header>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue"
let isTop = ref(true);
onMounted(() => {
// 可以做节流
window.addEventListener("scroll", changeTop)
});
let changeTop = () => {
let t = document.documentElement.scrollTop || document.body.scrollTo;
if (t > 50) {
isTop.value = false; // 添加背景色
} else {
isTop.value = true; // 移除背景色
}
}
onBeforeUnmount(() => {
window.removeEventListener("scroll", changeTop)
});
</script>
<style lang="less" scoped>
.home_box {
padding-bottom: 2000px;
.header_box {
position: fixed;
width: 100%;
height: 50px;
display: flex;
top: 0;
left: 0;
.left_box {
width: 50px;
background-color: rgba(0, 0, 0, .2);
}
.middle_box {
flex: auto;
background-color: rgba(255, 255, 0, .2);
}
.right_box {
width: 50px;
background-color: rgba(0, 0, 0, .2);
}
&.active {
background: #1baeae;
}
}
}
</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
效果如下:
# 2,请求首页面的数据
通过network面板分析如下:
先封装一个API接口,如下:
在页面中导入接口,定义相关的状态,如下:
发出ajax请求,获取数据,给状态赋值,如下:
在调试工具查看之,如下:
# 3,渲染轮播图数据
轮播图组件在vant中提供现成的,如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/swipe
直接上代码如下:
对应的样式,如下:
效果如下:
# 4,渲染宫格组件
vant中也是有现在的组件,如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/grid
直接上代码,如下:
样式大家自己书写一下,效果如下:
# 5,渲染商品列表
在宫格下面有三个模块,如下:
上面的三个模块一样,封装成一个组件,传递什么数据,就渲染什么模块,封装如下:
在首页面中,引入组件,使用组件,如下:
效果如下:
书写对应的样式,如下:
Goods组件参考对应的样式:
<style lang="less" scoped>
.good {
width: 100%;
background: rgb(243, 243, 243);
.good-header {
text-align: center;
height: 40px;
line-height: 40px;
font-size: 16px;
font-weight: 500;
background-color: #fff;
color: rgb(15, 196, 181);
}
.good-box {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.good-item {
width: 49%;
background: white;
margin: 2px 0;
box-sizing: border-box;
padding: 10px;
.img_box {
width: 90%;
height: 200px;
margin: 10px auto;
overflow: hidden;
}
.good-desc {
.title {
font-size: 0.32rem;
color: #222333;
}
.price {
margin-top: 10px;
font-size: 0.32rem;
color: red;
}
}
img {
width: 100%;
height: auto;
}
}
}
}
</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
再次看效果如下:
同理,其它模块也是一样的,处理如下:
效果如下:
给home_box添加一行样式,如下 :
首页面参考对应的样式:
<style lang='less' scoped>
.home_box {
.header_box {
position: fixed;
width: 100%;
height: 50px;
display: flex;
z-index: 200;
top: 0;
left: 0;
.left_box {
width: 50px;
flex: none;
background: rgba(0, 0, 0, 0.2);
}
.right_box {
width: 50px;
flex: none;
background: rgba(0, 0, 0, 0.2);
}
.middle_box {
flex: auto;
background: rgba(255, 255, 0, 0.2);
}
&.active {
background: #1baeae;
}
}
padding-bottom: 1000px;
.my-swipe .van-swipe-item {
color: #fff;
font-size: 20px;
text-align: center;
img {
width: 100%;
height: auto;
}
}
}
.good {
margin-top: 10px;
}
</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
到此,首页面的数据就渲染完毕了。
# 五,详情页面渲染
# 1,接口封装
分析接口,如下:
此时,我们需要在响应拦截器中做处理,如下:
现在登录后,继续分析如下:
先把接口封装好,如下:
# 2,创建详情组件配置对应的路由
创建详情组件,如下:
配置路由,如下:
测试是否可以跳到商品详情页面,如下:
在商品列表页面,点击商品,就需要到达商品的详情,如下:
测试OK,如下:
在详情页面中,需要获取ID,如下:
# 3,根据ID发送请求获取详情页面数据
接下来,在详情页面中就需要根据ID获取详情页面的数据了,接口也封装好了。代码如下:
现在我登录上,登录上之后,就有token,后面调用详情接口,会自动带上token。
登录上之后,我们再测试之,如下:
定义一个状态,把详情数据保存起来,如下:
在调试工具,看一下,info有没有数据,如下:
到此,详情页面的数据就已经回来了。然后要渲染数据。
# 4,渲染详情页面的数据
现在详情页面的数据就回来了,需要渲染数据,页面如下:
开始书写详情页面的结构,如下:
样式直接copy,如下:
<style lang="less" scoped>
.van-nav-bar {
position: fixed;
width: 100%;
height: 1.17333rem;
top: 0;
left: 0;
z-index: 100;
line-height: 1.17333rem;
padding: 0 0.26667rem;
box-sizing: border-box;
background: #fff;
border-bottom: 0.02667rem solid #dcdcdc;
}
.product-info {
margin-top: 1.17333rem;
padding: 0 0.26667rem;
.info-goodsName {
font-size: 0.48rem;
text-align: left;
color: #333;
}
.info-p {
font-size: 0.37333rem;
text-align: left;
color: #999;
padding: 0.13333rem 0;
}
.info-price {
color: #f63515;
font-size: 0.58667rem;
text-align: left;
}
.info-ul {
width: 100%;
height: 30px;
margin: 26px 0;
display: flex;
justify-content: space-between;
li {
flex: 1;
padding: 0.13333rem 0;
text-align: center;
font-size: 0.4rem;
border-right: 0.02667rem solid #999;
box-sizing: border-box;
}
}
.info-ul .info-ul-li {
border-right: none;
}
.info-content {
padding: 0 0.53333rem;
}
}
</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
效果如下:
# 5,绘制action-bar
action-bar在vant中有现成的组件,如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/action-bar
如果vant中没有封装,其实自己也是可以实现的,在这里,我们就不使用人家封装的,我们自己写的,代码如下:
对应的样式,如下:
大家参考样式,如下:
.actionBar {
position: fixed;
bottom: 0;
width: 100%;
height: 50px;
left: 0;
background-color: #fff;
display: flex;
justify-content: space-around;
align-items: center;
.left-content {
display: flex;
justify-content: space-around;
width: 80px;
.custom {
font-size: 10px;
}
}
.right-content {
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
font-size: 16px;
button {
line-height: 40px;
height: 40px;
border-radius: inherit;
text-align: center;
color: white;
cursor: pointer;
width: 135px;
background: linear-gradient(to right,
rgb(255, 208, 30),
rgb(255, 137, 23));
border: 0px;
}
button:nth-child(1) {
border-radius: 20px 0 0 20px;
}
button:nth-child(2) {
border-radius: 0 20px 20px 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
效果如下:
# 6,点击加入购物车
需求:
不着急,先分析接口,封装接口,如下:
下面还有一个接口,也是shop-cart,分析之,如下:
重点关注第一个接口,因为第1个接口,是用于把商品添加到购物车。第2个接口稍后再说。
封装添加商品到购物车的接口,如下:
在组件中,引入,调用接口,如下:
测试之,如下:
# 六,购物车
# 1,把购物车中的数据存储到pinia中
创建仓库如下:
在main.js中需要注册仓库,如下:
封装一个接口,获取购物车中的数据,此接口,之前分析过了,如下:
在仓库中,就可以发送ajax获取数据,如下:
在组件中,当我们点击添加商品到购物车时,就需要派发上面的action,如下:
给每一个数据,添加一个checked选项,表示这个商品是否选中,如下:
此时,我们再看一下购物车中有没有数据,如下:
到此,我们就把购物车数据存储到了仓库中。
# 2,处理购物车中商品数量
需求:
你要知道,购物车中的商品都是在pinia中,我们只需要统计它的数量就可以了,使用getters,如下:
点击加入购物车,测试之,如下:
在组件中就可以使用数据,如下:
效果如下 :
在详情页面中,一上来就需要加载购物车中的数据,如下:
再次测试之,如下:
到此,商品的数据就统计好了。然后,点击购物车,要去购物车页面了。
# 3,返回上一页
当我们点击购物车,去购物页面,如下:
测试之,如下:
上面的操作是在我登录的情况下做的,如果说没有登录,去购物车,需要跳到登录页面,关键是登录成功后,要去哪?
答:登录成功后,要去购物车,就是所谓的返回上一页。
现在的把登录后的token删除掉,你把token删除了,就相当于没有登录,如下:
需要在购物车模块,获取购物车数据,如下:
在响应拦截器中处理,如下:
登录成功后,判断一个这个needback,如下:
现在我们尝试点击购物车,去了登录页面,如下:
点击登录,如下:
登录成功后,就自动去了购物车。
# 4,购物车页面数据渲染
需求:
现在仓库中就有购物车的数据了,如下:
在组件中就可以获取数据,渲染数据,如下:
效果如下:
写一点样式,如下:
效果如下:
最下面还差了一个submitbar,在vant中也是有现成的,如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/submit-bar
绘制submitbar,如下:
完整参考代码如下:
<template>
<div class="cart-box">
<!-- 头部 -->
<van-nav-bar title="购物车" left-arrow @click-left='$router.back()'>
<template #right>
<van-icon name="ellipsis" />
</template>
</van-nav-bar>
<!-- 购物车列表 -->
<div class="swipe-wraper">
<van-swipe-cell v-for="item in store.cartList" :key="item.goodsId">
<template #right>
<van-button class="delete-button" square type="danger" text="删除" />
</template>
<template #default>
<div class="goods">
<!-- 昨天给每一个购物车数据添加了checked选项 -->
<van-checkbox class="btn" v-model='item.checked'></van-checkbox>
<van-card :num="item.goodsCount" :price="item.sellingPrice" :desc="item.goodsName"
:title="item.goodsName" class="goods-card" :thumb="item.goodsCoverImg">
<template #num>
<van-stepper @change="change(item)" v-model="item.goodsCount" />
</template>
</van-card>
</div>
</template>
</van-swipe-cell>
</div>
<!-- 底部 -->
<van-submit-bar :price="110" button-text="结算" v-show="store.cartList.length" @submit='onSubmit'>
<van-checkbox v-model="checkAll">全选</van-checkbox>
<template #tip>
你的收货地址不支持同城送, <span>修改地址</span>
</template>
</van-submit-bar>
</div>
</template>
<script setup>
import { onMounted, computed } from "vue"
import { useCart } from '@/store/cartStore.js'
let store = useCart();
onMounted(() => {
// 派发action,获取购物车数据,放到仓库中
store.getCartListAsync();
});
// 点击步进器
let change = () => {
}
let checkAll = computed({
get() {
},
set() {
}
})
let onSubmit = () => { }
</script>
<style lang="less" scoped>
.cart-box {
padding-bottom: 120px;
.swipe-wraper {
.delete-button {
height: 100%;
}
.goods {
display: flex;
margin: 5px;
.van-checkbox {
padding: 0 10px;
}
.van-card {
width: 90%;
margin-top: 0px;
}
}
}
.van-submit-bar {
margin-bottom: 50px;
}
}
</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
效果如下:
# 5,删除购物车数据
在network中分析如下:
封装一个API接口,如下:
你要知道,购物车的数据是在仓库中,删除时,需要定义一个action来处理,如下:
在组件中,点击删除按钮时,需要派发action,如下:
测试之,如下:
# 6,计算总价和全选实现
分析:
数据都是在仓库中,使用计算属性计算总价,如下:
效果如下 :
然后实现全选,如下:
# 7,步进器实现
分析,如下 :
封装API接口,如下:
你要知道,购物车数据在仓库中,定义一个action,如下:
然后,在组件中要派发action,如下:
测试之,如下:
到此,步进器就实现了。
还有一个地方,也需要调用接口,没有这个接口,我们就不去调用了。如下:
# 七, 地址管理
# 1,封装地址管理的API接口和创建两个组件
根据之前的经验,封装API接口如下:
// ======================= 获取地址列表
export function getAddressList() {
return http.get("/address")
}
// ======================= 根据ID获取某个地址,实现数据回显
export function getAddressDetail(id) {
return http.get("/address/" + id)
}
// ======================= 编辑地址
export function updateAddress(obj) {
return http.put("/address", obj)
}
// ======================= 新增地址
export function addAddress(obj) {
return http.post("/address", obj)
}
// ======================= 删除地址
export function removeAddress(id) {
return http.delete("/address/" + id)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
直接copy到代码中,如下:
创建地址相关的两个组件,一个是地址列表,一个是新增和编辑地址,新增和编辑共用一个组件,如下:
配置路由,如下:
测试之,如下:
# 2,获取地址相关的数据渲染之
地址列表如下:
地址列表在vant中有现在的组件,如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/address-list
在地址列表组件中就去调接口获取数据,如下:
效果如下:
看一下控制台,有没有获取数据,如下:
分析添加地址接口,如下:
传递的参数的数据格式,如下:
使用postman添加一些测试数据,如下:
json数据格式如下:
{
"cityName": "北京市",
"defaultFlag": 0,
"detailAddress": "1234567890",
"provinceName": "北京市",
"regionName": "东城区",
"userName": "121313",
"userPhone": "17001100482",
"areaCode": "110101",
"postalCode": "123456"
}
2
3
4
5
6
7
8
9
10
11
服务器返回如下:
因为我们需要带上token,如下:
现在添加地址就OK了。
再看一下,控制台中有没有数据,如下:
需要把数据加工完后,赋值给状态,我们需要的数据格式如下:
我们的接口返回的数据不是这样的,需要把数据整理成vant需要的,如下:
效果如下:
上面实现的地址列表渲染,并没有完全实现好。先放一下。
# 3,实现新增地址
实现新增地址:
实现路由跳转,如下:
实现Add方法,如下:
测试,可以实现跳转,如下:
现在我们就需要实现新增了。新增地址的表单在vant中也有现成的,如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/address-edit
开始绘制表单,如下:
参考代码如下:
<template>
<!-- 头部 -->
<van-nav-bar :title="'新增地址'" left-arrow @click-left="$router.back()">
<template #right>
<van-icon name="ellipsis" />
</template>
</van-nav-bar>
<!-- 表单 -->
<van-address-edit :area-list="areaList" show-delete show-set-default
:area-columns-placeholder="['请选择', '请选择', '请选择']" @save="onSave" @delete="onDelete" />
</template>
<script setup>
import { areaList } from "@vant/area-data"
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
效果如下:
当我们点击保存时,能不能收集到数据呢?如下:
测试如下:
调用接口,传递给服务器的数据如下 :
当点击保存时,把数据转化成接口需要的数据,并调用接口,如下:
服务器响应下:
实现了新增后,就可以跳到地址列表了,如下:
效果如下 :
然后,我们去完善一下地址列表,如果这个地址是默认的,需要让这个地址选中,如下:
我们有一个状态,叫chosenAddressId,来控制地址是否选中。我们需要给chosenAddressId赋值,如下:
效果如下 :
# 4,实现编辑地址
分析如下:
点击编辑,实现路由跳转,传递地址ID,如下:
就可以实现跳转化了,在编辑页面中获取id,如下:
测试之,如下:
处理上面的两种情况下,如下:
测试如下 :
有了ID,就根据ID获取地址详情,实现数据回显,如下:
看服务器,响应的结果,如下:
实现数据回显的数据格式和接口得到的数据格式不一样,回显需要的数据格式如下:
定义一个状态,存储结果,如下:
使用info,如下 :
效果如下 :
修改完数据,点击保存,要实现编辑,如下:
效果如下:
到此,编辑地址就实现了。
# 5,实现地址的删除
只能在编辑情况下才能删除,删除绑定点击事件,如下:
导入接口,如下:
实现onDelete方法,如下:
测试之,如下:
到此,地址管理模块就实现了。
# 八,生成订单模块
# 1,创建两个组件并配置路由
看一下,什么是生成订单,当我们选中了某个地址,会到生成订单模块,如下:
分析上面的页面,如下:
除了上面的生成订单页面,还有一个我的订单页面,如下:
现在我们就创建出这两个组件,如下:
配置对应的路由,如下:
在路由的元信息中,可以指定til选项,是用来设置页面的title,如下:
测试之,如下:
# 2,点击地址去生成订单页面
在购物车页面中,点击结算,去地址列表页面,如下:
现在就到达了地址列表页面,如下:
现在点击某个地址,要去生成订单页面,如下:
实现对应的方法,如下:
测试之,如下:
我们定义一个标识来区分,如下:
再次测试之,如下:
点击地址,询问是否选中此地址,引入询问框,如下:
测试是否可以到达生成订单页面,如下:
# 3,获取地址ID,发送ajax请求,渲染数据
需求:
封装一个接口,根据ID,获取地址详情,还是昨天的接口,如下:
直接上代码如下:
看一下,控制台中有没有数据,如下:
渲染数据,如下:
效果如下:
书写对应的样式,如下:
参考之,如下:
<style lang="less" scoped>
.address-wrap {
margin-bottom: 0.53333rem;
background: #fff;
position: relative;
font-size: 0.37333rem;
padding: 0.63rem 0.4rem 0.66667rem;
color: #222333;
.name {
display: flex;
justify-content: left;
margin: 0.26667rem 0 0.27667rem;
line-height: 0.37333rem;
span:nth-child(1) {
margin-right: 0.04rem;
}
}
.address {
display: flex;
justify-content: left;
line-height: 0.37333rem;
}
.van-icon {
position: absolute;
right: 0.26667rem;
top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
font-size: 0.53333rem;
}
}
.address-wrap::before {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 0.05333rem;
background: -webkit-repeating-linear-gradient(135deg,
#ff6c6c,
#ff6c6c 20%,
transparent 0,
transparent 25%,
#1989fa 0,
#1989fa 45%,
transparent 0,
transparent 50%);
background: repeating-linear-gradient(-45deg,
#ff6c6c,
#ff6c6c 20%,
transparent 0,
transparent 25%,
#1989fa 0,
#1989fa 45%,
transparent 0,
transparent 50%);
background-size: 2.13333rem;
content: "";
}
</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
效果如下:
# 4,商品数据的渲染
需求:
现在回到购物车页面,当点击结算时,需要把商品的id存储到本地,如下:
点击结算时,把ids存储到状态中,如下:
效果如下:
我们在生成订单页面中是不能获取状态,可以使用localstorage进行通信,如下:
数据存储到本地,会自动地toString,如下:
到此,在购物车模块中,就把选中的商品存储到本地。那么在生成订单页面中,就可以从本地获取这些ids了。
在生成订单页面中,获取ids,如下:
效果如下 :
定义一个状态,保存这些id,如下:
封装一个接口,此接口是根据这些ids,获取商品详情,如下:
调用接口,获取数据,如下:
服务器响应如下:
定义一个状态,保存数据,如下:
查看状态没有没值,如下:
最后,渲染数据,在vant中有现成的组件,如下:
效果如下:
# 5,绘制总价和底部
分析状态如下:
把状态定义出来,如下:
实现底部,如下:
效果如下:
copy样式,如下:
效果如下:
刚才我们默认让它显示了,但是只有点击生成订单时候才能显示,如下:
测试之,如下:
还有一个总价没有实现,实现之,如下:
效果如下:
# 6,生成订单完成支付
当点击生成订单,就会调用接口,生成订单,分析如下:
封装API接口,如下:
当点击生成订单时,就需要调用接口,如下:
测试之,如下:
现在购物车就没有数据了,如下:
完成支付,如下:
分析此API接口,如下:
封装API接口,如下:
点击微信支付或支付宝支付,就需要调用接口了,绑定点击事件,如下:
引入接口,实现对应的方式,如下:
浏览器测试之,如下:
还有一个取消支付,如下:
当点击取消支付时,此时订单已成生,仅仅是没有支付,直接去我的订单模块如下:
测试之如下:
到此,生成订单模块就实现了。
# 九,我的订单模块
# 1,分析van-tabs和van-list
先不去考虑调接口,就分析两个组件,一个叫van-tabs和van-list,组件如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/tab
还需要看一个van-list组件,如下:
https://vant-contrib.gitee.io/vant/#/zh-CN/list
把上面的两个组件放到订单模块中继续分析如下:
<template>
<div class="order-box">
<van-nav-bar fixed title="我的订单" left-arrow @click-left="$router.back()">
<template #right>
<van-icon name="ellipsis" size="18" />
</template>
</van-nav-bar>
<!-- van-tabs -->
<van-tabs>
<van-tab v-for="item in tabs" :key="item.text" :title="item.text"></van-tab>
</van-tabs>
</div>
</template>
<script setup>
import { reactive, ref } from "vue"
let tabs = reactive([
{
text: "全部",
status: "",
},
{
text: "待付款",
status: "0",
},
{
text: "待确认",
status: "1",
},
{
text: "待发货",
status: "2",
},
{
text: "已发货",
status: "3",
},
{
text: "交易完成",
status: "4",
},
]);
</script>
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
效果如下:
书写对应的样式,如下:
效果如下 :
重点要分析list组件,copy代码到组件中,大家需要把下面的代码分析清楚,如下:
<template>
<div class="order-box">
<van-nav-bar fixed title="我的订单" left-arrow @click-left="$router.back()">
<template #right>
<van-icon name="ellipsis" size="18" />
</template>
</van-nav-bar>
<!-- van-tabs -->
<van-tabs>
<van-tab v-for="item in tabs" :key="item.text" :title="item.text"></van-tab>
</van-tabs>
<!-- list -->
<van-list :offset="80" v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
<van-cell v-for="item in list" :key="item" :title="item" />
</van-list>
</div>
</template>
<script setup>
import { reactive, ref } from "vue"
let tabs = reactive([
{
text: "全部",
status: "",
},
{
text: "待付款",
status: "0",
},
{
text: "待确认",
status: "1",
},
{
text: "待发货",
status: "2",
},
{
text: "已发货",
status: "3",
},
{
text: "交易完成",
status: "4",
},
]);
const loading = ref(false);
const finished = ref(false);
const list = ref([]);
// 1)onLoad一上来就会触发一次 onLoad... 此时页面上没有数据
// 2)数据渲染完后,数据没有撑满整个屏幕,还会加载一次 onLoad... 此时页面上有10条数据
// 3)10条数据没有撑满屏幕,还会触发一次 onLoad... 此时页面上有20条数据
// 4)当滚动到底部时,还会触发onLoad
const onLoad = () => {
console.log("onLoad...");
// 异步更新数据
// setTimeout 仅做示例,真实场景中一般为 ajax 请求
setTimeout(() => {
for (let i = 0; i < 10; i++) {
list.value.push(list.value.length + 1);
}
// 加载状态结束
loading.value = false;
// 数据全部加载完成
if (list.value.length >= 40) {
// finished变成true表示数据加载完毕
finished.value = true;
}
}, 1000);
};
</script>
<style lang="less" scoped>
.order-box {
.van-tabs {
position: fixed;
top: 1.2rem;
width: 100%;
left: 0;
z-index: 300
}
.van-list {
padding-top: 2.2rem;
}
}
</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
效果如下:
# 2,获取订单数据,渲染订单数据
分析API接口,如下:
封装API接口,如下:
在我的订单页面中,引入接口,调用之,如下:
此时list数据源中就有数据,如下:
此时,就需要渲染数据,如下:
效果如下:
# 3,实现tab选项功能
定义一个状态,和tab进行绑定,如下:
当点击切换时,active也是会变化的,如下:
此时,就需要监听active的变化,如果ative变化了,就需要重新请求,如下:
效果如下 :
监听到了,改变状态,重新请求,如下 :
效果如下:
# 十,我的模块
# 1,渲染我的模块数据
封装API接口,如下:
在页面中调用接口,渲染数据,如下:
效果如下:
剩下的功能,需要自行实现。
# 2,其它API接口封装
// ================= 点击确认收货
export function OkOrderDetails(orderNo) {
return http.put('/order/' + orderNo + '/finish')
}
// ================= 点击取消订单
export function NotOkOrderDetails(orderNo) {
return http.put('/order/' + orderNo + '/cancel')
}
// ================= 点击订单跳转到确认收货页面拿数据
export function getOrderDetails(id) {
return http.get('/order/' + id)
}
// ================= 保存更改
export function save(introduceSign, nickName, pwd) {
return http.put('/user/info', {
introduceSign: introduceSign,
nickName: nickName,
passwordMd5: md5(pwd)
})
}
// ================= 退出登录
export function infos() {
return http.get('/index-infos')
}
// ================= 点击搜索
export function search(pn, kw, ob) {
return http.get('/search', {
params: {
pageNumber: pn,
keyword: kw,
orderBy: ob
}
})
}
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
# 十一,总结
# 1,为什么要写项目
- 第一个目的:把基础知识点串起来。
- 第二个目的:让大家多遇到错误。
- 第三个目的:启发大家学会拆解项目。
- 第四个目的:面试要问,简历要写。
# 2,项目实战总结(模板/简历)
项目介绍:移动端电商类的webapp
技术栈:vue(3) + vue-router(4) + pinia + vant(3) + axios + less
你负责哪些模块(建立说出独立开发):商品展示模块(首页、详情、推荐);品类搜索模块(品类、搜索导航、历史搜索);个人中心模块(设置、地址管理、积分会员、历史订单);购物车模块(列表、筛选、支付、抽奖、换购);鉴权模块(登录、注册、绑定手机)。
项目技术点
- 使用VantUI组件库编写移动端视图结构、使用rem处理移动端布局、使用less预编译器编写高质量的样式代码。
- 封装Swipe轮播图组件,使用PullDownRefresh实现列表页面下拉刷新、使用List组件实现触底加载、使用keep-alive实现列表页面的缓存、使用DOM滚动事件保留列表滚动条。
- 封装购物车系列组件,Cart商品组件、SearchPanel筛选组件、Present抽奖组件、NotData数据显示组件、Sumbit提交组件、MultiplePay支付选项卡组件等,编写符合用户体验的购物交互逻辑。
- 使用路由守卫、路由元信息实现鉴权流程;封装v-login自定义指令实现组件元素的权限拦截;使用路由懒加载、webpack代码分割技术,对部署打包进行代码优化。
- 使用组件按需加载、图片精灵图、图片懒加载、Vuex缓存技术、SEO优化策略、过渡动画,实现良好的SEO优化、提升移动端产品的用户体验。
# 3,关于移动端优化
# 1,性能优化(提升访问速度)
- 首页优化(尽可能减少各种请求;列表数据分批渲染;资源文件尽可能使用CDN加速;列表中图片使用缩略图;页面组件缓存和数据缓存;使用SSR对首页进行服务端渲染。)
- 图片优化:尽可能使用使用字体icon,图片icon使用精灵图,图片尽可能使用webp格式,在保证精度的前提下尽可能压缩(https://tinify.cn/),图片宽度不要超过640px,使用.png8格式的图片代替.gif图片。
- 代码优化:尽可能使用异步代码,减少定时器的使用、事件绑定尽可能使用事件委托、列表循环加key、灵活使用v-show和v-if、尽可能使用CSS3动画、尽可能使用ES6语法、防抖节流、减少没必要的DOM/ref操作、Vuex缓存、封装可复用的代码、把复杂运算封装成计算属性。
- 打包优化:使用路由懒加载切割代码、打包时给文件添加hash后缀、对CSS文件进行合并和压缩、使用babel编译JS并使用压缩工具对脚本进行压缩。
# 2,SEO优化(搜索引擎优化)
- 在head中,合理地设计title、meta等标签。
- 尽可能多地使用静态数据(能写死的文字尽可能写死,比如品类、学科、业务信息。。。)
- 尽可能多地使用HTML5语义化标签,比如nav、article、footer、header、main、aside等。
- 动态数据的标签上,尽可以地添加一些有意义的静态的title属性、alt属性、placeholder属性。
- 网站的尽可能把导航链接设计合理,菜单层级不要太深,还要尽可能地多使用h1~h6标题标签。
- 网站首页尽可能多放一些“花里胡哨”的信息,这些信息能静态尽量静态。
- 如果这个ToC产品特别重要,某些重要页面比如首页、业务页面,你可以使用SSR技术来做。
# 4,用户体验优化
交互开发:合理弹框提示、合理的Toast提示、请求数据添加Loading提示、长列表添加回到顶部等。
数据交互:axios拦截器做统一的数据过滤、对HTTP异常做统一处理,使用Vuex做合理的数据缓存。
代码层面:减少同步的异步代码,减少对回调的使用、对容易报错的代码进行try/catch、合理的严谨的条件判断等。
# 4,常见面试题
- 介绍一下你最近做的项目?介绍一下这个项目?
- 详细说一下你这个项目用到了哪些技术栈?
- 说一下你项目中有什么亮点?
- 说一下你项目中遇到过一些什么难点?你是怎么解决的?
- 你封装过哪些有代表性的组件?举两个例子。
- 你做过哪些项目优化?
- 你做过如此SEO优化?
- 你是怎么部署上线的?上线有哪些注意的?
- 你这个项目的鉴权是怎么做?
- 除了以上这些常问的题目,还会问一些场景题。