Vue-imooc,全模拟数据
目录结构
/ src
- components —— 组件
- mock —— 模拟数据
- public —— 静态资源
- router —— 路由
- store —— 状态管理
- views —— 页面
技术栈
vue-cli、vue-router、vuex、axios、mock.js
插件
vue-awsome-swiper、better-scroll
页面快速预览
下来玩玩
重点
使用Mockjs
vue-cli项目中使用mockjs模拟数据
封装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人点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