我从未见过这么美妙的项目,当然与我接触的项目少有关,但是这个项目满满的艺术气息,让人沉醉,让人忍不住的去研究代码。
先放项目地址:https://github.com/eidonlon/imitate-beautiful-thing
再来看一下项目的效果
接下来我们来研究代码吧~
我们先来看项目的入口文件main.js
main.js作为入口引入App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
};
</script>
我们来分析main.js里面的内容,逐条分析
关于引入的路由
import Vue from 'vue'
import Router from 'vue-router'
// 这种也好玩,引入组件,再在router中注册
import pageView from '@/pages/pageView'
import Home from '@/pages/home'
import Things from '@/pages/things'
import Designer from '@/pages/designer'
import Personal from '@/pages/personal'
import Pictoral from '@/pages/pictoral'
import Details from '@/pages/details'
import Comment from '@/pages/comment'
import About from '@/pages/about'
Router.prototype.goBack = function(){
this.isBack = true;
window.history.go(-1);
};
Vue.use(Router);
const routers = new Router({
routes: [
{
path:'',
name:'',
component:pageView,
children:[
{
path:'/',
name:'',
component:Home,
children:[
{
path:'',
name:'pictoral',
meta:{index:1},
component:Pictoral
},
{
path:'/things',
name:'things',
meta:{index:1},
component:Things
},
{
path:'/designer',
name:'designer',
meta:{index:1},
component:Designer
},
{
path:'/personal',
name:'personal',
meta:{index:1},
component:Personal
},
]
},
{
path:'/details/:id',
name:'details',
meta:{index:2},
component:Details
},
{
path:'/comment/:id',
name:'comment',
meta:{index:2},
component:Comment
},
{
path:'/about',
name:'about',
meta:{index:2},
component:About
}
]
}
]
})
routers.beforeEach(function (to, from, next) {
document.body.scrollTop = document.documentElement.scrollTop = 0
// 进入下一个页面的时候,都是页面的top为0那种
next()
});
export default routers;
我们会发现引入了vue-scroller,这个其实是实现下拉刷新,上拉获取数据的功能的。
store中的内容不多,只是对tabIndex做了一个简单的下标赋值
//index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state:{
tabIndex:0
},
mutations:{
changeTab: function(state,index){
state.tabIndex = index;
}
}
});
export default store
在mian.js页面还引入了tabBar,swiper,toast组件
先看tabBar组件
<template>
<div class="tab-bar">
<ul>
<li v-for="(item,index) in itemList"
:key="index" class="tab-bar-item"
:class="{active: index == isActive}"
@click="addActive(index,item)">
<span class="tab-bar-item_icon" :class="item.icon"></span>
<span class="tab-bar-item_Text">{{item.text}}</span>
</li>
</ul>
</div>
</template>
<script>
export default{
name:'tabbar',
data (){
return {
isActive:this.$store.state.tabIndex,
itemList:[
{text:"推荐",icon:"fa fa-clipboard",link:'/'},
{text:"作品",icon:"fa fa-smile-o",link:'/things'},
{text:"设计师",icon:"fa fa-pencil",link:'/designer'},
{text:"我",icon:"fa fa-user-o",link:'/personal'}
]
}
},
mounted(){
if(this.isActive != 0){
this.$router.push(this.itemList[this.isActive].link);
}
},
methods:{
addActive: function(index,item){
console.log('.....index',index)
console.log('...item',item.link)
this.isActive = index;
this.$router.push(item.link);
this.$store.commit("changeTab",index);
}
}
};
</script>
//swiper.vue
<template>
<div class="swiper-box">
<swiper :options="swiperOption" ref="swiper" >
<swiper-slide v-for="(data, index) in dataList"
:key="index"
:class="{active: activeIndex == index}">
<slot name="swiperMain" :data="data">{{data.text}}</slot>
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</div>
</template>
<script>
import 'swiper/dist/css/swiper.css'
import { swiper, swiperSlide} from 'vue-awesome-swiper'
export default{
name: 'appnav',
props:{
activeIndex:{},
swiperOption:{
type:Object
},
dataList:{}
},
components:{
swiper,
swiperSlide
}
};
</script>
//toast.vue
<template>
<transition name="toast-fade">
<div class="toast" v-show="visible" >
<div class="toast-content">{{message}}</div>
</div>
</transition>
</template>
<script>
export default {
name:'toast',
data(){
return {
visible:false,
message:''
}
}
}
</script>
//main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from 'axios'
import store from './store'
import scroller from 'vue-scroller'
import 'font-awesome/scss/font-awesome.scss'
import './assets/style/reset.css'
import './assets/style/style.scss'
import tabbar from './components/tabBar'
import swiper from './components/swiper'
import toast from './components/toast'
Vue.config.productionTip = false
Vue.config.devtools = true
Vue.prototype.$axios = axios;
Vue.use(scroller)
Vue.use(tabbar)
Vue.use(swiper)
Vue.use(toast)
new Vue({
router,
store,
components: { App },
render: h => h(App),
}).$mount("#app");
接下来我们结合router来看页面
//srcpagespageView.vue
<template>
<div class="page-view">
<transition :name="transitionName">
<router-view class="child-view"></router-view>
</transition>
</div>
</template>
<script>
export default {
name:"pageView",
data() {
return {
transitionName:'slide-left'
}
},
watch:{
$route: function(to,from){
console.log('what is to',to)
console.log('what is from',from)
let isBack = this.$router.isBack;
if(isBack){
this.transitionName = 'slide-right';
}else{
this.transitionName = 'slide-left';
}
this.$router.isBack = false;
},
}
}/* */
</script>
//srcpagespageView.vue
<template>
<div class="tab-bar">
<ul>
<li v-for="(item,index) in itemList"
:key="index" class="tab-bar-item"
:class="{active: index == isActive}"
@click="addActive(index,item)">
<span class="tab-bar-item_icon" :class="item.icon"></span>
<span class="tab-bar-item_Text">{{item.text}}</span>
</li>
</ul>
</div>
</template>
<script>
export default{
name:'tabbar',
data (){
return {
isActive:this.$store.state.tabIndex,
itemList:[
{text:"推荐",icon:"fa fa-clipboard",link:'/'},
{text:"作品",icon:"fa fa-smile-o",link:'/things'},
{text:"设计师",icon:"fa fa-pencil",link:'/designer'},
{text:"我",icon:"fa fa-user-o",link:'/personal'}
]
}
},
mounted(){
if(this.isActive != 0){
this.$router.push(this.itemList[this.isActive].link);
}
},
methods:{
addActive: function(index,item){
console.log('.....index',index)
console.log('...item',item.link)
this.isActive = index;
this.$router.push(item.link);
this.$store.commit("changeTab",index);
}
}
};
</script>
效果为
代码为:
<template>
<div class="personal">
<div class="personal-logo">
<div class="logo">
<span class="logo_img"><img src="/static/images/logo.jpg" alt=""></span>
<span>登录</span>
</div>
</div>
<div class="personal-main">
<div class="personal-main_menu">
<ul>
<li><i class="fa fa-thumbs-o-up"></i><span>我推荐的</span></li>
<li><i class="fa fa-heart-o"></i><span>我关注的</span></li>
<li><i class="fa fa-folder-open-o"></i><span>我的作品</span></li>
</ul>
</div>
<div class="personal-main_list">
<ul>
<li><i class="fa fa-bell-o"></i><span>消息中心</span><i class="fa fa-angle-right"></i></li>
<li><i class="fa fa-bullhorn"></i><span>联系我</span><i class="fa fa-angle-right"></i></li>
<li @click="toAbout"><i class="fa fa-meh-o"></i><span>关于</span><i class="fa fa-angle-right"></i></li>
</ul>
</div>
</div>
</div>
</template>
<script>
import {prevent} from '../utils'
export default{
name: 'personal',
mounted(){
document.body.removeEventListener("touchmove",prevent);
},
methods: {
toAbout:function(){
this.$router.push("/about");
}
}
};
</script>
//srcpagesabout.vue
<template>
<div class="about">
<div class="page-title">
<span @click="goBack" class="goBack"><i class="fa fa-2x fa-angle-left"></i></span>
<h3>关于</h3>
</div>
<div class="main">
<p>声明:这只是一个练习的demo :( </p>
</div>
</div>
</template>
<script>
export default{
name: '',
data (){
return{
desc:''
}
},
methods: {
goBack: function(){
this.$router.goBack();
}
}
};
</script>
//srcpages hings.vue
<template>
<div class="things">
<div class="things-nav">
<swiper :dataList="navList" :activeIndex="activeIndex" ref="navSwiper" :swiperOption="swiperOptionNav" ></swiper>
</div>
<div class="things-main">
<scroller
:on-refresh="refresh"
:refreshText="refreshText" ref="scroller">
<span style="20px;height:20px;" class="spinner" slot="refresh-spinner"></span>
<div class="things-time">TODAY</div>
<swiper
:dataList="navList"
:swiperOption="swiperOptionMain" ref="mainSwiper">
<div slot="swiperMain" slot-scope="slotProps">
<div v-for="(item, index) in slotProps.data.dataList" :key="index" class="things-item" >
<div class="things-img-wrapper" @click="showDetails(item.id)">
<img class="things-item_img" :src="item.img" alt="">
<span class="things-item_tips">{{item.author}}</span>
</div>
<div class="things-item-foot">
<div class="foot-desc">
<span class="foot-desc_logo"><img :src="item.icon" alt=""></span>
<div class="foot-desc_text">
<p>{{item.author}}</p>
<p class="origin">{{item.origin}}</p>
</div>
</div>
<div class="foot-action">
<span @click="like(item)" class="fa fa-meh-o action-up"></span><span v-show="item.likeNum" class="like">+<i>{{item.likeNum}}</i></span> | <span @click="dislike(item)" class="fa fa-frown-o action-down"></span><span v-show="item.dislikeNum" class="dislike"><i>{{item.dislikeNum}}</i></span>
</div>
</div>
</div>
</div>
</swiper>
</scroller>
<div class="to-top" @click="toTop"><i class="fa fa-arrow-up"></i></div>
</div>
</div>
</template>
<script>
import { mixin,pageAct } from '../utils'
export default{
name: 'things',
data (){
return{
likeNum:0,
dislikeNum:0,
}
},
mixins:[mixin,pageAct],
methods:{
showDetails: function(index){
this.$router.push("/comment/"+index);
},
getData: function(cb){
var self = this;
this.$axios.post("/things",{}).then(function(response){
console.log('aaaaa...',response)
var result = response.data;
if(result.code == 200){
self.navList = result.list;
}
}).catch(function(error){
console.log(error);
});
},
loadMore: function(cb){
var self = this;
this.$axios.post("/things",{author:self.activeIndex}).then(function(response){
var result = response.data;
cb && cb(result);
}).catch(function(error){
console.log(error);
});
}
}
};
</script>
//srcpagesdesigner.vue
<template>
<div class="designer">
<div class="designer-nav">
<swiper :dataList="navList" :activeIndex="activeIndex" ref="navSwiper" :swiperOption="swiperOptionNav" ></swiper>
</div>
<div class="designer-main">
<keep-alive>
<scroller :on-refresh="refresh" :refreshText="refreshText" ref="scroller">
<span style="20px;height:20px;" class="spinner" slot="refresh-spinner"></span>
<div class="designer-time">TODAY</div>
<swiper :dataList="navList" ref="mainSwiper" :swiperOption="swiperOptionMain">
<div slot="swiperMain" slot-scope="slotProps">
<div v-for="(item, index) in slotProps.data.dataList" :key="index" class="designer-item" >
<div class="designer-img-wrapper" @click="showDetails(item.id)">
<img class="designer-item_img" :src="item.img" alt="">
<span class="foot-desc_logo"><img :src="item.icon" alt=""></span>
</div>
<div class="designer-item-foot">
<div class="foot-desc">
<div class="foot-desc_text">
<p>{{item.author}} | <span class="origin">{{item.origin}}</span></p>
</div>
</div>
<div class="foot-action">
<span @click="showTotast" class="foot-actionr_follow">+ 关注</span>
</div>
</div>
</div>
</div>
</swiper>
</scroller>
</keep-alive>
<div class="to-top" @click="toTop">
<i class="fa fa-arrow-up"></i>
</div>
</div>
</div>
</template>
<script>
import {pageAct} from '../utils'
export default{
name: 'designer',
mixins:[pageAct],
methods:{
showDetails: function(index){
this.$router.push("/details/"+index);
},
getData: function(cb){
var self = this;
this.$axios.post("/designer",{}).then(function(response){
console.log('designer',response)
var result = response.data;
if(result.code == 200){
self.navList = result.list;
}
}).catch(function(error){
console.log(error);
});
},
loadMore: function(cb){
var self = this;
this.$axios.post("/designer",{author:self.activeIndex}).then(function(response){
var result = response.data;
cb && cb(result);
}).catch(function(error){
console.log(error);
});
},
showTotast: function(){
this.$toast({message:"敬请期待关注功能 :-)"});
}
}
};
</script>
//srcpagesdetails.vue
<template>
<div class="things-details" >
<div class="page-title">
<span @click="goBack" class="goBack"><i class="fa fa-2x fa-arrow-circle-left"></i></span>
<h3>{{title}}</h3>
</div>
<div class="details-main">
<div class="img-box">
<img :src="img" alt="">
</div>
<div class="things-label">简介</div>
<div class="things-desc">
<div class="desc-item" v-for="(item, index) in descList" :key="index">
<span class="desc-item_icon fl"><i class="fa" :class="item.icon"></i></span>
<div class="desc-item_text"><em>{{item.tips}}:</em><p>{{item.text}}</p></div>
</div>
</div>
<div class="things-label">其他作品</div>
<div class="desc-img-box clearfix">
<div class="desc-img-item" v-for="(item, index) in imgList" :key="index">
<div class="img-item_text">—{{item.originate}}—</div>
<div class="desc-item_img"><img :src="item.img" alt=""></div>
</div>
</div>
<p class="page-footer">—— 没有了呢 ——</p>
</div>
</div>
</template>
<script>
import {prevent,goBack} from '../utils/index.js'
export default{
name: 'thing-details',
mixins:[goBack],
data() {
return {
id:'',
title:'',
img:'',
descList:[
{icon:'fa-paint-brush',tips:"派系",text:''},
{icon:'fa-map-o',tips:"代表作",text:''},
{icon:'fa-newspaper-o',tips:"简介",text:''}
],
imgList:[]
}
},
created(){
this.id = this.$route.params.id;
this.loadMore();
},
mounted(){
this.id = this.$route.params.id;
document.body.removeEventListener("touchmove",prevent);
document.body.addEventListener("touchmove",function(){
},{passive: true});
},
methods:{
loadMore: function(cb){
var self = this;
this.$axios.post("/designer",{id:self.id}).then(function(response){
var result = response.data;
if(result.code == 200){
self.title = result.list.title;
self.img = result.list.portrait;
self.descList[0].text = result.list.genre;
self.descList[1].text = result.list.magnumOpus;
self.descList[2].text = result.list.desc;
self.imgList = result.list.arts;
}
}).catch(function(error){
console.log(error);
});
}
}
}
</script>
```vue
//srcpagescomment.vue
<template>
<div class="comment" >
<div class="page-title">
<span @click="goBack" class="goBack"><i class="fa fa-2x fa-arrow-circle-left"></i></span>
<h3>{{title}}</h3>
</div>
<div class="comment-main">
<div class="comment-main_img">
<swiper :dataList="imgList" ref="imgSwiper" :swiperOption="swiperOptionImg" >
<div slot="swiperMain" slot-scope="slotProps">
<img class="comment-item_img" :src="slotProps.data.img" alt="">
</div>
</swiper>
</div>
<div class="comment-act">
<span @click="like" class="fa fa-meh-o action-up"></span><span v-show="likeNum" class="comment-act__like"><i>+{{likeNum}}</i></span> ||
<span @click="dislike" class="fa fa-frown-o action-down"></span><span v-show="dislikeNum" class="comment-act__dislike"><i>{{dislikeNum}}</i></span>
</div>
<div class="comment-desc">
<div class="desc-item" v-for="(item, index) in descList" :key="index">
<span class="desc-item_icon fl"><i class="fa" :class="item.icon"></i></span>
<div class="desc-item_text"><em>{{item.tips}}:</em><p>{{item.text}}</p></div>
</div>
</div>
<div class="comment-designer-desc">
<span class="comment-desc-icon"><img :src="icon" alt=""></span>
<h3 class="comment-desc-designer" >{{title}}</h3>
<span @click="showTotast" class="foot-actionr_follow">+ 关注</span>
<div class="desc-item_text"><p>{{designer}}</p></div>
</div>
<div class="comment-designer-word clearfix">
<div class="img fr">
<img :src="wordImg" alt="">
</div>
<div class="word">
<h4>我同疯子的唯一区别,在于我不是疯子。我同人类的唯一区别,在于我是疯子</h4>
<p>——达利</p>
</div>
</div>
<div class="comment-list">
<span class="comment-list-tips">评论({{cNum}})</span>
<div class="list">
<ul>
<li v-for="(item,index) in commentList" :key="index">
<span class="comment-user_logo fl"><img :src="item.icon" alt=""></span>
<div class="comment-content">
<p class="user-name">{{item.name}}:</p>
<p class="user-time">{{item.time}}</p>
<p class="user-word">{{item.content}}</p>
</div>
</li>
</ul>
</div>
<p class="page-footer">—— 没有了呢 ——</p>
</div>
</div>
<div class="comment-area" >
<span class="icon"><img :src="icon" alt=""></span>
<input class="send-input" type="text" placeholder="留下爪印" v-model="sendMsg" @focus="setPosition">
<input type="button" class="send-btn" value="评论" @click="send">
</div>
<transition name="slide-fade">
<div class="showNew" v-show="showNew">
最新评论:{{commentList[0].content}}
</div>
</transition>
</div>
</template>
<script>
import {prevent,mixin} from '../utils'
export default{
name: 'comment-details',
mixins:[mixin],
data() {
return {
id:'',
title:'',
img:'',
icon:'',
wordImg:'',
likeNum:0,
dislikeNum:0,
swiperOptionImg:{
autoplay:true,
loop :true,
pagination: {
el: '.swiper-pagination',
dynamicBullets: true
}
},
designer:'',
descList:[
{icon:'fa-paint-brush',tips:"派系",text:''},
{icon:'fa-map-o',tips:"代表作",text:''}
],
imgList:[],
commentList:[
{icon:'./static/images/logo.jpg',name:'路人甲',time:'2018-10-01 12:12:12',content:'这是一条评论'},
{icon:'./static/images/logo.jpg',name:'路人甲',time:'2018-10-01 12:12:12',content:'这是另外一条评论'},
{icon:'./static/images/logo.jpg',name:'路人甲',time:'2018-10-01 12:12:12',content:'这是一条长长长长长长长长长长长长长长长长长长长长长长长长长长长的评论'},
],
cNum:3,
sendMsg:'',
showNew:false
}
},
created(){
this.id = this.$route.params.id;
this.loadData();
},
mounted(){
this.id = this.$route.params.id;
document.body.removeEventListener("touchmove",prevent);
},
watch:{
showNew: function(){
var self = this;
setTimeout(function(){
self.showNew = false;
},1000);
}
},
methods:{
loadData: function(cb){
var self = this;
this.$axios.post("/designer",{id:self.id}).then(function(response){
var result = response.data;
if(result.code == 200){
self.title = (result.list.title).split("/")[0];
self.icon = result.list.icon;
self.img = result.list.portrait;
self.descList[0].text = result.list.genre;
self.descList[1].text = result.list.magnumOpus;
self.designer = result.list.desc;
self.imgList = result.list.arts;
self.wordImg = result.list.arts[0].img;
}
}).catch(function(error){
console.log(error);
});
},
send: function(){
if(this.sendMsg){
this.commentList.unshift({
icon:'./static/images/logo.jpg',name:'路人乙',time:'刚刚',content:this.sendMsg
});
this.cNum ++;
this.showNew = true;
this.sendMsg = '';
}
},
setPosition: function(){
// 解决安卓手机输入时键盘弹起遮盖输入框的问题
document.querySelector(".page-footer").scrollIntoView(true);
}
}
}
</script>
//srcpagespictoral.vue
// 这个是对进入页面的动画的处理
<template>
<div class="pictoral">
<div class="pictoral-main">
<transition-group name="cell" tag="div" class="container"
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave">
<div v-for="(data, index) in dataList" class="pictoral-item"
:key="index"
:data-index="index" >
<span class="pictoral-item_logo"><img :src="data.icon" alt=""></span>
<h6>{{data.title}}</h6>
<p>——{{data.originate}}</p>
<img :src="data.img" alt="">
<p>{{data.desc}}</p>
</div>
</transition-group>
<div class="no-more-data" v-show="noData"><i class="fa fa-hand-o-up"></i><p>没有更多了,试试向上滑动吧!</p></div>
</div>
</div>
</template>
<script>
import AlloyFinger from 'alloyfinger'
import Velocity from 'velocity-animate/velocity.js'
import 'velocity-animate/velocity.ui.js'
import {prevent} from '../utils'
export default{
name: 'pictoral',
data (){
return{
dataList:[],
first:0,
second:1,
third:2,
forth:3,
slideStart:0,
slideEnd:0,
activeIndex:0,
stop:false,
noData:false
}
},
created: function(){
this.getData();
},
mounted: function(){
this.bindSlide();
document.body.addEventListener("touchmove",prevent);
},
watch:{
activeIndex:function(){
var list = document.querySelectorAll(".pictoral-item");
this.stop = true;
for(var i=0;i < list.length;i++){
this.leave(list[i]);
}
if(this.activeIndex == this.dataList.length){
this.noData = true;
}else{
this.noData = false;
}
},
slideEnd: function(){
if(!this.stop){
if(this.slideEnd - this.slideStart > 20){
if(!this.noData){
this.first += 1;
this.second += 1;
this.third += 1;
this.forth += 1;
this.activeIndex++;
}
}else if(this.slideEnd - this.slideStart < -20){
if(this.first > 0){
this.first -= 1;
this.second -= 1;
this.third -= 1;
this.forth -= 1;
this.activeIndex--;
}
}
}
}
},
methods:{
beforeEnter: function (el) {
el.style.opacity = 0;
el.style.height = 0;
},
enter: function (el, done) {
var delay = el.dataset.index * 50;
var top = '';
var width = this.setWidth(el.dataset.index);
if(el.dataset.index >= (this.first + 4)){
top = 15 + "%";
}else{
top = (4 - (el.dataset.index - this.first))*8 + "%";
}
setTimeout(function () {
Velocity(
el,
{ opacity: 1,
height: '80%',
translateY:top,
width,
zIndex: 1000 - el.dataset.index
},
{ complete: done }
)
}, delay);
},
leave: function (el, done) {
var self = this;
var top = '';
var delay = el.dataset.index * 10;
var width = this.setWidth(el.dataset.index);
if(el.dataset.index < this.first){
top = "99%";
}else{
if(el.dataset.index >= (this.first + 4)){
top = 15 + "%";
}else{
top = (4 - (el.dataset.index - this.first))*10 + "%";
}
}
Velocity(
el,
{
width,
translateY:top
},{
mobileHA:true,
easing: [ 0.4, 0.01, 0.165, 0.99 ],
complete: function(){
if(el.dataset.index == (self.dataList.length - 1)){
self.stop = false;
}
}
}
);
},
bindSlide: function(){
var self = this;
var el = document.querySelector(".pictoral-main");
var fl = new AlloyFinger(el,{
touchStart: function(evt){
self.slideStart = evt.changedTouches[0].clientY;
},
touchMove: function(evt){
self.slideEnd = evt.changedTouches[0].clientY;
},
touchEnd:function (evt) {
self.slideEnd = evt.changedTouches[0].clientY;
}
});
},
setWidth: function(index){
return index == this.first ? '76%' :
index == this.second ? '68%' :
index == this.third ? '56%' : '40%';
},
getData: function(){
var self = this;
this.$axios.post("/pictoral",{}).then(function(response){
var result = response.data;
if(result.code == 200){
self.dataList =self.dataList = result.list;
}
}).catch(function(error){
console.log(error);
});
}
}
};
</script>