危险

为之则易,不为则难

0%

04_项目搭建

今日目标

✔ 掌握如何使用 Vite 创建项目。

✔ 掌握通过 Vite 如何配置 alias 别名、Less 自动导入等。

项目介绍

项目介绍

电商发展十余年,是个成熟的模式,小兔鲜儿是 B2C 电商平台,综合品类平台,参考网易严选。平台理念:(品质)新鲜、(价格)亲民、(物流)快捷。

image-20210522214828989

  • 首页模块。

    顶部通栏,吸顶导航,网站头部,左侧分类,轮播图,新鲜好物,人气推荐等。

  • 一级分类。

    面包屑,轮播图,全部二级分类,二级分类推荐商品等。

  • 二级分类。

    筛选区域,排序功能,商品列表,无限加载等。

  • 商品详情。

    商品图片展示,基本信息展示,配送城市选择,SKU 选择,库存选择,商品详情展示,商品评价展示,24 小时热销,相关专题,加入购物车等。

  • 购物车。

    • 头部购物车:展示商品数量和列表,删除商品,跳转购物车页面等。

    • 购物车页面:购物车商品展示,选择商品,修改数量,修改商品规格,价格计算,跳转下单等。

  • 登录模块。

    表单校验,账户密码登录,手机号登录,第三方登录,绑定手机,完善信息等。

  • 填写订单。

    订单商品展示,收货地址选择,收货地址修改,支付方式选择,生成订单等。

  • 进行支付。

    订单信息展示,跳转支付网关,提示正在支付,等待支付结果,跳转支付成功页面等。

  • 个人中心。

    • 中心首页:展示个人信息,近期收藏商品,近期足迹,猜你喜欢等。

    • 订单管理:全部订单,待付款,待发货,待收货,待评价,已完成,已取消。立即付款,取消订单,确认收货,删除订单,查看物流等。

    • 订单详情:订单状态,订单进度,详细信息等。

  • 总结总结。

    完成电商支付闭环,之前负责登录/注册(没啥含金量)?现在负责商品管理、购物车、支付、订单管理等。

配套资源

目标

清楚企业开发都会用到哪些配套资源。

内容

开发配套:原型稿接口文档参照案例

1
2
3
# 支付账号
账号: jfjbwb4477@sandbox.com
密码: 111111

使用技术

目标

能够说出项目用到的技术栈和相关组件。

技术

  • Vue3.0 (使用组合 API 的方式来开发)。

  • Vite 开发。

  • Axios (请求接口)。

  • Vue Router (单页路由)。

  • Pinia (状态管理)。

  • normalize.css (初始化样式)。

  • @Vueuse/core (组合 API 常用工具库)。

  • 算法 Power Set

  • dayjs (日期处理)。

  • vee-validate (表单校验)。

组件

轮播图、面包屑、查看更多、骨架屏组件、单选框、复选框、对话框组件、消息提示、消息确认组件、标签页、城市选择组件等。

项目起步

创建项目

目标

使用 Vite 初始化小兔鲜项目。

内容

  1. 使用 Vite 初始化项目。
1
yarn create vite rabbit-vue3-ts --template vue-ts
  1. 安装依赖包,需要进入到 rabbit-Vue3-ts 项目中。
1
yarn
  1. 启动项目。
1
yarn dev

目录调整

目标

能够调整项目目录结构,规范开发环境。

修改文件

App.Vue,删除无用的内容。

1
2
3
<template>
<div>app组件</div>
</template>

删除文件

  • src/components/HelloWorld.Vue,HelloWorld 组件。

  • src/assets/logo.png,Vue 默认的 logo。

新增文件夹

  • utils,用于存放工具相关。

  • assets/images,用于存放图片相关。

  • assets/styles,用于存放样式相关。

  • router,用于存放路由相关。

  • store,用于存放数据相关。

  • views,用于存放页面级别的组件。

  • types,用于存放 ts 的公共类型。

管理项目

目标

能够使用 Git 管理项目并上传到码云。

内容

  1. 初始化项目:git init

  2. 将代码添加到暂存区: git add .

  3. 提交代码 git commit -m '初始化提交'

  4. 码云上创建项目 rabbit-Vue3-ts,选择开源。

  5. 关联远端仓库。

