<transition :name="transitionName"> <router-view class="child-view"/> </transition> <cube-tab-bar v-model="selectLabel" :data="tabs" @change="changeHandler"> <cube-tab v-for="(item, index) in tabs" :icon="item.icon" :label="item.value" :key="index"> <div>{{item.label}}</div> <span class="badge" v-if="item.label=='Cart'"> {{cartTotal}}</span> </cube-tab> </cube-tab-bar> data() { return { transitionName: 'route-forward', selectLabel: "/", tabs: [ { label: "Home", value: "/", icon: "cubeic-home" }, { label: "Cart", value: "/cart", icon: "cubeic-mall" }, { label: "Me", value: "/about", icon: "cubeic-person" } ] }; }, created() { // 初始化⻚籤設置,避免⻚⾯刷新 this.selectLabel = this.$route.path; }, watch: { $route(route) { // 監聽路由變化並動態設置⻚籤選中狀態 this.selectLabel = route.path; this.transitionName = this.$router.transitionName } }, methods: { changeHandler(val) this.$router.push(val); } },
import axios from "axios"; export default { // 獲取輪播圖、商品和分類數據 getGoodsInfo() { return axios.get("/api/goods").then(res => { const {code, data: goodsInfo, slider, keys} = res.data; // 數據處理 if (code) { return { goodsInfo, slider, keys }; } else { return null; } }); } };
<template> <!-- 輪播圖 --> <cube-slide :data="slider" :interval="5000"> <cube-slide-item v-for="(item,index) in slider" :key="index"> <router-link :to="`/detail/${item.id}`"> <img class="slider" :src="item.img"> </router-link> </cube-slide-item> </cube-slide> <!-- 商品列表 --> <good-list :data="goods"></good-list> </template> <script> import gs from "@/service/goods"; import GoodList from "@/components/GoodList.vue"; export default { name: "home", components: { GoodList }, data() { return { slider: [], keys: [], goodsInfo: {} }; }, created() { // ⾸次查詢 gs.getGoodsInfo().then(({ goodsInfo, slider, keys }) => { // 保存狀態 this.goodsInfo = goodsInfo; this.slider = slider; this.keys = keys; }); }, computed: { goods() { // [[{},{}]]=>[{},{}] return this.keys.flatMap(key => this.goodsInfo[key]); } } }; </script>
export default { state: { // 購物⻋初始狀態 list: JSON.parse(localStorage.getItem("cart")) || [] }, mutations: { addcart(state, item) { // 添加商品⾄購物⻋ const good = state.list.find(v => v.title == item.title); if (good) { good.cartCount += 1; } else { state.list.push({ ...item, cartCount: 1 }); } }, cartremove(state, index) { // count-1 if (state.list[index].cartCount > 1) { state.list[index].cartCount -= 1; } }, cartadd(state, index) { // count+1 state.list[index].cartCount += 1; } }, getters: { cartTotal: state => { // 商品總數 let num = 0; state.list.forEach(v => { num += v.cartCount; }); return num; }, total: state => { // 總價 return state.list.reduce( (total, item) => total + item.cartCount * item.price, 0 ); } } }
<template> <div class="ball-wrap" <transition @before-enter="beforeEnter" @enter="enter@afterEnter="afterEnter" <div class="ball" v-show="show"> <div class="inner"> <div class="cubeic-add"></div> </div> </div> </transition> </div> </template> <script> export default { name: "cartAnim", data() { return { show: false }; }, methods: { start(el) {// 啓動動畫接⼝,傳遞點擊按鈕元素 this.el = el; // 使.ball顯示,激活動畫鉤⼦ this.show = true; }, beforeEnter(el) { // 把⼩球移動到點擊的dom元素所在位置 const rect = this.el.getBoundingClientRect(); // 轉換爲⽤於絕對定位的座標 const x = rect.left - window.innerWidth / 2; const y = -(window.innerHeight - rect.top - 10 - 20); // ball只移動y el.style.transform = `translate3d(0, ${y}px, 0)`; // inner只移動x const inner = el.querySelector(".inner"); inner.style.transform = `translate3d(${x}px,0,0)`; }, enter(el, done) { // 獲取offsetHeight就會重繪 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) { // 動畫結束,開始清理⼯做 this.show = false; el.style.display = "none"; this.$emit("transitionend"); } } }; <style scoped lang="stylus"> .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; } } } } </style> </script>
<good-list @cartanim="$refs.ca.start($event)"></good-list> <cart-anim ref="ca"></cart-anim> import CartAnim from "@/components/CartAnim.vue"; components: { CartAnim }
<i class="cubeic-add" @click.stop.prevent="addCart($event,item)"></i> addCart(e,item) { // 須要傳遞事件⽬標 this.$store.commit("addcart", item); // 觸發動畫事件 this.$emit('cartanim',e.target) }
**使⽤cube-ui的create-ap
註冊,main.js**vue
import {createAPI} from 'cube-ui' import CartAnim from '@/components/CartAnim' createAPI(Vue, CartAnim, ['transitionend']) //
<good-list :data="goods" @startcartanim="startCartAnim"></good-list> methods: { startCartAnim(el) { const anim = this.$createCartAnim({ onTransitionend() { anim.remove(); } }); anim.start(el); } }
import Vue from "vue"; // 建立函數接收要建立組件定義 function create(Component, props) { // 建立⼀個Vue新實例 const instance = new Vue({ render(h) { // render函數將傳⼊組件配置對象轉換爲虛擬dom console.log(h(Component, { props })); return h(Component, { props }); } }).$mount(); //執⾏掛載函數,但未指定掛載⽬標,表示只執⾏初始化、編譯等⼯做 // 將⽣成dom元素追加⾄body instance.$el vue實例的真實dom document.body.appendChild(instance.$el); // 給組件實例添加銷燬⽅ instance.$children[0] 全部組件的實例的數組 const comp = instance.$children[0]; comp.remove = () => { //從body移除dom 全局組件頻繁創建與刪除 document.body.removeChild(instance.$el); instance.$destroy(); }; return comp; } // 暴露調⽤接⼝ export default create;
import create from '@/utils/create' Vue.prototype.$create = create;
startCartAnim(el) { const anim = this.$create(CartAnim); anim.start(el); anim.$on("transitionend", anim.remove); }
組件定義,Header.vueios
<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: "", required: true }, showback: { type: Boolean, default: false } }, 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>
<k-header title="開lll"> <i class="cubeic-tag"></i> </k-header> import KHeader from '@/components/Header.vue'; components: { GoodList,KHeader, }
**返回按鈕狀態⾃動判斷:history.length是不可靠的,它既包含了vue app路由記錄,也包括其餘
⻚⾯的。能夠添加⼀個⾃定義的歷史記錄管理棧,建立./utils/history.js**axios
const History = { _history: [], // 歷史記錄堆棧 install(Vue) { // vue插件要求的安裝⽅法 Object.defineProperty(Vue.prototype, "$routerHistory", { get() { return History; } }); }, push(path) { // ⼊棧 this._current += 1; this._history.push(path); }, pop() { // 出棧 this._current -= 1; return this._history.pop(); }, canBack() { return this._history.length > 1; } }; export default History;
router.js中引⼊,添加⼀個後退⽅法並監聽afterEach從⽽管理記錄api
import History from "./utils/history"; Vue.use(History); Router.prototype.goBack = function() { this.isBack = true; this.back(); }; 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"; } });
使⽤,Header.vue數組
<i v-if="$routerHistory.canBack()"></i> methods: { back() {this.$router.goBack();} }
後退動畫,App.vueapp
<transition :name="transitionName"> <router-view class="child-view"/> </transition> data() { return { transitionName: 'route-forward' }; }, watch: { $route() { // 動態設置動畫⽅ this.transitionName = this.$router.transitionName } }, /* ⻚⾯滑動動畫 */ /* ⼊場前 */ .route-forward-enter { transform: translate3d(-100%, 0, 0); } .route-back-enter { transform: translate3d(100%, 0, 0); } /* 出場後 */ .route-forward-leave-to { transform: translate3d(100%, 0, 0); } .route-back-leave-to { transform: translate3d(-100%, 0, 0); } .route-forward-enter-active, .route-forward-leave-active, .route-back-enter-active, .route-back-leave-active { transition: transform 0.3s; }