导航

  • 引言
  • 需求梳理
  • 页面的划分
  • 文章列表
    • 定义空表格
    • 定义表头
    • 定义表体
  • 文章新增和编辑
    • 常见的编辑器
    • 集成editor.md插件
  • 其他
  • 结语
  • 参考


引言

伴随着ChatGPT的横空出世,AI的落地应用场景逐渐浮现,各种GPT相关的应用如雨后春笋般出现。

但是,我们知道大模型的回答是比较宽泛的,要想GPT真正成为某个领域的专家,其实还是需要给它"挂载"行业的知识库。

而我们的博客文章无疑是一个极佳的知识库。

管理好这些知识库,不断丰富和完善它,就是我们不断成长的过程。

在前面的系列中我们完成了管理端模板的打造:

这就相当于大楼的地基和框架都已经好了,接下来就可以在此基础上修建不同的房间了。

今天,我们就一起来讲讲聊聊如何实现博客文章的管理。

需求梳理

常见的文章管理的功能:

  • 新增文章
  • 修改文章
  • 查看文章详情
  • 文章列表(支持查询)

但是,为了让用户体验更好,通常我们会使用三方或者自定义组件来让内容的呈现更加美观,编辑更加方便。

页面的划分

4个需求正好对应4个功能。那么,如何拆分页面呢?或者说完成这些功能需要几个页面呢?

在拆分页面前,让我们再来回顾一下前面系列中讲过的MVC模式的知识——《MVC架构》

  • MVC思想体现了“关注点分离”这一基本方针,它将一个人机交互的应用涉及的功能分为Model、Controller、View三部分,他们各自的职责如下。

  • Model是对应用状态和业务功能的封装,我们可以将它理解为同时包含数据和行为的领域模型(Domain Model),Model接收Controller的请求并完成相应的业务处理,在应用状态改变的时候可以向View发出相应的通知。

  • View实现可视化界面的呈现并最终捕获用户的交互操作(如鼠标、键盘操作);
    View捕获到用户的交互操作后直接发给Controller,Controller完成相应的UI逻辑。如果需要涉及业务功能的调用,Controller会直接调用Model.在完成UI处理之后,Controller会更加需要控制原View或者创建新的View对用户交互操作予以响应。

根据这个MVC的理论,我们将这些功能拆分成4个页面实现。


页面如下:

  • index.html 文章列表+查询
  • create.html 新增文章
  • modify.html 文章修改
  • detail.html 文章详情

文章列表

从功能定位上来看,这个页面更像是一个检索页面或者目录页面,主要是方便用户快速定位某篇文章。

所以,有几个注意点:

  • 不需要将文章的所有字段都展示出来,而是有选择的将部分重要字段进行展示,保持页面简洁
  • 增加、修改、删除的功能按钮都在这个页面展示,这也是约定俗成的布局方式
  • 支持条件查询+分页

文章列表的代码实现方式在《文章分页》章节有比较详细的介绍。

这里着重分享一下列表上查看修改删除的按钮是如何嵌入的。

尽管已经有成熟的三方表格插件,但是本项目中文章列表还是选择Html原生的<table>标签实现的。

这种纯手工打造的方式虽然效率不太高,但是能够很清晰的将前端的Html元素和后端的数据是如何黏贴在一起的,以及最后如何实现数据互通的过程呈现在大家面前。

定义空表格

  <table class="table table-hover"></div>

定义表头

<thead>
<tr class="table-row">
     <th scope="col"><input type="checkbox"/></th>
     <th scope="col">标签</th>
     <th scope="col">标题</th>
     <th scope="col">禁用</th>
     <th scope="col">创建时间</th>
     <th scope="col">修改时间</th>
     <th scope="col">操作</th>
</tr>
</thead>

定义表体

这个很简单,接下来如何定义<tbody>,才你能让每个单元格的数据填充进去呢?
这里借助一下thymeleaf的能力。

thymeleafth:each语法能够实现对后端返回的文章数组的对象进行遍历,然后填充到每个单元格——<td>里面。


<tbody>
 <tr th:each="article:${page?.records}" class="table-row">
        <td><input type="checkbox" class="check"></td>
        <td style="width:100px;" th:text="${article.Tags}"></td>
        ...
 </tr>
</tbody>

沿着这个思路我们就可以将文章实体中需要展示在前端的字段都在这里定义好。

那么,前端是绑定到后端返回的数据的呢?
这得益于ModelAndView对象。

ModelAndView干了什么事情呢?

Holder for both Model and View in the web MVC framework. Note that these are entirely distinct. This class merely holds both to make it possible for a controller to return both model and view in a single return value.
Represents a model and view returned by a handler, to be resolved by a DispatcherServlet. The view can take the form of a String view name which will need to be resolved by a ViewResolver object; alternatively a View object can be specified directly. The model is a Map, allowing the use of multiple objects keyed by name. ———— 《Class ModelAndView》

翻译成大白话如下:

ModelAndView是 Web MVC 框架中Model和View的持有人。 这两类是截然不同的。 ModelAndView仅保留两者,以使控制器有可能在单个返回值中返回模型和视图。

该视图由ViewResolver对象解析; 该模型是存储在Map中的数据。

想进一步了解的同学可以查看 《Spring ModelAndView教程》