1
git remote add origin https://gitee.com/ifercarly/rabbit-vue3-ts.git
  1. 推送到远程仓库。
1
git push -u origin master

Axios 封装

目标

基于 Axios 封装一个请求方法,调用接口时使用。

内容

  1. 安装 axios。
1
yarn add axios
  1. 新建模块,src/utils/request.ts
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
import axios from 'axios'

// 备用接口地址: http://pcapi-xiaotuxian-front-devtest.itheima.net/
const request = axios.create({
baseURL: 'http://pcapi-xiaotuxian-front.itheima.net/',
timeout: 5000,
})

// 添加请求拦截器
request.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error)
}
)

// 添加响应拦截器
request.interceptors.response.use(
function (response) {
return response
},
function (error) {
// 对响应错误做点什么
return Promise.reject(error)
}
)

export default request
  1. 代码测试,App.vue
1
2
3
4
5
6
7
8
9
10
11
<script lang="ts" setup>
import request from './utils/request'
const test = async () => {
const res = await request.get('/home/index')
console.log(res)
}
test()
</script>
<template>
<div>app组件</div>
</template>

配置路径别名

目标

能够配置 @ 路径别名,方便导入模块。

内容

  1. 修改 Vite 配置,vite.config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { defineConfig } from 'vite'
import vue from '@Vitejs/plugin-vue'
const path = require('path')
// https://Vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 3000,
},
})
  1. 安装 Node 的类型声明文件。
1
yarn add @types/node -D
  1. 修改 TS 配置,tsconfig.json
1
2
3
4
5
6
7
8
9
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}

🧐 注意:修改完 vite.config.ts 配置文件需要重启。

样式处理

less 变量与 mixins

目标

能够使用 less 变量定义项目中常用的颜色,使用 mixins 定义项目中常用的样式。

内容

  1. 安装 less 依赖包。
1
yarn add less -D
  1. 定义变量,assets/styles/variables.less
1
2
3
4
5
6
7
8
9
10
// 主题
@xtxColor: #27ba9b;
// 辅助
@helpColor: #e26237;
// 成功
@sucColor: #1dc779;
// 警告
@warnColor: #ffb302;
// 价格
@priceColor: #cf4444;
  1. 定义混入,assets/styles/mixins.less
1
2
3
4
5
6
7
8
// 鼠标经过上移阴影动画
.hoverShadow () {
transition: all 0.5s;
&:hover {
transform: translate3d(0, -3px, 0);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
}
}
  1. 测试,App.vue
1
2
3
4
5
6
7
8
9
<style lang="less" scoped>
// 必须导入variables.less
@import '@/assets/styles/variables.less';
@import '@/assets/styles/mixins.less';
h1 {
background-color: @warnColor;
.hoverShadow();
}
</style>
  1. 了解,混入也可以传递参数。
1
.hoverShadow(-20px);
1
2
3
4
5
6
7
.hoverShadow (@y: -3px) {
transition: all 0.5s;
&:hover {
transform: translate3d(0, @y, 0);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
}
}

自动导入

目标

能够自动导入项目中的 less 变量mixins

问题

variables.lessmixins.less 两个 less 文件是需要在多个组件中去使用的,每次使用都要重新导入,麻烦。

内容

参考文档

  1. 修改 Vite 配置,vite.config.ts
1
2
3
4
5
6
7
8
9
10
11
12
export default defineConfig({
css: {
preprocessorOptions: {
less: {
additionalData: `
@import "@/assets/styles/variables.less";
@import "@/assets/styles/mixins.less";
`,
},
},
},
})
  1. 修改代码,App.vue
1
2
3
4
5
6
<style lang="less" scoped>
h1 {
background-color: @warnColor;
.hoverShadow();
}
</style>

统一样式

目标

能够使用 normalize.css 统一项目中的样式。

内容

  1. 安装 normalize.css
1
yarn add normalize.css
  1. 使用 normalize.cssmain.ts
1
2
3
4
import { createApp } from 'Vue'
import App from './App.Vue'
import 'normalize.css'
createApp(App).mount('#app')
  1. 测试,App.vue
