blog-client/pages/post/_id.vue
2020-07-08 22:36:46 +08:00

571 lines
15 KiB
Vue

<template lang="pug">
div
article.articleDetails
.articleTitle
| {{ articels.post_title }}
.articalMeta
ul
li
Icon(:icon="['far', 'calendar-alt']")
| &nbsp; {{ articels.post_date }}
li
Icon(:icon="['far', 'bookmark']")
nuxt-link(v-for='(relationships, index) in articels.term_relationships', :key='index', v-if='relationships.term_taxonomy', :to="'/' + relationships.term_taxonomy.term.slug")
span
| &nbsp; {{ relationships.term_taxonomy.term.name }}
li(v-if='articels.postmetum.meta_value !== 0')
Icon(:icon="['fas', 'thermometer-' + articels.hotValue]")
| &nbsp; {{ articels.postmetum.meta_value }}
#articelToc.articelToc(v-if="articels.post_toc_show")
.articelTitle 目录
.articelTocList(v-html="articels.post_toc")
.articelContent(v-html='articels.post_content')
.tools
.reward(@click="reward") 打赏
.comment
.comment-title 交流区
span(v-if="comments.length") ({{commentAmount}})
.comment-item(v-for="(item, index) in comments" :key="index")
.comment-avatar
img(:src="item.comment_author_avatar_url ? item.comment_author_avatar_url : 'https://picsum.photos/100/100/?blur=' + item.user_id")
.comment-area
.comment-author {{ item.comment_author }}
.comment-content {{ item.comment_content }}
.comment-info {{ item.comment_date }}
span.reply 回复
p(v-if="!comments.length", style="color: #cecece; text-align: center; margin-top: 40px") 暂无
.comment-userinfo
.comment-avatar
img(:src="'https://picsum.photos/100/100/?blur=1'")
.comment-nickname 张三
#editor
ins.adsbygoogle(style="display:block; text-align:center; margin-top:20px;", data-ad-layout="in-article", data-ad-format="fluid", data-ad-client="ca-pub-2143583075951360", data-ad-slot="4741804954")
.articelRightToc(v-show="articels.post_toc_show && showRightToc", @click="articelRightTocClick")
.articelTitle 目录
.articelTocList(v-html="articels.post_toc")
nuxt-link(:to="articels.last ? '/post/' + articels.last.ID : ''")
.btnFooter.btnPrevt(:style="'color: ' + (articels.last ? '#333' : '#ccc')")
Icon(:icon="['fas', 'chevron-left']")
| &nbsp; 上一篇 {{ articels.last ? '(' + decodeTitle(articels.last.post_name) + ')' : ''}}
nuxt-link(:to="articels.next ? '/post/' + articels.next.ID : ''")
.btnFooter.btnNext(:style="'color: ' + (articels.next ? '#333' : '#ccc')")
| 下一篇 {{ articels.next ? '(' + decodeTitle(articels.next.post_name) + ')' : ''}} &nbsp;
Icon(:icon="['fas', 'chevron-right']")
</template>
<script>
export default {
async asyncData ({ route, app, $axios }) {
const data = await $axios.$get(`/public/article/details?id=${route.params.id}`)
const result = data.result
result.post_date = app.$moment(result.post_date).utc().format('lll')// 格式化时间
// 热度值计算
if (result.postmetum !== null && result.postmetum !== '') {
result.hotValue = app.$getHatValue(result.postmetum.meta_value)
} else {
result.hotValue = 0
result.postmetum = {}
result.postmetum.meta_value = 0
}
// 获取评论
const tmp = await $axios.$get(`/public/comments/post?ID=${route.params.id}`)
const comments = tmp.result.list || []
const commentAmount = tmp.result.amount || 0
return {
articels: result,
comments,
commentAmount,
showRightToc: false,
tocIds: result.post_toc_ids
}
},
mounted () {
// 创建编辑器
if (process.client) {
if (!window.editorObj && window.Vditor) {
window.editorObj = new window.Vditor('editor', {
toolbar: [
'emoji',
'headings',
'bold',
'italic',
'strike',
'link',
'list',
'ordered-list',
'outdent',
'indent',
'quote',
'line',
'code',
'inline-code',
'upload',
'table',
'insert-before',
'insert-after',
'undo',
'redo',
'fullscreen',
{
name: 'more',
toolbar: [
'edit-mode',
'check',
'both',
'code-theme',
'content-theme',
'export',
'outline',
'preview',
'format',
'devtools'
]
}],
height: 250,
toolbarConfig: {
pin: true
},
cache: {
enable: true
},
placeholder: '我们书写的不是代码,而是人生'
})
// window.editorObj.customConfig.uploadImgShowBase64 = true // 使用 base64 保存图片
// window.editorObj.create()
}
}
this.$finishLoad()
if (this.articels.post_toc_show) {
const tocShow = () => {
if (window.addEvent) {
window.addEvent(window, 'scroll', () => {
const windowWidth = window.innerWidth || document.documentElement.clientWidth
if (document.getElementById('articelToc') && !this.isOnScreen(document.getElementById('articelToc'))) {
if (windowWidth < 768) {
this.showRightToc = false
document.getElementById('mobileToc').style.display = 'block'
} else {
this.showRightToc = true
}
} else {
this.showRightToc = false
if (windowWidth < 768) {
document.getElementById('mobileToc').style.display = 'none'
}
}
})
} else {
setTimeout(() => {
tocShow()
}, 50)
}
}
tocShow()
const tocIdsTop = []
const tocIdsTopMap = new Map()
for (const n in this.tocIds) {
const tTop = document.getElementById(this.tocIds[n]).offsetTop
tocIdsTop.push(tTop)
tocIdsTopMap.set(tTop, '#' + this.tocIds[n])
}
const findInIds = (arr, num) => { for (let x = 0; x < arr.length; x++) { if (arr[x] > num) { return x - 1 } } }
let debounce = null
let lastIdInx = null
const scrollToc = () => {
let scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
const windowWidth = window.innerWidth || document.documentElement.clientWidth
if (windowWidth < 768) { scrollTop = scrollTop * 2.7 + 175 }
const tocIdsInx = findInIds(tocIdsTop, scrollTop + 130)
if (tocIdsInx !== -1 && lastIdInx !== tocIdsInx && document.getElementsByClassName('articelRightToc')[0]) {
const tocAList = document.getElementsByClassName('articelRightToc')[0].getElementsByTagName('a')
const nowId = tocIdsTopMap.get(tocIdsTop[tocIdsInx])
for (const i in tocAList) {
if (tocAList[i] && tocAList[i].getAttribute && tocAList[i].getAttribute('href') === nowId) {
tocAList[i].style.color = '#ff8a8a'
} else if (tocAList[i] && tocAList[i].style) {
tocAList[i].style.color = '#bbb'
}
}
lastIdInx = tocIdsInx
}
}
window.addEvent(window, 'scroll', () => {
clearTimeout(debounce)
debounce = setTimeout(() => {
scrollToc()
}, 20)
})
document.getElementById('mobileToc').onclick = () => {
this.showRightToc = !this.showRightToc
}
}
try {
(adsbygoogle = window.adsbygoogle || []).push({}) // eslint-disable-line
} catch (_) { }
},
methods: {
decodeTitle (name) {
try {
return this.urldecode(name)
} catch (err) {
console.log(err)
return name
}
},
urldecode (encodedString) {
let output = encodedString
let binVal, thisString
const myregexp = /(%[^%]{2})/
function utf8to16 (str) {
let c
let char2, char3
let out = ''
const len = str.length
let i = 0
while (i < len) {
c = str.charCodeAt(i++)
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
out += str.charAt(i - 1)
break
case 12: case 13:
char2 = str.charCodeAt(i++)
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F))
break
case 14:
char2 = str.charCodeAt(i++)
char3 = str.charCodeAt(i++)
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0))
break
}
}
return out
}
let match = null
while ((match = myregexp.exec(output)) != null &&
match.length > 1 &&
match[1] !== '') {
binVal = parseInt(match[1].substr(1), 16)
thisString = String.fromCharCode(binVal)
output = output.replace(match[1], thisString)
}
output = output.replace(/\\+/g, ' ')
output = utf8to16(output)
return output
},
articelRightTocClick () {
const windowWidth = window.innerWidth || document.documentElement.clientWidth
if (windowWidth < 768) {
this.showRightToc = false
}
},
isOnScreen (element) {
const ON_SCREEN_HEIGHT = 50
const ON_SCREEN_WIDTH = 50
const rect = element.getBoundingClientRect()
const windowHeight = window.innerHeight || document.documentElement.clientHeight
const windowWidth = window.innerWidth || document.documentElement.clientWidth
const elementHeight = element.offsetHeight
const elementWidth = element.offsetWidth
const onScreenHeight = ON_SCREEN_HEIGHT > elementHeight ? elementHeight : ON_SCREEN_HEIGHT
const onScreenWidth = ON_SCREEN_WIDTH > elementWidth ? elementWidth : ON_SCREEN_WIDTH
// 元素在屏幕上方
const elementBottomToWindowTop = rect.top + elementHeight
const bottomBoundingOnScreen = elementBottomToWindowTop >= onScreenHeight
// 元素在屏幕下方
const elementTopToWindowBottom = windowHeight - (rect.bottom - elementHeight)
const topBoundingOnScreen = elementTopToWindowBottom >= onScreenHeight
// 元素在屏幕左侧
const elementRightToWindowLeft = rect.left + elementWidth
const rightBoundingOnScreen = elementRightToWindowLeft >= onScreenWidth
// 元素在屏幕右侧
const elementLeftToWindowRight = windowWidth - (rect.right - elementWidth)
const leftBoundingOnScreen = elementLeftToWindowRight >= onScreenWidth
return bottomBoundingOnScreen && topBoundingOnScreen && rightBoundingOnScreen && leftBoundingOnScreen
},
reward () {
this.$modal.show('dialog', {
title: '感谢支持',
text: '<img src="https://cdn.wyr.me/imgs/reward.jpg" style="max-width: 100%; width: 400px"/>'
})
}
},
head () {
return {
title: `${this.articels.post_title} - 轶哥`
}
}
}
</script>
<style lang="stylus" socped>
.articleDetails
margin-top 30px
background-color #fff
padding 50px 10px
.articleTitle
text-align center
font-size 24px
.articalMeta
text-align center
padding 20px 0
font-size 12px
color #a1887f
ul
list-style none
padding-right 20px
> li
display inline-block
margin-left 20px
line-height 2
> i
margin-right 8px
> span a
color #a1887f
margin-left 10px
a span
color #a1887f
> span a:first-child
margin-left 0
> li :first-child
margin-left 0
.articelContent
margin-top 20px
margin-bottom 30px
line-height 2
font-weight 300
font-size responsive
pre
text-align 0.16rem
overflow-x auto
line-height 1
code
padding 16px
code
color #24292e
background-color rgba(27, 31, 35, 0.05)
border-radius 3px
font-size 85%
padding 2px 5px
.comment
width 100%
margin-top 60px
.comment-title
color #777
margin-bottom 30px
.comment-item
margin-top 20px
margin-left 10px
.comment-avatar
float left
margin-top 3px
img
border-radius 50%
width 40px
border-style none
cursor pointer
box-sizing border-box
margin 0
.comment-area
margin-left 50px
.comment-author, .comment-info
font-size 10px
.comment-author
color #a1887f
.comment-info
color #777
.reply
color #a1887f
cursor pointer
user-select none
margin-left 10px
.comment-userinfo
margin-top 90px
margin-left 10px
.comment-avatar
float left
margin-top 3px
img
border-radius 50%
width 60px
border-style none
cursor pointer
box-sizing border-box
margin 0
.comment-nickname
color #a1887f
margin-left 80px
height 70px
line-height 70px
#editor
margin-top 20px
margin-bottom 10px
.btnFooter
font-size 12px
margin-top 25px
i
margin 10px
.btnPrevt
float left
.btnNext
float right
footer .footer
color #cccccc
text-align left
line-height 1.5
margin 20px 0
.anchor-fix
display block
height 70px /* same height as header */
margin-top -70px /* same height as header */
visibility hidden
.articelRightToc
position fixed
width 15%
top 0
right 0
overflow-y auto
background-color #000
padding 10px
box-shadow 0 0 8px #000
z-index 999999
.articelToc
width 100%
.articelToc, .articelRightToc
color #bbb
user-select none
.articelTitle
font-weight 400
margin-bottom 10px
.articelTocList
color #bbb
a
color #bbb
&:visited
color #bbb
&:hover
color #d01c2e
ol.toc, ol.toc ol
counter-reset toc
list-style none
ol li
counter-increment toc
.toc li:before
content counters(toc, '.') ' '
ol
margin-left 20px
li
font-weight 400
ol
margin-left 20px
li
font-weight 300
.tools
margin-top 80px
margin-bottom 10px
text-align center
user-select none
.reward
margin 15px 0
border 1px solid #888
padding 5px 10px
display inline-block
color #888
font-weight 300
cursor pointer
margin auto
@media (max-width: 768px)
.articleDetails
padding 20px
margin-top 50px
.articelRightToc
position fixed
width 80%
top 100px
bottom 95px
right 10px
background-color #000
padding 10px
overflow-y auto
box-shadow 0 0 8px #000
.mobileToc
bottom 48px
</style>