09-Vue3.x 码路严选

11/17/2001

# 一,项目的搭建

项目参考:http://tubie.gitee.io/maluyanxuan

仓库地址:https://gitee.com/tubie/20221222maluyanxuan.git

已经注册的用户名和密码:

用户名:18338812345

密码:123456

# 1,创建远程仓库和项目

创建远程仓库,如下:

1671689167177

创建一个项目,如下:

1671689261484

把项目初始化成一个仓库,如下:

1671689364306

本地仓库与远程仓库进行关联,如下:

1671689491973

看一下git忽略文件,如下:

1671689567491

先进行本地仓库管理,如下:

1671689606183

1671689630194

把代码推送到远程仓库,如下:

1671689659433

1671689736408

把仓库开源,如下:

1671689777861

# 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"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

直接把上面的依赖,copy一份,复制到package.json中,如下:

1671689868252

1671689902054

运行项目如下:

1671690092686

效果如下:

1671690080333

# 3,部署项目

配置如下:

1671689997480

打包如下:

1671690138200

生成dist文件夹,如下:

1671690163118

把代码推送到仓库中,如下:

1671690194186

1671690230151

1671690276270

1671690294504

1671690316861

部署如下:

1671690354714

1671690380451

效果如下:

1671690416879

要求,大家在写项目过程中,提交两个地址,一个是代码仓库的地址,一个是部署好项目的地址。

# 二,搭建五大组件,并配置路由

# 1,搭建5大组件,并配置路由

创建一个开发分支,如下:

1671691693859

接下来的开发都是在dev分支上进行的。

在views文件夹下,创建5大组件,如下:

1671691964110

代码如下:

1671692031621

配置路由如下:

1671692915333

对应的规则如下:

1671692416370

在main.js中注册路由,如下:

1671692503262

配置路由出口,如下:

1671692941089

测试之,如下:

1671692954117

1671692969933

其它的就不测试了。

# 2,配置重置样式,字体图标,Vant样式

vant的文档地址:https://vant-contrib.gitee.io/vant/#/zh-CN/

1671693338172

重置样式,字体图标,适配的JS文件,如下:

1671693118546

把准备好的资源,放到项目中,如下:

1671693159069

在main.js中引入之,如下:

1671693230853

测试一下vant组件是否可以使用,如下:

1671693390724

测试如下:

1671693400168

到此,vant也配置成功了。

# 3,创建tabbar组件

创建tabbar组件,如下:

1671695163380

对应的样式如下:

1671695182902

在App组件中,使用之,如下:

1671695493481

效果如下:

1671695508833

1671695520835

# 三,注册与登录

# 1,绘制注册和登录界面

需求:

1671695713383

1671695725099

绘制如下:

<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> 
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
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

效果如下:

1671697362421

# 2,配置SSH

创建一个空的仓库,使用空的仓库来演示,如下:

1671758148960

我们就有了一个远程仓库,如下:

1671758168823

创建一个本地仓库,如下:

1671758215985

让本地仓库与远程仓库进行关联,如下:

1671758309567

我们使用https协议,如下:

1671758361412

我把电脑的凭据删除掉,如下:

1671758425477

开始写代码,进行本地仓库管理,如下:

1671758615567

点击确定,实现推送,如下:

1671758645671

电脑会记录凭据,如下:

1671758701569

除了使用用户名和密码记录凭据之外,还可以使用秘钥,我们再一次把用户名和密码凭据删除掉,如下:

1671758796687

现在我们要走ssh协议,就要生成秘钥,如下:

ssh-keygen -t rsa -C "717628672@qq.com"
1

打开cmd窗口,生成秘钥,如下:

1671758936468

把秘钥放在了如下的位置:

1671758960564

找到秘钥,如下:

1671759035965

1671759071134

打开gitee,把上面的字符串,放到gitee上,如下:

1671759163737

现在我们把之前关联的地址删除掉,如下:

1671759258021

现在我们关联远程仓库,使用ssh协议,如下:

1671759307162

1671759403817

现在我们推送代码,就不需要输入用户名和密码,如下:

1671759490408

# 3,axios二次封装

在项目,通常都会对axios进行二次封装,也是一个面试必问的。

