基于阿里egg框架搭建博客(6)——瀏覽威始、發(fā)表文章

相關文章

基于阿里egg框架搭建博客(1)——開發(fā)準備
基于阿里egg框架搭建博客(2)——Hello World
基于阿里egg框架搭建博客(3)——注冊與登錄
基于阿里egg框架搭建博客(4)——權限控制
基于阿里egg框架搭建博客(5)——置頂導航條
基于阿里egg框架搭建博客(6)——瀏覽、發(fā)表文章
基于阿里egg框架搭建博客(7)——編輯文章

git

https://github.com/ZzzSimon/egg-example
喜歡就點個贊吧镰绎!

正文

瀏覽跟狱、發(fā)表文章簡單來講就是對article表的讀/寫操作驶臊。

Article表設計

字段說明

名稱 解釋
id 主鍵id
title 文章標題
url 文章訪問path
detail 文章內容
author 作者扛门,對應username
invisible 是否保密论寨,保密則不顯示在文章列表
create_time 文章第一次發(fā)表時間
update_time 文章最后一次修改時間

sql腳本

DROP TABLE IF EXISTS `article`;
CREATE TABLE `article` (
  `id` varchar(20) NOT NULL,
  `title` varchar(255) NOT NULL,
  `url` varchar(255) NOT NULL,
  `detail` varchar(4096) NOT NULL,
  `author` varchar(255) NOT NULL,
  `invisible` int(1) NOT NULL DEFAULT '0',
  `create_time` datetime NOT NULL,
  `update_time` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

頁面設計

瀏覽文章


發(fā)表文章

功能設計

瀏覽文章

  1. 點擊文章標題查看文章詳細內容

發(fā)表文章

  1. 輸入文章標題
  2. 選擇是否保密葬凳,保密則不顯示在文章列表
  3. 保存文章
  4. 支持markdown

markdown支持

對于前端md編輯器,我們選擇了Editor.md火焰。

官方文檔:http://pandao.github.io/editor.md/

前端代碼

瀏覽文章

list.tpl 文章列表

我們創(chuàng)建app/view/article/list.tpl文件:

{% extends "parent.tpl" %}

{% block head %}
<title>文章列表</title>
{% endblock %}


{% block content %}
<ul class="article-view view">
    {% for item in list %}
    <li class="item">
        <dl>
            <dt><a href="{{ item.url }}">{{ item.title }}</a></dt>
            <dd><small>{{item.author}}</small> 最后更新于 {{helper.formatTime(item.update_time)}}</dd>
        </dl>
    </li>
    {% endfor %}
</ul>
{% endblock %}

detail.tpl 文章詳情

我們創(chuàng)建app/view/article/detail.tpl文件:

{% extends "parent.tpl" %}

{% block head %}
<title>{{article.title}}</title>
<link rel="stylesheet" href="/public/editormd/editormd.css">
<script src="/public/editormd/lib/marked.min.js"></script>
<script src="/public/editormd/lib/prettify.min.js"></script>
<script src="/public/editormd/lib/raphael.min.js"></script>
<script src="/public/editormd/lib/underscore.min.js"></script>
<script src="/public/editormd/lib/sequence-diagram.min.js"></script>
<script src="/public/editormd/lib/flowchart.min.js"></script>
<script src="/public/editormd/lib/jquery.flowchart.min.js"></script>
<script type="text/javascript" src="/public/editormd/editormd.js"></script>
{% endblock %}


{% block content %}
<div class="page-header">
    <h1>{{article.title}} <small style="font-size: small">{{article.author}} 最后更新于 {{helper.formatTime(article.update_time)}}</small></h1>
</div>

<div id="detail" style="visibility: hidden">{{article.detail}}</div>
<div id="layout">
    <div id="test-editormd-view">

    </div>
</div>
{% endblock %}

{%block script%}
<script type="text/javascript">
    $(function () {
        const markdown = $('#detail').text();
        var testEditormdView = editormd.markdownToHTML("test-editormd-view", {
            markdown: markdown,//+ "\r\n" + $("#append-test").text(),
            //htmlDecode      : true,       // 開啟 HTML 標簽解析,為了安全性谦疾,默認不開啟
            htmlDecode: "style,script,iframe",  // you can filter tags decode
            //toc             : false,
            tocm: true,    // Using [TOCM]
            //tocContainer    : "#custom-toc-container", // 自定義 ToC 容器層
            //gfm             : false,
            //tocDropdown     : true,
            // markdownSourceCode : true, // 是否保留 Markdown 源碼,即是否刪除保存源碼的 Textarea 標簽
            emoji: true,
            taskList: true,
            tex: true,  // 默認不解析
            flowChart: true,  // 默認不解析
            sequenceDiagram: true,  // 默認不解析
        });

    });
</script>
{% endblock %}

此處需要注意1點:
md的內容先通過模板樊诺,渲染在一個隱藏的div中词爬。之后顿膨,通過editormd動態(tài)渲染出來恋沃。

發(fā)表文章

article.tpl 發(fā)表文章

我們創(chuàng)建app/view/article/article.tpl文件:

{% extends "parent.tpl" %}

{% block head %}
<title>Markdown Editor</title>
<link rel="stylesheet" href="/public/editormd/editormd.css">
<script type="text/javascript" src="/public/editormd/editormd.js"></script>
{% endblock %}

{% block content %}

<div class="row">
    <div class="form-group">
        <label for="title">文章標題:</label>
        <input id="title" type="text" class="form-control">
    </div>
    <div class="checkbox ">
        <label>
            <input id="invisible" type="checkbox">保密(勾選后將<strong style="color: red">不顯示</strong>在文章列表)
        </label>
    </div>
    <div class="form-group pull-right">
        <button id="save" class="btn btn-success ">保存</button>
    </div>
</div>
<div class="row">
    <div id="layout">
        <div id="test-editormd"></div>
    </div>
</div>
{% endblock %}


{% block script %}
<script type="text/javascript">

    let testEditor = editormd("test-editormd", {
        width: "100%",
        height: 740,
        path: '/public/editormd/lib/',
        // theme: "dark",
        // previewTheme: "dark",
        // editorTheme: "pastel-on-dark",
        // markdown: md,
        codeFold: true,
        //syncScrolling : false,
        saveHTMLToTextarea: true,    // 保存 HTML 到 Textarea
        searchReplace: true,
        //watch : false,                // 關閉實時預覽
        htmlDecode: "style,script,iframe|on*",            // 開啟 HTML 標簽解析,為了安全性梅割,默認不開啟
        //toolbar  : false,             //關閉工具欄
        //previewCodeHighlight : false, // 關閉預覽 HTML 的代碼塊高亮户辞,默認開啟
        emoji: true,
        taskList: true,
        tocm: true,         // Using [TOCM]
        tex: true,                   // 開啟科學公式TeX語言支持刃榨,默認關閉
        flowChart: true,             // 開啟流程圖支持枢希,默認關閉
        sequenceDiagram: true,       // 開啟時序/序列圖支持晴玖,默認關閉,
        //dialogLockScreen : false,   // 設置彈出層對話框不鎖屏,全局通用敬察,默認為true
        //dialogShowMask : false,     // 設置彈出層對話框顯示透明遮罩層莲祸,全局通用,默認為true
        //dialogDraggable : false,    // 設置彈出層對話框不可拖動缴阎,全局通用蛮拔,默認為true
        //dialogMaskOpacity : 0.4,    // 設置透明遮罩層的透明度建炫,全局通用肛跌,默認值為0.1
        //dialogMaskBgColor : "#000", // 設置透明遮罩層的背景顏色,全局通用西饵,默認為#fff
        imageUpload: true,
        imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
        imageUploadURL: "/edit/uploadPic?_csrf={{ ctx.csrf | safe }}",

   /*  后端需返回:   {
            success : 0 | 1, //0表示上傳失敗;1表示上傳成功
            message : "提示的信息",
            url     : "圖片地址" //上傳成功時才返回
        }*/
        onload: function () {
            console.log('onload', this);
            //this.fullscreen();
            //this.unwatch();
            //this.watch().fullscreen();

            //this.setMarkdown("#PHP");
            //this.width("100%");
            //this.height(480);
            //this.resize("100%", 640);
        }
    });

    $('#save').bind('click', function () {
        data = {
            article: {
                title: $('#title').val(),
                detail: testEditor.getMarkdown(),
                invisible: $('#invisible').prop('checked')  ? 1:0
            }
        };

        $.post('/edit/save?_csrf={{ ctx.csrf | safe }}', data, function (resp) {
            if (resp.flag === '1') {
                window.location.href = resp.url;
            }
        })
    })


</script>
{% endblock %}

此處需要注意1點:
ajax默認是不重定向的期虾,所以當保存成功,我們需要返回文章的訪問url茂蚓,在回調函數(shù)里重定向聋涨。

后端代碼

ArticleController

我們創(chuàng)建app/controller/article.js文件:

const Controller = require('egg').Controller;

class ArticleController extends Controller {
    async list() {
        const ctx = this.ctx;
        const articleList = await ctx.service.article.list();
        await ctx.render('article/list.tpl', { list: articleList });
    }

    async detail(){
        const ctx = this.ctx;
        const queryRes = await ctx.service.article.detail(ctx.params.id);
        ctx.logger.info(queryRes);
        await ctx.render('article/detail.tpl', { article: queryRes[0] });
    }
}

module.exports = ArticleController;

EditController

我們創(chuàng)建app\controller\edit.js文件:

const Controller = require('egg').Controller;
const fs = require('mz/fs');


class EditController extends Controller{
    async editHtm(){
        await this.ctx.render('article/edit.tpl');
    }
    async save(){
        const ctx = this.ctx;
        const article = ctx.request.body.article;
        article.id = ctx.helper.uuid();
        article.url = '/article/'+article.id+'.htm';
        article.author = ctx.session.user.username;
        const nowTime = new Date();
        article.create_time = nowTime;
        article.update_time = nowTime;
        const result = await ctx.service.article.save(article);
        if (result) {
            ctx.body = {flag:'1',msg:'保存成功',url:article.url}
        }else {
            ctx.body = {flag:'0',msg:'保存失敗'}
        }
    }

    async uploadPic(){
        const { ctx } = this;
        const file = ctx.request.files[0];
        let filenameNew = ctx.helper.uuid() +'.'+  file.filename.split('.').pop();
        let filepathNew = this.config.baseDir+'\\app\\public\\mdPic\\'+filenameNew;
        //把臨時文件剪切到新目錄去
        await fs.rename(file.filepath, filepathNew);
        //按editormd要求格式返回
        ctx.body = {
            success : 1, //0表示上傳失敗;1表示上傳成功
            message : "上傳成功",
            url     : filepathNew.split(this.config.baseDir+'\\app')[1] //上傳成功時才返回
        }
    }
}

module.exports = EditController;

此處需要注意1點:

  1. uoloadPic方法主要用于md編輯器的圖片上傳茂腥。

ArticleService

我們創(chuàng)建app/service/article.js文件:

const Service = require('egg').Service;

class ArticleService extends Service {
    async list() {
        const sql = "SELECT url,title,author,update_time FROM article WHERE invisible = 0";
        const list =await this.app.mysql.query(sql);
        return list;
    }

    async detail(id = 1){
        const sql = "SELECT title,detail,author,update_time FROM article WHERE id = ?";
        return await this.app.mysql.query(sql,[id])
    }

    async save(article = {}){
        const res = await this.app.mysql.insert('article',article);
        return res.affectedRows === 1;
    }


}

module.exports = ArticleService;

router.js

我們往 app/router.js中添加一下內容:

router.get('/edit.htm',controller.edit.editHtm);
router.get('/article/:id.htm',controller.article.detail);
router.get('/articleList.htm', controller.article.list);

router.post('/edit/save',controller.edit.save);
router.post('/edit/uploadPic',controller.edit.uploadPic);

結尾

如果看完覺得有用朝捆,請給作者一個喜歡吧右蹦!謝謝啦何陆!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市铝穷,隨后出現(xiàn)的幾起案子曙聂,更是在濱河造成了極大的恐慌宁脊,老刑警劉巖稳衬,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異街夭,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凡人,你說我怎么就攤上這事“痘蓿” “怎么了启上?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵按摘,是天一觀的道長兴使。 經常有香客問我发魄,道長汰寓,這世上最難降的妖魔是什么有滑? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任苛秕,我火速辦了婚禮吼驶,結果婚禮上蟹演,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好苟弛,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般滚婉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骇窍,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天驱犹,我揣著相機與錄音蛔钙,去河邊找鬼桑涎。 笑死攻冷,一個胖子當著我的面吹牛里烦,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了庭猩?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤嘁信,失蹤者是張志新(化名)和其女友劉穎潘靖,沒想到半個月后穿剖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡卦溢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年糊余,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片单寂。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡贬芥,死狀恐怖,靈堂內的尸體忽然破棺而出宣决,到底是詐尸還是另有隱情蘸劈,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布尊沸,位于F島的核電站威沫,受9級特大地震影響,放射性物質發(fā)生泄漏洼专。R本人自食惡果不足惜棒掠,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屁商。 院中可真熱鬧句柠,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谜酒,卻和暖如春叹俏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背僻族。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工粘驰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人述么。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓蝌数,卻偏偏與公主長得像,于是被迫代替她去往敵國和親度秘。 傳聞我的和親對象是個殘疾皇子顶伞,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354