1
2
3
4
5
6
7
8
9
10
<template>
<div>
<p>哈哈哈</p>
<ul>
<li>123</li>
<li>456</li>
</ul>
<h1>这是大标题</h1>
</div>
</template>

🧐 通过效果会发现,h1 ul 等样式还是保留的,但是不一致的样式已经被统一了(例如 body 的 margin)。

公共样式

目标

能够给项目设置公用的样式。

内容

  1. 新建文件,assets/styles/common.less
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// 按照网站自己的需求,提供公用的样式
* {
box-sizing: border-box;
}

html {
height: 100%;
font-size: 14px;
}
body {
height: 100%;
color: #333;
min-width: 1240px;
font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
}

ul,
h1,
h3,
h4,
p,
dl,
dd {
padding: 0;
margin: 0;
}

a {
text-decoration: none;
color: #333;
outline: none;
}

i {
font-style: normal;
}

input[type='text'],
input[type='search'],
input[type='password'],
input[type='checkbox'] {
padding: 0;
outline: none;
border: none;
-webkit-appearance: none;
&::placeholder {
color: #ccc;
}
}

img {
max-width: 100%;
max-height: 100%;
vertical-align: middle;
// background: #ebebeb;
}

ul {
list-style: none;
}

#app {
background: #f5f5f5;
// 不能选中文字
user-select: none;
}

.container {
width: 1240px;
margin: 0 auto;
position: relative;
}

// 一行省略
.ellipsis {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
// 二行省略
.ellipsis-2 {
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}

.fl {
float: left;
}

.fr {
float: right;
}

.clearfix:after {
content: '.';
display: block;
visibility: hidden;
height: 0;
line-height: 0;
clear: both;
}

// 闪动画
.shan {
&::after {
content: '';
position: absolute;
animation: shan 1.5s ease 0s infinite;
top: 0;
width: 30%;
height: 100%;
background: linear-gradient(to left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0) 100%);
transform: skewX(-45deg);
}
}
@keyframes shan {
0% {
left: -100%;
}
100% {
left: 120%;
}
}

// 离开淡出动画
.fade {
&-leave {
&-active {
position: absolute;
width: 100%;
transition: opacity 0.5s 0.2s;
z-index: 1;
}
&-to {
opacity: 0;
}
}
}

// 1. 离开,透明度 1---->0 位移 0---->30
// 2. 进入,透明度 0---->1 位移 30---->0
// 执行顺序,先离开再进入
.pop {
&-leave {
&-from {
opacity: 1;
transform: none;
}
&-active {
transition: all 0.5s;
}
&-to {
opacity: 0;
transform: translateX(20px);
}
}
&-enter {
&-from {
opacity: 0;
transform: translateX(20px);
}
&-active {
transition: all 0.5s;
}
&-to {
opacity: 1;
transform: none;
}
}
}

// 表单
.xtx-form {
padding: 50px 0;
&-item {
display: flex;
align-items: center;
width: 700px;
margin: 0 auto;
padding-bottom: 25px;
.label {
width: 180px;
padding-right: 10px;
text-align: right;
color: #999;
~ .field {
margin-left: 0;
}
}
.field {
width: 320px;
height: 50px;
position: relative;
margin-left: 190px;
.icon {
position: absolute;
left: 0;
top: 0;
width: 40px;
height: 50px;
text-align: center;
line-height: 50px;
color: #999;
~ .input {
padding-left: 40px;
}
}
.input {
border: 1px solid #e4e4e4;
width: 320px;
height: 50px;
line-height: 50px;
padding: 0 10px;
&.err {
border-color: @priceColor;
}
&:focus,
&:active {
border-color: @xtxColor;
}
}
}
.error {
width: 180px;
padding-left: 10px;
color: @priceColor;
}
}
.submit {
width: 320px;
height: 50px;
border-radius: 4px;
background: @xtxColor;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 16px;
color: #fff;
display: block;
margin: 0 auto;
}
}
  1. 导入样式,main.ts
1
2
3
4
5
import { createApp } from 'Vue'
import App from './App.Vue'
import 'normalize.css'
import '@/assets/styles/common.less'
createApp(App).mount('#app')