一、起步
1、项目初始化:脚手架3
npm install @vue/cli -g
vue create mall
2、连接github远程仓库
两种方式:
- 本地项目不需要初始化git,github新建仓库后,克隆到本地新文件夹,然后将本地项目拷贝到新文件夹中,
git clone github地址
、git add
、git commit
、git push
; - 本地项目初始化仓库:
git init
、git add
、git commit
、git remote add origin github地址
、git push -u origin master
,远程新建空项目,将本地仓库与远程仓库连接;
3、划分目录结构
4、初始化项目css:如引入第三方重置样式normalize.css
,结合项目自身base.css
;来统一不同浏览器中的标签显示;main.js:<style>@import "./assets/css/base.css"</style>
5、使用vue.config.js
简单设置文件路径别名;别名在DOM标签中使用时要带上~assets/css/img
6、修改网站图标
jsp语法:<link rel="icon" href="<%= BASE_URL %>favicon.ico">
,BASE_URL:当前文件所在路径,前端原本不支持,打包后就能转为可支持的语法;
打包后:<link rel=icon href=/favicon.ico>
7、底部tabbar封装,路由映射配置
8、在总的网络封装request.js
基础上,对每个页面的网络请求进行单独建js文件封装,以便后期好修改;如Home.vue面向home.js开发
;最后使用created(){}
创建后生命周期中使用请求并保存在data(){return{}}
;
9、关于别名设置:vue.config.js
module.exports = {
configureWebpack:{
resolve:{
alias:{
'assets':'@/assets',
'common':'@/common',
'components':'@/components',
'network':'@/network',
'views':'@/views'
}
}
}
}
注意:在使用别名时,标签中使用需要添加~
符号;在模块化系统中可以直接使用;
- 如加载组件:
import TabBar from 'components/common/TabBar/TabBar'
- 路由懒加载:
const Home = ()=>import('views/Home/home')
- 标签中引入普通文件:
<img src=" ~assets/img/home/1.jpg " alt="">
10、插槽<slot>
的使用情况:
- 常用于当一个模块变化较大,内部各有样式时;
- 当一个组件复用率高但是变化不大,比如导航栏中子项文字、数目,可以不用插槽直接用组件传值,将子项内容
props
进去,然后循环展示即可;
<div v-for="(item,index) in titles"
class="tab-control-item"
:class="{active:index===activeIndex}"
@click="activeClick(index)">
<span>{{item}}</span>
</div>
v-for
相当于把当前元素与内部元素都循环产生,所以类是加在每一个子项上的;
11、关于打包文件:
chunk-xx.css/js
:懒加载的打包文件- 打包前css样式以
<style>标签
形式展示,打包后形成css文件
,打包后如下;
12、导航栏吸顶效果:
涉及到的问题:
1、position:sticky;top:44px;
:相对距离是相对于具有滚动框的最近祖先元素的顶部:.content
,而不是视口;
2、offsetTop:
是自身元素顶部到具有(relative,absolute,fixed)定位祖先元素顶部的距离;
3、fixed定位的参考物问题:
当祖先元素没有使用transform
产生位置变化时,相对于浏览器视口;如果有,就是相对于这个祖先元素;better-scroll库
恰巧就是通过transform
实现滚动。
- css属性:
position:sticky;top:44px;
当小于top值,由static
变成fixed
;这个属性中,元素规定的相对距离是相对于离它最近的具有滚动框的祖先元素的顶部,如果祖先元素都不可以滚动,那么是相对于viewport来计算元素的位置。 - better-scroll框架中,内部
content
层具有滚动框,导致相对偏移没有变化,position:sticky
失效; - 而且better-scroll,通过改变
transform:translate(0px,-xx px)
实现的滚动;而fixed元素的父元素加了transform就会以父元素为参考对象!!
,所以在这个库中通过判断滚动偏移量是否大于offsetTop
来动态改变滚动区域内某元素为fixed定位
时会脱标并上移找类为content的父元素
做top
参照; - 补充:offsetTop是元素到offsetParent顶部的距离,offsetParent是距离元素最近的一个具有定位的祖宗元素(relative,absolute,fixed),项目中给
.wrapper类加了绝对定位,如果设置了padding-top,这个也算顶部里面
,若祖宗都不符合条件,offsetParent为body。 - 解决方案:只用
offsetTop
判断位置,动态显示与隐藏另一个新的吸顶导航栏,并且两个导航栏数据共通;
13、vue中遍历数组与对象补充:
VUE列表渲染 FOR-IN和FOR-OF的区别
总结:
- V-for循环遍历数组时推荐使用of,语法格式为(item,index)
- item:在每次迭代时,item会被赋值为不同的数组元素的值。(第一个参数位置)
- index:当前元素的索引。(第二个参数位置)
- 只写
v-for="index in array"
,也是数值,只跟位置有关,命名随意。
- V-for循环对象时使用in,语法格式为(item,name,index)
- item:在每次迭代时,item会被赋值为不同的对象的属性值。
- name:在每次迭代时,name会被赋值为不同的键名。
- index:当前元素的索引
- 普通JS中:
for-in:
遍历索引、通过原型添加的属性名、手动添加的属性名;for-of:
遍历数组值;
14、for、for-in、for-of、forEach的区别
15、better-scroll
- 安装:
npm install better-scroll -S # install 1.x
- 在
mounted
生命周期中使用;
16、计算首页除上下导航栏以外的高度:
- 方式一:利用css3计算属性height:calc(100%-93px);
- 方式二:利用子绝父相脱标定位,设置
top:44px;bottom:49px;right:0;left:0;
17、better-scroll
mounted(){
this.scroll = new BScroll(this.$refs.wrapper,{
click:true,//可滚动元素中非button元素,可以触发点击事件
probeType:this.probeType,//通过外部使用者决定是否记录滚动数据:0,1,2,3
pullUpLoad:this.pullUpLoad//通过使用者决定是否上拉加载:true/false
})
监听滚动位置
this.scroll.on('scroll',(position)=>{
this.$emit('scroll',position)
})
监听上拉加载更多
this.scroll.on('pullingUp',()=>{
this.$emit('pullingUp')
})
}
常见问题:
- 当数据为异步请求时,数据获取时间不确定,在
created周期函数时请求图片,在mounted周期时创建BScroll
,图片还没有获取完,导致可滚动区域比实际小;- 解决:当每个图片加载后都调用
BScroll的刷新方法:refresh()
- 利用事件总线,
main.js:Vue.prototype.$bus= new Vue()
,this.$bus.$emit('itemImageLoad');this.$bus.$on('itemImageLoad',()=>{})
- 改进:不用每张图片都调用,使用防抖函数,设置时间间隔,只有在这个时间段内没有新加载才执行,有新加载的图片就会重置时间,重新等待,使得最终只会执行一次刷新的效果;
- 解决:当每个图片加载后都调用
mounted(){
const refresh = this.debounce(this.$refs.scroll.refresh,500)
this.$bus.on('itemImageLoad',()=>{
refresh()//传('a','b'),其中序列就是...args;而args就是数组['a','b']
})
},
methods:{
debounce(func,delay){
const timer = null;
return function(...args){
if(timer) clearTimeout(timer)
timer = setTimeout((...args)=>{
func.apply(this,args)//apply() 方法接受对象、数组形式的参数,时间到了,apply方法自动执行函数;
},delay)
}
}
}
- 上拉加载更多默认只会执行一次:
- 在加载方法结尾调用完成加载函数,说明本次加载已完成;
this.scroll.finshPullUp()
- 在加载方法结尾调用完成加载函数,说明本次加载已完成;
- 点击详情页再回到首页,会回到顶部,无法记录回到的位置,使用
keep-alive
也不行;- 原因:滚动区域中轮播图内部使用了
transform CSS属性
,与这个库内部transform
互相作用导致, - 使用
1.13.2
版本的better-scroll; - 借助
activated、deactivated钩子
,离开时获取当前滚动的位置this.$refs.scroll.scroll.y
并保存到data中;返回时根据保存的y值
滚动到指定位置:this.$refs.scroll.scrollTo(0,this.saveY,0)
。
- 原因:滚动区域中轮播图内部使用了
18、商品详情页组件的复用问题
之前在路由展示中使用了
keep-alive
,导致点击商品跳转到/detail
时没有重新发请求,
因为没有销毁,created周期就不会重新执行去发请求
,数据一直是第一次点击的商品
解决方案:从keep-alive
效果中排除详情页组件;
<keep-alive exclude = "Detail"><router-view/></keep-alive>
//组件中添加:name:'Detail'
19、除了防抖函数,也可以使用计数器,当@load
传过来事件的次数等于最终所有图片时,就可以执行刷新better-scroll;当然也可能出现图片数量计算不准的问题。
20、 vue中过滤器的使用:{{[数据] | [过滤器名]}}
- 处理数字为格式化价格:
如58--¥58.00
methods:{}
filters:{//自动将前面的数据传进来当参数
showPrice(price){
return '¥' + price.toFixed(2)
}
}
const date = new Date(1598709040*1000) /第一步:将时间戳转为`Data对象`
`使用插件或自己封装函数` /第二步:将date对象格式化为对应字符串,
/因为太常用,基本语言中都封装有格式化函数:`.format(date,'yyyy-MM-dd hh:mm:ss')`
/小时:H(24小时制)/h(12小时制)
---
filters:{
showDate(value){
const date = new Date(value*1000)
return formatDate(date,'yyyy-MM-dd hh:mm:ss')//导入自己封装好的格式化函数
}
}
{{date|showDate}}//使用过滤器
21、非关系组件传值:事件总线的使用
main.js
中创建事件总线:Vue.prototype.$bus= new Vue()
- 组件内部发出事件总线:
this.$bus.$emit('itemImageLoad');
; - 外部组件接收事件总线并执行函数:
this.$bus.$on('itemImageLoad',[具体函数或函数名])
- 删除某个总线:
this.$bus.$off('itenImageLoad')
- 取消接收事件总线后要触发的其中一个函数:
this.$bus.$off('itemImageLoad',this.listener)
22、vue组件对象中的代码复用:混入mixin
- 在多个类中属性重复时,我们可以采用
ES6的extends继承
方式简化每个类中重复代码;class Preson extends AA {}
;这样就继承了AA类中的属性。
- 在vue组件中,因为都是vue对象,所以当多个组件有重复js代码时,不能继承,采用
mixin混入
方法;- 当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项;
- 混入对象通常写到单独
js文件
中,哪个组件使用就引入;
export var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
---
import {mixin} from "../aa.js"
new Vue({
mixins: [mixin],/要混入的对象名,数组形式
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合并
22、数据信息的汇总
- 当接受的数据比较分散时,通过将数据传入类中,进行重新赋值,然后实例化这个类,来简化数据的使用;
- 在
JS文件
中创建一个类,在构造器中接收参数,并进行this.
重新赋值;
export class Goods{
constructor(itemInfo,columns,service){
this.desc = itemInfo.desc;
this.price = itemInfo.price;
this.oldPrice = itemInfo.oldPrice;
this.columns = columns;
this.service = service;
}
}
---
import {Goods} from "../a.js"
created(){
getDetail(this.iid).then(res=>{
const data = res.result;
this.goodsInfo = new Goods(data.itemInfo,data.columns,data.shopInfo.services)
/类的参数传入构造函数中
})
}
23、添加购物车数据时检查重复
- 目的:将数据存储到vuex中(需要刷新的话再存到本地),同一个商品对象,第一次添加应该给这个商品加
count属性
,第二次及以后添加只改变count属性
即可;
const store = new Vuex.Store({
state:{
cartList:[]//已有属性,是响应式
}
mutations:{
addCart(state,payload){
let oldProduct = state.cartList.find(
item=>{item.iid == payload.iid}
)/检测是否有老商品,有就拿过来,没有就是undefined(布尔值就是false)
if(oldProduct){
oldProduct.count += 1;
}else{
payload.count = 1;
state.cartList.push(payload)
}
}
}
})
24、全选按钮的状态:
- 逻辑:
- 不点击时:没商品时不选中,有一个没选中就是不选中,只有商品全选中才选中;
- 点击:原本状态取反,且根据
false/true
改变每个商品的选中状态;
不点击时:
<check-button :is-checked="isSelectAll" />
computed: {
isSelectAll(){
if(this.cartList.length == 0){ return false}
return !(this.cartList.filter(item=>{!item.checked}).length)//方式一:返回所有未选中商品对象
return !(this.cartList.find(item=>{!item.checked}))//方式二:返回第一个未选中的商品对象
/方式三:
for(let item of this.cartList){
if(!item.checked){
return false//return结束函数体
}
}
return true
}
}
---
点击时:
methods: {
checkClick(){
if(this.isSelectAll){//全选时
return this.cartList.forEach(item=>{item.checked = false})
}else{//部分或全部未选中
return this.cartList.forEach(item=>{item.checked = true})
}
}
}
25、toast弹框提示
- 普通组件方式
- 插件封装方式
- 方便全局使用,在入口文件
main.js
中导入toast.js
,并安装,执行内部install方法
; - 在方法中做两件事:
- 第一,将toast对象定义到
Vue.prototype.$toast原型上
,用于其他组件可以this.$toast
使用这个对象; - 第二,将
Toast.vue
组件挂载到html页面中,让组件中定义的show方法
有意义,用于其他组件直接使用这个方法可以在html页面中决定显示/隐藏;
- 方便全局使用,在入口文件
main.js:
import toast from 'components/common/toast'
Vue.use(toast)/自动执行内部install方法
----
toast.js:
import Toast from './Toast' /导入组件.vue文件的对象形式,被vue-template-compiler插件将文件解析成对象的,(包含render函数属性),不能使用组件中属性/方法,【官方叫法:组件的选项对象】
const obj = {}
obj.install = function(Vue){/默认会传入Vue构造函数
/第二件事:
const toastConstructor = Vue.extend(Toast)/创建组件模板构造器,传入Toast的普通文件对象形式
const toast = new toastConstructor()/创建实例,组件对象,生命周期开始
toast.$mount(document.createElement('div'))/手动挂载到一个新元素上,
/相当于子组件执行完了mounted钩子函数
document.body.appendChild(toast.$el)/最后将新元素添加到body元素
/第一件事:
Vue.prototype.$toast = toast/这个才是新的组件对象,区分选项对象
}
---
Hello.vue:
通过事件触发方法:this.$toast.show('提示文字',2000)
---
Toast.vue:
组件内定义了方法:
methods: {
show(msg='默认文字',duration=2000){
// duration = duration||2000;es6之前的默认值写法
this.msg = msg;
this.isShow = true;
setTimeout(()=>{
this.isShow = false;
this.msg = '';
},duration)
}
}
26、解决移动端单击300ms延迟(为了判断双击)
- 用于不需要双击的应用;
npm i fastclick -S
---
main.js:
import Fastclick from 'fastclick'
Fastclick.attach(document.body)
27、图片懒加载:vue-lazyload模块
npm i vue-lazyload -S
---
main.js:
import LazyLoad from 'vue-lazyload'
Vue.use(LazyLoad,{
loading:require('./assets/img/common/a.png')/配置加载前背景图
})
---
<img v-lazy="" alt=""/>