first commit

This commit is contained in:
2019-02-07 16:26:17 +08:00
commit ab3e337e60
117 changed files with 17487 additions and 0 deletions

11
components/Footer.vue Normal file
View File

@ -0,0 +1,11 @@
<template lang="pug">
footer
a#gotop.scroll-top(href='javascript:;', style='display: none;')
Icon(:icon="['fas', 'angle-double-up']")
#mobileToc.mobileToc(style='display: none;')
| 目录
.footer
| Copyright © 2018 WangYi.
br
| All Rights Reserved. 浙ICP备14013313号-2
</template>

113
components/LeftContent.vue Normal file
View File

@ -0,0 +1,113 @@
<template lang="pug">
#leftcontent
#wrap
#leftMain
h1.title
nuxt-link(to='/') 轶哥博客
.widget.avatar
.widgetTitle
label 关于作者
img(src='https://cdn.wyr.me/avatar.png')
p.aboutMe
| 妄图改变世界的全栈程序员
.social
ul
li
a(href='https://github.com/yi-ge', target='_blank')
Icon(:icon="['fab', 'github']")
li
Icon(:icon="['fab', 'qq']" @click="info('qq')")
li
Icon(:icon="['fab', 'weixin']" @click="info('weixin')")
li
a(href='http://weibo.com/syxj', target='_blank')
Icon(:icon="['fab', 'weibo']")
li
a(href='https://twitter.com/FYTencel', target='_blank')
Icon(:icon="['fab', 'twitter']")
li
a(href='https://www.facebook.com/cnyige', target='_blank')
Icon(:icon="['fab', 'facebook']")
li
a(href='https://plus.google.com/u/0/101716298673782941484', target='_blank')
Icon(:icon="['fab', 'google-plus']")
li
a(href='https://t.me/cnyige', target='_blank')
Icon(:icon="['fab', 'telegram']")
li
Icon(:icon="['fas', 'envelope']" @click="info('email')")
.social-info-box(v-if='socialInfo')
| {{ socialInfo }}
#navication.widget.navication
.widgetTitle
label 导航
ul
li
nuxt-link(to='/') 主页
li(v-for='(categorys, index) in $store.state.categorys', :key='index', v-show="categorys.name !== '未分类'")
nuxt-link(:to="'/' + categorys.slug") {{ categorys.name }}
.search-box
input.search(type='text', placeholder='搜索', v-model='searchVal', @keyup.enter='search')
Icon(:icon="['fas', 'search']" @click="search")
.widget.comment
.widgetTitle
label 最新评论
ul
li(v-for='(newComment, index) in $store.state.newComments', :key='index')
strong {{ newComment.comment_author }}
span
nuxt-link(:to="'/post/' + newComment.post.ID + '#comments'") {{ newComment.comment_content }}
.meta
span
Icon(:icon="['far', 'clock']")
| &nbsp; {{ newComment.comment_date }}
span
|
nuxt-link(:to="'/post/' + newComment.post.ID") {{ newComment.post.post_title }}
.widget.posts
.widgetTitle
label 热门文章
ul
li(v-for='(hotArticle, index) in $store.state.hotArticle', :key='index')
.thumbnail
img(width='150', :src="hotArticle.postimages[0].guid + '?x-oss-process=image/resize,m_lfit,h_150,w_150'")
.detail
nuxt-link(:to="'/post/' + hotArticle.ID", :title='hotArticle.post_title') {{ hotArticle.post_title }}
.meta {{ hotArticle.post_modified }}
#tags.widget.tags
.widgetTitle
label 标签
.tagcloud
nuxt-link(:to="'/tag/' + tag.slug", :title='tag.name', v-for='tag in $store.state.tags', :key='tag.term_id') {{ tag.name }}
</template>
<script>
export default {
data() {
return {
socialInfo: false,
searchVal: ''
}
},
methods: {
info(type) {
if (type === 'qq') {
this.socialInfo = 'QQ: 373226722'
} else if (type === 'weixin') {
this.socialInfo = '微信: wy373226722'
} else if (type === 'email') {
this.socialInfo = 'Email: a@wyr.me'
}
setTimeout(() => {
this.socialInfo = false
}, 3000)
},
search() {
console.log(this.searchVal)
if (this.searchVal !== '') this.$router.push({ path: 'search?q=' + this.searchVal })
}
}
}
</script>

