公共组件都存放在components目录下,项目地址是:GitHub - Ercyao/VUE-douban: 仿豆瓣vue项目
头部效果图
head.jpg
search.jpg
头部组件header.vue,通过v-if判断头部导航栏或搜索界面的显示和隐藏,input文本框的@keyup.enter是监听键盘回车事件
<template>
<div>
<template v-if="isShow">
<div id="header-nav">
<router-link :to="{name:'Home'}" ><img src="../assets/img/title.png" class="title"/></router-link>
<ul class="nav" >
<li @click="gotoAddress({name:'Movie'})" style="color: #2384E8;">电影</li>
<li @click="gotoAddress({name:'Books'})" style="color: #9F7860;">图书</li>
<li @click="gotoAddress({name:'Broadcast'})" style="color: #E4A813;">广播</li>
<li @click="gotoAddress({name:'Group'})" style="color: #2AB8CC;">小组</li>
</ul>
<span class="search" @click="showSearch"></span>
</div>
</template>
<template v-else>
<div class="search_box">
<div class="search_input">
<input type="text" v-model.trim="keyword" placeholder="请输入关键字" @keyup.enter="goSearch()"/>
<span class="close" @click="closeSearch()">关闭</span>
</div>
<ul class="search_class" >
<li v-for="item in SearchClass">
<a :href="item.url">
<p class="name" :style="{color:item.color}">{{item.name}}</p>
<p class="des">{{item.des}}</p>
</a>
</li>
</ul>
</div>
</template>
</div>
</template>
<script>
import {getSearchClass} from '@/store/API'
export default {
data () {
return {
keyword:'',
isShow:true,
SearchClass:null,
}
},
created(){
this.getSearchClass();
},
methods:{
async getSearchClass(){
this.SearchClass = await getSearchClass().then(res => res.json());
},
gotoAddress(name){
this.$router.push(name)
},
showSearch(){
this.isShow = false;
},
closeSearch(){
this.isShow = true;
},
goSearch(){
this.$router.push({path:'/search',query:{q:this.keyword}})
}
}
}
</script>
<style lang="less" scoped>
@import '../assets/style/less.less';
#header-nav{
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 2;
max-width: 650px;
margin: 0 auto;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
height: 3.5rem;
padding: 0 18px;
background: #fff;
border-bottom: .1rem solid #f3f3f3;
.title{
-ms-flex: 1;
flex: 1;
max-width: 3.5rem;
word-break: break-all;
}
.nav{
display: -ms-flexbox;
display: flex;
-ms-flex: 1;
flex: 1;
-ms-flex-pack: end;
justify-content: flex-end;
li{
display: inline-block;
font-size: 1.1rem;
margin-right: 1rem;
}
}
.search{
display: inline-block;
width: 2rem;
height: 1.5rem;
margin-top: .4rem;
background: url(../assets/img/search.png) no-repeat;
background-size: cover;
}
}
.search_box{
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100%;
height: 100%;
background: #FFFFFF;
.search_input{
width: 100%;
height: 2.5rem;
padding: 15px;
border-bottom: 1px solid #f3f3f3;
input{
float: left;
width: 75%;
height: 2.5rem;
font-size: 14px;
background: #F1F1F1;
margin-right: 10px;
border: 0;
border-radius: 5px;
padding:0 10px;
}
.close{
color: #42bd56;
font-size: 16px;
margin-top: 5px;
}
}
.search_class{
margin: 20px 0;
li{
float: left;
width: 20%;
height: 4rem;
margin: 10px 6%;
text-align: center;
.name{
font-weight: 400;
font-size: 24px;
line-height: 28px;
}
.des{
color: #999;
height: 17px;
font-size: 12px;
letter-spacing: 1px;
}
}
}
}
</style>
底部效果图
drownapp.jpg
底部组件drownapp.vue
<template>
<div id="drown-app">
<div class="logo">
<div class="left"><img src="../assets/img/logo.png" /></div>
<div class="right"><p>豆瓣</p><p>我们的精神角落</p></div>
</div>
<a href="https://www.douban.com/doubanapp/card/log?category=group_home&cid=&action=click_download&ref=http%3A//www.douban.com/doubanapp/app%3Fchannel%3Dcard_group_home%26direct_dl%3D1">去App Store 免费下载IOS客户端 </a>
</div>
</template>
<script>
export default {
data () {
return {
}
},
methods:{
gotoAddress(name){
this.$router.push(name)
}
}
}
</script>
<style lang="less" scoped>
@import '../assets/style/less.less';
#drown-app{
margin:3rem 0 2rem;
.logo{
width:11rem;
margin:0 auto;
display: flex;
-webkit-justify-content: space-between;
.left{
img{
width:3.5rem
}
}
.right{
p:first-child{font-size: 1.6rem;font-weight: normal;}
}
}
a{display:block;width:100%;text-align: center;color: @green;margin: 1rem 0;}
}
</style>
加载中的效果图
load.jpg
等待加载组件loading.vue
<template>
<div class="loading_container">
<div class="file"></div>
<span>Loading</span>
</div>
</template>
<script>
export default {
data(){
return{
}
}
}
</script>
<style lang="less" scoped>
.loading_container{
position: absolute;
z-index: 99;
top: 40%;
left: 40%;
width: 80px;
height: 80px;
border: 1px solid #5CB85C;
border-radius: 50%;
background:#5CB85C;
overflow: hidden;
span{
position: absolute;
top: 30px;
z-index: 999;
width: 100%;
text-align: center;
}
.file{
position: absolute;
top: -100px;
left: -40px;
width: 160px;
height: 160px;
border-radius: 40%;
background:#fff;
animation: file 3s ease infinite;
}
@keyframes file{
0% {transform: translate(0%, 10px) rotateZ(0deg);}
50% {transform: translate(-10%, -10px) rotateZ(180deg);}
100% {transform: translate(5%, 10px) rotateZ(360deg);}
}
}
</style>
columBox的效果图
imgCover.jpg
txtCover.jpg
电影页和图书页引用的公共组件column.vue
<template>
<div class="column-box">
<p class="title"><span>{{title}}</span><span><a href="#">更多</a></span></p>
<slot name="promItem"></slot>
<ul class="ImgCover" v-if="type === 'ImgCover'">
<li v-for="item in items">
<a :href="item.alt">
<img :src="item.cover.url" />
<p class="name">{{item.title}}</p>
<rating v-if="item.rating" :rating="item.rating.value"></rating>
<p v-else style="color: #AAAAAA;">¥{{item.price}}</p>
</a>
</li>
</ul>
<ul class="TextCover" v-if="type === 'TextCover'">
<template v-for="item in items">
<li v-if="item.name"><a :href="item.url" :style="{color:item.color,borderColor:item.color}"> {{item.name}}</a></li>
<li v-else class="line"></li>
</template>
</ul>
</div>
</template>
<script>
import Rating from './rating'
export default{
props: ['title', 'type', 'items'],
data(){
return {
}
},
components: { Rating },
}
</script>
<style lang="less" scoped>
@import '../assets/style/less.less';
.column-box{
margin:1rem 0 1.5rem;
.title{
display: flex;
justify-content: space-between;
span:first-child{
font-size: 1.2rem;
}
}
.ImgCover{
.name{
margin-top: .7rem;
line-height: 1.1rem;
font-size: 1.1rem;
color: #111;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
li {
display: inline-block;
width: 7.5rem;
padding-right: .5rem;
img{
height: 12rem;
}
}
}
ul{
width: 106%;
padding: .8rem 0;
overflow-x: auto;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.TextCover{
.line {
width: 100%;
display: block;
height: 1px;
border: 0;
margin: 0;
}
li{
margin: 0 8px 8px 0;
font-size: .94rem;
display: inline-block;
a {
height: 50px;
line-height: 50px;
padding: 0 1.55rem;
letter-spacing: .1em;
overflow: auto;
display: block;
text-align: center;
border-radius: .25rem;
border: solid 1px;
}
}
}
}
</style>
星级评分rating.vue,根据评分实现星级
<template>
<div class="rating">
<div v-if="rating == 0">
<span>暂无评分</span>
</div>
<div v-else>
<template v-for="n in full">
<span class="star star-full"></span>
</template>
<template v-for="n in gray">
<span class="star star-gray"></span>
</template>
<span class="value">{{rating}}</span>
</div>
</div>
</template>
<script>
export default {
props: ['rating'],
data () {
return {
full: 0,
gray: 0
}
},
created () {
let value = this.rating;
this.full = parseInt(value / 2);
this.gray = 5 - this.full;
}
}
</script>
<style lang="less" scoped>
.rating{
margin: 5px 0;
span{display: inline-block;}
}
.star{
margin: 0 -3px;
border-style: solid;
border-top-width: 5px;
border-right-width: 10px;
border-left-width: 10px;
height: 0;
position: relative;
width: 0;
transform: scale(0.7);
}
.star:before,
.star:after {
border-style: solid;
border-top-width: 5px;
border-right-width: 10px;
border-left-width: 10px;
content: '';
display: block;
height: 0;
left: -10px;
position: absolute;
top: -5px;
width: 0;
}
.star:before {
transform: rotate(70deg);
}
.star:after {
transform: rotate(-70deg);
}
.star-full {
border-color: #fd4 transparent transparent transparent;
}
.star-full:before,
.star-full:after {
border-color: #fd4 transparent transparent transparent;
}
.star-gray {
border-color: #C0C0C0 transparent transparent transparent;
}
.star-gray:before,
.star-gray:after {
border-color: #C0C0C0 transparent transparent transparent;
}
</style>
分类浏览的效果图
分类
分类浏览组件category.vue
<template>
<div class="category-box">
<p class="title">分类浏览</p>
<ul>
<li v-for="item in items"><a :href="item.url">{{item.name}}<span class="arrow"></span></a></li>
</ul>
</div>
</template>
<script>
export default {
props: ['items'],
data () {
return {
}
}
}
</script>
<style lang="less" scoped>
@import '../assets/style/less.less';
.category-box{
.title{font-size:1.2rem;}
width: 110%;
ul{
margin-top: 1.6rem;
overflow: hidden;
.border(.1rem,0,0,0);
li{
float: left;
box-sizing: border-box;
width: 50%;
padding-right: 1.5rem;
height: 3rem;
line-height: 3rem;
font-size: 1.1rem;
.border(0,.1rem,0,.1rem);
a{color: @green;padding-left: .5rem;}
}
.arrow{
float:right;
width: .5rem;
height: .5rem;
margin-top: 1rem;
.border(.2rem,0,0,.2rem);
transform: rotate(45deg);
}
}
}
</style>
小组和搜索结果的效果图
小组
搜索结果
小组和搜索结果引用的公共组件team.vue
<template>
<div class="team-box">
<p class="title">{{title}}</p>
<div v-if="type === 'groupsCover'">
<ul class="content">
<li v-for="item in items">
<a :href="item.url">
<p class="avatar"><img :src="item.avatar" class="img"/><span class="name">{{item.name}}</span><span class="member">{{item.member_count}}人</span></p>
<p class="des">{{item.desc_abstract}}</p>
</a>
</li>
</ul>
<a href="#" class="more_group">更多相关小组</a>
</div>
<div v-if="type === 'searchCover'">
<ul class="content">
<li v-for="item in items">
<a :href="item.alt">
<div class="search_avatar">
<template v-if="item.image">
<img :src="item.image" class="img"/>
</template>
<template v-else>
<img :src="item.images.small" class="img"/>
</template>
<div class="search_des">
<p class="title">{{item.title}}</p>
<rating v-if="item.rating" class="price" :rating="item.rating.average"></rating>
</div>
</div>
</a>
</li>
</ul>
<a href="#" class="more_group" v-if="items">查看更多结果</a>
<a href="#" class="no_group" v-if="!items">暂无结果</a>
</div>
</div>
</template>
<script>
import Rating from './rating'
export default{
props: ['title','type','items'],
data(){
return {}
},
components: { Rating },
}
</script>
<style lang="less" scoped>
@import '../assets/style/less.less';
.team-box{
padding: 15px 0 30px;
.title{
font-size: 18px;
color: #111;
}
.content{
li{.border(0, 0.1rem, 0, 0)}
a{
font-size: 18px;
display: block;
padding: 15px 0;
color: #111;
.avatar{height: 3rem;line-height: 3rem;}
.img{
border-radius: 3px;
width: 40px;
height: 40px;
border: 1px solid #f1f1f1;
vertical-align: baseline;
}
.name{
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
width: 60%;
padding-left: 10px;
padding-right: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.member{
float: right;
color: #CCCCCC;
font-size: 1rem;
}
.des{
margin-top: 10px;
font-size: 15px;
color: #aaa;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.search_img{
width: 40px;
height: 40px;
}
.search_text{
}
}
}
}
.search_des{
display: inline-block;
margin-left: 10px;
width: 80%;
overflow: hidden;
.title{margin-bottom: 5px;}
.price{
color: #aaa;
font-size: 12px;
height: 14px;
margin: 0;
}
}
}
.more_group{
display: block;
width: 100%;
height: 3.2rem;
line-height: 3.2rem;
font-size: 1.2rem;
text-align: center;
color: @green;
.border(0, 0.1rem, 0, 0)
}
.no_group{
display: block;
width: 100%;
height: 3.2rem;
line-height: 3.2rem;
font-size: 1.1rem;
text-align: center;
color: #AAAAAA;
.border(0, 0.1rem, 0, 0)
}
</style>