面试官:你在项目中是如何对axios进行二次封装的?

答:......

在src目录下面,创建一个api文件夹,在api下面创建一个http.js文件,就是对axios进行的二次封装,如下:

1671759768635

开始写代码,如下:

// 对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;
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

# 4,配置代理,配置@别名,配置vant按需加载

什么跨域?

答:存在在浏览器中的ajax中,ajax是用来向服务器发请求的,默认情况下,如果不同源,ajax是拿不到数据的,如果得到数据,就是我们需要解决的跨域问题,在开发中,通常是使用代理解决跨域,在上线时,可以使用nginx来解决跨域,也可以让后端解决跨域。

代理解决跨域问题:

1671762244992

配置如下:

1671762924154

1671762905479

# 5,封装API接口

在api文件夹下面,创建一个index.js,如下:

1671762992610

分析一下注册接口,如下:

1671763211628

看请求头,如下:

1671763327082

看响应头,如下:

1671763376718

看通用头,如下:

1671763428629

给服务器传递的参数,如下:

1671763473624

看服务器给出的响应,如下:

1671763585545

如果服务器给出的业务状态不是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;
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

开始封装接口,如下:

1671764001318

按这样的套路,再封装一个登录接口,如下:

1671766429822

# 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 // 注册成功后开始登录
            }
        })
    }
}
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

到此,就实现注册,如下:

1671765085418

# 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 // 注册成功后开始登录
            }
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

服务器响应的数据如下:

1671766460633

处理之,如下:

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 // 注册成功后开始登录
            }
        })
    }
}
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

测试之,如下:

1671766674992

看一下localstorage中有没有token,如下:

1671766710748

后面再去调用其它接口,就会自动地带个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>
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

# 8,验证码实现

只有在验证码正确的前提下才能实现登录 和 注册,大家自己实现。

# 9,github的使用

通常我们是不能直接访问github的,需要科学上网,如下:

1671776703714

访问github,如下:

1671776796326

需要注册一个账号,如下:

1671776845757

1671776918918

创建一个远程仓库,如下:

1671776966300

1671777048375

1671777064188

1671777180614

把远程仓库克隆下来,如下:

1671777343641

创建index.html,如下 :

1671777407918

代码如下:

1671777385968

进行本地仓库的管理,如下:

1671777499007

第一次,也需要输入用户名和密码,如下:

1671777678927

现在github不支持用户名和密码登录,参考如下 连接:

https://blog.csdn.net/lm_is_dc/article/details/120745701

现在就可以把代码推送到仓库中了,如下:

1671778225244

当然,我们也可以配置SSH,如下:

1671778270775

部署如下:

1671778325015

1671779911853

1671779925625

分享一个github仓库:https://github.com/GrowingGit/GitHub-Chinese-Top-Charts

# 10,freewha

可以免费部署静态页面。

网站:https://freewha.com/

使用步骤,如下:

1671780214856

1671780242329

1671780282185

1671780299889

此时它就给了你一片空间,直接访问之,如下:

1671780339809

你需要把代码扔到服务器上,通过一个工具来扔,如下:

1671780416185

安装此工具,安装完后,有如下图标:

1671780482598

双击打开此软件,如下:

1671780577658

连上去,如下:

1671780633184

1671780759152

可以把项目更换一个名字,如下:

1671780802822

访问之,如下:

1671780858476

# 四,首页面数据渲染

需求如下:

1672017689944

# 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>
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

效果如下:

1672018511216

1672018519941

# 2,请求首页面的数据

通过network面板分析如下:

1672018659548

1672018729384

先封装一个API接口,如下:

1672018827032

在页面中导入接口,定义相关的状态,如下:

1672019222973

发出ajax请求,获取数据,给状态赋值,如下:

1672019411385

在调试工具查看之,如下:

1672019433192

# 3,渲染轮播图数据

轮播图组件在vant中提供现成的,如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/swipe

直接上代码如下:

1672020697095

对应的样式,如下:

1672020731252

效果如下:

1672020744598

# 4,渲染宫格组件

vant中也是有现在的组件,如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/grid

直接上代码,如下:

1672021077908

