前端|Vue电商项目实战(三)

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

> export default { props: { title: { type: String, default: '', }, showback: { type: Boolean, default: true, }, }, methods: { back() { this.$router.back(); } }, } 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; } }

1.2 全局引入组件 修改main.js文件:
import KHeader from './components/Header.vue'// 全局引入Header.vue Vue.component('k-header', KHeader)

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

运行效果:
前端|Vue电商项目实战(三)
文章图片

二、管理访问历史 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)修改后退条件:

运行效果:
前端|Vue电商项目实战(三)
文章图片

二、页面切换特效 修改App.vue,把transition标签的name属性改为动态属性。

在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'; // 前进特效 } });

三、加购物车添加动画效果 实现效果:
前端|Vue电商项目实战(三)
文章图片

3.1 定义小球 在Home.vue模版中定义一个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事件。

定义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。
> export default { name: 'notice', }

【前端|Vue电商项目实战(三)】定义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; } } } },

添加样式:
.alert { position fixed width 100% top 30 left 0 text-align center .alert-content { display inline-block padding 8px background #fff margin-bottom 10px } }

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: '加购物车成功'}); },

运行效果:
前端|Vue电商项目实战(三)
文章图片

    推荐阅读