- 列表数据1
- 列表数据2 ...
从项目开始
1.项目目录列表有config文件夹是脚手架2,没有的话是脚手架3
2.使用github
2.1 通过拷贝的方式提交代码到github--------------------
1)github创建项目
2)本地创建项目vue create supermall
3)git clone https://...git
4)本地项目复制到git的clone的文件夹中(node_modules和.git不需要复制)
5)添加:git add .
6)提交到本地:git commit -m '项目初始化'
7)提交到远程:git push
2.2 不通过拷贝的方式直接提交本地代码到github--------------------
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/q124467623/supermall_test.git
git push -u origin main
common公用的常量或工具
components/common可能下一个项目也可以用到的组件
content本项目用到的公共组件
network: axios
router :路由跳转
store:vuex状态管理
home和category视图划分
1.--color-text相当于设置的全局变量
第二处引用:
1.vue.config可以配置一些别名,在项目最外层目录创建即可:
module.exports = {
configureWebpack:{
resolve:{
alias:{
'assets':'@/assets',
'common':'@/common',
'components':'@/components',
'network':'@/network',
'views':'@/views',
}
}
}
}
2.cli3默认把.editorconfig删掉了,但是其实很重要,里边管理格式(如缩进2空格等),需要手动在最外层创建:
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
注意事项:
1.slot在cli4中依然可以使用,使用过程中名字要对应
1.小图标的文件是放在public/favicon.ico
2.小图标的引用是在public/index.html中:
favicon.ico">
public文件夹的内容在打包的时候,会原封不动的到dist文件夹
1.因为导航栏(NavBar.vue)在多个页面会用到,因此考虑封装,采用插槽的方式:
2.Home.vue开始使用导航栏:
3。导航栏颜色在不同的组件中不一样,可以考虑在Home.vue中写,这里采用变量的方式:
注:base.css::root { --color-tint: #ff8198; }
1.network文件夹新建request.js用于axios的创建:
import axios from 'axios'
export function request(config) {
//1.创建axios实例
const instance = axios.create({
baseURL: 'http://1xx.xxx.xxx.xxx:7878/api/m5',
timeout: 5000
})
//2.axios拦截器
instance.interceptors.request.use(config => {
return config //如果不返回出去,后边就拿不到config了
}, err => {
console.log(err);
});
instance.interceptors.response.use(res => {
return res.data
}, err => {
console.log(err);
});
//3.返回结果
return instance(config)
}
2.network文件夹新建home.js,封装函数:设置请求的url,导出函数用于创建home.vue组件需要的数据:
import { request } from './request'
export function getHomeMultifata() {
return request({
url: 'home/multidata'
})
}
3.Home.vue组件中使用函数,拿到后台传来的数据:
...
import {getHomeMultifata} from '@/network/home'
export default {
name:'Home',
data(){
return {
banners:[],
recommends:[],
}
},
components:{
NavBar
},
created(){
getHomeMultifata().then(res=>{
this.banners = res.data.banner.list;
this.recommends = res.data.recommend.list;
})
}
}
1.轮播图可以找网上的组件,目前老师提供的轮播图组件:Swiper.vue和SwiperItem.vue,可以通过引入到home/childComps/HomeSwiper.vue中进行组件的使用(首页的内容都可以写到childComps内):
使用模板:
HomeSwiper.vue代码:
2.组件引入到Home.vue中:
购物街
1.创建组件home/childComps/HomRecommendView.vue:
2.Home.vue中使用,并发送数据到子组件:
...
FeatureView只有一张图片,不建议在Home.vue中直接写,因此封装FeatureView.vue组件,组件内包裹再导出。
1.仅仅是文字不一样,没必要设置插槽:
2.components/content/tabControl/TabControl.vue:
{{item}}
...
//Home.vue设置停顿
...
.tab-control{
position: sticky;
top: 44px;
background-color: #fff;
}
Home.vue:
2.数据的请求和保存:
1)请求network/home.js:
export function getHomeGoods(type, page) {
return request({
url: 'home/data',
params: {
type,
page
}
})
}
2)保存Home.vue:
注:数组push到另一个数组:
totalNums.push(...nums1) 等价于nums1.forEach( item => { totalNums.push( item ) } )
data(){
return {
banners:[],
recommends:[],
titles:["流行","新款","精选"],
goods:{
'pop':{page:0,list:[]},
'new':{page:0,list:[]},
'sell':{page:0,list:[]},
}
}
},
created(){
//1.请求多个数据
this.getHomeMultifata()
//2.请求商品数据
this.getHomeGoods('pop')
this.getHomeGoods('new')
this.getHomeGoods('sell')
},
methods:{
getHomeMultifata(){
getHomeMultifata().then(res=>{
this.banners = res.data.banner.list;
this.recommends = res.data.recommend.list;
})
},
getHomeGoods(type){
const page = this.goods[type].page +1;
getHomeGoods(type,page).then(res=>{
this.goods[type].list.push(...res.data.list)
this.goods[type].page += 1
})
}
}
1. 数据从Home.vue -> GoodsList.vue ->GoodsListItem.vue通过props传递
1)Home.vue::cgoods="goods['pop'].list"/>
2)GoodsList.vue:
cgoods" :key="index" :goodsItem="item"/>
3)GoodsListItem.vue:
2.补充flex布局(GoodsList.vue中):
1.TabControl.vue组件中,有商品点击事件,TabControl.vue又是Home.vue组件的子组件,因此是子组件向父组件传递事件:
TabControl.vue:
Home.vue:
...
1.该插件的好处是在移动端更流畅和有弹簧效果
2.使用过程:
1)npm install better-scroll --save
2)导入import BScroll from 'better-scroll'
3)在mounted()内使用new BScroll('.wrapper')
4)文档结构要求,.wrapper内只能是单标签,如:
3.Category.vue代码:
- 列表1
- 列表1
...
1. 本地测试better-scroll插件,中途要装插件:npm install @better-scroll/pull-up --save
- 列表数据1
- 列表数据2
...
- 列表1
- 列表2
...
1.如果每个组件都直接导入BScroll使用,如果组件需要换,维护成本太大,因此考虑对BScroll封装一层,封装之后,别的组件使用过程:
1)import Scroll from '@/components/common/scroll/Scroll'
2)注册Scroll
3)使用需要滚动的放这里
2.ref绑定元素:
ref如果是绑定在组件中的,通过this.$refs.refname获取到的是一个组件对象;
ref如果是绑在普通元素中,通过this.$refs.refname获取到的是一个元素对象;
3.scoped的含义是作用域,样式只在本组件中起作用;
1.组件不能直接监听点击事件,如下错误用法:
@click="backClick">
正确用法:
@click.native="backClick">
2.$refs是从父组件中拿子组件data中的数据 或者 methods的某个方法:
ref="scroll">
通过 this.$refs.scroll.子组件属性或方法
属性 | this.$refs.scroll.scroll.scrollTo(0,0,500) |
包装一次拿方法 | this.$refs.scroll.scrollTo(0,0) |
1.过程:
1)Scroll子组件发出位置事件:
this.scroll.on('scroll',position=>{ this.$emit('scroll',position) })
2)Home父组件拿到事件并把postion保存起来:
3)back-top组件通过v-show判断显隐,isShowBack为计算属性,判断postion数值:
注:props中的类型:default默认值为数组或对象时,使用函数,其他类型不用函数。
1.过程:
1)Scroll组件中监听上拉事件,并发出消息给Home:
this.scroll.on('pullingUp',()=>{ this.$emit('pullingUp') })
2)Home中接收消息,并请求数据:
loadMore(){
this.getHomeGoods(this.currentType)
this.$refs.scroll.finishPullUp() },
2.注意:cprobe-type="3" @pullingUp="loadMore">属性可以驼峰,消息不可以
1.Better-Scroll在决定有多少区域滚动时,根据scrollerHeight属性决定
- scrollerHeight属性是根据放Better-Scroll的content中的子组件的高度
- 但是我们的首页中, 刚开始在计算scrollerHeight属性时, 是没有将图片计算在内的
- 所以, 计算出来的告诉是错误的(1300+)
- 后来图片加载进来之后有了新的高度, 但是scrollerHeight属性并没有进行更新.
- 所以滚动出现了问题
2.如何解决这个问题了?
- 监听每一张图片是否加载完成, 只要有一张图片加载完成了, 执行一次refresh()
- 如何监听图片加载完成了?
- 原生的js监听图片: img.onload = function() {}
- Vue中监听: @load='imageLoad'
- 调用scroll的refresh()
1. 因为涉及到非父子组件的通信, 所以这里我们选择了事件总线
bus ->总线
Vue.prototype.$bus = new Vue()
this.$bus.$emit('事件名称', 参数)
this.$bus.$on('事件名称', 回调函数(参数))
2.在GIS开发中:
this.$root.$emit('show-area', this.form.coordinate);
this.$root.$on('show-area', this.showarea);
this.$root.$off('show-area', this.showarea);
1、防抖(debounce):触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
举例:就好像在百度搜索时,每次输入之后都有联想词弹出,这个控制联想词的方法就不可能是输入框内容一改变就触发的,他一定是当你结束输入一段时间之后才会触发。
节流(thorttle):高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
举例:预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。就好像你在淘宝抢购某一件限量热卖商品时,你不断点刷新点购买,可是总有一段时间你点上是没有效果,这里就用到了节流,就是怕点的太快导致系统出现bug。
3、区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。
4.异步打印顺序排在非异步之后:
4.防抖具体实现:
如果直接执行: this.$bus.$on('itemImageLoad',()=>{ this.$refs.scroll.refresh() })的话,refresh会被执行30次,服务器压力大,可以把refresh函数传入到debounce中,生成一个新的函数,新函数不会被频繁调用,如果下次执行非常快,会将上次的函数清理掉:
1.this.tabOffsetTOP = this.$refs.tabControl.$el.offsetTop;
$el是拿到组件的元素;
offsetTop是拿到当前对象到其上级层顶部的距离.
注意:1)拿到offsetTop之前,最好等上边的元素加载完,如在mounted中获取就是错的,因为没有等轮播图加载完成
2)想要获取正确的值,要等轮播图加载完成
1.过河拆桥法实现轮播图加载一张的时候就发出消息,之后停止发消息
2.吸顶效果实现原理:scroll滚动的y值 > tabControl距顶部距离时,对第二个tabControl设置显示
1)this.tabOffsetTOP = this.$refs.tabControl2.$el.offsetTop;在Home中拿到tabControl距顶部距离;
2)this.isTabFixed = -position.y>this.tabOffsetTOP 判断距离
3) 设置显隐,采用了复制技巧
8.(掌握)首页开发-Home离开时记录状态和位置
1.App.vue设置keep-alive
2.如果1中不生效,增加以下内容1)记录离开组件时y 2)激活时,定位到y:
1.通过路由技术实现:
1)index.js:{ path: '/detail/:iid', component: Detail }
2)GoodsListItem设置点击事件,并设置路由跳转:
itemClick(){ this.$router.push('/detail/'+this.goodsItem.iid) }
3)详情页Detail获取iid:
created(){ this.iid = this.$route.params.iid }
//activated(){ this.iid = this.$route.params.iid }
1.只封装导航栏,考虑使用NavBar组件如下所示:
2.DetailNavBar:左边是返回,右边空,中间用循环遍历:
3.返回事件:this.$router.back()
1.数据请求:
import { request } from './request'
export function getDetail(iid) {
return request({
url: '/detail',
params: {
iid
}
})
}
2.Detail组件中获取去请求的数据并传到DetailSwiper中:
3.HomeSwiper接收数据并导入轮播图插件,显示数据
...
import {Swiper , SwiperItem} from '@/components/common/swiper'
1.复杂的数据可以整合成一个对象之后传给子组件
2.
遍历index的时候,值从1开始的
3.判断对象是否为空:const obj = {} ; Object.keys(obj).length === 0 说明为空对象
4.null和{}区别:{}是一个不完全空的对象,因为他的原型链上还有Object呢,而null就是完全空的对象,啥也没有,原型链也没有,所以null instanceof Object === false;[]就更不用说了,它的原型链上还比{}多一个Array。
所以,纯粹意义上初始化一个空对象应该用null,{}更像是初始化对象,和new一个没有key的Obejct是一样的。
{{shop.name}}
{{shop.sells | sellCountFilter}}
总销量
{{shop.goodsCount}}
全部宝贝
{{item.name}}
{{item.score}}
{{item.isBetter ? '高':'低'}}
进店逛逛
核心代码:
1.商品详情数据还是一个组件,DetailGoodsInfo.vue主要代码:
{{detailInfo.desc}}
{{detailInfo.detailImage[0].key}}
2.注意事项:滚动条卡顿问题,解决:要等图片加载完的时候refresh()一下
1)
2)图片加载完之后发出消息 imgLoaded(){ if(++this.counter ===this.imagesLength) { this.$emit('imageLoad') } }
3)watch监听属性变化,属性变化(peops数据传过来的时候)的时候调用
1.时间戳转年月日格式化:formatDate(date, 'yyyy-MM-dd') 或者formatDate(date, 'yyyy-MM-dd hh:mm')
import {formatDate} from "@/common/utils";
export default {
name: "DetailCommentInfo",
props: {
commentInfo: {
type: Object,
}
},
filters: {
showDate: function (value) {
let date = new Date(value*1000);
return formatDate(date, 'yyyy-MM-dd')
}
}
}
...
utils的内容
export function formatDate(date, fmt) {
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
let o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
};
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
let str = o[k] + '';
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
}
}
return fmt;
};
13.(掌握)详情页-商品推荐数据的展示
1.还利用GoodsList和GoodsListItem组件,传数组给GoodsList就行:
2.注意,GoodsListItem拿到的单个图片对象goodsItem的数据结构可能不一样,需要用计算属性重新计算路径:(还有img和image注意区别)
...
computed:{
showImage(){
return this.goodsItem.image || this.goodsItem.show.img
}
},
1.this.$route.path.indexOf('/home')>-1 ,indexOf用于判断字符串中包含的某个字符串首次出现的位置,-1代表没找到,大于-1证明找到了
2.mixin混入(Vue高级用法),如两个组件中的mounted中(也可以是data)部分代码完全相同,考虑使用混入技术:
1)混入和类的继承的区别,混入针对的是对象
2)官网案例:
var mixin = {
created: function () { console.log(1) }
}
var vm = new Vue({
created: function () { console.log(2) },
mixins: [mixin]
})
// => 1
// => 2
3)我的案例,Home组件和Detail组件mounted和data的部分值相同,1)考虑新建混入文件Mixin.js;2)组件中import {itemListenerMixin}; 3)mixins:[itemListenerMixin],
//Home.vue
data(){
return {
itemImgListener:null
}
},
mounted() {
let newRefresh = debounce(this.$refs.scroll.refresh,100)
this.itemImgListener = ()=>{newRefresh()}
this.$bus.$on('itemImageLoad',this.itemImgListener)
// console.log('我是混入内容');
},
//Detail.vue同上
//Mixin.js:
import {debounce} from './utils'
const itemListenerMixin = {
data(){
return {
itemImgListener:null //加这个可以让对监听的事件进行保存,使得Home组件和Detail组件各自执行自己的事件
}
},
mounted() {
let newRefresh = debounce(this.$refs.scroll.refresh,100)
this.itemImgListener = ()=>{newRefresh()}
this.$bus.$on('itemImageLoad',this.itemImgListener)
// console.log('我是混入内容');
},
}
export {itemListenerMixin}
this.$refs.tabControl1.currentIndex = index;
this.$refs.tabControl2.currentIndex = index;
详情页图片加载两种防抖方法:
1)法1防抖,在DeatilGoodsList设置++this.counter ===this.imagesLength再发出事件
2)法2防抖,Mixin内有防抖函数
methods:{
//1.法1防抖,在DeatilGoodsList设置++this.counter ===this.imagesLength再发出事件
detailImageLoad(){
this.$refs.scroll.refresh()
},
//2.法2防抖,Mixin内有防抖函数
detailImageLoad(){
this.newRefresh()
}
},
1.标题点击事件传给父组件(Detail): this.$emit('titleClick',index)
2.父组件接收子组件的点击事件,可以确定点击的是哪一个,this.$refs.scroll.scrollTo可以用于定位到,某个:
titleClick(index) { this.$refs.scroll.scrollTo(0,-this.themeTopYs[index]+44,500) }
3.themeTopYs数组的获取,通过子组件名字拿到子组件距离顶部的距离
this.themeTopYs.push(this.$refs.Name.$el.offsetTop);
4.在哪里获取组件的offsetTop值呢:
1)created内不行,因为还没获取到原素
2)mounted内也不行,数据还没获取到
3)created内的获取到数据的回调也不行,DOM还没渲染完
4)$nextTick也不行,因为图片高度还没被计算在内
5)detailImageLoad(){}内可以,因为该函数是图片加载完之后$emit的事件
1)监听滚动事件,发出消息传到Detail中:
@scroll="contentScroll">
2)contentScroll方法(普通判断方法):
判断position.y和this.themeTopYs = [0 , 553.2 , 1567.3 , 2343.4 ]数组中四个值的比较分别
contentScroll(position){
// console.log(this.themeTopYs);
// console.log(-position.y);
this.themeTopYs.forEach((item,index) =>{
if((this.currentIndex !== index)&&((index=item&&-position.y=item))){
this.currentIndex = index;
console.log(this.currentIndex);
this.$refs.navbar.currentIndex = this.currentIndex
}
})
},
2)contentScroll方法(hack方法):push进去一个最大值:[0 , 553.2 , 1567.3 , 2343.4 ,Number.MAX_VALUE ]
”空间换时间”
//方法2:this.themeTopYs.push(Number.MAX_VALUE)
this.themeTopYs.push(Number.MAX_VALUE);
let length = this.themeTopYs.length;
for(let i=0;i=this.themeTopYs[i]&&-position.y
3)currentIndex传给子组件:
this.$refs.navbar.currentIndex = this.currentIndex
1.DetailBottomBar组件:
1. 因为Home页面已经做过返回顶部功能了,如果不使用混入,Detail需要:引入组件--注册组件--中保存v-show的变量--methods中写backClick方法,相当于重复性工作,因此考虑混入:
把data,methods抽到Mixin.js:
注意:混入的内容中,导入部分、data()、components、methods、生命周期函数可以进行合并,如created(),但是methods内部的方法不能进行混入,相混入只能在源组件this.listenShowBackTop(position) 然后把方法抽到listenShowBackTop()方法在Mixin中完成
import BackTop from '@/components/content/backTop/BackTop'
import { BACK_POSITION } from "@/common/const";
export const backTopMixin = {
components: {
BackTop
},
data() {
return {
isShowBackTop: false
}
},
methods: {
listenShowBackTop(position) {
//1.判断BackTop是否显示
this.isShowBackTop = position.y < BACK_POSITION
},
backClick() {
this.$refs.scroll.scrollTo(0, 0)
},
},
}
@addCart="addCart"/>
//只需获取购物车中需要展示的信息:图片、标题、描述、价格、数量、iid
addCart(){
//只需获取购物车中需要展示的信息:图片、标题、描述、价格、数量
const product = {}
product.image = this.topImages[0]
product.title = this.goods.title
product.desc = this.goods.desc
product.realPrice = this.goods.realPrice
product.iid = this.iid
console.log(product);
}
},
1.采用vuex进行状态管理(相当于全局变量),先安装:npm install vuex --save
2.Detail组件发出消息:
选择第二种封装的多
如果是直接在mutations中处理: | this.$store.commit('addCart',product) |
如果在actions中处理: | this.$store.dispatch('addCart',product) |
3.按第二种:
1)首先安装插件、创建store对象、然后挂载到Vue实例(main中导入一下)
import Vue from 'vue'
import Vuex from 'vuex'
import actions from './actions'
import mutations from './mutations'
//1.安装插件
Vue.use(Vuex)
//2.创建store对象
const state = {
cartList: []
}
const store = new Vuex.Store({
state,
mutations,
actions,
})
//3.挂载Vue实例上
export default store
2)actions.js中接收this.$store.dispatch('addCart',product)传来的方法和参数:
import { ADD_COUNTER, ADD_TO_CART } from './mutation-types'
export default {
// context={state,commit}
addCart(context, payload) {
//数组常用方法:push pop unshift shift sort reverse slice splice forEach map some filter every reduce flat join find
let product = context.state.cartList.find(item => item.iid === payload.iid)
if (product) {
context.commit(ADD_COUNTER, product)
} else {
payload.count = 1
context.commit(ADD_TO_CART, payload)
}
}
}
3)mutations.js接收actions.js发出的消息:
注:export const ADD_COUNTER = 'add_counter'
import { ADD_COUNTER, ADD_TO_CART } from './mutation-types'
export default {
//方法1:简单写法,state是累积的数据集,payload是每次传过来的数据
// addCart(state, payload) {
// let product = state.cartList.find(item => item.iid === payload.iid)
// if (product) {
// product.count += 1
// } else {
// payload.count = 1
// state.cartList.push(payload)
// }
// console.log(product.count);
// },
//mutations唯一的目的就是修改state中的状态
//mutations中的每个方法尽可能完成的事情比较单一,下边的判断是两个事情
//方法2:重构
[ADD_COUNTER](state, payload) {
payload.count++;
console.log("payload.count++ :", payload.count);
},
[ADD_TO_CART](state, payload) {
state.cartList.push(payload)
}
}
1.可以采用nav-bar组件的插槽:
购物车({{cartLength}})
2.购物车中商品种类的数量:
方法一:
购物车({{$store.state.cartList.length}})
方法二getters的用法,计算属性:
注: ...mapGetters(['cartLength','cartList'])的第二种用法 ...mapGetters([length:'cartLength',list:'cartList']),然后{{ }}内部就可以用length和list去代替了
//index.js
const store = new Vuex.Store({
state,
mutations,
actions,
getters,
})
//getters.js
export default {
cartLength(state) {
return state.cartList.length
},
cartList(state) {
return state.cartList
}
}
//cart.vue
购物车({{cartLength}})
import {mapGetters} from 'vuex'
computed:{
...mapGetters(['cartLength','cartList'])
}
1.滚动:Cart组件中对cart-list包scroll:
2.显示:CartList中通过mapGetters获得getters.js的数据(如下),在通过cart-list-item对数据进行解析(itemInfo.image、itemInfo.title、itemInfo.realPrice等)和显示
1.关于position:
1)父级相对定位+100vh之后,子级是相对父级的最上角位置进行定位的
2)top和left同时设置可以上下拉伸,left和right同理
附:购物车组件代码
购物车({{cartLength}})
2.父级display: flex;左右固定宽度,中间flex:1把两边顶走
3.合计和去计算:
合计:¥ {{totalPrice}}
去计算({{checkLength}})
第一部分:通过判断列表中是否全部选择 -> 确定全选按钮是否显示 isSelectAll()
some()是对数组中每一项运行给定函数,如果该函数对任一项返回true,则返回true。
//some方法效率更高,找到一个没有选中的,就判定全选按钮为false:
this.isAllChecked = !this.cartList.some(item => {return !item.checked})
this.isAllChecked = this.checkLength === this.cartList.length? true:false
第二部分:点击全选按钮,控制商品里列表:allClick()
...
1)补充知识点:Actions.js返回一个Promise,增加return new Promise(resolve,reject){}
2)Detail.js接受回调结果:
import { mapActions } from 'vuex'
methods:{
...mapActions(['addCart']),
//非映射法:
this.$store.dispatch('addCart',product)
.then(res=>{
console.log(res);
})
//映射法
this.addCart(product).then(res => {
console.log(res);
})
}
两种映射对比:
mapGetters |
mapActions |
import {mapGetters} from 'vuex' |
import { mapActions } from 'vuex' |
computed:{ ...mapGetters(['cartLength']) }, |
methods{ this.addCart(product).then(res => { console.log(res) }) } |
//Toast.vue
{{message}}
外部调用:
//Detail.vue
this.addCart(product).then(res => {
this.message = res;
this.isShow = true;
setTimeout(() => {
this.isShow = false;
this.message = '';
}, 1000);
})
1)main.js中安装toast插件
import toast from '@/components/common/toast'
//安装toast插件
Vue.use(toast)
2)toast插件内容:toast/index.js:
//插件和混入的区别就是混入还要导入,插件直接在vue对象上面直接使用就可以了
import Toast from './Toast'
const obj = {}
obj.install = function(Vue) {
//1.创建组件构造器
const toastContrustor = Vue.extend(Toast)
//2.new的方式,根据组件构造器,可以创建出来一个组件对象
const toast = new toastContrustor()
//3.将组件对象,手动挂载到某一个元素上
toast.$mount(document.createElement('div'))
//4.toast.$el对应的就是div
document.body.appendChild(toast.$el)
Vue.prototype.$toast = toast
}
export default obj
3)Toast具体内容如下,被注册到index.js中:
{{message}}
4)任意一个地方使用,如Detail.vue:
this.addCart(product).then(res => {
this.$toast.show(res,2000)
})
三步走(2和3在main.js中):
1)安装npm install --save fastclick
2)导入import Fastclick from 'fastclick'
3)使用Fastclick.attach(document.body)
懒加载:图片需要显示在屏幕上时,再加载
1) 安装 npm install vue-lazyload --save
2) 导入 import VueLazyload from 'vue-lazyload'
3)使用 Vue.use(VueLazyload),也可以下面设置占位图
Vue.use(VueLazyload, {
loading: require('@/assets/img/common/placeholder.png')
})
4)修改替换成
1)安装,因为是开发时依赖(打包时用到):npm i postcss-px-to-viewport --save-dev
2)最外层写配置文件:postcss.config.js:
module.exports = {
plugins: {
autoprefixer: {},
"postcss-px-to-viewport": {
viewportWidth: 375, // UI设计稿的宽度
viewportHeight: 667, // UI设计稿的宽度
unitPrecision: 5, // 转换后的精度,即小数点位数
viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
selectorBlackList: ['ignore', 'tab-bar', 'tab-bar-item'], //不需要转换的类
minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
mediaQuery: false, // 是否在媒体查询的css代码中也进行转换,默认false
unitToConvert: "px", // 要转化的单位
// propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
selectorBlackList: ["wrap"], // 指定不转换为视窗单位的类名,
replace: true, // 是否转换后直接更换属性值
exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
landscape: false, // 是否处理横屏情况
}
}
}
1.服务器:一台电脑(没有显示器,只有一个主机),24小时开着的,为用户提供服务。
主机 -> 操作系统 -> Windows/Linux -> Tomcat/nginx(服务器上提供服务的软件)
2.自己计算机nginx安装过程(win):下载nginx稳定版 ->双击exe ->把dist内的所有文件放到nginx-1.20.1/html文件夹内 -> 浏览器输入localhost回车。(远程服务器同理)
3.也可以修改默认启动文件:
1)任务管理器关闭nginx
2)打开nginx-1.20.1\conf\nginx.conf 修改html为dist
3)重新打开exe,运行localhost
1. 学习Linux一般用Ubuntu版本、真正使用选择Centos版本
2.yum是Linux安装包管理工具
3.安装过程(以centos为例):
1)yum install nginx
2)systemctl start nginx service 开启nginx服务
3)systemctl enable nginx.service 跟随系统启动
注:windows上通过xshell可以控制linux命令(如上面3个),传文件用xftp(如修改nginx.conf)
(面试题)
1.问题:app.message修改数据,Vue内部是如何监听message数据的改变?
答:Object.defineProperty -> 监听对象属性的改变,每个属性都有一个Dep对象,name -> Dep对象 -> subs ->[watcher1,watcher 2]
2.问题:当数据发生改变,Vue是如何知道要通知哪些message,界面要发生刷新?
答:发布 订阅者模式
{{message}} {{message}} {{message}}