blog-client/pages/post/_id.vue
2021-08-04 16:03:32 +08:00

1463 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template lang="pug">
.postPage
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 && relationships.term_taxonomy.term && relationships.term_taxonomy.taxonomy === "category"',
: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', v-viewer)
.copyright
| 除特别注明外本站所有文章均为原创原创文章均已备案且受著作权保护未经作者书面授权请勿转载
.tools
.reward(@click='reward') 打赏
#comment.comment
.comment-title 交流区
span(v-if='comments.length') ({{ commentAmount }})
.comment-item(
v-for='(item, index) in comments',
:id='"comment_ID_" + item.comment_ID',
: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 }}
span.comment-status(v-if='item.comment_approved === "0"') &nbsp; 此内容正在审核中...
.comment-content(v-html='item.comment_content_html', v-viewer)
.comment-info {{ item.comment_date }}
span.reply(
@click='reply(item.comment_ID, null, true)',
v-if='replyLastID === null && item.comment_ID === replyID'
) 取消回复
span.reply(@click='reply(item.comment_ID, null)', v-else) 回复
span.reply(
@click='check(item.comment_ID)',
v-if='item.comment_approved === "0" && visitorInfo.manage'
) 通过审核
span.reply(
@click='check(item.comment_ID, 2)',
v-else-if='visitorInfo.manage'
) 设为垃圾评论
.comment-item(
v-for='(i, inx) in item.children',
:id='"comment_ID_" + i.comment_ID',
:key='inx',
style='margin-top: 15px; margin-left: 50px; background-color: #eeeeee; border-radius: 4px;'
)
.comment-avatar
img(
:src='i.comment_author_avatar_url ? i.comment_author_avatar_url : "https://picsum.photos/100/100/?blur=" + i.user_id'
)
.comment-area
.comment-author {{ i.comment_author }}
span.comment-status(v-if='i.comment_approved === "0"') &nbsp; 此内容正在审核中...
.comment-content(v-html='i.comment_content_html')
.comment-info {{ i.comment_date }}
span.reply(
@click='reply(item.comment_ID, i.comment_ID)',
v-if='replyLastID !== i.comment_ID'
) 回复
span.reply(
@click='reply(item.comment_ID, i.comment_ID, true)',
v-else
) 取消回复
span.reply(
@click='check(i.comment_ID)',
v-if='i.comment_approved === "0" && visitorInfo.manage'
) 通过审核
span.reply(
@click='check(i.comment_ID, 2)',
v-else-if='visitorInfo.manage'
) 设为垃圾评论
p(
v-if='!comments.length',
style='color: #cecece; text-align: center; margin-top: 40px'
) 暂无内容
.comment-default-commit
.login(v-show='!visitorToken')
.item(v-for='(item, index) in loginPlatform', :key='index')
transition(name='qrcode')
img.qrcode(
v-show='!commentLoading && useWeixinLogin && currentLoginType === item.type',
:src='weixinLoginQrcode'
)
img.logo(
:class='{ active: currentLoginType === item.type && useWeixinLogin && !commentLoading, loading: commentLoading && currentLoginType === item.type }',
:src='item.logo',
@click.stop='login(item.type)'
)
#editor
.comment-toolbar
.comment-no-userinfo(v-if='!visitorToken') 尚未登陆
.comment-userinfo(v-else)
.comment-avatar
img(:src='visitorInfo.avatarURL')
.comment-nickname {{ visitorInfo.nickname ? visitorInfo.nickname : visitorInfo.username || "匿名" }}
.comment-setting(@click='commentSetting') 设置
.comment-logout(@click='commentLogout') 退出
.comment-btn(@click='commentCommit("editor")') 发布
#comment-reply-commit.comment-reply-commit(, v-show='replyID')
.login(v-show='!visitorToken')
.item(v-for='(item, index) in loginPlatform', :key='index')
transition(name='qrcode')
img.qrcode(
v-show='!commentLoading && useWeixinLogin && currentLoginType === item.type',
:src='weixinLoginQrcode'
)
img.logo(
:class='{ active: currentLoginType === item.type && useWeixinLogin && !commentLoading, loading: commentLoading && currentLoginType === item.type }',
:src='item.logo',
@click.stop='login(item.type)'
)
#editor-reply
.comment-toolbar
.comment-no-userinfo(v-if='!visitorToken') 尚未登陆
.comment-userinfo(v-else)
.comment-avatar
img(:src='visitorInfo.avatarURL')
.comment-nickname {{ visitorInfo.nickname ? visitorInfo.nickname : visitorInfo.username || "匿名" }}
.comment-setting(@click='commentSetting') 设置
.comment-logout(@click='commentLogout') 退出
.comment-btn(@click='commentCommit("editor-reply")') 发布
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"]')
modal.modal-setting(name='setting')
.vue-modal-content(v-loading='form.loading')
p.vue-modal-title
| 评论回复提醒
Form(ref='form', :model='form', label-width='130px')
FormItem(label='手机号')
Input(v-model='form.phone', size='small')
FormItem(label='发送提醒到手机')
VueSwitch(v-model='form.sendPhone')
FormItem(label='邮箱')
Input(v-model='form.email', size='small')
FormItem(label='发送提醒到邮箱')
VueSwitch(v-model='form.sendEmail')
.vue-modal-buttons
button.vue-modal-button(type='button', @click.stop='hideSettings')
| 关闭
button.vue-modal-button(
type='button',
@click.stop='saveSettings',
style='background-color: #409eff;color: #fff;'
)
| 保存
</template>
<script>
import remark from 'remark'
import strip from 'strip-markdown'
import { Form, FormItem, Input, Switch } from 'element-ui'
export default {
components: {
Form, FormItem, Input, VueSwitch: Switch
},
async asyncData ({ route, app, $axios, redirect }) {
let draftStr = ''
if (route.query && route.query.draft === 'true') {
draftStr = '&draft=true'
}
const data = await $axios.$get(`/public/article/details?id=${route.params.id}${draftStr}`)
if (data.status === 404) {
redirect('/404')
return
}
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 truncated = (str, num) => {
return Array.from(str).slice(0, num).join('')
}
const removeMarkdownFormat = (str) => {
return new Promise((resolve, reject) => {
remark()
.use(strip)
.process(str, function (err, file) {
if (err) {
reject(err)
return
}
resolve(String(file))
})
})
}
// 获取评论
const tmp = await $axios.$get(`/public/comments/post?ID=${route.params.id}`)
const comments = tmp.result.list || []
const commentAmount = tmp.result.amount || 0
const markdownToText = await removeMarkdownFormat(result.post_excerpt)
const description = truncated(markdownToText.replace(/[\r\n]/g, '').replace(/\s+/g, '').trim(), 200)
return {
postID: route.params.id,
articels: result,
description,
comments,
commentsMap: new Map(),
commentAmount,
showRightToc: false,
tocIds: result.post_toc_ids,
replyLastID: null,
replyID: null,
visitorToken: null,
visitorInfo: {
nickname: null,
avatarURL: null,
email: null,
blog: null
},
editorObj: null,
editorReplyObj: null,
commentLoading: false,
useWeixinLogin: false,
weixinLoginQrcode: null,
currentLoginType: null,
loginPlatform: [
{
type: 'github',
logo: 'https://cdn.wyr.me/imgs/GitHub-Login.png'
},
{
type: 'weixin',
logo: 'https://cdn.wyr.me/imgs/Weixin-Login.png'
},
{
type: 'qq',
logo: 'https://cdn.wyr.me/imgs/QQ-Login.png'
},
{
type: 'weibo',
logo: 'https://cdn.wyr.me/imgs/Weibo-Login.png'
}
],
form: {
loading: true,
phone: '',
sendPhone: true,
email: '',
sendEmail: true
}
}
},
head () {
const keywords = []
for (let i = 0; i < this.articels.term_relationships.length; i++) {
if (this.articels.term_relationships[i].term_taxonomy && this.articels.term_relationships[i].term_taxonomy.term && this.articels.term_relationships[i].term_taxonomy.taxonomy === 'post_tag') {
keywords.push(this.articels.term_relationships[i].term_taxonomy.term.name)
}
}
return {
title: `${this.articels.post_title} - 轶哥`,
meta: [
{
hid: 'description',
name: 'description',
content: this.description
},
{
hid: 'keywords',
name: 'keywords',
content: keywords.join(',')
}
]
}
},
mounted () {
// 创建编辑器
if (process.client) {
this.visitorToken = window.localStorage.visitorToken
if (this.visitorToken && window.localStorage.visitorInfo) {
try {
this.visitorInfo = JSON.parse(window.localStorage.visitorInfo)
this.getCommentWithVisitorInfo(this.postID)
this.checkSettings()
} catch (_) { }
}
window.setAuthToken = (visitorToken, visitorInfo) => {
window.localStorage.visitorToken = visitorToken
window.localStorage.visitorInfo = JSON.stringify(visitorInfo)
this.visitorToken = visitorToken
this.visitorInfo = visitorInfo
this.editorObj.vditor.options.upload.headers = {
Authorization: 'Bearer ' + this.visitorToken
}
this.editorReplyObj.vditor.options.upload.headers = {
Authorization: 'Bearer ' + this.visitorToken
}
this.checkSettings()
}
this.$nextTick(() => {
if (this.$route.query.hash) {
setTimeout(() => {
window.location.hash = '#' + this.$route.query.hash
}, 50)
}
if (!this.editorObj && window.Vditor) {
this.initEditor()
}
})
this.setCommentMap()
}
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: {
initEditor () {
const upload = {
accept: 'image/*, video/*, audio/*, text/*, application/*, .rar, .zip, .php, .pptx, .ppt, .doc, .docx, .txt, .xls, .xlsx',
url: process.env.baseURL + '/visitor/file/upload',
max: 10485760, // 10 MB
linkToImgUrl: process.env.baseURL + '/visitor/file/fetch',
headers: {
Authorization: 'Bearer ' + this.visitorToken
},
filename: name => encodeURIComponent(name.replace(/[^(a-zA-Z0-9\u4E00-\u9FA5\.)]/g, '') // eslint-disable-line
.replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, '') // eslint-disable-line
.replace('/\\s/g', '')),
format (files, responseText) {
return responseText
},
error: (_) => {
this.$modal.show('dialog', {
title: '提示',
text: '上传失败,请检查网络稍后重试。<br>允许上传的文件类型图片、视频、压缩文件、文本文件、Office文件、各类代码文件。'
})
}
}
this.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',
'devtools'
]
}],
minHeight: 250,
toolbarConfig: {
pin: true
},
cache: {
enable: true
},
upload,
placeholder: '我们书写的不是代码,而是人生'
})
this.editorReplyObj = new window.Vditor('editor-reply', {
toolbar: [
'emoji',
'headings',
'bold',
'italic',
'strike',
'link',
'list',
'ordered-list',
'outdent',
'indent',
'quote',
'line',
'code',
'inline-code',
'upload',
'insert-before',
'insert-after',
'undo',
'redo',
{
name: 'more',
toolbar: [
'table',
'fullscreen',
'edit-mode',
'check',
'both',
'code-theme',
'content-theme',
'export',
'outline',
'preview',
'devtools'
]
}],
minHeight: 220,
toolbarConfig: {
pin: true
},
cache: {
enable: false
},
upload,
placeholder: '我们书写的不是代码,而是人生'
})
},
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"/>'
})
},
reply (id, replyLastID, cancel) {
const editorReplyObject = document.getElementById('comment-reply-commit')
if (cancel) {
this.replyID = null
this.replyLastID = null
window.location.hash = '_'
} else {
this.replyID = id
this.replyLastID = replyLastID
document.getElementById('comment_ID_' + id).appendChild(editorReplyObject)
const comment = this.commentsMap.get(replyLastID)
this.editorReplyObj.setValue('', true)
this.$nextTick(() => {
this.editorReplyObj.focus()
if (replyLastID !== null) {
this.editorReplyObj.insertValue('> ' + comment.comment_content + ' \n\n\n', true)
} else {
this.editorReplyObj.insertValue('', true)
}
window.location.hash = '#comment_ID_' + id
})
}
},
async commentCommit (type) {
const parentID = this.replyID || 0
const lastID = this.replyLastID || 0
const postID = this.postID
if (!postID) {
alert('致命错误')
return
}
const editorObj = type === 'editor-reply' ? this.editorReplyObj : this.editorObj
const content = editorObj.getValue()
if (!content || content === '\n') {
this.$toasted.show('请输入内容', {
position: 'top-center',
duration: 5000
})
return
}
const { data } = await this.$axios({
method: 'post',
url: process.env.baseURL + '/comments/commit',
headers: {
Authorization: 'Bearer ' + this.visitorToken
},
data: {
postID,
lastID,
parentID,
content
}
})
if (data.status === 1) {
this.replyID = null
this.replyLastID = null
window.location.hash = '_'
this.getCommentWithVisitorInfo(postID)
editorObj.setValue('', true)
this.$nextTick(() => {
editorObj.focus()
editorObj.insertValue('', true)
})
}
},
async check (ID, type) {
const { data } = await this.$axios({
method: 'post',
url: process.env.baseURL + '/comments/check',
headers: {
Authorization: 'Bearer ' + this.visitorToken
},
data: {
ID,
type
}
})
if (data.status === 1) {
this.getCommentWithVisitorInfo(this.postID)
this.$toasted.show(type === 2 ? '已删除' : '已通过', {
position: 'top-center',
duration: 5000
})
}
},
async getCommentWithVisitorInfo (postID) {
// 获取评论
const tmp = await this.$axios({
method: 'get',
url: process.env.baseURL + `/public/comments/post?ID=${postID}`,
headers: {
Authorization: 'Bearer ' + this.visitorToken
}
})
const comments = tmp.data.result.list || []
const commentAmount = tmp.data.result.amount || 0
this.comments = comments
this.commentAmount = commentAmount
this.setCommentMap()
},
commentLogout () {
window.localStorage.removeItem('visitorToken')
this.visitorToken = null
},
getCode (uuid, last) {
try {
this.$axios({
type: 'get',
url: 'https://weixin.openapi.site/check?uuid=' + uuid + (last ? '&last=' + last : ''),
timeout: 6e4
}).then(async (res) => {
if (res.data.status === 405) {
const { data } = await this.$axios.post(`${process.env.baseURL}/visitor/user/weixin`, {
code: res.data.result.code
})
if (data.status !== 1) {
this.$toasted.show('微信登录失败,请重试或换用其它登录方式。', {
position: 'top-center',
duration: 5000
})
return
}
window.authSuccess(data)
} else if (res.data.status === 404) {
this.getCode(uuid, res.data.result.wxErrCode)
} else if (res.data.status === 403) {
this.getCode(uuid, res.data.result.wxErrCode)
} else if (res.data.status === 500) {
this.login('weixin')
} else {
setTimeout(() => {
this.getCode(uuid)
}, 2000)
}
})
} catch (err) {
setTimeout(function () {
this.getCode(uuid)
}, 2000)
}
},
async login (type) {
this.currentLoginType = type
this.useWeixinLogin = false
if (type === 'github') {
window.open('https://github.com/login/oauth/authorize?client_id=ce1673a37333e47e482d&redirect_uri=https://www.wyr.me/auth', 'Github授权', 'scrollbars=yes,resizable=yes,status=no,location=yes,toolbar=no,menubar=no,width=800,height=600,left=0,top=0')
} else if (type === 'weixin') {
this.useWeixinLogin = true
this.commentLoading = true
try {
if (navigator.userAgent.toLowerCase().includes('micromessenger')) { // 如果在微信中打开
this.commentLoading = false
this.currentLoginType = null
this.useWeixinLogin = false
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx9d5e677f533d1e84&redirect_uri=${encodeURIComponent('https://tool.sercretcore.com/auth.php')}&response_type=code&scope=snsapi_userinfo&state=blog-${this.postID}#wechat_redirect`
} else {
const { data } = await this.$axios.get('https://weixin.openapi.site/img?appid=wx2d1d6aa2f86768d7&redirect_uri=https://wyr.me')
if (data.status === 1) {
const uuid = data.result.wxUUID
this.weixinLoginQrcode = data.result.imgData
this.getCode(uuid)
this.commentLoading = false
} else {
this.commentLoading = false
this.currentLoginType = null
this.$toasted.show('获取微信登录所需参数错误,请重试或换用其它登录方式。', {
position: 'top-center',
duration: 5000
})
}
}
} catch (err) {
this.commentLoading = false
this.currentLoginType = null
this.useWeixinLogin = false
this.$toasted.show('暂时无法使用微信登录,请重试或换用其它登录方式。', {
position: 'top-center',
duration: 5000
})
}
} else if (type === 'qq') {
window.location.href = 'https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=101000700&redirect_uri=' + decodeURIComponent('https://www.wyr.me/auth') + '&state=qq-' + this.postID
} else if (type === 'weibo') {
window.location.href = 'https://api.weibo.com/oauth2/authorize?client_id=4159322735&redirect_uri=https://wyr.me/auth&response_type=code&state=weibo-' + this.postID
} else {
this.$toasted.show('敬请期待', {
position: 'top-center',
duration: 5000
})
}
},
setCommentMap () {
const comments = this.comments
for (let i = 0; i < comments.length; i++) {
this.commentsMap.set(comments[i].comment_ID, comments[i])
if (comments[i].children) {
for (let j = 0; j < comments[i].children.length; j++) {
this.commentsMap.set(comments[i].children[j].comment_ID, comments[i].children[j])
}
}
}
},
commentSetting () {
this.form.loading = true
this.$modal.show('setting')
this.getSettings()
},
hideSettings () {
this.$modal.hide('setting')
this.form.loading = true
if (!localStorage.checkSetting) {
localStorage.checkSetting = new Date().getTime().toString()
}
},
async checkSettings () {
if (!localStorage.checkSetting) {
// 获取设置信息
const tmp = await this.$axios({
method: 'get',
url: process.env.baseURL + '/user/settings',
headers: {
Authorization: 'Bearer ' + this.visitorToken
}
})
if (tmp.status === 200 && tmp.data.status === 1 && tmp.data.result && tmp.data.result.sendPhone && tmp.data.result.sendEmail && (!tmp.data.result.phone || !tmp.data.result.email)) {
this.commentSetting()
this.$toasted.show('温馨提示:设置手机号或邮箱,可及时收到回复提醒', {
position: 'top-center',
duration: 5000
})
}
}
},
async getSettings () {
// 获取设置信息
const tmp = await this.$axios({
method: 'get',
url: process.env.baseURL + '/user/settings',
headers: {
Authorization: 'Bearer ' + this.visitorToken
}
})
if (tmp.status === 200 && tmp.data.status === 1 && tmp.data.result) {
this.form.phone = tmp.data.result.phone
this.form.sendPhone = tmp.data.result.sendPhone
this.form.email = tmp.data.result.email
this.form.sendEmail = tmp.data.result.sendEmail
this.form.loading = false
} else {
this.$toasted.show('获取设置内容失败,请重试', {
position: 'top-center',
duration: 5000
})
}
},
async saveSettings () {
// 获取设置信息
const tmp = await this.$axios({
method: 'post',
url: process.env.baseURL + '/user/settings',
headers: {
Authorization: 'Bearer ' + this.visitorToken
},
data: this.form
})
if (tmp.status === 200 && tmp.data.status === 1) {
this.hideSettings()
this.$toasted.show('保存成功', {
position: 'top-center',
duration: 5000
})
} else {
this.$toasted.show('保存设置失败,请重试', {
position: 'top-center',
duration: 5000
})
}
if (!localStorage.checkSetting) {
localStorage.checkSetting = new Date().getTime().toString()
}
}
}
}
</script>
<style lang="stylus">
$qrcode-width = 96px
$qrcode-height = 96px
$logo-width = 64px
modal
display none
.postPage
.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
.copyright
margin-top 80px
font-size 10px
color #888
.comment
width 100%
margin-top 60px
.comment-title
color #777
margin-bottom 30px
.comment-item
margin-top 10px
padding 10px 8px
.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-no-userinfo
float left
font-size 12px
line-height 50px
margin-left 10px
.comment-userinfo
width 200px
position relative
cursor pointer
user-select none
margin-top 10px
margin-left 10px
display inline-block
height 30px
.comment-avatar
position absolute
left 0
top 0
width 30px
height 30px
overflow hidden
img
border-radius 50%
width 30px
height 30px
border-style none
box-sizing border-box
margin 0
.comment-nickname
color #a1887f
margin-left 50px
height 30px
line-height 30px
font-size 12px
width 150px
overflow hidden
text-overflow ellipsis
white-space nowrap
.comment-content
width 100%
padding 5px 0
img
max-width 100%
.comment-status
color #ca7474
.comment-logout, .comment-setting
width 51px
height 100%
line-height 30px
border 1px solid #586069
font-size 12px
border-radius 2px
box-sizing border-box
display none
text-align center
float left
.comment-logout
margin-left 10px
.comment-userinfo:hover
.comment-avatar, .comment-nickname
display none
.comment-logout, .comment-setting
display block
.comment-userinfo:active
.comment-logout, .comment-setting
background-color #586069
color #fff
#editor
margin-top 10px
border-bottom-color #e6e5e5
border-bottom-style dotted
border-bottom-left-radius 0
border-bottom-right-radius 0
.comment-toolbar
width 100%
height 50px
flex-direction column
border 1px solid #d1d5da
border-radius 3px
box-sizing border-box
border-top none
border-top-left-radius 0
border-top-right-radius 0
color #586069
.comment-btn
color #586069
float right
line-height 12px
margin-top 12px
margin-right 10px
padding 6px 12px
border 1px solid #586069
box-sizing border-box
font-size 12px
cursor pointer
user-select none
border-radius 2px
.comment-btn:active
background-color #586069
color #fff
.comment-reply-commit
margin 10px 0 10px 50px
.comment-default-commit
margin-top 125px
.comment-default-commit, .comment-reply-commit
position relative
.login
position absolute
left 0
top 0
width 100%
height 100%
text-align center
line-height 1
margin auto
z-index 99
cursor pointer
user-select none
display flex
align-items center
justify-content center
.item
flex 1
display flex
width 100%
height 100%
flex-direction column
align-items center
position relative
.qrcode
width $qrcode-width
height $qrcode-height
display flex
align-items center
justify-content center
background blue
border-radius 12px
margin-bottom 10px
color #fff
overflow hidden
cursor pointer
position absolute
z-index 1000
top 'calc(50% - %s / 2)' % $qrcode-height
.logo
background-color #f5f4f4
width 64px
height 64px
border-radius 50%
color #fff
user-select none
cursor pointer
display flex
align-items center
justify-content center
position absolute
z-index 1000
top 'calc(50% - %s / 2)' % $logo-width
transition transform 0.2s cubic-bezier(0.075, 0.82, 0.165, 1), top 0.2s cubic-bezier(0.075, 0.82, 0.165, 1)
&.active
top 'calc(50% + %s / 2 - %s / 4 + 5px)' % ($qrcode-height $logo-width)
transform scale(0.5)
&.loading
animation spin 1s linear infinite
&:hover
background-color #fff
.qrcode-enter, .qrcode-leave-to
transform scale(0)
.qrcode-enter-active, .qrcode-leave-active
transition transform 0.5s cubic-bezier(0.075, 0.82, 0.165, 1)
.qrcode-enter-to
transform scale(1)
@keyframes spin
from
transform rotate(0)
to
transform rotate(360deg)
.login:hover
background-color #fff
.login:before
content ''
position absolute
background rgba(0, 0, 0, 0.1)
top 0
left 0
width 100%
height 100%
filter blur(5px)
z-index 9
#editor-reply
margin 0
.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
font-size 12px
position fixed
width 15%
top 0
right 0
overflow-y auto
background-color #000
padding 10px
box-shadow 0 0 8px #000
z-index 99999
padding-bottom 20px
min-width 240px
max-width 300px
.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
margin-top 6px
.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 30px
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
.vditor--fullscreen
z-index 9999
.modal-setting
z-index 99999
.vue-modal-content
padding 10px 20px 10px 10px
box-sizing border-box
.vue-modal-title
text-align center
margin-top 5px
margin-bottom 10px
font-weight 500
.el-form-item
margin-bottom 10px
.vue-modal-buttons
display flex
flex 0 1 auto
width 100%
border-top 1px solid #eee
box-sizing border-box
text-align center
bottom 0
position absolute
.vue-modal-button
font-size 12px !important
background transparent
padding 0
margin 0
border 0
cursor pointer
box-sizing border-box
line-height 40px
height 40px
color inherit
font inherit
outline none
text-align center
width 100%
bottom 0
@media (max-width 768px)
.postPage
.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>