26
components/Slide.vue Normal file
View File

@ -0,0 +1,26 @@
<template>
<div
class="slide"
@click="$emit('click')"
>
<slot />
</div>
</template>
<script>
export default {
name: 'Slide'
}
</script>
<style lang="stylus">
.slide
height 100%
width 100%
flex-shrink 0
z-index 10
overflow hidden
img
-webkit-user-drag none
</style>

414
components/Swiper.vue Normal file
View File

@ -0,0 +1,414 @@
<template>
<div
class="swiper-container"
@touchmove="fn"
>
<div
class="default-swiper-box"
:class="box"
@transitionend="transitionend"
@touchstart="s"
@touchmove="m"
@touchend="e"
@mousedown="s"
@mousemove="m"
@mouseup="e"
>
<slot />
</div>
<slot name="pagination">
<!-- 默认提供了一个 pagination -->
<div
v-if="pagination"
class="swiper-pagination"
>
<div
v-for="(value, key) in reallySlidesNumber"
:key="key"
class="swiper-dot"
:class="{'swiper-dot-active': currentSlide === key}"
/>
</div>
</slot>
<!-- 这两个就不默认提供了 -->
<slot name="arrowLeft" />
<slot name="arrowRight" />
<!-- 当你需要在全局的内容里面加一些玩意的时候 -->
<slot name="g" />
</div>
</template>
<script>
function toArray(arraylike) {
return Array.prototype.slice.call(arraylike)
}
export default {
name: 'Swiper',
props: {
/* 一次滑动的默认时间 */
duration: {
type: Number,
default: 500
},
/* 两次滑动的间隔时间 */
interval: {
type: Number,
default: 2500
},
/* 是否自动播放 */
autoplay: {
type: Boolean,
default: true
},
/* 用户滑动多少距离, 翻页 */
therehold: {
type: Number,
default: 110
},
defaultSlide: {
type: Number,
default: 0
},
pagination: {
type: Boolean,
default: true
},
/* 有时候全屏滚动, 的确想要禁用垂直方向的滚动的时候 */
vLock: {
type: Boolean,
default: false
}
},
data() {
return {
swiper: null,
swiperWidth: 0,
slides: null,
slidesNumber: 0,
reallySlidesNumber: 0,
currentSlide: 0,
timer: null,
userDuration: 200,
pos: {
startX: 0,
moveX: 0,
endX: 0,
local: 0,
distance: 0
},
moving: false,
unlock: false,
activeId: '',
mousedown: false,
box: '',
isOnly: false
}
},
mounted() {
this.box = 'swiper-box-' + Math.random().toFixed(2) * 1000
setTimeout(() => {
/* 初始化的时候, 拿到所有的 DOM 元素以及相关属性 */
this.initElement()
if (this.isOnly) {
return
}
/* 克隆两个节点, 用来实现 loop 效果 */
this.cloneSlide()
/* 克隆结束之后, 需要设置默认显示的slide */
this.setDefaultSlide()
/*
## start
设置默认slide之后, 就需要开始设置定时器, 自动轮播
*/
if (this.autoplay) {
this.play()
}
}, 100)
},
methods: {
/* 阻止容器的上下滚动, 并且只有在水平方向上面滚动超过 10px 才可以阻止 */
fn(e) {
if (this.vLock || Math.abs(this.pos.startX - this.pos.moveX) > 10) {
e.preventDefault()
}
},
/* 滑动到指定的页面 */
slideTo(index) {
if (!this.moving) {
const currentSlide = Math.round(Math.abs(this.left()) / this.swiperWidth)
/* 如果索引值不合法 或者和目前的值相等 */
if (index > this.slidesNumber - 2 - 1 || index < 0 || currentSlide === index + 1) {
return
}
this.moving = true
clearTimeout(this.timer)
/*
说明要往右边滑动
注意这里不管需要滑动多少个, duration 都是 300, 这个如果需要, 可以
自己根据起点/终点计算出一个合适的值.
*/
this.transitionDuration(300)
this.translateX(-this.swiperWidth * (index + 1))
}
},
next() {
if (!this.moving) {
clearTimeout(this.timer)
this.moving = true
this.transitionDuration(this.userDuration)
this.translateX(this.left() - this.swiperWidth)
}
},
previous() {
if (!this.moving) {
clearTimeout(this.timer)
this.moving = true
this.transitionDuration(this.userDuration)
this.translateX(this.left() + this.swiperWidth)
}
},
initElement() {
/* 因为传递过来的是个字符串, 所以要手动加点 */
this.swiper = document.querySelector('.' + this.box)
this.swiperWidth = this.swiper.clientWidth
this.slides = toArray(this.swiper.children)
this.slidesNumber = this.slides.length
/* 实际的 slide 个数, 因为 slidesNumber 会在后面重新赋值 */
this.reallySlidesNumber = this.slides.length
/* 如果就仅仅只有一个 slide, 那么不克隆, 不绑定, 就纯展示就可以了 */
if (this.reallySlidesNumber === 1) {
this.isOnly = true
}
},
cloneSlide() {
const head = this.slides[0].cloneNode(this.slides[0], true)
const tail = this.slides[this.slidesNumber - 1].cloneNode(this.slides[this.slidesNumber - 1], true)
this.swiper.appendChild(head)
this.swiper.insertBefore(tail, this.slides[0])
/* 克隆节点之后, 需要重置一些属性 */
this.slides = toArray(this.swiper.children)
this.slidesNumber = this.slides.length
},
/* 根据用户给定的 defaultSlide 设置 init slide 的值 */
setDefaultSlide() {
/*
一切用户给定的值, 都是从 0 - x 开始, 比如用户数据里面有 6个数据
那么给定的就是 0 - 5
但是我们内部运算的时候, 实际上我们的索引能到 0 - 7
0 是实际的 5 的拷贝, 7 实际上是实际的0的拷贝
所以当用户给定的 defaultSlide =0, 我们实际上是要让展示内部索引为 1 的元素
*/
/* 如果用户设置了一个非法值, 直接抛出异常 */
if (this.defaultSlide < 0 || this.defaultSlide > this.slidesNumber - 2 - 1) {
throw new Error('[swiper:Error]: You have set a wrong defaultSlide value with defaultSlide = ' + this.defaultSlide)
}
this.translateX(-this.swiperWidth * (this.defaultSlide + 1))
//
// this.currentSlide = this.defaultSlide;
},
/*
## start
*/
play() {
this.timer = setTimeout(() => {
clearTimeout(this.timer)
this.moving = true
this.unlock = false
this.transitionDuration(this.duration)
this.translateX(-(this.swiperWidth + Math.abs(this.left())))
}, this.interval)
},
transitionend() {
this.transitionDuration(0)
/*
一次滑动结束之后, 通过计算, 实际上我们可以拿到当前处于内部索引的第几个 slide
拿到这个 currentSlide 我们就知道当前是不是滚动到最后一个了
*/
const currentSlide = Math.round(Math.abs(this.left()) / this.swiperWidth)
this.currentSlide = currentSlide - 1
/* 如果滚动到最后一个, 那么就要瞬间跳转一下, 到外部看起来的第一个, 内部的 */
if (currentSlide === this.slidesNumber - 1) {
this.translateX(-this.swiperWidth)
this.currentSlide = 0
}
if (currentSlide === 0) {
this.translateX(-this.swiperWidth * (this.slidesNumber - 2))
this.currentSlide = this.slidesNumber - 3
}
this.$emit('transitionend', this.currentSlide)
/*
防止极限操作, 用户在滑动结束之后事件还没发送出去又滑动导致计算
结果错误, 所以等事件发出去之后再解开
*/
this.moving = false
/*
##start
*/
if (this.autoplay) {
this.play()
}
},
/* toushstart handler */
s(e) {
if (this.isOnly) {
return
}
if (e.type === 'mousedown' && !this.moving) {
this.mousedown = true
this.pos.startX = e.pageX
this.pos.local = this.left()
clearTimeout(this.timer)
this.transitionDuration(0)
} else {
this.activeId = toArray(e.changedTouches)[0].identifier
if (!this.moving) {
const active = e.touches.length - 1
clearTimeout(this.timer)
this.transitionDuration(0)
this.unlock = true
this.pos.startX = e.touches[active].clientX
/* 一次 touch 的 起始local 点, 是固定的 */
this.pos.local = this.left()
}
}
},
/* toushmove handler */
m(e) {
if (this.isOnly) {
return
}
if (e.type === 'mousemove' && this.mousedown && !this.moving) {
this.pos.moveX = e.pageX
this.pos.distance = this.pos.moveX - this.pos.startX
this.translateX(this.pos.local + this.pos.distance)
} else if (!this.moving && this.unlock) {
const active = e.touches.length - 1
this.pos.moveX = e.touches[active].clientX
this.pos.distance = this.pos.moveX - this.pos.startX
this.translateX(this.pos.local + this.pos.distance)
}
},
/* toushend handler */
e(e) {
if (this.isOnly) {
return
}
if (e.type === 'mouseup' && this.mousedown && !this.moving) {
this.mousedown = false
this.pos.endX = e.pageX
this.pos.distance = this.pos.endX - this.pos.startX
this.recover()
} else {
const curId = toArray(e.changedTouches)[0].identifier
if (!this.moving && this.unlock && (curId === this.activeId)) {
this.unlock = false
this.pos.endX = e.changedTouches[0].clientX
this.pos.distance = this.pos.endX - this.pos.startX
this.recover()
}
}
},
/* 响应用户滚动行为 */
recover() {
this.transitionDuration(this.userDuration)
const distance = Math.abs(this.left()) % this.swiperWidth
let point = []
let direction = ''
/*
主要是为了拿到当前状态下面, swiper 距离正常状态的, 左右移动的距离分别是多少.
*/
if (this.left() > 0) {
point = [distance, this.swiperWidth - distance]
} else {
point = [this.swiperWidth - distance, distance]
}
if (this.pos.distance > 0) {
direction = 'to-right'
} else if (this.pos.distance < 0) {
direction = 'to-left'
} else {
direction = 'none'
}
if (direction === 'none') {
if (this.autoplay) {
this.play()
}
}
if (direction === 'to-right') {
this.moving = true
/* 说明需要向右边移动 */
if (point[0] > this.therehold) {
this.translateX(this.left() + point[1])
const next = (this.left() + point[1]) / this.swiperWidth
if (Math.abs(next) === 0) {
this.unlock = false
}
} else {
this.translateX(this.left() - point[0])
}
}
if (direction === 'to-left') {
this.moving = true
if (point[1] > this.therehold) {
this.translateX(this.left() - point[0])
const next = (this.left() - point[0]) / this.swiperWidth
if (Math.abs(next) === this.slidesNumber - 1) {
this.unlock = false
}
} else {
this.translateX(this.left() + point[1])
}
}
},
translateX(value) {
this.swiper.style.transform = 'translate3d(' + value + 'px, 0, 0)'
},
transitionDuration(ms) {
this.swiper.style.transitionDuration = ms + 'ms'
},
left() {
return this.swiper.getBoundingClientRect().left
}
}
}
</script>
<style lang="stylus">
.swiper-container
position relative
width 100%
overflow hidden
.default-swiper-box
height 100%
width 100%
display flex
// 分页
.swiper-pagination
position absolute
bottom 10px
height 18px
width 100%
background transparent
display flex
align-items center
justify-content center
.swiper-dot
height 8px
width 8px
background #000
opacity 0.2
margin 0 5px
border-radius 50%
.swiper-dot-active
opacity 1
background #3498db
</style>