样式大家自己书写一下,效果如下:

1672021094526

# 5,渲染商品列表

在宫格下面有三个模块,如下:

1672021174754

1672021187828

1672021200779

上面的三个模块一样,封装成一个组件,传递什么数据,就渲染什么模块,封装如下:

1672021660246

在首页面中,引入组件,使用组件,如下:

1672021791501

效果如下:

1672021803279

书写对应的样式,如下:

1672021849771

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>
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

再次看效果如下:

1672021892847

同理,其它模块也是一样的,处理如下:

1672021953800

效果如下:

1672021982817

1672021993661

1672022008551

给home_box添加一行样式,如下 :

1672022138795

首页面参考对应的样式:

<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>
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

到此,首页面的数据就渲染完毕了。

# 五,详情页面渲染

# 1,接口封装

分析接口,如下:

1672023480242

1672023497037

1672023550827

此时,我们需要在响应拦截器中做处理,如下:

1672023854052

现在登录后,继续分析如下:

1672023911544

1672024179788

先把接口封装好,如下:

1672024232385

# 2,创建详情组件配置对应的路由

创建详情组件,如下:

1672024333926

配置路由,如下:

1672024391873

测试是否可以跳到商品详情页面,如下:

1672024427543

在商品列表页面,点击商品,就需要到达商品的详情,如下:

1672024540280

测试OK,如下:

1672024565545

在详情页面中,需要获取ID,如下:

1672024711200

# 3,根据ID发送请求获取详情页面数据

接下来,在详情页面中就需要根据ID获取详情页面的数据了,接口也封装好了。代码如下:

1672025014406

现在我登录上,登录上之后,就有token,后面调用详情接口,会自动带上token。

登录上之后,我们再测试之,如下:

1672025091864

定义一个状态,把详情数据保存起来,如下:

1672025181885

在调试工具,看一下,info有没有数据,如下:

1672025267815

到此,详情页面的数据就已经回来了。然后要渲染数据。

# 4,渲染详情页面的数据

现在详情页面的数据就回来了,需要渲染数据,页面如下:

1672025400397

开始书写详情页面的结构,如下:

1672025635327

样式直接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>
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

效果如下:

1672025709516

# 5,绘制action-bar

action-bar在vant中有现成的组件,如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/action-bar

如果vant中没有封装,其实自己也是可以实现的,在这里,我们就不使用人家封装的,我们自己写的,代码如下:

1672035467663

对应的样式,如下:

1672035485557

大家参考样式,如下:

.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;
        }
    }
}
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

效果如下:

1672035528464

# 6,点击加入购物车

需求:

1672035597369

不着急,先分析接口,封装接口,如下:

1672035773480

1672035822705

1672035846840

下面还有一个接口,也是shop-cart,分析之,如下:

1672035890118

1672035921856

重点关注第一个接口,因为第1个接口,是用于把商品添加到购物车。第2个接口稍后再说。

封装添加商品到购物车的接口,如下:

1672036071198

在组件中,引入,调用接口,如下:

1672036281104

测试之,如下:

1672036310992

1672036335996

# 六,购物车

# 1,把购物车中的数据存储到pinia中

创建仓库如下:

1672036590991

在main.js中需要注册仓库,如下:

1672037055715

封装一个接口,获取购物车中的数据,此接口,之前分析过了,如下:

1672036682556

在仓库中,就可以发送ajax获取数据,如下:

1672036754916

在组件中,当我们点击添加商品到购物车时,就需要派发上面的action,如下:

1672037153753

给每一个数据,添加一个checked选项,表示这个商品是否选中,如下:

1672037309417

此时,我们再看一下购物车中有没有数据,如下:

1672037595036

到此,我们就把购物车数据存储到了仓库中。

# 2,处理购物车中商品数量

需求:

1672038969641

你要知道,购物车中的商品都是在pinia中,我们只需要统计它的数量就可以了,使用getters,如下:

1672039295965

点击加入购物车,测试之,如下:

1672039222795

在组件中就可以使用数据,如下:

1672039340270

效果如下 :

1672039356584

在详情页面中,一上来就需要加载购物车中的数据,如下:

1672039479674

再次测试之,如下:

1672039508122

