美文网首页程序员
基于阿里egg框架搭建博客(6)——浏览、发表文章

基于阿里egg框架搭建博客(6)——浏览、发表文章

作者: 妖云小离 | 来源:发表于2019-03-19 18:00 被阅读0次

    相关文章

    基于阿里egg框架搭建博客(1)——开发准备
    基于阿里egg框架搭建博客(2)——Hello World
    基于阿里egg框架搭建博客(3)——注册与登录
    基于阿里egg框架搭建博客(4)——权限控制
    基于阿里egg框架搭建博客(5)——置顶导航条
    基于阿里egg框架搭建博客(6)——浏览、发表文章
    基于阿里egg框架搭建博客(7)——编辑文章

    git

    https://github.com/ZzzSimon/egg-example
    喜欢就点个赞吧!

    正文

    浏览、发表文章简单来讲就是对article表的读/写操作。

    Article表设计

    字段说明

    名称 解释
    id 主键id
    title 文章标题
    url 文章访问path
    detail 文章内容
    author 作者,对应username
    invisible 是否保密,保密则不显示在文章列表
    create_time 文章第一次发表时间
    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;
    
    

    页面设计

    浏览文章


    发表文章

    功能设计

    浏览文章

    1. 点击文章标题查看文章详细内容

    发表文章

    1. 输入文章标题
    2. 选择是否保密,保密则不显示在文章列表
    3. 保存文章
    4. 支持markdown

    markdown支持

    对于前端md编辑器,我们选择了Editor.md

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

    前端代码

    浏览文章

    list.tpl 文章列表

    我们创建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 文章详情

    我们创建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动态渲染出来。

    发表文章

    article.tpl 发表文章

    我们创建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,在回调函数里重定向。

    后端代码

    ArticleController

    我们创建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

    我们创建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

    我们创建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);
    

    结尾

    如果看完觉得有用,请给作者一个喜欢吧!谢谢啦!

    相关文章

      网友评论

        本文标题:基于阿里egg框架搭建博客(6)——浏览、发表文章

        本文链接:https://www.haomeiwen.com/subject/fmhnmqtx.html