【vue】移动商城项目学习记录

本文详细介绍Vue项目从初始化到优化的全过程,包括脚手架搭建、目录结构规划、网络请求封装、性能优化技巧等核心内容,助你快速掌握Vue项目开发流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、起步
1、项目初始化:脚手架3
npm install @vue/cli -g
vue create mall

2、连接github远程仓库
两种方式:

  • 本地项目不需要初始化git,github新建仓库后,克隆到本地新文件夹,然后将本地项目拷贝到新文件夹中,git clone github地址git addgit commitgit push
  • 本地项目初始化仓库:git initgit addgit commitgit 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(布尔值就是falseif(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=""/>

28、响应式原理

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值