到此,商品的数据就统计好了。然后,点击购物车,要去购物车页面了。

# 3,返回上一页

当我们点击购物车,去购物页面,如下:

1672039604334

测试之,如下:

1672039625156

上面的操作是在我登录的情况下做的,如果说没有登录,去购物车,需要跳到登录页面,关键是登录成功后,要去哪?

答:登录成功后,要去购物车,就是所谓的返回上一页。

现在的把登录后的token删除掉,你把token删除了,就相当于没有登录,如下:

1672039850196

需要在购物车模块,获取购物车数据,如下:

1672040281045

在响应拦截器中处理,如下:

1672039982138

登录成功后,判断一个这个needback,如下:

1672040385450

现在我们尝试点击购物车,去了登录页面,如下:

1672040443211

点击登录,如下:

1672040481165

登录成功后,就自动去了购物车。

# 4,购物车页面数据渲染

需求:

1672103718880

现在仓库中就有购物车的数据了,如下:

1672103602071

在组件中就可以获取数据,渲染数据,如下:

1672104531982

1672104540714

效果如下:

1672104558599

写一点样式,如下:

1672105258444

效果如下:

1672105269824

最下面还差了一个submitbar,在vant中也是有现成的,如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/submit-bar

1672105391930

1672105472881

绘制submitbar,如下:

1672105802654

完整参考代码如下:

<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>

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

效果如下:

1672105843687

# 5,删除购物车数据

在network中分析如下:

1672107484225

1672107513513

封装一个API接口,如下:

1672107595578

你要知道,购物车的数据是在仓库中,删除时,需要定义一个action来处理,如下:

1672107775059

在组件中,点击删除按钮时,需要派发action,如下:

1672107978318

1672108014019

测试之,如下:

1672108045633

# 6,计算总价和全选实现

分析:

1672108156753

数据都是在仓库中,使用计算属性计算总价,如下:

1672108452044

效果如下 :

1672108464719

然后实现全选,如下:

1672108654267

# 7,步进器实现

分析,如下 :

1672108736746

1672108781018

1672108795575

封装API接口,如下:

1672108899747

你要知道,购物车数据在仓库中,定义一个action,如下:

1672109062185

然后,在组件中要派发action,如下:

1672109182658

1672109165242

测试之,如下:

1672109224072

到此,步进器就实现了。

还有一个地方,也需要调用接口,没有这个接口,我们就不去调用了。如下:

1672109289448

# 七, 地址管理

# 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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

直接copy到代码中,如下:

1672120058166

创建地址相关的两个组件,一个是地址列表,一个是新增和编辑地址,新增和编辑共用一个组件,如下:

1672121299152

配置路由,如下:

1672121403748

测试之,如下:

1672121412285

1672121433142

# 2,获取地址相关的数据渲染之

地址列表如下:

1672121517230

地址列表在vant中有现在的组件,如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/address-list

1672121626420

在地址列表组件中就去调接口获取数据,如下:

1672122036995

效果如下:

1672122045696

看一下控制台,有没有获取数据,如下:

1672122107040

分析添加地址接口,如下:

1672122268984

1672122293947

1672122351779

传递的参数的数据格式,如下:

1672122399281

使用postman添加一些测试数据,如下:

1672122460061

json数据格式如下:

{
    "cityName": "北京市",
    "defaultFlag": 0,
    "detailAddress": "1234567890",
    "provinceName": "北京市",
    "regionName": "东城区",
    "userName": "121313",
    "userPhone": "17001100482",
    "areaCode": "110101",
    "postalCode": "123456"
}
1
2
3
4
5
6
7
8
9
10
11

服务器返回如下:

1672122477853

因为我们需要带上token,如下:

1672122559659

现在添加地址就OK了。

再看一下,控制台中有没有数据,如下:

1672122617186

需要把数据加工完后,赋值给状态,我们需要的数据格式如下:

1672122714628

我们的接口返回的数据不是这样的,需要把数据整理成vant需要的,如下:

1672122929527

1672122983885

效果如下:

1672123000736

上面实现的地址列表渲染,并没有完全实现好。先放一下。

# 3,实现新增地址

实现新增地址:

