購物車 組件全局註冊 路由返回處理

App.vue

<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);
  }
  },

輪播圖和商品列表

goods服務,service/goods.js

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;
  }
  });
  }
};

輪播圖、商品列表,Home.vue

<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>

購物⻋狀態,cart.js

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
  );
  }
  }
  }

動畫設計

添加購物⻋動畫,CartAnim.vue

<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>

使⽤動畫,Home.vue

<good-list @cartanim="$refs.ca.start($event)"></good-list>
<cart-anim ref="ca"></cart-anim>
import CartAnim from "@/components/CartAnim.vue";
components: { CartAnim }

觸發動畫,GoodList.vue

<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']) //

調⽤api,Home.vue

<good-list :data="goods" @startcartanim="startCartAnim"></good-list>
 
 methods: {
 startCartAnim(el) {
 const anim = this.$createCartAnim({
 onTransitionend() {
 anim.remove();
 }
 });
 anim.start(el);
 }
 }

組件動態建立並掛載的具體實現./utils/create.js

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;

掛載到vue實例上,main.js

import create from '@/utils/create'
Vue.prototype.$create = create;

調⽤,Home.vue

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>

使⽤,Home.vue

<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;
}
相關文章
相關標籤/搜索