本篇文章主要实现的是App.vue组件、头部组件header、蒙层组件detal和Modal以及路由切换router
App组件是整个项目的大的组件,其中可以划分为头部组件、导航栏切换、以及对应的导航栏内容显示。
<template>
<div id="app">
<Header :seller="seller"></Header>
<div class="tab">
<div class="tab-item">
<router-link :to="{name:'goods'}">商品</router-link>
</div>
<div class="tab-item">
<router-link :to="{name:'ratings'}">评价</router-link>
</div>
<div class="tab-item">
<router-link :to="{name:'seller'}">商家</router-link>
</div>
</div>
<keep-alive>
<router-view :seller="seller"></router-view></keep-alive>
</div>
</template>
本次项目中主要使用的是axios实现与后端的数据交互
引入axios
import axios from 'axios'
数据获取 获取的数据使用seller接收
<script>
// eslint-disable-next-line no-unused-vars
import Header from '@/components/header/header'
import axios from 'axios'
const ERR_OK = 0
export default {
data () {
return {
seller: {
}
}
},
created () {
axios.get('/api/seller').then((response) => {
response = response.data
if (response.errno === ERR_OK) {
this.seller = response.data
}
})
},
components: {
Header
},
name: 'App'
}
</script>
<template>
<div id="app">
<Header :seller="seller"></Header>
<div class="tab">
<div class="tab-item">
<router-link :to="{name:'goods'}">商品</router-link>
</div>
<div class="tab-item">
<router-link :to="{name:'ratings'}">评价</router-link>
</div>
<div class="tab-item">
<router-link :to="{name:'seller'}">商家</router-link>
</div>
</div>
<keep-alive>
<router-view :seller="seller"></router-view></keep-alive>
</div>
</template>
<script>
// eslint-disable-next-line no-unused-vars
import Header from '@/components/header/header'
import axios from 'axios'
const ERR_OK = 0
export default {
data () {
return {
seller: {
}
}
},
created () {
axios.get('/api/seller').then((response) => {
response = response.data
if (response.errno === ERR_OK) {
this.seller = response.data
}
})
},
components: {
Header
},
name: 'App'
}
</script>
<style scoped>
/* 评论商家商品使用flex布局 */
.tab{
display: flex;
width: 100%;
/* 展示的高度是height的两倍 */
height: 40px;
line-height: 40px;
}
.tab .tab-item{
flex: 1;
text-align: center;
}
.tab .tab-item ::after{
display: block;
content: ' ';
border-top: 1px solid #ddd ;
left: 0;
right: 0;
width: 100%;
}
.tab .tab-item router-link{
display: block;
font-size:14px;
color: rgb(77,85,93);
}
.tab .tab-item .router-link-exact-active{
/* 选中时样式 */
color: rgb(240,20,20);
}
</style>>
header组件主要实现的是商家的名称和公告的展示,并且商家的公告在展示过程中要做到单行展示,超出部分缩略的效果。通过文字超出部分隐藏三要素实现
// 文字超出部分隐藏三要素
white-space: nowrap;
// // 消除子元素之间的空白间隙 或者可以删掉span标签之间的空格
// font-size:0;
overflow: hidden;
text-overflow: ellipsis;
背景图片模糊效果 使用通过设置背景图片撑满整个header,将背景图片的z-index设置为-1,使用CSS3中的filter: blur(10px);
实现
<template>
<div class="header">
<div class="content-wrapper">
<!-- 头像 -->
<div class="avatar">
<img :src="seller.avatar" width="64" height="64" alt="">
</div>
<!-- 内容 -->
<div class="content">
<div class="title">
<span class="brand"></span>
<span class="name">{{ seller.name }}</span>
</div>
<div class="description">{{ seller.description }}/{{ seller.deliveryTime }}分钟送达</div>
<div v-if="seller.supports" class="support">
<span class="icon" :class="classMap[seller.supports[0].type]"></span>
<span class="text">{{seller.supports[0].description}}</span>
</div>
<div v-if="seller.supports" class="support-count" @click="handleClick">
<span class="count">{{seller.supports.length}}个</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
</div>
</div>
<!-- 详情简介 -->
<div class="bulletin-wrapper" @click="handleClick">
<span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
<!-- 背景 -->
<div class="background">
<img :src="seller.avatar" width="100%" height="100%" alt="">
</div>
<!-- 弹层 -->
<Detal ref="child" :seller="seller" :classMap="classMap"/>
</div>
</template>
在父组件中通过
将数据传递给header组件,在header组件中通过prop属性接收seller数据
props: {
seller: {
type: Object
}
},
商家头像展示通过动态绑定img的src属性获取商家的图片
<div class="avatar">
<img :src="seller.avatar" width="64" height="64" alt="">
</div>
在商家内容展示中,难点就是商家会有不同的折扣方式,不同的折扣方式对应着不同的折扣图片和文字信息。定义一个classMap用来存储折扣方式
created () {
// 定义一个数组用来存储折扣的图标
this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
},
通过:class="classMap[seller.supports[0].type]"
绑定对应的折扣图标通过{{seller.supports[0].description}}
展示文字信息
<div v-if="seller.supports" class="support-count" @click="handleClick">
<span class="count">{{seller.supports.length}}个</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
<div class="bulletin-wrapper" @click="handleClick">
<span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
<template>
<div class="header">
<div class="content-wrapper">
<!-- 头像 -->
<div class="avatar">
<img :src="seller.avatar" width="64" height="64" alt="">
</div>
<!-- 内容 -->
<div class="content">
<div class="title">
<span class="brand"></span>
<span class="name">{{ seller.name }}</span>
</div>
<div class="description">{{ seller.description }}/{{ seller.deliveryTime }}分钟送达</div>
<div v-if="seller.supports" class="support">
<span class="icon" :class="classMap[seller.supports[0].type]"></span>
<span class="text">{{seller.supports[0].description}}</span>
</div>
<div v-if="seller.supports" class="support-count" @click="handleClick">
<span class="count">{{seller.supports.length}}个</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
</div>
</div>
<!-- 详情简介 -->
<div class="bulletin-wrapper" @click="handleClick">
<span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
<!-- 背景 -->
<div class="background">
<img :src="seller.avatar" width="100%" height="100%" alt="">
</div>
<!-- 弹层 -->
<Detal ref="child" :seller="seller" :classMap="classMap"/>
</div>
</template>
<script>
import '../../common/stylus/mixin.styl'
// import Modal from '@/components/Modal/Modal.js'
import Detal from '@/components/detal/detal'
export default {
// 父组件使用:seller='seller'将数据传给子组件,子组件使用props接收
props: {
seller: {
type: Object
}
},
created () {
// 定义一个数组用来存储折扣的图标
this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
},
components: {
Detal
},
methods: {
handleClick () {
// 父组件修改子组件数据
this.$refs.child.detailShow = true
}
}
}
</script>
<style scoped lang="stylus" rel="stylesheet/stylus">
.header{
color: #fff;
background: rgba(7,17,27,0.5);
position: relative;
overflow: hidden;
}
.header .content-wrapper{
padding: 24px 12px 18px 24px;
font-size: 0;
position: relative;
}
.header .content-wrapper .avatar{
display: inline-block;
// 顶部对齐
vertical-align: top;
}
.header .content-wrapper .avatar img{
border-radius:2px;
}
.header .content-wrapper .content{
display: inline-block;
margin-left: 16px;
// font-size: 14px;
}
.header .content-wrapper .content .title{
margin: 2px 0 8px 0;
}
.header .content-wrapper .content .title .brand{
width: 30px;
height: 18px;
vertical-align: top;
display: inline-block;
// bg-image('brand');
background-image :url('./[email protected]')
background-size :30px 18px;
background-repeat : no-repeat;
}
.header .content-wrapper .content .title .name{
margin-left: 6px;
font-size: 16px;
line-height: 18px;
font-weight: bold;
}
.header .content-wrapper .content .description{
margin-bottom: 10px;
line-height: 12px;
font-size: 12px;
}
.header .content-wrapper .content .support .icon{
display: inline-block;
width: 12px;
height: 12px;
margin-right: 4px;
background-size: 12px 12px;
background-repeat: no-repeat;
}
.header .content-wrapper .content .support .decrease{
background-image: url('./[email protected]')
}
.header .content-wrapper .content .support .discount{
background-image: url('./[email protected]')
}
.header .content-wrapper .content .support .guarantee{
background-image: url('./[email protected]')
}
.header .content-wrapper .content .support .invoice{
background-image: url('./[email protected]')
}
.header .content-wrapper .content .support .special{
background-image: url('./[email protected]')
}
.header .content-wrapper .content .support .text{
line-height: 12px;
font-size: 12px;
vertical-align: top;
}
.header .content-wrapper .support-count{
position: absolute;
right: 12px;
bottom: 18px;
padding: 0 8px;
height: 24px;
line-height: 24px;
border-radius: 14px;
background: rgba(0,0,0,0.2);
text-align: center;
}
.header .content-wrapper .support-count .count,
.header .content-wrapper .support-count .iconfont{
vertical-align: top;
font-size: 10px;
}
.header .bulletin-wrapper{
height: 28px;
line-height: 28px;
position: relative;
padding: 0 22px 0 12px;
// 文字超出部分隐藏三要素
white-space: nowrap;
// // 消除子元素之间的空白间隙 或者可以删掉span标签之间的空格
// font-size:0;
overflow: hidden;
text-overflow: ellipsis;
background: rgba(7,17,27,0.2);
}
.header .bulletin-wrapper .bulletin-title{
display: inline-block;
width: 22px;
height: 12px;
background-image: url('./[email protected]');
background-size: 22px 12px;
background-repeat: no-repeat;
vertical-align: top;
margin-top: 7px;
}
.header .bulletin-wrapper .bulletin-text{
font-size: 10px;
margin: 0 4px;
vertical-align: top;
}
.header .bulletin-wrapper .iconfont{
position: absolute;
font-size: 10px;
right: 12px;
}
.header .background{
//背景图片
position: absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:-1;
// 模糊效果
filter: blur(10px);
}
</style>
<template>
<transition name="slide-fade">
<div class="detail" v-if="detailShow">
<Modal :seller="seller" :classMap="classMap" @hide-detail="changeDetail">
</Modal>
</div>
</transition>
</template>
默认detal组件是不显示的,因此在data中定义detailShow
,通过改变detal的值来控制蒙层。
data () {
return {
detailShow: false
}
},
类似的子组件通过定义prop属性来接收父组件传过来的值
props: {
seller: {
type: Object
},
classMap: {
type: Array
}
},
Modal组件主要实现蒙层的内容展示
<template>
<div class="modal">
<div class = "detail-wrapper clearfix">
<div class = "detail-main">
<div class="detail-content">
<h1 class="name">{{seller.name}}</h1>
<div class="star-wrapper">
<Star :score="seller.score"></Star>
</div>
<div class="title">
<div class="line"></div>
<div class="text">优惠信息</div>
<div class="line"></div>
</div>
<ul v-if="seller.supports" class="supports">
<li class="supports-item" v-for="(item,index) in seller.supports" :key="index">
<span class="icon" :class="classMap[seller.supports[index].type]"></span>
<span class="text">{{seller.supports[index].description}}</span>
</li>
</ul>
<div class="title">
<div class="line"></div>
<div class="text">商家公告</div>
<div class="line"></div>
</div>
<div class="bulletin">
<p class="content">{{seller.bulletin}}</p>
</div>
</div>
</div>
<div class = "detail-close" @click="hideDetail">
<i class="iconfont icon-quxiao"></i>
</div>
</div>
</div>
</template>
展示
在header组件中通过给
<div v-if="seller.supports" class="support-count" @click="handleClick">
<span class="count">{{seller.supports.length}}个</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
<div class="bulletin-wrapper" @click="handleClick">
<span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
<i class="iconfont icon-moreinfo-copy"></i>
</div>
绑定点击事件handleClick
事件来控制蒙层的展示
由于控制蒙层的展示数据在detal组件中,detal组件是header的子组件,父组件想要修改子组件的数据可以通过
methods: {
handleClick () {
// 父组件修改子组件数据
this.$refs.child.detailShow = true
}
}
修改。
隐藏
在Modal组件中通过点击
<div class = "detail-close" @click="hideDetail">
<i class="iconfont icon-quxiao"></i>
</div>
可以实现隐藏,由于控制蒙层的隐藏数据在detal组件中,Modal组件是detal组件的子组件,子组件无法直接修改父组件的元素,因此子组件可以抛出一个emit
事件(触发事件)
methods: {
hideDetail () {
this.$emit('hide-detail')
}
}
父组件detal为子组件设置事件处理程序(注册事件)
<Modal :seller="seller" :classMap="classMap" @hide-detail="changeDetail">
</Modal>
methods: {
changeDetail () {
this.detailShow = false
}
}
detal.vue
<template>
<transition name="slide-fade">
<div class="detail" v-if="detailShow">
<Modal :seller="seller" :classMap="classMap" @hide-detail="changeDetail">
</Modal>
</div>
</transition>
</template>
<script>
import Modal from '@/components/Modal/Modal.vue'
export default {
props: {
seller: {
type: Object
},
classMap: {
type: Array
}
},
components: {
Modal
},
data () {
return {
detailShow: false
}
},
methods: {
changeDetail () {
this.detailShow = false
}
}
}
</script>
<style >
.detail{
position: fixed;
z-index: 100;
top:0;
left:0;
width: 100%;
height: 100%;
/* overflow: auto 可以当内容超过屏幕高度时可以实现屏幕滚动*/
overflow: auto;
background: rgba(7, 17, 27, 0.8);
}
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
</style>
Modal.vue
<template>
<div class="modal">
<div class = "detail-wrapper clearfix">
<div class = "detail-main">
<div class="detail-content">
<h1 class="name">{{seller.name}}</h1>
<div class="star-wrapper">
<Star :score="seller.score"></Star>
</div>
<div class="title">
<div class="line"></div>
<div class="text">优惠信息</div>
<div class="line"></div>
</div>
<ul v-if="seller.supports" class="supports">
<li class="supports-item" v-for="(item,index) in seller.supports" :key="index">
<span class="icon" :class="classMap[seller.supports[index].type]"></span>
<span class="text">{{seller.supports[index].description}}</span>
</li>
</ul>
<div class="title">
<div class="line"></div>
<div class="text">商家公告</div>
<div class="line"></div>
</div>
<div class="bulletin">
<p class="content">{{seller.bulletin}}</p>
</div>
</div>
</div>
<div class = "detail-close" @click="hideDetail">
<i class="iconfont icon-quxiao"></i>
</div>
</div>
</div>
</template>
<script>
import Star from '@/components/Star/star'
export default {
props: {
seller: {
type: Object
},
classMap: {
type: Array
}
},
components: {
Star
},
methods: {
hideDetail () {
this.$emit('hide-detail')
}
}
}
</script>
<style scoped>
/* .clearfix{
display: inline-block;
} */
.clearfix ::after{
display: table;
content: ' ';
clear: both;
}
.detail-wrapper{
position: fixed;
width: 100%;
height: 100%;
z-index: 100;
left: 0;
top: 0;
overflow: auto;
}
.detail-wrapper .detail-main{
display: inline-block;
min-height: 100%;
width: 100%;
}
.detail-wrapper .detail-main .detail-content{
margin-top: 64px;
padding-bottom: 64px;/**撑开高度 给按钮留出位置 */
}
.detail-wrapper .detail-main .detail-content .name{
line-height: 16px;
text-align: center;
font-size: 16px;
font-weight: 700;
}
.detail-wrapper .detail-main .detail-content .star-wrapper{
margin-top:18px;
padding: 2px 0;
text-align: center;
}
.detail-wrapper .detail-main .detail-content .title{
display: flex;
width: 80%;
margin: 28px auto 24px;
}
.detail-wrapper .detail-main .detail-content .title .line{
flex: 1;
position: relative;
top: -6px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.detail-wrapper .detail-main .detail-content .title .text{
padding: 0 12px;
font-size: 14px;
font-weight: 700;
}
.detail-wrapper .detail-main .detail-content .supports{
width: 80%;
margin: 0 auto;
}
.detail-wrapper .detail-main .detail-content .supports .supports-item{
padding: 0 12px;
margin-bottom: 12px;
font-size: 0;
}
.detail-wrapper .detail-main .detail-content .supports .supports-item .icon{
display: inline-block;
width: 16px;
height: 16px;
vertical-align: top;
margin-right: 6px;
background-size: 16px 16px;
background-repeat: no-repeat;
}
.detail-wrapper .detail-main .detail-content .supports .supports-item .decrease{
background-image: url('./[email protected]')
}
.detail-wrapper .detail-main .detail-content .supports .supports-item .discount{
background-image: url('./[email protected]')
}
.detail-wrapper .detail-main .detail-content .supports .supports-item .guarantee{
background-image: url('./[email protected]')
}
.detail-wrapper .detail-main .detail-content .supports .supports-item .invoice{
background-image: url('./[email protected]')
}
.detail-wrapper .detail-main .detail-content .supports .supports-item .special{
background-image: url('./[email protected]')
}
.detail-wrapper .detail-main .detail-content .supports .supports-item .text{
line-height: 16px;
font-size: 12px;
}
.detail-wrapper .detail-main .detail-content .bulletin{
width: 80%;
margin: 0 auto;
}
.detail-wrapper .detail-main .detail-content .bulletin .content{
padding: 0 12px;
line-height: 24px;
font-size: 12px;
}
.detail-close{
position: relative;
width: 32px;
height: 32px;
margin: -64px auto 0 auto;
clear: both;
font-size: 32px;
}
</style>
在这里使用了页面懒加载,当我们执行这个函数的时候再加载页面
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{ // 定义首页
path: '',
redirect: {name: 'goods'}
},
{
path: '/goods',
name: 'goods',
// 这里使用了Webpack的页面懒加载,当我们运行到这个函数的时候再加载这个页面
component: () => import('@/components/goods/goods')
},
{
path: '/ratings',
name: 'ratings',
component: () => import('@/components/ratings/ratings')
},
{
path: '/seller',
name: 'seller',
component: () => import('@/components/seller/seller')
}
]
})