1672124681408

实现路由跳转,如下:

1672124740919

实现Add方法,如下:

1672124802733

测试,可以实现跳转,如下:

1672124822885

现在我们就需要实现新增了。新增地址的表单在vant中也有现成的,如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/address-edit

1672124901523

开始绘制表单,如下:

1672125274299

参考代码如下:

<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

效果如下:

1672125296579

当我们点击保存时,能不能收集到数据呢?如下:

1672125385453

测试如下:

1672125485955

调用接口,传递给服务器的数据如下 :

1672125522787

当点击保存时,把数据转化成接口需要的数据,并调用接口,如下:

1672126076258

服务器响应下:

1672126089278

实现了新增后,就可以跳到地址列表了,如下:

1672126232724

效果如下 :

1672126211212

然后,我们去完善一下地址列表,如果这个地址是默认的,需要让这个地址选中,如下:

1672126339524

我们有一个状态,叫chosenAddressId,来控制地址是否选中。我们需要给chosenAddressId赋值,如下:

1672126641893

1672126702335

效果如下 :

1672126718840

# 4,实现编辑地址

分析如下:

1672127961772

点击编辑,实现路由跳转,传递地址ID,如下:

1672128084036

就可以实现跳转化了,在编辑页面中获取id,如下:

1672128156459

测试之,如下:

1672128213896

处理上面的两种情况下,如下:

1672128282433

测试如下 :

1672128306303

有了ID,就根据ID获取地址详情,实现数据回显,如下:

1672128429569

看服务器,响应的结果,如下:

1672128458801

实现数据回显的数据格式和接口得到的数据格式不一样,回显需要的数据格式如下:

1672128590961

1672128652505

定义一个状态,存储结果,如下:

1672128874505

1672129063636

使用info,如下 :

1672129084147

效果如下 :

1672129092797

修改完数据,点击保存,要实现编辑,如下:

1672129271316

1672129258185

效果如下:

1672129243286

到此,编辑地址就实现了。

# 5,实现地址的删除

只能在编辑情况下才能删除,删除绑定点击事件,如下:

1672129363764

导入接口,如下:

1672129425212

实现onDelete方法,如下:

1672129474657

测试之,如下:

1672129500443

到此,地址管理模块就实现了。

# 八,生成订单模块

# 1,创建两个组件并配置路由

看一下,什么是生成订单,当我们选中了某个地址,会到生成订单模块,如下:

1672190297805

分析上面的页面,如下:

1672190641777

除了上面的生成订单页面,还有一个我的订单页面,如下:

1672190733291

现在我们就创建出这两个组件,如下:

1672190828045

1672191074426

配置对应的路由,如下:

1672190934364

在路由的元信息中,可以指定til选项,是用来设置页面的title,如下:

1672191006016

测试之,如下:

1672191111630

1672191141841

# 2,点击地址去生成订单页面

在购物车页面中,点击结算,去地址列表页面,如下:

1672191378667

现在就到达了地址列表页面,如下:

1672191396546

现在点击某个地址,要去生成订单页面,如下:

1672191484698

实现对应的方法,如下:

1672191554059

测试之,如下:

1672191720189

我们定义一个标识来区分,如下:

1672191896303

再次测试之,如下:

1672191926470

1672191957177

点击地址,询问是否选中此地址,引入询问框,如下:

1672192025673

1672192161062

测试是否可以到达生成订单页面,如下:

1672192197767

1672192213085

# 3,获取地址ID,发送ajax请求,渲染数据

需求:

1672193810861

封装一个接口,根据ID,获取地址详情,还是昨天的接口,如下:

1672193924640

直接上代码如下:

1672194347294

看一下,控制台中有没有数据,如下:

1672194362491

渲染数据,如下:

1672194449097

效果如下:

1672194464661

书写对应的样式,如下:

1672194535737

参考之,如下:

<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>
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

效果如下:

1672194771536

# 4,商品数据的渲染

需求:

1672194967602

现在回到购物车页面,当点击结算时,需要把商品的id存储到本地,如下:

1672195200865

点击结算时,把ids存储到状态中,如下:

1672195442391

效果如下:

1672195398515

