Vue电商项目实战(三)

一、显示头部

1.1 新建头部组件

在components目录下新建Header.vue文件。其中该组件接收两个参数:title和showback。title代表头部组件的标题内容;showback代表是否显示后退按钮。

<template>
    <div class="header">
        <h1>{{title}}</h1>
        <i v-if="showback" @click="back" class="cubeic-back"></i>
        <div class="extend">
            <slot></slot>
        </div>
    </div>
</template>

<script>
    export default {
        props: {
            title: {
                type: String,
                default: '',
            },
            showback: {
                type: Boolean,
                default: true,
            },
        },
        methods: {
            back() {
                this.$router.back();
            }
        }, 
    }
</script>

<style lang="stylus" scoped>
.header {
  position: relative;
  height: 44px;
  line-height: 44px;
  text-align: center;
  background: #edf0f4;

  .cubeic-back {
    position: absolute;
    top: 0;
    left: 0;
    padding: 0 15px;
    color: #fc915b;
  }

  .extend {
    position: absolute;
    top: 0;
    right: 0;
    padding: 0 15px;
    color: #fc915b;
  }
}
</style>

1.2 全局引入组件

修改main.js文件:

import KHeader from './components/Header.vue'

// 全局引入Header.vue
Vue.component('k-header', KHeader)

1.3 使用组件

在首页Home.vue中显示组件。

<k-header title="乐购商城"></k-header>

运行效果:
在这里插入图片描述

二、管理访问历史

2.1 Vue插件开发

资料地址:https://cn.vuejs.org/v2/guide/plugins.html

现在我们想定义一个访问浏览历史记录的实例方法。我们把该方法添加到Vue.prototype上实现。

第一步:新建utils目录,该目录存放我们项目中定义的工具;

第二步:新建history.js文件,在该文件中实现插件功能;

import Vue from 'vue'

const History = {
	_history: [], // 历史记录
	install(vue) { 
		// 给Vue对象添加实例方法
		Object.defineProperty(Vue.prototype, '$routerHistory', {
			get() {
				return History;
			}
		});
	},
	push(path) { // 存储访问页面
		this._history.push(path);
	},
	pop() { // 删除历史页面
		this._history.pop();
	},
	canBack() {
		return this._history.length > 1;
	}
}

export default History;

Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。

第三步:通过全局方法Vue.use()安装插件;

import History from '../utils/history'

Vue.use(History) 

插件安装成功后,可以使用History访问插件方法。

History.pop(); // 删除访问历史的最后记录

2.2 路由配置

第一步:扩展Router功能,新增后退方法。

import VueRouter from 'vue-router'

VueRouter.prototype.goBack = function() {
	this.isBack = true; // 新增isBack属性,true代表当前操作是后退
	this.back(); // 调用路由的back方法执行后退
}

第二步:定义路由拦截方法。

import History from '../utils/history'

// 把History当成插件引入
Vue.use(History) 

// 记录访问历史
router.afterEach((to, from) => {
    if (router.isBack) {
      // 如果执行后退,删除访问历史的最后记录
      History.pop(); 
      router.isBack = false;
    } else {
      // 如果不是执行后退,则添加访问历史记录
      History.push(to.path); 
    }
});

2.3 改造后退按钮

(1)修改Header.vue的back方法:

methods: {
   back() {
        // this.$router.back(); 
        this.$router.goBack(); // 替换成该方法
    }
},

(2)修改后退条件:

<i v-if="$routerHistory.canBack()" @click="back" class="cubeic-back"></i>

运行效果:
在这里插入图片描述

二、页面切换特效

修改App.vue,把transition标签的name属性改为动态属性。

<transition :name="transitionName">
      <router-view class="child-view"/>
</transition>

在data返回的json对象中添加transitionName属性。

data () {
    return {
      ...
      transitionName: 'route-forward',
    }
  },

当路由发生变化时,同步更新transitionName属性。

watch: { 
    // 监控route状态变化。当路由发生变化,同步tabs选中状态
    $route(route) { 
      this.selectedLabel = route.path;

      this.transitionName = this.$router.transitionName;
    }
  },

修改页签样式:

/* 页签滚动条样式 */
.cube-tab-bar-slider {
  top: 0; 
}

/* 动画设置 */
.route-forward-enter { /* 入场前 */
  transform: translate3d(-100%, 0, 0);
}

.route-forward-leave-to { /* 出场后 */
  transform: translate3d(100%, 0, 0);
}

