参考博文https://blog.csdn.net/shixue7758/article/details/117200504?utm_source=app&app_version=4.12.0
一、项目介绍
这个项目是自学vue后的练习项目,使用vue-cli搭建vue.js环境和webpack配置环境,采用组件化思想划分目录结构,创建 assets用来存放 img 和 css ;components中划分成通用的common组件和本项目中业务相关的context组件;common中用来存放公共的 js文件;network用来网络封装模块;router用来实现组件和路径之间的映射关系;store用来存放状态管理;view用来存放 home category等大的视图;主要实现了首页商品页,商品详情页,购物车页的基本功能
1、项目创建和GitHub托管
1、先查看vscode有没有node环境 node -v,如果没有的话从官网下载http://nodejs.cn/download/与Windows对应的node版本即可,下载之后进行安装。安装成功的标志是 windows+R打开命令行之后输入 cmd 打开命令行,输入 node -v查看node版本。
2、安装 npm install vue
3、创建项目 vue create supermall
4、这时候查看 package.json文件中的script行,进行npm run build打包,npm run serve运行项目,即可在页面中查看项目了。
5、在github上创建自己的项目,readme和gitingore都没有必要选,因为本地的项目中有。
6、建立远程和本地的联系。
注意>、如果本地项目中没有 .git文件夹的话,就 git init 一下就有了
1>、在本地项目中的同级的文件夹下git clone 远程仓库的位置,这样就把远程仓库下载到本地了;之后在把interviewer中的内容复制到interviewer-network中;之后项目的编写在interviewer-network文件夹中进行,首先git status查看文件状态,之后git add . 把所有的文件都加入到项目中,在git commit -m ‘项目介绍’,在git push 推送到远程仓库。
2>、不用clone就可以建立连接。在远程建立一个空白仓库,在本地项目中首先 git remote add origin https://github.com/wanglu458512/supermall.git之后 git push -u origin maste
2、划分目录结构
assets/common/components(common,context)/network/router/store/views/App.vue/main.js
1、删除src中原有的内容
2、创建 assets用来存放 img 和 css ;components中划分成所有项目都可能用到的common组件和本项目中用到的context组件;common中用来存放公共的 js文件;network用来网络封装模块;router用来实现组件和路径之间的映射关系;store用来存放状态管理;view用来存放 home category等大的视图
3、引入基本的CSS文件
normalize.css文件作用:让不同的浏览器在渲染网页元素的时候样式更统一,比如有的浏览器规定超链接下边没有下划线有的有,在比如有的超链接是蓝色的有的是黑色的,这样可以对几乎所有的默认设置进行重置。
normalize.css的引入:在github上找到源文件进行引入。
base.css文件“就是引入了一些基本的样式。在base.css中导入@import “./normalize.css”;并且在App.vue中导入@import “./assets/css/base.css”;
4、配置别名和.editorconfig文件
使用vue-cli3.0搭建项目比之前更简洁,没有了build和config文件夹。vue-cli3的一些服务配置都迁移到CLI Service里面了,对于一些基础配置和一些扩展配置需要在根目录新建一个vue.config.js文件进行配置
`module.exports = {
//配置别名
configureWebpack: {
resolve: {
alias: {
'@': 'src',
'assets': '@/assets',
'common': '@/common',
'components': '@/components',
'network': '@/network',
'router': '@/router',
'store': '@/store',
'view': '@/view',
}
}
}
}`
在一个就是.editorconfig
文件配置整个项目的代码规范,这个下载EditorConfig for VS Code插件才管用
5、项目的模块划分通过底部tabbar组件
在components中的common中放进tabbar文件夹,用来引入一个通用的组件。然后在context中放入mainTabBar文件夹,然后在App.vue中导入注册使用MainTabBar组件。
6、TabBar的封装过程(第五步前)
1.如果在下方有一个单独的TabBar组件,你如何封装
自定义TabBar组件,在APP中使用
让TabBar出于底部,并且设置相关的样式
2.TabBar中显示的内容由外界决定
定义插槽
flex布局平分TabBar
3.自定义TabBarItem,可以传入 图片和文字
定义TabBarItem,并且定义两个插槽:图片、文字。
给两个插槽外层包装div,用于设置样式。
填充插槽,实现底部TabBar的效果
4.传入高亮图片
定义另外一个插槽,插入active-icon的数据
(暂时)定义一个变量isActive,通过v-show来决定是否显示对应的icon
5.TabBarItem绑定路由数据
安装路由:npm install vue-router —save
完成router/index.js的内容,以及创建对应的组件
main.js中注册router
APP中加入组件
6.点击item跳转到对应路由,并且动态决定isActive
监听item的点击,通过this.$router.replace()替换路由路径
通过this.$route.path.indexOf(this.link) !== -1来判断是否是active
7.动态计算active样式
封装新的计算属性:this.isActive ? {'color': 'red'} : {}
二、首页的开发
2.1 首页导航栏
首先封装一个nav-bar的组件:
<template>
<div class="nav-bar ignore">
<div class="left"><slot name="left"></slot></div>
<div class="center"><slot name="center"></slot></div>
<div class="right"><slot name="right"></slot></div>
</div>
</template>
<script>
export default {
name: "NavBar"
}
</script>
<style scoped>
.nav-bar {
display: flex;
height: 44px;
line-height: 44px;
text-align: center;
}
.left {
width: 60px;
}
.right {
width: 60px;
}
.center {
flex: 1;
}
</style>
之后在views中的home文件夹下的Home.vue中导入 注册 使用该组件。使用的时候只用center这个中间的插槽,然后给该组件设置自己的样式。
<nav-bar class="home-nav">
<div slot="center">购物街</div>
</nav-bar>
2.2. 请求首页数据
先安装网络封装模块 axios npm install axios
,之后在network文件夹中的request.js
文件中导入axios模块,之后创建网络请求,请求所有前端页面需要展示的数据
封装一个axios请求
创建axios实例
拦截响应,返回.data数据
import axios from 'axios'
export function request(config){
const instance=axios.create({
baseURL:'http://152.136.185.210:7878/api/m5',
timeout:5000
})
// 请求拦截
instance.interceptors.request.use(config=>{
return config
},err=>{
console.log(err);
})
// 响应拦截:一般会过滤数据
instance.interceptors.response.use(res => {
return res.data
}, error => {
console.log(error);
})
return instance(config)
}
为了把不同页面展示的数据分开从request.js中请求,现在把首页性需要的数据放在network文件夹中的home.js文件中,暴露方法
import {
request} from "./request"
// 轮播图数据
export function getHomeMultidata(){
return request({
url:"/home/multidata"
})
}
// 请求首页商品列表数据
export function getHomeGoods(type,page){
return request({
url:"/home/data",
params:{
type,
page
}
})
}
从服务器请求过来的首页数据怎么才能在home.vue
中展示出来呢,就是在home.vue中导入一下这两个数据模块 这样的话 就是 home.vue面向home.js开发
import {getMultiData, getProductData} from "network/home"; //从服务器中请求过来的首页需要展示的数据
请求过来的轮播图数据home.vue中的created中先调用一下请求函数,然后在methods做一下数据处理,请求过来的数据在data中做保存。
// 网络请求数据
created() {
// 1.请求多个数据
this.getHomeMultidata();
//2 调用请求首页商品数据
this.getHomeGoods("pop");
this.getHomeGoods("new");
this.getHomeGoods("sell");
},
methods: {
...
// 网络请求相关
// 1.请求首页轮播图、recmmoned的数据
getHomeMultidata() {
getHomeMultidata().then((res) => {
this.banners = res.data.banner.list;
this.recommends = res.data.recommend.list;
});
},
// 2.请求首页商品数据,动态获取page
getHomeGoods(type) {
const page = this.goods[type].page + 1;
getHomeGoods(type, page).then((res) => {
// console.log(res.data);
this.goods[type].list.push(...res.data.list);
this.goods[type].page += 1;
// 完成上拉加载更多
this.$refs.scroll.finishPullUp();
});
},
...
}
2.3. 把轮播图数据在首页进行展示
在components中的common文件夹下引入封装好的组件swipper, 不抽的话接下来是这样的。在home.vue中导入封装好的组件import {Swiper, SwiperItem} from 'components/common/swiper’然后注册组件,最后在template中使用组件。
但是如果把整个轮播图放在Home.vue这里的话,后边的代码会越来越复杂,把这个轮播图抽出来,进行封装,放在home文件夹中的childcomps文件夹中,在该文件夹中建立HomeSwipper.vue文件,把刚才放在home.vue中的代码剪过来。
<template>
<swiper>
<swiper-item v-for="(item, index) in banners" :key="index">
<!-- 动态的获取图片的链接 -->
<a :href="item.link">
<!-- 动态的获取轮播图中的图片 -->
<img :src="item.image" alt="">
</a>
</swiper-item>
</swiper>
</template>
<script>
import {
Swiper, SwiperItem} from 'components/common/swiper'
export default {
name: "HomeSwiper",
//得从父亲那里 拿数据 啊
props: {
banners: {
type: Array,
required: true
}
},
components: {
Swiper,
SwiperItem
},
}
</script>
<style scoped>
</style>
然后在home.vue中导入该组件import HomeSwiper from './childComps/HomeSwiper',
然后注册一下,然后在template中使用<home-swiper :banners="banners" ></home-swiper>
2.4. 首页推荐数据的展示
在home文件夹的childcomps中封装一个组件RecommendView.vue
<template>
<div class="recommend">
<div class="recommend-item" v-for="(item, index) in recommends" :key="index">
<a :href="item.link">
//因为图片和标题都是可以被点击链接的,所以要包含在a标签中
<img :src="item.image" alt="">
<span>{
{
item.title}}</span>
</a>
</div>
</div>
</template>
<script>
export default {
name: "RecommendView",
//获取从 上一级请求过来的推荐数据
props: {
recommends: {
type: Array,
required: true
}
}
}
</script>
<style scoped>
//布局样式
.recommend {
display: flex;
margin-top: 10px;
font-size: 14px;
padding-bottom: 30px;
border-bottom: 10px solid #eee;
}
.recommend-item {
flex: 1;
text-align: center;
}
.recommend img {
width: 80px;
height: 80px;
margin-bottom: 10px;
}
</style>
然后在home.vue中导入该组件import RecommendView from './childComps/RecommendView'
然后注册,最后使用<recommend-view :recommends="recommends"></recommend-view>
组件接收数据的时候通过:recommends="recommends"相当于用on来接收。
2.5. 封装FeatureView
本质上就是一张图片
首先在home文件夹下的childcomps中封装一个组件FeatureView.vue,
然后在home.vue中导入import FeatureView from './childComps/FeatureView'
,注册使用<feature-view></feature-view>
2.6. 流行新款精选:封装tabControl
逻辑:
接收从父级传来的titles,遍历div盒子,v-for="(item,index) in titles得到item,index;
实现选中哪一个tab, 哪一个tab的文字颜色变色, 利用 currentIndex,添加一个样式 :class="{active:index==currentIndex}",监听点击事件,设置this.currentIndex = index;
实现点击tab后显示对应的商品列表,在tab-control.vue组件的methods中将点击事件tabClick发出去
在Home.vue中使用tab-control组件中接受该事件,在methods中建立对应的tabClick方法,监听流行、新款、精选的点击,默认显示流行列表页的数据,data中设置参数currentType:pop
TabControl组件:
@<template>
<div class="tab-control">
<div :class="{active:index==currentIndex}" v-for="(item,index) in titles" class="tab-control-item" @click="itemClick(index)" :key="item.index">
<span>{
{
item }}</span>
</div>
</div>
</template>
<script>
export default {
name: "TabControl",
data() {
return {
currentIndex: 0,
};
},
methods: {
itemClick(index) {
this.currentIndex = index;
this.$emit('tabClick',index)
},
},
props: {
titles: {
type: Array,
default() {
return [];
},
},
},
};
</script>
<style>
.tab-control {
display: flex;
font-size: 15px;
height: 40px;
line-height: 40px;
text-align: center;
}
.tab-control-item {
flex: 1;
}
.tab-control-item span {
padding: 5px;
}
.active span {
color: var(--color-high-text);
border-bottom: 3px solid var(--color-tint);
}
然后把这个组件在home.vue中导入import TabControl from ‘components/content/tabControl/TabControl’,注册使用
Home.vue中:
<tab-control
:titles="['流行', '新款', '精选']"
@tabClick="tabClick"
ref="tabControl2"
></tab-control>
methods: {
// 事件监听
// 1.监听tabclick 流行 精选 热卖的点击,以及吸顶功能
tabClick(index) {
switch (index) {
case 0:
this.currentType = "pop";
break;
case 1:
this.currentType = "new";
break;
case 2:
this.currentType = "sell";
break;
}
this.$refs.tabControl1.currentIndex = index;
this.$refs.tabControl2.currentIndex = index;
},
2.7. 商品列表数据的保存
因为要一次性从服务器请求流行 新款 精品的三类数据,并且实现点击哪一个就跳转到哪一个的功能,所以把这三类数据保存在一个对象goods中
-
定义goods数据结构,用于存储请求到的商品数据,包含三个对象,pop news sell,每个对象中包含用于记录当前页码的page以及请求的商品数据列表list
goods: {
pop: { page: 0, list: [] },
new: { page: 0, list: [] },
sell: { page: 0, list: [] },
},
-
在network文件夹下的home.js中封装getHomeGoods(type, page)函数
-
在Home.vue中导入该函数,import { getHomeMultidata, getHomeGoods } from “network/home”;
-
在created中调用该函数,加了this才能是指代该组件的函数
-
在Home.vue 的 methods中处理该函数 getHomeGoods(type)
home.js
import {
request} from "./request"
export function getHomeMultidata(){
return request({
url:"/home/multidata"
})
}
// 请求首页商品数据
export function getHomeGoods(type,page){
return request({
url:"/home/data",
params:{
type,
page
}
})
}
Home.vue中:
// 网络请求数据
created() {
// 1.请求多个数据
this.getHomeMultidata();
//2 调用请求首页商品数据
this.getHomeGoods("pop");
this.getHomeGoods("new");
this.getHomeGoods("sell");
},
methods:{
...
getHomeGoods(type) {
const page = this.goods[type].page + 1;
getHomeGoods(type, page).then((res) => {
// console.log(res.data);
this.goods[type].list.push(...res.data.list);
this.goods[type].page += 1;
// 完成上拉加载更多
this.$refs.scroll.finishPullUp();
});
},
2.8. 商品数据的展示:封装GoodsList和GoodsListItem
- 展示商品列表,封装GoodsList,然后将该组件在Home.vue中导入注册使用
<template>
<div class="goods">
<goods-list-item v-for="item in goods" :goodsItem="item" :key="item.index"></goods-list-item>
</div>
</template>
<script>
import GoodsListItem from "./GoodsListItem"
export default {
name: "GoodsList",
components:{
GoodsListItem
},
props:{
goods:{
type:Array,
default(){
return []
}
}
}
}
</script>
<style scoped>
.goods{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
padding: 2px;
}
</style>
- 列表中每一个商品,封装GoodsListItem
- 在Home.vue中使用GoodList的时候,动态绑定goods,动态获取pop\news\sell数据,利用currentType
<goods-list :goods="goods[this.currentType].list" />
为了代码好看,可以进一步封装为方法
computed: {
showGoods() {
return this.goods[this.currentType].list;
},
},
然后就可以这样使用
<goods-list :goods="showGoods" />
- 注意CSS属性的设置即可
- 点击切换商品的流行、新款、精选
2.9. 滚动的封装Scroll
1.学习BetterScroll的使用
- 安装better-scroll
作用:如果按照之前的滚动的话就会出现手指停留在哪里就会滚到哪里的效果而不会出现滚动到之后仍然下滑(弹簧效果)的效果,并且部署在移动端的话会出现非常卡顿的现象,所以才引入这个框架。
BetterScorll使用步骤:
1、首先安装 npm install better-scroll --save
2、在组件中导入 import bscroll from ‘better-scroll’
3、使用,在最外边包一层 wrapper 在里边的滚动内容也得放在一个标签中,然后给wrapper一个高度,然后在 mounted中新建一个 new BScroll(‘要滚动的元素标签’,{一些参数的设置}),这里不能在created中new 因为这个时候 template还没有被挂载,即dom元素还没有形成。
BetterScorll第二个参数:
1、better-scroll默认情况下是不实时监测滑动位置的,
对better-scroll的位置的实时监测有一个属性 probeType
就是在new一个BScroll的时候传入对象形式的第二个参数,probeType: 2,( 0 1都是默认不监测,2是手指滑动过程中监测,在手指离开的惯性过程中不监测 ,3是只要是滚动都监测)。
2、better-scroll默认情况下是不会有DOM元素事件操作,设置click:true就可以了。
3、上拉加载更多的实现 better-scroll默认情况下是不会有上拉加载更多,设置pullupLoad: true就可以了,在添加事件的时候用 BSccroll.on(pullingUp,function) 但是这个只能执行一次,如果想要接着实现上拉加载更多的话,就得在BSccroll.on(pullingUp,function) 这里边在加入BScrroll.finishPullUp(),表示这次上拉加载更多的操作已经结束
- 封装一个独立的组件,用于作为滚动组件:Scroll
Scroll组件(片段):
<template>
<div ref="wrapper" class="wrapper">
<div>
<slot></slot>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
name: "Scroll",
//防止better-scroll创建完之后在执行完mounted之后会被销毁 所以提前保存下来
data() {
return {
scroll: null
}
},
mounted() {
this.scroll = new BScroll(this.$refs.wrapper
}
</script>
<style scoped>
.wrapper {
overflow: hidden;
}
</style>
然后在Home.vue组件中导入注册,使用
给scroll设置content高度,上下高度确定,如何自适应中间的高度,用定位
.content {
overflow: hidden;
position: absolute;
top: 44px;
bottom: 49px;
left: 0;
right: 0;
/* height: calc(100vh-93px); 该方法2报错*/
}
2.解决问题,利用事件总线,防抖优化
可能会出现加载到一半拉不动的问题,在创建的BScroll对象的时候,滚动高度是由scrollerHeight(根据放在scroll中的子组件的高度决定的)决定的,但因为图片是异步加载的,刚开始的时候子组件中的图片可能并没有请求过来,所以scrollerHeight的高度就是错误的,但是当新的图片已经被请求过来的时候,scrollerHeight不会进行更新,所以,会出现加载到一半加载不动的问题
解决方法:
监听GoodsListItem组件的每一张图片是否加载完成,只要有一张图片加载完成,就让Scroll组件刷新一次
1、那么如何监听到图片加载完成了呢?
vue中@load事件可以监听图片加载完成,在GoodsListItem组件中,添加加载完成事件<img @load=“imgLoad”> 然后在下边的methods中,创建imgLoad()的方法
methods:{
// vue中监听图片记载完成用@load
// 非父子组件之间通信,这里用事件总线的方式
// Vue.prototype.$bus = new Vue() 在main.js中利用原型创建一个bus事件
// this.bus.emit('事件名称', 参数)
// this.bus.on('事件名称', 回调函数(参数))
imageLoad