为了账号安全,请及时绑定邮箱和手机立即绑定

Vue-imooc,全模拟数据

目录结构

/ src

  • components —— 组件
  • mock —— 模拟数据
  • public —— 静态资源
  • router —— 路由
  • store —— 状态管理
  • views —— 页面
技术栈

vue-cli、vue-router、vuex、axios、mock.js

插件

vue-awsome-swiper、better-scroll

页面快速预览

下来玩玩
重点

封装storage

/*
 * @Author: likang xie 
 * @Date: 2018-08-21 19:33:25 
 * @Purpose: 本地存储操作
 */

let storage = (function () {

  let st = null;

  if (!window.localStorage) {
    throw new Error('sorry, your browser is not suport localStorage!')
  } else {
    st = window.localStorage
  }

  let set = function (key, val) {
    if (typeof val == 'object') {
      val = JSON.stringify(val);
    }
    st.setItem(key, val);
  }

  let get = function (key) {
    return st.getItem(key);
  }

  let clear = function () {
    st.clear();
  }

  return {
    set,
    get,
    clear
  }

})()

export default storage

需要管理的状态

state.js

/*
 * @Author: likang xie 
 * @Date: 2018-08-22 11:34:50 
 * @Purpose: 状态
 */

export default {
  userInfo: null, // 用户信息
  shopCartInfo: null, // 购物车信息
  toast: "" // toast提示的内容
}

action.js

/*
 * @Author: likang xie 
 * @Date: 2018-08-22 11:34:50 
 * @Purpose: 分发
 */

export default {
  set_userInfo(context, userInfo) {
    context.commit('set_userInfo', userInfo)
  },
  set_shopCartInfo(context, shopCartInfo) {
    context.commit('set_shopCartInfo', shopCartInfo)
  },
  set_toast(context, toast) {
    context.commit('set_toast', toast)
  }
}

mutations.js

/*
 * @Author: likang xie 
 * @Date: 2018-08-22 11:34:50 
 * @Purpose: 提交
 */

export default {
  set_userInfo(state, userInfo) {
    state.userInfo = userInfo
  },
  set_shopCartInfo(state, shopCartInfo) {
    state.shopCartInfo = shopCartInfo
  },
  set_toast(state, toast) {
    state.toast = toast
  }
}

封装toast轻提示插件

toast.vue

<template>
  <div class="toast imooc-flex imooc-flex-center" v-show="$store.state.toast != ''">
    <div class="toast-value">{{ $store.state.toast }}</div>
  </div>
</template>

<script>
export default {};
</script>

<style lang="scss">
.toast {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 1.5rem;
  margin: auto;
  max-width: 30rem;
  min-height: 4rem;
  text-align: center;
  z-index: 101;
  .toast-value {
    padding: 1rem 1.5rem;
    background-color: #474747;
    color: #fff;
    border-radius: 2rem;
    font-size: 1.2rem;
  }
}
</style>

toast.js

/*
 * @Author: likang xie 
 * @Date: 2018-08-21 19:33:25 
 * @Purpose: toast提示组件操作
 */

import store from '../../store'

let toast = (function () {

  let timer = null

  let show = function (content) {

    // 清除定时器
    clearTimeout(timer)

    // 提交set_toast方法
    store.commit('set_toast', content)

    // 只显示3秒
    timer = setTimeout(() => {
      store.commit('set_toast', '')
    }, 3000)

  }

  return {
    show
  }

})()

export default toast

登录、登出

// 登录
this.$storage.set("userInfo", userInfo);
this.$store.commit("set_userInfo", userInfo); 

// 登出
this.$storage.set("userInfo", "");
this.$store.commit("set_userInfo", ""); 

登录状态

app.vue

created() {
  let userInfo = this.$storage.get("userInfo") || null;
  userinfo && userInfo = JSON.parse(userInfo);
  this.$store.commit("set_userInfo", userInfo); 
}

其他组件判断是否已登录

if (this.$store.state.userInfo)

加入购物车动画

css

/* 小球的样式加个过渡时间 */
.circle {
  transition: all 1s cubic-bezier(0.075, 0.82, 0.165, 1);
}

/* 购物车的样式加个类名和过渡 */
.icon-cart {
  transition: all 0.1s ease;
  &.scale {
    transform: scale(1.5);
  }
 }
// 获取鼠标点击时的x,y坐标
let { pageX, pageY } = e;

 // 获取购物车图标的x,y坐标
let cart = document.querySelector(".icon-cart");
let { x, y } = cart.getBoundingClientRect();

// 在鼠标点击处生成小球
let circle = document.createElement("span");
circle.className = "circle";
circle.style.cssText = `top: ${pageY}px; left: ${pageX}px`; // 初始化小球位置
document.body.appendChild(circle);

// 小球移动到购物车
setTimeout(() => {
  circle.style.cssText = `top: ${y}px; left: ${x + 5}px`; // 移动到购物车的位置
  // 根据css设置的过渡时间移除小球、购物车做放大动画
  setTimeout(() => {
    circle.remove();
    cart.classList.add("scale");
    setTimeout(() => {
      cart.classList.remove("scale");
    }, 100);
  }, 900);
}, 50);

轮播图

参数

options: {
  effect: "coverflow",
  slidesPerView: 1.1,
  spaceBetween: 65,
  centeredSlides: true,
  autoplay: {
    delay: 3000,
    disableOnInteraction: false
  },
  coverflowEffect: {
    rotate: 0,
    stretch: 10,
    depth: 100,
    modifier: 2,
    slideShadows: true
  },
  loop: true,
  pagination: {
    el: ".swiper-pagination"
  }
}

