简单作个博客。
功能点:
注册、登陆、cookie、权限控制、文章列表、文章详情、文章目录、点赞、评论、分页加载css
npm install -g @vue/cli vue create hello-world
为了避开烦人的 eslint,选择了手动选择特性:
同时没有选择 Linter/Formatter,使用 vscode 中的插件 prettier 和 vetur 配合格式化代码。html
参考了vue中Axios的封装和API接口的管理,对网络请求进行了发封装。
网络请求统一放在/src/request
中,config.js
中是基本配置信息:http.js
中封装请求拦截、响应拦截、错误统一处理。api.js
中是网站全部接口,将其导出后,挂载到 vue.prototype.$api
上,这样全局可使用 this.$api.xxx
使用接口:
而后在main.js
引入并挂载:前端
博客采用cookie/session的方式来记录会话状态,在app.vue
的created
生命周期函数中经过checkLogin()
接口查询用户登陆状态,具体的逻辑为:vue
let documentEle = document.documentElement let needLoadMore = documentEle.scrollTop + documentEle.clientHeight + 50 > documentEle.scrollHeight; if (needLoadMore && !this.nomore) { this.loadingMore = true; //暂时不能再滚动加载数据 this.nomore = true; this.loadMoreArticle(); }
document.documentElement.scrollTop
:文档滚动的距离document.documentElement.clientHeight
:文档在可视范围内的高度document.documentElement.scrollHeight
:文档总高度java
判断思路是:视口的高度 + 文档的滚动距离 >= 文档总高度,能够预留50的距离作预加载。前端每一次判断触底后,会向后端请求下一页数据,并返回 nomore
字段,表示是否还有未加载文章。这里有个细节是:每一次触底后手动将 nomore
设为 true
,后端返回 nomore
后再修改其值,这样作的目的是防止在请求返回前再次发送请求。
参考:搞清clientHeight、offsetHeight、scrollHeight、offsetTop、scrollTopmysql
没有实际作博客以前,觉得每一篇博文都是经过单独写html标签的方式排版的...而后经过别人的项目,发现了正确的作法是:
使用markdown/富文本编辑器编辑文章,生成html
片断,在vue中使用v-html
语法插入该html
片断,文章便以html
标签的形式渲染出来可。博客中使用的markdown编辑器是mavonEditor,另外使用highlightjs
高亮代码。linux
<div v-html="articleInfo.contentHtml" class="article-container" ref="content"></div>
extractCatalog() { let contentElementRef = Array.from( this.$refs.content.querySelectorAll("h1,h2,h3,h4,h5,h6") ); contentElementRef.forEach((item, index) => { item.id = item.localName + "-" + index; this.catalogList.push({ tagName: item.localName, href: `#${item.localName}-${index}`, text: item.innerText }); }); }
上一小节中,将文章的html
片断渲染到了div
容器元素中,接下来可使用querySelectorAll("h1,h2,h3,h4,h5,h6")
函数来找出文中全部的标题Dom节点,querySelectorAll()
的好处在于它会按照传入参数的顺序进行查找,因此不用担忧乱序。获取到目录后对各级目录编号,以便生成锚点进行跳转。ios
watchPageScrollFunc() { let contentElementRef = Array.from( this.$refs.content.querySelectorAll("h1,h2,h3,h4,h5,h6") ); for (let index = 0; index < contentElementRef.length; index++) { const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; const elementHeight = contentElementRef[index].clientHeight; const el = contentElementRef[index]; const top = el.getBoundingClientRect() && el.getBoundingClientRect().top + elementHeight; if (top <= viewPortHeight && top > 0) { this.firstVisibleElemetHref = this.catalogList[index].href; break; } } } window.addEventListener("scroll", this.watchPageScrollFunc);
判断方法:元素上边到视口上边的距离 + 元素自身高度 <= 视口高度 && 元素上边到视口上边的距离 + 元素自身高度 > 0
其中,获取元素到视口上边的距离用到:getBoundingClientRect()
,某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性:nginx
rectObject = object.getBoundingClientRect(); rectObject.top // 元素上边到视窗上边的距离; rectObject.right // 元素右边到视窗左边的距离; rectObject.bottom // 元素下边到视窗上边的距离; rectObject.left // 元素左边到视窗左边的距离;
在整个文章的Dom结构中,使用querySelectorAll("h1,h2,h3,h4,h5,h6")
去搜索全部标题类的Dom节点,每一次滚动都去依次动态检查这些标题元素距离视口上方的距离,找到第一个出如今视口中的Dom元素就返回。另外加上节流函数可优化性能。git
评论数据采用的数据结构以下,这里只作到了二级评论,数据结构定下来,具体的实现仍是比较简单的。
let comments = [ { author: "admin", content: "留言1", articleId: "9", time: "2020-04-12 10:59", id: "0", replyList: [ { author: "ghm", content: "回复留言1", articleId: "9", replyTo: "admin", time: "2020-04-12 10:59", id: "0-0" } ] }, { author: "admin", content: "留言2", articleId: "9", time: "2020-04-12 10:59", id: "1", replyList: [ { author: "ghm", content: "回复留言2", articleId: "9", replyTo: "admin", time: "2020-04-12 11:00", id: "1-0" } ] } ];
初版的实现方式:在<input>
中直接插入emoji
的unicode
,展现效果以下所示:
这样作的问题在于:展现效果没有图片好,而且在不一样的浏览器中会展示出不一样的效果。果断换用图片形式展现表情,可是<input>
是不能插入<img>
标签的,因而参考了掘金的实现方式,使用contenteditable
这个css属性将普通Dom元素变为可编辑的Dom元素,这样就可使用appendChild()
的方式将<img>
插入,最终的显示效果也是明显优于直接使用emoji
的。
<div class="textarea" ref="inputContent" contenteditable="true" autocomplete="off" :placeholder="placeholder" draggable="false" spellcheck="false" ></div>
1.一台linux服务器
做者买的阿里云最便宜的ECS云服务器,配置为1核2G,1M带宽,做为学习使用彻底够了,操做系统选择的CentOS。而后参照阿里云给出的教程在服务器上部署NodeJs环境,部署mysql。
2.购买域名
在阿里云上购买域名后,按照新手引导设置域名解析,同时设置 www 和 @,网站即可经过 www.xxx.com 和 xxx.com 访问。同时,想要使用域名访问网站,还须要对域名进行备案。
3.本地数据库迁移到云服务器
做者使用的数据库可视化工具是navicat,在navicat中对选中的数据库作转储SQL文件处理,再在云服务器的mysql中新建数据库,运行此SQL文件,便完成了数据库的迁移。
1.下载文件传输工具 Xftp
2.部署前端代码
npm run build
打包好后,使用 Xftp 将 dist
文件夹上传到服务器中
3.部署后端代码
将后端代码 git clone
到服务器中
npm i npm start
1.安装nginx
yum install nginx
安装好的 nginx 会在 /etc/nginx
中
2.使用 Xftp 修改 nginx 配置
找到/etc/nginx
目录下的 nginx.conf
文件,使用记事本打开编辑:
user root; worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; root /root/project/dt_blog/frontend/dist/; index index.html; add_header Access-Control-Allow-Origin *; location /api { proxy_pass http://127.0.0.1:7001; proxy_set_header HOST $host; } location / { try_files $uri $uri/ @router; index index.html; } location @router { rewrite ^.*$ /index.html last; } } }
其中这两个配置很重要:
location / { try_files $uri $uri/ @router; index index.html; } location @router { rewrite ^.*$ /index.html last; }
网站部署好后,能够正常访问,可是打开二级页面后再刷新,就会404,缘由是:v-router设置的路径并非真实存在的路由,工程中的路由跳转都是经过 js 实现的,而将前端打包好的 dist
目录部署在服务器后,在浏览器中访问根路径会默认打到 index.html 中,可是直接访问其余路径,就没有一个真实的路径与其对应。(参考:https://www.cnblogs.com/kevingrace/p/6126762.html)
作好以上配置后,就能够经过 服务器ip:80
来访问部署好的网站了,可是一直经过 ip 地址访问网站不太合理,那么就须要给网站配置域名。