外卖APP项目—No.4header组件开发
外卖APP项目—No.4header组件开发
本文重点跟大家介绍外卖APP项目的header组件开发,主要包括三部分:axios应用、外部组件、详情弹层页。废话不多说,正文马上开始!
axios应用
- header组件涉及很多商家数据,这些数据需要发送请求来获取,本项目使用axios来发送异步请求。
- axios使用前需要手动安装,安装命令如下:
npm install axios
- 在main.js中引入axios,具体如下:
import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import axios from 'axios' import './common/stylus/index.styl'; axios.defaults.baseURL = 'http://localhost:8080/' const app = createApp(App) app.config.globalProperties.$http = axios app.use(store).use(router).mount('#app')
- 在App.vue中发送请求,获取商家数据,并赋值给data中的seller对象,具体如下:
import Header from './components/header/HeaderVue' export default { data() { return { seller: {}, } }, created() { this.getSeller() }, methods: { getSeller(){ this.$http.get('/api/data.json').then((res) => { // axios.get('http://localhost:8080/mock/data.json').then((res) => { console.log(res) if(res.status === 200) { this.seller = res.data.seller } }, err => { console.log(err); }); }, }, components: { Header } }
外部组件
- 父子组件传值:通过props实现
- App.vue通过v-bind将seller传给HeaderVue.vue
- HeaderVue.vue通过以下方式接收seller
props: { seller: { type: Object } }
- 将resource目录中的img子目录的所有图片复制到common目录的stylus子目录下
- 通用样式实现
- 清除浮动:使用after伪元素(修改common目录的stylus子目录下的base.stylus文件,具体如下:)
body,html line-height: 1 font-weight: 200 font-family: 'PingFang SC', 'STHeitisc-Light', 'Helvetica-Light', arial, sans-serif .clearfix display: inline-block &:after display: block content: "." height: 0 line-height: 0 clear: both visibility: hidden @media (-webkit-min-device-pixel-ratio:1.5),(min-device-pixel-ratio:1.5) .border-1px &::after -webkit-transform: scaleY(0.7) transform: scaleY(0.7) @media (-webkit-min-device-pixel-ratio:2),(min-device-pixel-ratio:2) .border-1px &::after -webkit-transform: scaleY(0.5) transform: scaleY(0.5)
- 根据设备的dpr设置背景图片(修改common目录的stylus子目录下的mixin.stylus文件,具体如下:)
border-1px($color) position:relative &:after display:block position:absolute left:0 bottom:0 width:100% border-top:1px solid $color content:'' border-none() &:after display: none bg-image($url) background-image: url($url + "@2x.png") @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3) background-image: url($url + "@3x.png")
渲染效果如下:
详情弹层页
sticky footers布局实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>sticky-footers布局</title>
<style>
body {
width: 100vw;
height: 100vh;
}
.top {
/* 关键点1:设置内容元素的父容器的min-height: 100%; */
min-height: 100%;
width: 100%;
}
.footer {
position: relative;
width: 100px;
height: 32px;
/* 关键点3:设置底部元素的margin-top: -64px;,也就是等于内容元素的padding-bottom属性值,从而把底部元素提到内容元素的padding-bottom里 */
margin: -64px auto 0 auto;
clear: both;
font-size: 32px;
}
.main {
margin-top: 64px;
/* 关键点2:设置内容元素的padding-bottom: 64px;,为底部元素预留位置 */
padding-bottom: 64px;
}
.clearfix {
display: inline-block;
}
.clearfix:after {
display: block;
content: ".";
height: 0;
line-height: 0;
clear: both;
visibility: hidden;
}
</style>
</head>
<body>
<div class="top clearfix">
<div class="main">
This is the main content.This is the main content.This is the main content.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet
dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit
esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla.
This is the main content.This is the main content.This is the main content.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet
dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit
esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla.
This is the main content.This is the main content.This is the main content.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet
dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit
esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla.
</div>
</div>
<div class="footer">
close
</div>
</body>
</html>
渲染效果如下:
StarVue.vue组件实现及应用(五星评价组件)
- 在components目录下新建star子目录,并在该子目录下新建StarVue.vue文件,文件内容具体如下:
<template> <div class="star" :class="starType"> <span v-for="(itemClass, index) in itemClasses" :key="index" :class="itemClass" class="star-item"></span> </div> </template> <script type="text/ecmascript-6"> const LENGTH = 5 const CLS_ON = 'on' const CLS_HALF = 'half' const CLS_OFF = 'off' export default { data () { return {} }, props:{ size:{ type: Number }, score:{ type: Number } }, computed:{ starType(){ return 'star-' + this.size; }, itemClasses(){ let result = []; let score = Math.floor(this.score * 2) / 2; let hasDecimal = score % 1 !== 0; let integer = Math.floor(score); for(let i = 0; i < integer; i++){ result.push(CLS_ON); } if(hasDecimal){ result.push(CLS_HALF); } while(result.length < LENGTH){ result.push(CLS_OFF); } return result; } }, methods: {}, } </script> <style lang="stylus" rel="stylesheet/stylus"> @import "../../common/stylus/mixin.styl"; .star z-index: 200 font-size: 0px .star-item display: inline-block background-repeat: no-repeat &.star-48 .star-item width: 20px height 20px margin-right: 22px background-size: 20px 20px &:last-child margin-right: 0 &.on bg-image('star48_on') &.half bg-image('star48_half') &.off bg-image('star48_off') &.star-36 .star-item width: 15px height 15px margin-right: 16px background-size: 15px 15px &:last-child margin-right: 0 &.on bg-image('star36_on') &.half bg-image('star36_half') &.off bg-image('star36_off') &.star-24 .star-item width: 10px height 10px margin-right: 3px background-size: 10px 10px &:last-child margin-right: 0 &.on bg-image('star24_on') &.half bg-image('star24_half') &.off bg-image('star24_off') </style>
- 在HeaderVue.vue中引入组件,具体如下:
import Star from '../star/StarVue.vue'
flex布局实现
完整的HeaderVue.vue组件内容如下:
<template>
<div class="header">
<div class="content-wrapper">
<div class="avater">
<img width="64" :src="seller.avater" alt="商家头像">
<!-- <img width="64" src="../../../static/img/01_seller_avater.png" 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>
<div v-if="seller.supports" class="support-count" @click="showDetail()">
<span class="count">{{seller.supports.length}}个</span>
<i class="icon-circle-right"></i>
</div>
</div>
<div class="bulletin-wrapper" @click="showDetail()">
<span class="bulletin-title"></span><span class="bulletin-text">{{seller.bulletin}}</span>
<i class="icon-circle-right"></i>
</div>
<div class="background">
<img :src="seller.avater" alt="商家头像" width="375" height="132">
</div>
<transition name="fade">
<div v-show="detailShow" class="detail">
<div class="detail-wrapper clearfix">
<div class="detail-main">
<h1 class="name">{{seller.name}}</h1>
<div class="star-wrapper">
<Star :size="48" :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="support-item" v-for="(item, i) in seller.supports" :key="i">
<span class="icon" :class="classMap[seller.supports[i].type]"></span>
<span class="text">{{seller.supports[i].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="icon-cancel-circle"></i>
</div>
</div>
</transition>
</div>
</template>
<script >
import Star from '../star/StarVue.vue'
export default {
data() {
return {
detailShow: false
}
},
props: {
seller: {
type: Object
}
},
created(){
this.classMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
},
components:{
Star
},
methods: {
showDetail(){
this.detailShow = true
},
hideDetail(){
this.detailShow = false
}
},
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import "../../common/stylus/mixin.styl";
.header
position: relative
overflow: hidden
color:#fff
background: rgba(7, 17, 27, 0.5)
.content-wrapper
position relative
padding: 24px 12px 18px 24px
font-size: 0
.avater
display: inline-block
vertical-align: top
img
border-radius: 2px
.content
display: inline-block
margin-left: 16px
.title
margin: 2px 0 8px 0
.brand
display: inline-block
vertical-align: top
width: 30px
height: 18px
bg-image('brand')
background-size: 30px 18px
background-repeat: no-repeat
.name
margin-left: 6px
font-size: 16px
line-height: 18px
font-weight: bold
.description
margin-bottom: 10px
line-height: 12px
font-size: 12px
.support
.icon
display: inline-block
vertical-align: top
width: 12px
height: 12px
margin-right: 4px
background-size: 12px 12px
background-repeat: no-repeat
&.decrease
bg-image('decrease_1')
&.discount
bg-image('discount_1')
&.guarantee
bg-image('gurantee_1')
&.invoice
bg-image('invoice_1')
&.special
bg-image('special_1')
.text
line-height: 12px
font-size: 10px
.support-count
position: absolute
right: 12px
bottom: 18px
padding: 0 8px
height 24px
line-height: 24px
border-radius: 14px
background-color: rgba(0, 0, 0, 0.2)
text-align: center
.count
vertical-align: top
font-size: 10px
.icon-circle-right
display: inline-block
margin-left: 6px
line-height: 24px
font-size: 10px
.bulletin-wrapper
position: relative
height: 28px
line-height: 28px
padding: 0 22px 0 12px
white-space: nowrap
overflow: hidden
text-overflow: ellipsis
background: rgba(7, 17, 27, 0.2)
.bulletin-title
display: inline-block
vertical-align: top
margin-top: 8px
width: 22px
height: 12px
bg-image('bulletin')
background-size: 22px 12px
background-repeat: no-repeat
.bulletin-text
vertical-align: top
margin:0 4px
font-size: 10px
.icon-circle-right
position: absolute
font-size: 10px
right: 12px
top: 8px
.background
position: absolute
top: 0
left: 0
width: 100%
height: 100%
z-index: -1
filter: blur(10px)
.detail
position: fixed
z-index: 100
top: 0
left: 0
width: 100%
height: 100%
overflow: auto
// transition: all 0.5s
background: rgba(7, 17, 27, 0.8)
backdrop-filter: blur(10px)
&.fade-enter-active, &.fade-leave-active
transition: all 0.5s
&.fade-enter, &.fade-leave-to
opacity: 0
background: rgba(7, 17, 27, 0.8)
// &.fade-transition
// opacity: 1
// background: rgba(7, 17, 27, 0.8)
// &.fade-enter, &.fade-leave
// opacity: 0
// background: rgba(7, 17, 27, 0)
.detail-wrapper
min-height: 100%
width: 100%
.detail-main
margin-top: 64px
padding-bottom: 64px
.name
line-height: 16px
text-align: center
font-size: 16px
font-weight: 700
.star-wrapper
margin-top: 18px
padding: 2px 0
text-align-last: center
.title
display: flex
width: 80%
margin: 28px auto 24px auto
.line
flex: 1
position: relative
top:-6px
border-bottom: 1px solid rgba(255, 255, 255, 0.2)
.text
padding: 0 12px
font-weight: 700
font-size: 14px
.supports
width: 80%
margin: 0 auto
.support-item
padding: 0 12px
margin-bottom: 12px
font-size: 0
&:last-child
margin-bottom: 0
.icon
display: inline-block
width: 16px
height: 16px
vertical-align: top
margin-right: 6px
background-size: 16px 16px
background-repeat: no-repeat
&.decrease
bg-image('decrease_2')
&.discount
bg-image('discount_2')
&.guarantee
bg-image('gurantee_2')
&.invoice
bg-image('invoice_2')
&.special
bg-image('special_2')
.text
line-height: 16px
font-size: 12px
.bulletin
width: 80%
margin: 0 auto
.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>