.route-back-enter { /* 入场前 */
  transform: translate3d(100%, 0, 0);
}

.route-back-leave-to { /* 出场后 */
  transform: translate3d(-100%, 0, 0);
}

.route-forward-enter, 
.route-forward-leave-active,
.route-back-enter, 
.route-back-leave-active { /* 播放动画过程中 */
  transition: transform 0.3s; /* 完成动画需要的时间 */
}

修改路由配置文件,在router中定义transitionName属性。如果router.isBack为true,代表是回退操作,则transitionName为route-back;如果router.isBack为false,代表前进操作,则设置transitionName为route-forward。

router.afterEach((to, from) => {
    if (router.isBack) {
      History.pop(); // 删除访问历史的最后记录
      router.isBack = false;
      
      router.transitionName = 'route-back'; // 后台特效
    } else {
      History.push(to.path);

      router.transitionName = 'route-forward'; // 前进特效
    }
});

三、加购物车添加动画效果

实现效果:
在这里插入图片描述

3.1 定义小球

在Home.vue模版中定义一个div元素,该元素代表动画中的小球。

<!-- 加购动画的圆球 -->
<div class="ball-wrap">
 <transition 
   @before-enter="beforeEnter"
   @enter="enter"
   @after-enter="afterEnter">
   <!-- <div class="ball" v-show="ball.show">球</div> -->
   <div class="ball" v-show="ball.show">
     <div class="inner">
       <div class="cubeic-add"></div>
     </div>
   </div>
 </transition>
</div>

其中v-show属性用于控制小球的显示。因此,需要定义ball.show属性。

data() {
  return {
    ...
    ball: {
      show: false,
      el: null, // 目标dom的引用
    }
  }
},

ball对象有两个属性:show代表小球的显示状态,true代表显示,false不显示;el代表的是小球所属DOM元素的引用。

小球样式:

/* 圆球样式 */
.ball-wrap {
  .ball {
    position: fixed;
    left: 50%;
    bottom: 10px;
    z-index: 100000;
    color: red;
    transition: all 0.5s cubic-bezier(0.49, -0.29, 0.75, 0.41);

    .inner {
      width: 16px;
      height: 16px;
      transition: all 0.5s linear;

      .cubeic-add {
        font-size: 22px;
      }
    }
  }
}

3.2 添加动画事件

transition标签有三个事件属性,它们分别代表动画执行的三种不同状态。
@before-enter:动画开始前
@enter:动画进行中
@after-enter:动画结束后

下面是三个事件对应的处理方法:

methods: {
  ...
  beforeEnter(el) {
    console.log('动画开始前...');
    // 设置小球初始位置
    // 小球移动到点击的位置
    // 1. 获取点击的dom位置
    const dom = this.ball.el;
    const rect = dom.getBoundingClientRect();
    console.log(rect.top, rect.left);
    // 2. 把小球移动到点击的位置
    const x = rect.left - window.innerWidth / 2;
    const y = -(window.innerHeight - rect.top - 10 - 20);
    el.style.display = "block";
    // ball只移动y
    el.style.transform = `translate3d(0, ${y}px, 0)`;
    const inner = el.querySelector(".inner");
    // inner只移动x
    inner.style.transform = `translate3d(${x}px,0,0)`;
  },
  enter(el, done) { // el代表当前动画的元素
    console.log('动画执行中...');
    // 把小球移动到初始位置 加上动画
    // 获取offsetHeight就会重绘,前面的变量名随意 主要为了eslint校验
    document.body.offsetHeight;
    // 动画开始,移动到初始位置
    // 小球移动到购物车位置
    el.style.transform = `translate3d(0, 0, 0)`;
    const inner = el.querySelector(".inner");
    inner.style.transform = `translate3d(0,0,0)`;
    el.addEventListener("transitionend", done);
  },
  afterEnter(el) {
    console.log('执行动画结束后的清理工作...');
    // 隐藏小球
    this.ball.show = false;
    el.style.display = 'none';
  },
},

3.3 为加购物车按钮派发事件

首先修改GoodsList.vue,在addCart方法中派发addCart事件。

// 向父组件Home派发点击事件
this.$emit('addCart', $event.target); // $event.target代表事件发生的元素

然后首页商品列表中监听addCart事件。

<goods-list :goods="filterGoods" @addCart="onAddCart"></goods-list>

定义addCart事件的处理方法。

onAddCart(el) {
  console.log('显示小球');
  this.ball.el = el; // 初始化ball.el属性
  this.ball.show = true; // 显示小球
},

