【Vue】仿小米商城系统(二)
文章目录
前言
本项目是基于Vue全家桶的仿小米商城系统,系列笔记(已完结)如下👇🏻
【Vue】仿小米商城系统(二)
🐶项目地址:
点击这里👉vue仿小米商城源码
这里是小飞侠Pan🥳,立志成为一名优秀的前端程序媛!!!
本篇博客收录于我的github前端笔记仓库中,持续更新中,欢迎star~
👉https://github.com/mengqiuleo/myNote
三、商品首页的开发
mixin的使用
//flex布局的复用
@mixin flex($hov:space-between,$col:center) {
display: flex;
justify-content: $hov;
align-items: $col;
}
商标移出和进入
商标其实是一个a标签,a标签的大小是两个图片的大小,要把两个图片包起来。给a标签上设置两个伪类(相当于加了两个伪元素),当鼠标移动到a标签上时,要给第一个元素设置margin-left为负值,这样整个图片就向左移了。
.header-logo{
display: inline-block;
width: 55px;
height: 55px;
background-color: #FF6600;
a{
display: inline-block;
width: 110px;
height: 55px;
&:before{
content: "";
@include bgImg(55px,55px,'/imgs/mi-logo.png',55px);
transition: margin .2s;
}
&:after{
content: "";
@include bgImg(55px,55px,'/imgs/mi-home.png',55px);
}
&:hover:before{
margin-left: -55px;
transition: margin .2s;
}
}
鼠标移动到菜单上显示下拉列表
给下拉的列表设置定位(绝对定位),子绝父相,然后给下拉元素设置top=上面的高度,left=0。
然后设置hover:,给下拉列表的外层容器(就是设置position:absolute的那个)设置overflow:hidden。
.children{
position: absolute;//!!!
top:112px;
left:0;
width: 1226px;
height: 0; //起初children的高度为0,当鼠标移动到上面时才有高度
opacity: 0;//透明度为0
overflow: hidden;//!!!
border-top:1px solid #E5E5E5;
box-shadow: 0px 7px 6px 0px rgba(0,0,0,0.11);
z-index: 10;
transition: all .5s;//!!!
background-color: #ffffff;
//给外层设置
&:hover{
color: $colorA;
.children{
height: 220px;//高度出现
opacity: 1;
}
}
对返回的数据统一样式 --> 使用过滤器
filters:{
currency(val){
if(!val) return '0.00';
return '¥' + val.toFixed(2) + '元';
}
}
鼠标移动到左侧菜单上,右侧列表打开
可以使用display:none 和 display:block。
对于六行四列的渲染,可以使用flex,对于外面的ul设置display:flex,并设置排列方式…between。对于里面的li设置flex:1。
和上面的区别是:上面的是从上往下滑的,只要控制height是否为0就行。
而这个是要整个出现,宽高都要设置。
所以用 display:none 和 display:block。
弹框组件实现 --> Modal组件
Modal.vue
<template>
<transition name="slide">
<div class="modal" v-show="showModal">
<div class="mask"></div>
<div class="modal-dialog">
<div class="modal-header">
<span>{{title}}</span>
<a href="javascript:;" class="icon-close" v-on:click="$emit('cancel')"></a>
</div>
<div class="modal-body">
<slot name="body"></slot>
</div>
<div class="modal-footer">
<!-- 判断是几个按钮 -->
<a href="javascript:;" class="btn" v-if="btnType==1" @click="$emit('submit')">{{sureText}}</a>
<a href="javascript:;" class="btn" v-if="btnType==2" @click="$emit('cancel')">{{cancelText}}</a>
<div class="btn-group" v-if="btnType==3">
<a href="javascript:;" class="btn" @click="$emit('submit')">{{sureText}}</a>
<a href="javascript:;" class="btn btn-default" @click="$emit('cancel')">{{cancelText}}</a>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
export default{
name:'modal',
props:{
// 弹框类型:小small、中middle、大large、表单form
modalType:{
type:String,
default:'form'
},
// 弹框标题
title:String,
// 按钮类型: 1:确定按钮 2:取消按钮 3:确定取消
btnType:String,
sureText:{
type:String,
default:'确定'
},
cancelText:{
type:String,
default:'取消'
},
showModal:Boolean
}
}
</script>
Vue动画:modal.scss
.modal{
@include position(fixed);
z-index: 10;
transition: all .5s;
.mask{
@include position(fixed);
background-color: $colorI;
opacity: 0.5;
}
&.slide-enter-active{
top:0;
}
&.slide-leave-active{
top:-100%;
}
//对于这些顺序都有严格的要求,最好按照官方文档的顺序
&.slide-enter{
top:-100%;
}
.modal-dialog{
@include position(absolute,40%,50%,660px,auto);
background-color: $colorG;
transform:translate(-50%,-50%);
.modal-header{
height:60px;
background-color: $colorJ;
padding:0 25px;
line-height: 60px;
font-size:$fontI;
.icon-close{
@include positionImg(absolute,23px,25px,14px,14px,'/imgs/icon-close.png');
transition: transform .3s;
&:hover{
transform: scale(1.5);
}
}
}
.modal-body{
padding:42px 40px 54px;
font-size:14px;
}
.modal-footer{
height: 82px;
line-height: 82px;
text-align: center;
background-color: $colorJ;
}
}
}
首页使用Modal组件
<!-- 加载模态框 -->
<modal
title="提示"
sureText="查看购物车"
btnType="1"
modalType="middle"
v-bind:showModal="showModal"
v-on:submit="goToCart"
v-on:cancel="showModal=false">
<template v-slot:body>
<p>商品添加成功!</p>
</template>
</modal>
需要注意的是,使用动画的部分必须要用标签进行包裹,在定义动画的时候,在以下类名中定义:
-
v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
-
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
-
v-enter-to:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
-
v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
-
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
-
v-leave-to:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
对于这些在过渡中切换的类名来说,如果使用一个没有名字的 ,则 v- 是这些类名的默认前缀。因为这里定义name为slide,所以以slide-开头。
图片懒加载
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad,{
loading:'/imgs/loading-svg/loading-bars.svg'
})
使用vue-lazyload
:将:src
换成v-lazy
即可
// 原来
<img :src="'/imgs/banner-1.png'" alt="">
// 懒加载形式
<img v-lazy="'/imgs/banner-1.png'" alt="">
四、登录页面的开发
用户名和密码都是jack
登录模块
<div class="login-form">
<h3>
<span class="checked">帐号登录</span><span class="sep-line">|</span><span>扫码登录</span>
</h3>
<div class="input">
<input type="text" placeholder="请输入帐号" v-model="username">
</div>
<div class="input">
<input type="password" placeholder="请输入密码" v-model="password">
</div>
<div class="btn-box">
<a href="javascript:;" class="btn" @click="login">登录</a>
</div>
<div class="tips">
<div class="sms" @click="register">手机短信登录/注册</div>
<div class="reg">立即注册<span>|</span>忘记密码?</div>
</div>
</div>
将登录的信息进行保存到vuex
methods:{
//登录初始为 jack,密码也为jack
login(){
//拿到用户输入的账号密码,因为数据是双向绑定的
let { username,password } = this;
this.axios.post('/user/login',{
//这里是简写,key和value一样
username,
password
}).then((res)=>{
//将信息存到cookie中,expires表示过期时间
this.$cookie.set('userId',res.id,{expires:'Session'});
//因为登录成功以后要在首页显示用户名,所以使用vuex进行管理
// this.$store.dispatch('saveUserName',res.username);
this.saveUserName(res.username);
//登录成功,跳转到首页
this.$router.push({
name:'index',
params:{
from:'login'
}
});
})
}
}
上面是登录逻辑的实现,当用户点击登录按钮后,会触发登录所对应的事件函数。
在函数里,要将用户的账号和密码通过axios发送给后端,然后将用户id保存在cookie中,并且跳转到首页,还要对用户名进行vuex管理,因为首页要显示用户名。
注册模块
当用户点击注册按钮后,同样是在methods中定义一个函数
这里默认注册的用户名和密码都是admin1
//注册
register(){
this.axios.post('/user/register',{
username:'admin1',
password:'admin1',
email:'admin1@163.com'
}).then(()=>{
Message.success('注册成功');
})
}
登录后获取用户信息和购物车商品数量
第一次登录进去保存数据到vuex
在刚登录进去的时候,要获取一次用户数据,所以在APP.vue中
mounted(){
if(this.$cookie.get('userId')){
this.getUser();
this.getCartCount();
}
},
methods:{
getUser(){
this.axios.get('/user').then((res)=>{
this.$store.dispatch('saveUserName',res.username);
})
},
getCartCount(){
this.axios.get('/carts/products/sum').then((res)=>{
this.$store.dispatch('saveCartCount',res);
})
}
}
在vuex中设置action和mutation
//action.js
export default{
saveUserName(context,username){
context.commit('saveUserName',username);
},
saveCartCount(context,count){
context.commit('saveCartCount',count);
}
}
//mutation.js
export default{
saveUserName(state,username){
state.username = username;
},
saveCartCount(state,count){
state.cartCount = count;
}
}
在首页读取用户名和购物车数量
这里需要用计算属性:
原因:
组件加载是需要时间的,首先加载APP.vue组件,然后加载首页组件。在首页组件中只是纯渲染,没有请求时间,在APP.vue进去后会执行接口,而接口的调用需要花时间,所以会先在首页组件中读取到变量的值,但此时变量的值为空。相当于先取的值后保存的数据,所以值为空,可以通过计算属性解决。
当值返回后,值发生变化,计算属性会重新计算。
这里使用到了computed计算属性,如果我们将这些数据直接定义在data中,他就是纯渲染,没有请求的时间。当我们进入APP.vue文件时,会执行两个接口请求,执行请求需要一定的时间,执行完获得数据之后,页面数据早已经渲染出来,渲染的值时请求之前的默认值,所以数据就不对了。
使用computed属性,当数据的值发生变化时,computed就会执行,来更新数据,这样就可以保证数据是正确的了。
computed:{
username(){
return this.$store.state.username;
},
cartCount(){
return this.$store.state.cartCount;
}
}