在接触vue
之后,就一直想用vue把原来老旧的博客(基于jquery
和php
)重新搭一遍,在摸了几个月后,总算摸出来了?.前端部分只涉及到vue
,关于koa2
和mongodb
请移步到后端部分.
博客地址:
dawkey.top
推荐在PC端访问,移动端做了相关兼容,不过在PC端上效果要更好.
github地址:
github.com/Dawkey/dkyS…
1. 技术栈:
vue
,vuex
,vue-router
(vue
的全家桶走一下流程);axios
发送ajax请求(这个也不用多说);marked
把markdown
转为html
(比自己瞎写的md-html
好用多了);highlight
代码语法高亮;
基本上就是搭建博客常用的一些库.
2. 博客页面:
我要开始甩图片了?
主页:
分类:
归档:
更新日志:
博客文章:
3. 博客的后台管理:
后台管理界面也是集成在前端部分的,后端部分只负责处理数据就行了.
想要通过浏览器看一下后台界面的朋友,可以通过
login
界面的visit
按钮(不需要账号密码),直接进入后台管理哦,不过没有相关权限就是了?(移动端没有login
选项)
管理界面:
添加博客到草稿箱:
修改和发布博客:
删除草稿和博客:
新增和删除分类:
新增和删除更新日志:
4. 博客搭建相关的
发了这么多演示图,还没有些什么实质性的东西.
主要记录一下博客搭建中的遇到的一些问题:
4.1 搭建的思路:
博客做的是单页面的,所以我们所以的数据获取都是通过ajax
的.
我的思路是在页面加载时,就请求一个main
的关键数据,它是一个数组,其中包含着每篇博客的标题,标签,分类,时间,描述,以及关键的id
号(这里id
号在请求博客文章数据时会用到).
对这一个数组数据用js
进行加工,我们就能得到了home(首页)
,tag(标签)
,classify(分类)
,archive(归档)
这四个页面(也是一般博客最基础的)所需要的数据,把这些数据存入到vuex
中.
这样,单页面较多页面的优势就体现出来了,我们只请求了一次数据,之后,我们再访问上面提到的四个页面,就再也不会请求任何数据,甚至断网也能正常访问(除非刷新页面)
当我们想要查看一篇博客文章的具体内容时,点击之后,根据前面提到的对应的id
号,就会通过ajax
请求对应的文章数据,最终显示浏览器上就完事了.
至于还有一个update(更新日志)
,这个在访问它时,单独请求数据就行了.
原理上可以说是非常简单?
4.2 页面间的切换:
因为做的是单页面,页面间的切换是不可避免的,在写页面切换时,我尝试了很多种动画切换(本人是极端的外观党?),最终还选择了现在这种比较传统的方式.
具体的实现方式是,使用vue-router
中的导航守卫中的beforeEach
(具体查看vue-router的文档),在每次切换路由时,都先显示一段时间的加载动画,之后才显示出页面,传统,但是实在没想出或者说实现出什么炫酷的切换方式.
4.3 marked
和highlight
实现的文本编辑:
这两哥们应该是搭建博客系统时,文本编辑的标配:
关于这两者的使用网上也是有很多了,这里我主要记录一下我在使用这两者时,遇到的一些坑,个人的一些理解,方便同样想要搭建自己博客系统的朋友使用.
4.3.1 marked
和highlight
组合达到语法高亮:
这个问题应该是首要的,博客文章代码不高亮,干巴巴一片,就太丑了
其实就是要用到marked
的renderer
,直接看代码:(终于要上代码了?)
import marked,{Renderer} from "marked";
import hljs from "highlight.js";
const renderer = new Renderer();
renderer.code = (code,language) => {
if(!language){
language = "code";
}
let lang_is_valid = (language !== "code" && hljs.getLanguage(language));
let highlighted = lang_is_valid ? hljs.highlight(language, code).value : code;
return `<pre><div class="language">${language}</div><code class="hljs ${language}">${highlighted}</code></pre>`;
};
marked.setOptions({renderer});
复制代码
我们先把工作分配明确,在实现语法高亮时,highlight
负责把代码字符串转换为具有语法高亮结构的html
字符串,marked
只负责告诉highlight
这串代码用的什么编程语言.
好了,接着看上面的代码,marked
的renderer
适用于我们来DIY它最终生成的html
代码,代码中的renderer.code
自然指的是最终生成的代码相关的html
.
它是一个函数,这里我们可以理解为要重写这个函数,这个函数最终调用时,会传入两个参数,第一个code
是代码字符串,第二个language
是代码的编程语言.
代码中的hljs.highlight(language, code).value
就是最终根据marked
提供的两个参数值,所生成的具有代码高亮结构的html
字符串.
再来看最终的return
值,我们可以注意到code
标签里面class
值是"hljs空格+编程语言"
,这个class
格式是必须的,以告诉highlight
最终怎么高亮.
以上工作做完之后,我们marked()
返回的就是具有高亮代码格式的html
字符串,当然前提是我们有引入highlight
提供的css
,最终我们才能看到高亮的代码.
marked
是怎么知道我们的代码是什么编程语言?,好吧,是我当时孤陋寡闻了?,使用栅栏代码块来写代码,让我们来告诉marked
我们的语言.
4.3.2marked
生成的链接能跳转到新的标签:
默认情况先,
marked
生成的a
标签是在当前页跳转的,同样用renderer
我们可以适当修改一下就好了:
renderer.link = (href,title,text) => {
return `<a href="${href}" target="_blank">${text}</a>`;
}
复制代码
4.4 Mayuri开口说话:
Mayuri指的就是博客左上角的那个动漫头像,在最开始搭建博客时,我就已经想好要做这个了,当时还准备做几个表情,截了相关的图,不过因为暑假摸了太久,导致目前博客上线时,还只有这个初始的表情.
将来也许可能会新增几个表情吧?
4.4.1 嘴部动画
如果你不仔细看,可能不会发现在出现消息文字时,Mayuri的嘴部是在动的.
其实就是三张图片之间在互相切换,因为本人没有一点动漫制作的相关知识,所写这个css
的动画完全是凭感觉(瞎)写的,最终的效果还行吧,不过还是有一点小瑕疵的?.
因为只涉及到一点css3
的知识,这里就不贴代码了.
4.4.2 文字动画
打字动画的实现,网上讲述的也不少了,但是,我还是想结合我的项目写一写,对这个不感兴趣的朋友可以直接跳过.
打字动画用js
实现效果肯是要比css
要好的,本质上就是,通过不断的更换一个元素的innerHTML
来达到打字的效果.
这里我用到了Promise
链,当时刚刚看了promise
,就用了,不知道有没有把这个问题复杂化?.
贴代码:
export default function type(el,word){
let word_array = word.split("");
let promise = Promise.resolve();
word_array.reduce((meno,value,index)=>{
let str = meno + value;
if(index === 1){
promise = type_timer(el,meno);
}
promise = promise.then(()=>{
return type_timer(el,str);
});
return str;
});
return promise;
}
//生成新的promise,串成promise链
function type_timer(el,str){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
el.innerHTML = str;
resolve();
},115);
});
}
复制代码
这里我们export
出了type
方法,type
方法,第一个参数是输出文字元素对象,第二个参数是输出的文字,执行type(el,word),就能实现打字效果了.
具体看代码,type_timer
生成一个含有setTimeout
的Promise
,我们对输出的文字进行分割,得到word_array
数组,再用数组的reduce
方法串出一条Promise
链;
使用Promise
链的好处就是,我们可以通过then
很好的知道什么时候打字动画结束了.
对话之间的冲突?
有时候,我们一条消息对话还没有打完,下一条消息就产生了,这时,肯定会产生两条Promise
链作用于同一个元素,这就发生了冲突.
这时,我们有两种选择:
- 等前一条
Promise
链执行完,再执行下一条; reject
当前Promise
链,执行下一条.
我选择了第二种做法,因而需要在上面的代码上稍作修改:
export default function type(el,word,$store){
let word_array = word.split("");
let promise = Promise.resolve();
word_array.reduce((meno,value,index)=>{
let str = meno + value;
if(index === 1){
promise = type_timer(el,meno,$store);
}
promise = promise.then(()=>{
return type_timer(el,str,$store);
});
return str;
});
return promise;
}
//生成新的promise,串成promise链
function type_timer(el,str,$store){
return new Promise((resolve,reject)=>{
//如果talk_flag === false,则reject,以防止生成多条promise链产生冲突.
if($store.getters["talk_flag"] === false){
reject("talk_is_break");
}
setTimeout(()=>{
el.innerHTML = str;
resolve();
},115);
});
}
复制代码
用了vuex
?,在vuex
中存储一个变量talk_flag
,在执行type
方法时,传入第三个参数,vuex
的$store
对象.
每次我们创建新的消息对话时,先把vuex
中的talk_flag
设为false
,保证,先前的Promise
链一定会断掉,而在Promise
链断后,调用的type
方法就会catch
到reject
,在catch
中,我们再重新把talk_flag
设为true
,保证新的Promise
链能顺利执行.
好吧,写到这里我突然意识到,根本不需要用到
vuex
,一个普通的对象就行了,当时编写时,可能觉得vuex
对象更厉害吧?.
当然,如果有的朋友有更好的实现手段可以和我交流(应该没人有耐心看完这段碎碎念吧~)
5. 兼容性
- Chrome上效果很好,Firefox上效果一般,ie上效果未知(并不想测试~)
- 移动端布局做了相关自适应,不过效果不是太满意,后面可能会考虑更好的适配一下移动端吧.
6. 写在最后
不知不觉居然写了这么多,算是这几个月的成果的一个总结,不管怎样,接着努力吧,当然更重要的是希望能对和我一样,想要亲手搭建一个属于自己博客的朋友有所帮助吧.
如果你有耐心看到这里,不防?这里点个star⭐吧,也算是对我的一点小小的鼓励?
7. TODO
不存在的?
- 移动端更好的视觉效果;
- 文章新增锚点列表;
- 文本编辑时,tab等键位能有对应的作用,更好输入体验;
- 把旧博客的日记功能也加上.