Pagination切换动画,修改样式即可

.swiper-container {
  padding-bottom: 3rem;
  .swiper-pagination {
    bottom: 0rem;
    span {
      background-color: #d9dde1;
      opacity: 0.6;
      transition: width 0.2s ease-in;
      border-radius: 0.6rem;
      &.swiper-pagination-bullet-active {
        width: 3rem;
        background-color: #d9dde1;
        opacity: 1;
      }
    }
  }
}

better-scroll,左右联动
better-scroll文档

联动脚本

<li class="menu-item" :class="{'active': currentIndex == index}" @click="selectMenu(index)"></li>

data() {
  return {
    mscroll: null, // 左边的scroll区域
    cscroll: null, // 右边的scroll区域
    scrollY: 0, // 当前滚动的Y值
    listHeight: [] // 左边内容块距离顶部的距离
  }
},
initBetterScroll() {
  this.$nextTick(function() {
  setTimeout(() => {
      let wrapper = this.$refs.cwrapper;
      // 初始化better-scroll插件
      this.cscroll = new BScroll(wrapper, {
        click: true,
        probeType: 3
      });
      // 监听内容区的滚动
      this.cscroll.on("scroll", pos => {
        this.scrollY = Math.abs(Math.round(pos.y));
      });
      // 计算高度
      this.caclHeight();
    }, 500);
  });
},
// 计算右边内容块距离顶部的高度(叠加), 等scroll插件初始化完成并且内容渲染完成再开始计算
caclHeight() {
  let content = document.querySelectorAll(".content-wrapper .content-item");
  let height = 0;
  this.listHeight.push(height);
  content.forEach(item => {
    height += item.clientHeight;
    this.listHeight.push(height);
  });
},
// 选中左边的item
selectMenu(index) {
  let content = document.querySelectorAll(".content-wrapper .content-item");
  let el = content[index];
  this.cscroll.scrollToElement(el, 300);
},
computed: {
  // 当前活跃的item索引值
  currentIndex() {
    for (let i = 0; i < this.listHeight.length; i++) {
      let height1 = this.listHeight[i];
      let height2 = this.listHeight[i + 1];
      if (
        !height2 ||
        (this.scrollY >= height1 && this.scrollY <= height2 - 50)
      ) {
        return i;
      }
    }
    return 0;
  }
},

过渡动画

给单个组件添加过渡动画

template

<template>
  <transition name="slidedown">
    <div class="cart"></div>
  </transition>
</template>

css

/* 进入时的动画 */
.slidedown-enter-active {
  animation: bounce-in 0.3s;
}

/* 离开时的动画 */
.slidedown-leave-active {
  animation: bounce-in 0.3s reverse;
}

@keyframes bounce-in {
  0% {
    transform: translateY(-100%);
  }

  100% {
    transform: translateY(0);
  }
}

上滑加载

封装onReachBottom函数

let onReachBottom = function (callback) {
  window.onscroll = function () {
    // 滚动高度 + 视窗高度 >= 内容高度
    let isBottom = window.scrollY + document.documentElement.clientHeight >= document.body.clientHeight;
    if (isBottom) {
      callback && callback();
    }
  }
}

export.default = onReachBottom;

组件内调用

import onReachBottom from 'reachbottom.js';

export default {
  data () {
    return {
      datalist: [],
      page: 0
    }
  },
  activated() {
    onReachBottom(this.getDataList);
  },
  methods: {
    getDataList() {
      this.$http.get(url).then(res => {
        // 拼接上去即可,template中遍历datalist,数据双向绑定
        this.datalist = this.datalist.concat(res.data.datalist);
        page ++; // 页数加一
      })  
    }
  }
}

查看全部

<ul class="chapter-list" :class="{'show-all': showAll}">
  <li></li>
  ...
  ...
  <li class="more-mask" v-if="!showAll"></li>
</ul>
<div class="check-all" v-if="!showAll" @click="showAll = !showAll">查看全部</div>
.chapter-list {
  position: relative;
  height: 50rem;
  overflow: hidden;
  transition: all 0.5s linear;
  &.show-all {
    height: auto;
  }
}

.more-mask {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 20rem;
  background: linear-gradient(
    bottom,
    #fff 0,
    rgba(255, 255, 255, 0) 100%
  );
}

数据一样,类型不一样

<li v-for="item in typelist" class="change-btn" @click="changeType(item.type)">{{ item.text }}</li>
methods: {
  changeType(type) { 
    let url = `/api/datalist?type=${type}`;
    this.$http.get(url).then(res => {
      this.datalist= res.data.datalist;  // 数据区遍历datalist即可
    })
  }
}

购物车的添加和删除

此处删除的弹框没录下来

抛开本地存储,mutations可以把set_shopCartInfo拆成add_shopCart、del_shopCart、clear_shopCart:

/*
 * @Author: likang xie 
 * @Date: 2018-08-22 11:34:50 
 * @Purpose: 提交
 */

export default {
  add_shopCart(state, shop) {
    state.shopCartInfo.push(shop);
  },
  del_shopCart(state, index) {
    // 拿到需要删除得商品索引
    state.shopCartInfo.splice(index, 1);
  },
  clear_shopCart(state) {
    state.shopCartInfo = [];
  },
}

推荐
像我上面的都是vuex + localStorage结合,其实早就有一个vuex状态持久性方案,原理也是一样vuex-persistedstate

欢迎所有前端爱好者关注我的个人微信公众号,我会经常分享最新,实用性高的前端文章以及技巧,祝你在前端开发的道路上刀过竹解!

点击查看更多内容
14人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消