我们在生成订单页面中是不能获取状态,可以使用localstorage进行通信,如下:

1672195522668

数据存储到本地,会自动地toString,如下:

1672195578843

到此,在购物车模块中,就把选中的商品存储到本地。那么在生成订单页面中,就可以从本地获取这些ids了。

在生成订单页面中,获取ids,如下:

1672195815448

效果如下 :

1672195804285

定义一个状态,保存这些id,如下:

1672195871638

1672195912608

封装一个接口,此接口是根据这些ids,获取商品详情,如下:

1672195967158

调用接口,获取数据,如下:

1672196001886

1672196105252

服务器响应如下:

1672196139911

定义一个状态,保存数据,如下:

1672196188251

1672196243626

查看状态没有没值,如下:

1672196282387

最后,渲染数据,在vant中有现成的组件,如下:

1672196377532

效果如下:

1672196402206

# 5,绘制总价和底部

分析状态如下:

1672197666413

1672197709258

把状态定义出来,如下:

1672197837124

实现底部,如下:

1672198167948

效果如下:

1672198179928

copy样式,如下:

1672198255343

效果如下:

1672198264271

刚才我们默认让它显示了,但是只有点击生成订单时候才能显示,如下:

1672198419852

测试之,如下:

1672198431148

还有一个总价没有实现,实现之,如下:

1672198568883

效果如下:

1672198580937

# 6,生成订单完成支付

当点击生成订单,就会调用接口,生成订单,分析如下:

1672207803693

1672207876615

1672207908877

封装API接口,如下:

1672207959145

当点击生成订单时,就需要调用接口,如下:

1672208016215

1672208194160

测试之,如下:

1672208266301

现在购物车就没有数据了,如下:

1672208340093

完成支付,如下:

1672208417417

分析此API接口,如下:

1672208562660

1672208589715

1672208607837

封装API接口,如下:

1672208651928

点击微信支付或支付宝支付,就需要调用接口了,绑定点击事件,如下:

1672208711745

引入接口,实现对应的方式,如下:

1672208982700

1672209026893

浏览器测试之,如下:

1672209046142

还有一个取消支付,如下:

1672209074874

当点击取消支付时,此时订单已成生,仅仅是没有支付,直接去我的订单模块如下:

1672209265815

测试之如下:

1672209305917

到此,生成订单模块就实现了。

# 九,我的订单模块

# 1,分析van-tabs和van-list

先不去考虑调接口,就分析两个组件,一个叫van-tabs和van-list,组件如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/tab

1672210755731

还需要看一个van-list组件,如下:

https://vant-contrib.gitee.io/vant/#/zh-CN/list

1672210948732

把上面的两个组件放到订单模块中继续分析如下:

<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>
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

效果如下:

1672211160245

书写对应的样式,如下:

1672211277285

效果如下 :

1672211285100

重点要分析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>
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

效果如下:

1672212066689

# 2,获取订单数据,渲染订单数据

分析API接口,如下:

1672212239482

1672212265009

1672212364128

封装API接口,如下:

1672212419438

在我的订单页面中,引入接口,调用之,如下:

1672213085129

此时list数据源中就有数据,如下:

1672212973661

此时,就需要渲染数据,如下:

1672213335682

效果如下:

1672213500143

# 3,实现tab选项功能

定义一个状态,和tab进行绑定,如下:

1672214859618

当点击切换时,active也是会变化的,如下:

1672214933642

此时,就需要监听active的变化,如果ative变化了,就需要重新请求,如下:

1672214974855

1672215057931

效果如下 :

1672215044912

监听到了,改变状态,重新请求,如下 :

1672215288112

效果如下:

1672215298949

# 十,我的模块

# 1,渲染我的模块数据

封装API接口,如下:

1672215469773

在页面中调用接口,渲染数据,如下:

1672215704182

效果如下:

1672215714365

剩下的功能,需要自行实现。

# 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
        }
    })
}
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

# 十一,总结

# 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优化?
  • 你是怎么部署上线的?上线有哪些注意的?
  • 你这个项目的鉴权是怎么做?
  • 除了以上这些常问的题目,还会问一些场景题。
Last Updated: 12/28/2022, 4:56:47 PM