在接觸vue
之後,就一直想用vue把原來老舊的部落格(基於jquery
和php
)重新搭一遍,在摸了幾個月後,總算摸出來了?.前端部分只涉及到vue
,關於koa2
和mongodb
請移步到後端部分.
部落格地址:
dawkey.top
推薦在PC端訪問,移動端做了相關相容,不過在PC端上效果要更好.
github地址:
github.com/Dawkey/dkyS…
後端部分:
github.com/wwk321/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等鍵位能有對應的作用,更好輸入體驗;
- 把舊部落格的日記功能也加上.