四、全局组件

下面以toast为例,介绍如何定义全局组件。

// toast组件的定义格式
const toast = this.$createToast({
    time: 2000,
    txt: message || '登录失败',
    type: 'error'
});
toast.show();

4.1 定义组件

首先,在components目录下新建Notice.vue。

<template>
    <div class="alert">
        <div class="alert-container" v-for="item in alerts" :key="item.id">
            <div class="alert-content">{{item.content}}</div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'notice',
    }
</script>

定义alerts属性,该属性用于存储多个通知。

data() {
    return {
        alerts: [],
    }
},

在created方法中初始化通知id。通知id应该是自增长。

created() {
    this.id = 0; 
},

定义添加和删除通知的方法。

methods: {
	// 添加通知,options参数代表通知选项(duration-显示时间,content-通知内容)
    add(options) { 
    	// 通知id自增
        const id = 'id_' + this.id++;
        // 创建通知对象
        const _alert = {
            ...options,
            id,
        }
        // 保存通知
        this.alerts.push(_alert);
        // 自动关闭
        const duration = options.duration || 1; // 延迟关闭时间,单位为秒
        setTimeout(() => {
            this.del(id);
        }, duration * 1000);
    },
    // 删除通知
    del(id) {
        for (let i = 0; i < this.alerts.length; i++) {
            const alert = this.alerts[i];
            if (alert.id === id) {
                this.alerts.splice(i, 1);
                break;
            }
        }
    }
},

添加样式:

<style lang="stylus" scoped>
    .alert {
        position fixed
        width 100%
        top 30
        left 0
        text-align center
        .alert-content {
            display inline-block
            padding 8px
            background #fff
            margin-bottom 10px
        }
    }
</style>

4.2 使用create-api模块创建通知组件

资料地址:https://didi.github.io/cube-ui/#/zh-CN/docs/introduction

4.2.1 导入create-api模块

修改main.js文件:

import {createAPI} from 'cube-ui'

4.2.2 加载Notice组件

修改main.js文件:

import Notice from './components/Notice.vue'

createAPI(Vue, Notice, true);

createAPI方法第三个参数代表是否单例。true代表Notice组件是单例,即在应用中只创建一次。

4.2.3 使用Notice组件

修改GoodsList.vue,在addCart方法中显示通知。

addCart($event, item) {
    // 把添加购物车的商品放在state中
    this.$store.commit('addCart', item);
    // 显示通知
    const notice = this.$createNotice();
    notice.add({duration: 2, content: '加购物车成功'});
},

4.3 自定义通知组件

第一步:新建一个services目录,然后在该目录下新建notice.js文件。

第二步:导入Notice.vue,并且定义一个创建Notice实例的静态方法。

import Vue from 'vue';
import Notice from '@/components/Notice.vue'

// 给Notice添加一个属性函数,用于创建Notice组件实例,并动态编译后的Notice组件挂载到页面上
Notice.getInstance = props => {
    // 创建一个Vue实例
    const instance = new Vue({
        // 渲染函数,该函数用于把指定模版渲染为DOM
        render(h) {
            return h(Notice, {props});
        }
    }).$mount();
	// 把Notice组件添加到Body元素中
    document.body.appendChild(instance.$el); // instance.$el用于获取当前实例里面的DOM
    // 从vue实例中获取notice实例
    return instance.$children[0];
}

注意:Notice实例在应用中只需要创建一次即可。所以我们通过对单例来创建Notice实例。

// 单例创建Notice实例
let noticeInstance = null;
function getInstance() {
    if (noticeInstance == null) {
        noticeInstance = Notice.getInstance();
    }
    return noticeInstance;
}

第三步:对外暴露接口。

// 暴露接口
export default {
    info({duration = 2, content = ''}) {
        getInstance().add({
            duration,
            content
        });
    }
}

第四步:把notice配置到Vue实例中。

import notice from '@/services/notice'

Vue.prototype.$notice = notice;

第五步:测试。

// 添加购物车
addCart($event, item) {
    // 把添加购物车的商品放在state中
    this.$store.commit('addCart', item);
    
    // 显示通知
    // const notice = this.$createNotice();
    // notice.add({duration: 2, content: '加购物车成功'});

    this.$notice.info({duration: 3, content: '加购物车成功'});
},

运行效果:
在这里插入图片描述

发布了111 篇原创文章 · 获赞 41 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhongliwen1981/article/details/103374966