【Vue】Vue项目实战2

此文项目代码:https://github.com/bei-yang/I-want-to-be-an-architect
码字不易,辛苦点个star,感谢!

引言


此篇文章主要涉及以下内容:

  1. 数据和状态管理实践
  2. vuex模块化
  3. vue动画设计(项目中有购物时,跳的小球动画,很值得学习)
  4. 全局组件实现与原理
  5. 全局回退管理

学习资源


  • TarBar导航栏使用
  • 轮播组件使用
  • vuex模块化
  • vue动画设计
  • 动态全局组件创建
  • vue渲染函数原理
  • vue的$mount函数

轮播图和商品列表


  • mock数据,vue.config.js
    • 分析/api/goods接口数据结构
  • 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;
                }
            })
        }
    }
    
  • 定义actions,store.js
    import gs from "@/service/goods";
    
    export default {
      state: {
        slider: [],
        keys: [],
        goodsInfo: {}
      },
      mutations: {
        setGoodsInfo(state, { slider, keys, goodsInfo }) {
          state.slider = slider;
          state.keys = keys;
          state.goodsInfo = goodsInfo;
        }
      },
      getters: { // 添加一个goods属性,转换对象形式为数组形式便于循环渲染
        goods: state => {
          return state.keys
            .map(key => state.goodsInfo[key])
            .reduce((prev, next) => prev.concat(next), []);
        }
      },
      actions: {
        getGoods({ state, commit }) {
            if (!state.keys.length) {
                // 没有数据采去获取
                gs.getGoodsInfo().then(goodsInfo => {
                    commit('setGoodsInfo', goodsInfo)
                })
            }
        }
      }
    };
    
  • 轮播图、商品列表模板,Home.vue
    
    
    
  • 轮播图、商品列表数据获取, Home.vue
      created() {
        this.getGoods(); // 数据初始化
      },
      computed: {
        ...mapState({ slider: state => state.goods.slider }),
        ...mapGetters(["goods"])
      },
      methods: {
        ...mapActions(["getGoods"]),
    

购物车

  • 购物车状态,store.js
    export default {
      state: {
        // 购物车初始状态
        cart: JSON.parse(localStorage.getItem("cart")) || []
      },
      mutations: {
        addcart(state, item) {
          // 添加商品至购物车
          const good = state.cart.find(v => v.title == item.title);
          if (good) {
            good.cartCount += 1;
          } else {
            state.cart.push({
              ...item,
              cartCount: 1
            });
          }
        },
        cartremove(state, index) {
          // count-1
          if (state.cart[index].cartCount > 1) {
            state.cart[index].cartCount -= 1;
          }
        },
        cartadd(state, index) {
          // count+1
          state.cart[index].cartCount += 1;
        }
      },
      getters: {
        cartTotal: state => {
          // 商品总数
          let num = 0;
          state.cart.forEach(v => {
            num += v.cartCount;
          });
          return num;
        },
        total: state => {
          // 总价
          return state.cart.reduce(
            (total, item) => total + item.cartCount * item.price,
            0
          );
        }
      }
    };
    
  • 购物车显示,Cart.vue
  • 在导航栏里显示购物数量,App.vue
    import {mapGetters} from 'vuex'
    
    computed:{
    ...mapGetters(['cartTotal'])
    }
    
    
      
        
    {{item.label}}
    {{cartTotal}}
  • vuex模块化
    • 创建store目录,创建user.js/cart.js/goods.js
    // goods.js
    import gs from "@/service/goods";
    
    export default {
      state: {
        slider: [],
        keys: [],
        goodsInfo: {}
      },
      mutations: {
        setGoodsInfo(state, { slider, keys, goodsInfo }) {
          state.slider = slider;
          state.keys = keys;
          state.goodsInfo = goodsInfo;
        }
      },
      getters: { // 添加一个goods属性,转换对象形式为数组形式便于循环渲染
        goods: state => {
          return state.keys
            .map(key => state.goodsInfo[key])
            .reduce((prev, next) => prev.concat(next), []);
        }
      },
      actions: {
        getGoods({ state, commit }) {
            if (!state.keys.length) {
                // 没有数据采去获取
                gs.getGoodsInfo().then(goodsInfo => {
                    commit('setGoodsInfo', goodsInfo)
                })
            }
        }
      }
    };
    // cart.js
    export default {
      state: {
        // 购物车初始状态
        cart: JSON.parse(localStorage.getItem("cart")) || []
      },
      mutations: {
        addcart(state, item) {
          // 添加商品至购物车
          const good = state.cart.find(v => v.title == item.title);
          if (good) {
            good.cartCount += 1;
          } else {
            state.cart.push({
              ...item,
              cartCount: 1
            });
          }
        },
        cartremove(state, index) {
          // count-1
          if (state.cart[index].cartCount > 1) {
            state.cart[index].cartCount -= 1;
          }
        },
        cartadd(state, index) {
          // count+1
          state.cart[index].cartCount += 1;
        }
      },
      getters: {
        cartTotal: state => {
          // 商品总数
          let num = 0;
          state.cart.forEach(v => {
            num += v.cartCount;
          });
          return num;
        },
        total: state => {
          // 总价
          return state.cart.reduce(
            (total, item) => total + item.cartCount * item.price,
            0
          );
        }
      }
    };
    
    • 将store.js移进去,重命名为index.js
    import Vue from "vue";
    import Vuex from "vuex";
    import user from './user'
    import goods from './goods'
    import cart from './cart'
    
    Vue.use(Vuex);
    
    export default new Vuex.Store({
    modules:{
      user, goods, cart
    }
    });
    
    • 代码中只有state映射需要修改
    // home.vue
    ...mapState({
      slider:state=>state.goods.slider
    })
    // cart.vue
    ...mapState({cart:state=>state.cart.cart})
    

动画设计


  • vue动画
  • 页面切换动画,App.vue
  • 添加购物车动画
    • 创建购物车动画组件,CartAnim.vue
    
    
    
    
  • 使用动画,Home.vue
 
 
 import CartAnim from '@/componets/CartAnim.vue'
 components: { CartAnim }
  • 触发动画,GoodList.vue
 
 addCart (e, item) {  // 需要传递事件目标
   this.$store.commit("addcart", item);
   // 触发动画时间
   this.$emit('startcartanim', e.target)
 }

动画有两个问题:
1. 使用比较麻烦
2. 不能生成多个动画实例

动态全局组件设计与实现


  • 使用cube-ui的create-api

    • 注册
    import {createAPI} from 'cube-ui'
    import CartAnim from '@/components/CartAnim'
    
    createAPI(Vue,BallAnim,['transitionend'])
    
    • 调用api,Home.vue
    
    methods:{
      startCartAnim(el){
        const anim=this.$createCartAnim({
          onTransitionend(){
            anim.remove();
          }
        });
        anim.start(el);
      }
    }
    

    create-api的原理是动态创建组件并全局挂载至body中,下面我们自己实现一下

    • 组件动态创建并挂载的具体实现
      • 定义动态创建函数:./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
        document.body.appendChild(instance.$el)
        // 给组件实例添加销毁方法
        const comp = instance.$children[0];
        comp.remove = () => {
          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);
      }
      
      • 还可以传递属性到组件,增加组件可用性
      // Home.vue
      const anim=this.$create(CartAnim,{
        pos:{left:'45%',bottom:'10px'}
      });
      // CartAnim.vue
      
      props:['pos']

页头组件


  • 组件定义,Header.vue
    
    
    
    
    
  • 使用,Home.vue
    
      
    
    
    import KHeader from '@/components/Header.vue';
    components:{
      KHeader
    }
    
  • 返回按钮状态自动判断:history.length是不可靠的,它既包含了vue app路由记录,也包括其他页面的。可以添加一个自定义的历史记录管理栈,创建./utils/history.js
    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从而管理记录
    import History from './utils/history';
    import router from './【Vue】Vue项目实战2/vue-mart/src/router';
    
    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
    
    methods:{
      back(){this.$router.goBack()}
    }
    
  • 后退动画,App.vue
    // 动态设置名称
    
      
    
    watch: {
    // 动态设置动画方式
    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;
    }
    

你的赞是我前进的动力

求赞,求评论,求分享...

你可能感兴趣的:(【Vue】Vue项目实战2)