回到我们的项目中,我们在ArticleController中定义了ModelAndView对象,并通过addObject(String attributeName, Object attributeValue)key/value的方式绑定数据。

(1) 定义定义了ModelAndView对象,并返回到指定的页面
ModelAndView构造方法可以指定返回的页面名称

new ModelAndView("article/index");

(2)返回参数到指定页面的request作用于中,使用addObject()设置需要返回的值,返回到新页面的request作用域中

modelAndView.addObject("page",page);

  //定义一个视图对象名字时index.html  前缀和后缀都有封装,只需要写名字
  ModelAndView modelAndView = new ModelAndView("article/index");
  IPage<ArticleVO> page=articleService.searchByPage(pageNum,pageSize,article.getTitle());

  //相当于setAttriute("pageInfo",pageInfo)
  modelAndView.addObject("page",page);
  modelAndView.addObject("article",article);;

这样Controller-Model-View的数据就打通了。

文章新增和编辑

文章的新增相对比较复杂一点,通常文章是要支持富文本的,也就是说需要一个富文本编辑器。

常见的博客文章编辑器



editor.md

目前主流的大概就是传统的富文本编辑器和Markdown格式编辑器。
本文将选择Markdwon格式编辑器作为文章内容编辑工具——Editor.md

下载 & 安装

下载包:

Github download

NPM 安装 :

npm install editor.md
使用
创建Markdown编辑器
<link rel="stylesheet" href="editor.md/css/editormd.min.css" />
<div id="editor">
    <!-- Tips: Editor.md can auto append a `<textarea>` tag -->
    <textarea style="display:none;">### Hello Editor.md !</textarea>
</div>
<script src="jquery.min.js"></script>
<script src="editor.md/editormd.min.js"></script>
<script type="text/javascript">
    $(function() {
        var editor = editormd("editor", {
            // width: "100%",
            // height: "100%",
            // markdown: "xxxx", // dynamic set Markdown text
            path : "editor.md/lib/"  // Autoload modules mode, codemirror, marked... dependents libs path
        });
    });
</script>

superblog后台项目中,我是将Editor.md插件包下载到本地使用的。



在页面上引入相关js,css文件

<head>
    <meta charset="UTF-8">
    <title>文章发布</title>

    <link href="/assets/editorMd/css/editormd.min.css" rel="stylesheet"/>
    <script src="/assets/editorMd/js/jquery.min.js"></script>
    <script src="/assets/editorMd/editormd.js"></script>
    <script src="/assets/js/article/create.js"></script>
</head>

定义文章内容编辑区

<div class="form-group">
    <div id="content-editormd">
             <!-- Server-side output Markdown text -->
            <textarea style="display:none;" id="mdContent" name="mdContent"
                                                  class="form-control">
</textarea>
    </div>
</div>

为了让前端代码看起来清晰,各个模块之间解耦合,我为每个页面做了一个js文件,按照页面名字/功能.js的规则存储和命名。

比如,文章新增页面的js逻辑封装在/article/create.js
存放结构如下:

└─js
     bootstrap.min.js
     popper.min.js
      
    └─article
            create.js
            detail.js
            modify.js

初始化mdEditor

    var contentEditor;
    $(function () {
        contentEditor = editormd("content-editormd", {
            width"90%",
            height640,
            syncScrolling"single",
            path"/assets/editorMd/lib/",
            saveHTMLToTextareatrue,
            emojitrue,
            imageUploadtrue,
            imageFormats: ["jpg""jpeg""gif""png""bmp""webp"],
            imageUploadURL"/Admin/Editor/UpImage",
        });
    });

接下来,我们只需要把文章的其他字段和内容编辑器做成Form表单,前端工作就大功告成了。
启动项目,我们看看效果吧!



后端的实现逻辑比较简单,这里贴一下代码:

    @GetMapping("/create")
    public String create(){
        return "article/create";
    }

    @PostMapping("/create")
    public String create(ArticleCreateDto dto){
        ArticleCreateBo bo = BeanHelper.convertBean(dto, ArticleCreateBo::new);
        HttpSession session =  ServletUtil.getRequest().getSession();
        String account = (String) session.getAttribute("aname");
        Adminuser loginUser=userManager.findByAccount(account);
        bo.setUserId(loginUser.getId());
        articleService.create(bo);
        //返回列表页的处理
        return "redirect:/article/index";
    }

其他

文章管理模块剩下的其他功能,如文章详情删除功能相对比较简单,可以在源码中查看。
特别是文章详情的实现可以参考《用户端文章详情》章节。

结语

授人以鱼不如授人以渔。

在十多年的职业生涯中,笔者主导和参与了大大小小的项目也不少,后台管理的项目更是不胜枚举。

然而,纯手工打造一个轻量级的后台管理主题样式仍然还是让自己感觉很激动。

坚持以实战出发,原理理解为主,框架的学习为辅。

本教程从一个新手的角度出发,以bootstrap基础框架为基础,图文并茂的介绍了经典管理后台主题模板的实现。

这个案例比较简单,希望能够起到抛砖引玉的效果。

由于篇幅限制,视图层和控制器层的源码没有全部展示出来,完整源码请参考《Springboot实战纪实源码》

参考

版权声明: 本文为智客工坊「顶级码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

results matching ""

    No results matching ""