SpringBoot开发一个小而美的个人博客(六)博客管理

博客管理分两个页面,一个编辑博客页面,一个列表展示页面。

编辑博客页面

整个页面分为几个模块,分别是选择博客创造类型是原创还是转载,博客内容,博客分类及标签,博客描述,博客图片地址(目前没有支持本地文件上传,并且只能选择一张作为展示图,博客内容中可以有多张图片),是否打开推荐/转载声明/赞赏/留言功能,保存草稿还是直接发布。

前端核心代码

HTML代码

<div class="m-container-big m-padded-tn-big">
        <div class="ui container">
            <form id="blog-form" action="#" th:object="${blog}" th:action="@{/admin/blogs}" method="POST" class="ui form">
                <!--       隐含域   获取是否发布      -->
                <input type="hidden" name="published">
                <!--       隐含域   获取博客id      -->
                <input type="hidden" name="id" th:value="*{id}">
                <!-- required  必须输入 -->
                <div class="required field">  
                    <div class="ui left labeled input">
                        <div class="ui selection compact teal basic  dropdown label">
                            <input type="hidden" value="创作类型" name="flag" th:value="*{flag}">
                            <i class="dropdown icon"></i>
                            <div class=" text"> 创作类型</div>
                            <div class="ui menu">
                                <div class="item" data-value="原创">原创</div>
                                <div class="item" data-value="转载">转载</div>
                                <div class="item" data-value="翻译">翻译</div>
                            </div>
                        </div>
                        <input type="text" name="title"  placeholder="标题" th:value="*{title}" >
                    </div>
                </div>

                <div  class="required field">
                    <div id="md-content" style="z-index: 1 !important;">
                        <textarea name="content" placeholder="博客内容" style="display: none" th:text="*{content}">
                            [TOC]

                            #### Disabled options

                            - TeX (Based on KaTeX);
                            - Emoji;
                            - Task lists;
                        </textarea>
                    </div>
                </div>
                <div class="two fields">
                    <div  class="required field">
                        <div class="ui left labeled action input">
                            <label  class="ui  compact teal basic label">分类</label>
                            <div class="ui fluid selection  dropdown ">
                                <input type="hidden" name="type.id" th:value="*{type}!=null ? *{type.id}">
                                <i class="dropdown icon"></i>
                                <div class="default text">分类</div>
                                <div class="menu">
                                    <div th:each="type : ${types}" th:data-value="${type.id}" th:text="${type.name}" class="item" >1</div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="field">
                        <div class="ui left labeled action input">
                            <label  class="ui  compact teal basic label">标签</label>
                            <div class="ui fluid selection  multiple search dropdown ">
                                <input type="hidden" name="Tagids" th:value="*{Tagids}">
                                <i class="dropdown icon"></i>
                                <div class="default text">标签</div>
                                <div class=" menu">
                                    <div th:each="tag : ${tags}" th:data-value="${tag.id}" th:text="${tag.name}" class="item" >1</div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div  class="required field">
                        <textarea name="description" placeholder="博客描述....."  th:text="*{description}">

                        </textarea>

                </div>
                <div  class="required field">
                    <div  class="ui left labeled input">
                        <label  class="ui teal basic label">首图</label>
                        <input type="text" name="firstPicture" placeholder="首图引用地址" th:value="*{firstPicture}">
                    </div>
                </div>
                <div class="inline fields">
                    <div class="field">
                        <div class="ui checkbox" >
                            <input type="checkbox" id="recommende" name="recommende" th:checked="*{recommende}" checked class="hidden">
                            <label for="recommende">推荐</label>
                        </div>
                    </div>
                    <div class="field">
                        <div class="ui checkbox" >
                            <input type="checkbox" id="shareStatement" name="shareStatement" th:checked="*{shareStatement}" class="hidden">
                            <label for="shareStatement">转载声明</label>
                        </div>
                    </div>
                    <div class="field">
                        <div class="ui checkbox" >
                            <input type="checkbox" id="appreciation" name="appreciation" th:checked="*{appreciation}" class="hidden">
                            <label for="appreciation">赞赏</label>
                        </div>
                    </div>
                    <div class="field">
                        <div class="ui checkbox" >
                            <input type="checkbox" id="commentable" name="commentable" th:checked="*{commentable}" class="hidden">
                            <label for="commentable">留言</label>
                        </div>
                    </div>
                </div>
                <!-- 错误信息会显示在这里 -->
                <div class="ui error message"> </div> 
                <div class="ui right aligned container">
                    <button type="button" class="ui button" onclick="window.history.go(-1)">返回</button>
                    <button type="button" id="save-btn" class="ui secondary button">保存</button>
                    <button type="button" id="publish-btn" class="ui red button">发布</button>
                </div>
            </form>
        </div>
    </div>

整个博客都是在一个form表单中,因为新增博客和编辑博客是同一个页面,所以需要通过判断id是否存在来确定是新增还是编辑。这里同样设置了隐含域。

js代码

        $('#save-btn').click(function(){
            $("[name='published']").val(false)
            $('#blog-form').submit();
        });
        $('#publish-btn').click(function(){
            $("[name='published']").val(true)
            $('#blog-form').submit();
        });

        $('.ui.form').form({
            fields:{
                title:{
                    identifier: 'title', //和表单里的name值一致
                    rules: [{ 
                        type: 'empty', //非空验证
                        prompt: '请输入博客标题',
                    }]
                },
                content:{
                    identifier: 'content', //和表单里的name值一致
                    rules: [{
                        type: 'empty', //非空验证
                        prompt: '请输入博客内容',
                    }]
                },
                typeId:{
                    identifier: 'typeId', //和表单里的name值一致
                    rules: [{
                        type: 'empty', //非空验证
                        prompt: '请选择博客分类',
                    }]
                },
                firstPicture:{
                    identifier: 'firstPicture', //和表单里的name值一致
                    rules: [{
                        type: 'empty', //非空验证
                        prompt: '请选择博客首图',
                    }]
                },
                description:{
                    identifier: 'description', //和表单里的name值一致
                    rules: [{
                        type: 'empty', //非空验证
                        prompt: '请输入博客描述',
                    }]
                },
            }
        })

后端核心代码

进入新增博客时初始化

首先是创作类型、分类、标签选择,大致是一样的,不过创作类型是固定的,而分类和标签则需要从数据库中获取,查找到所有的分类和标签。直接调用JPA提供的findAll()方法就行。

Controller层

    @GetMapping("/blogs/input")
    public String input(Model model){
        //初始化
        model.addAttribute("tags",tagService.listTag());
        model.addAttribute("types",typeService.listType());
        model.addAttribute("blog",new Blog());
        return INPUT;
    }

新增/编辑博客

博客的新增/编辑其实和之前的分类/标签差不多,都是根据id是否存在进行判断。不过需要对标签进行多一步的处理,因为标签在数据库中存储的是标签id组成的字符串 如  1,2,3   ,所以我们在新增博客时需要将标签id拼接后存储,而在编辑时则需要将标签字符串分割后,根据标签id查到标签名再返回到前端。对分类的处理就简单许多,因为一篇博客只做了一个分类。

controller层

    //新增博客
    @PostMapping("/blogs")
    public String  addblog(Blog blog, RedirectAttributes redirectAttributes, HttpSession                 
    session){
        blog.setUser((User) session.getAttribute("user"));
        blog.setType(typeService.getTypebyId(blog.getType().getId()));
        blog.setTags(tagService.listTag(blog.getTagids()));
        Blog  b=blogService.saveBlog(blog);
        if(b==null){
            redirectAttributes.addFlashAttribute("message","操作失败");
        }else{
            redirectAttributes.addFlashAttribute("message","操作成功");
        }
        return "redirect:/admin/blogs";
    }
    //编辑博客
    @GetMapping("/blogs/{id}/input")
    public String editblog(Model model ,@PathVariable  Long id){
        //初始化
        model.addAttribute("tags",tagService.listTag());
        model.addAttribute("types",typeService.listType());
        Blog blog=blogService.getBlog(id);
        blog.init();  //  处理tag id
            model.addAttribute("blog",blog); //根据id获取博客内容 返回到发布博客页面
        return INPUT;
    }

service层

博客新增时对标签的处理,在JPA中存在这个方法List<T> findAllById(Iterable<ID> ids); 可以对集合进行迭代,查到数据。所以我们可以将标签字符串转化成list集合后调用这个方法,查到对应的标签集合。

    @Override
    public List<Tag> listTag(String ids) {  //根据前端传过来的 {1,2,3} 查到标签
        return tagRepository.findAllById(idlist(ids));
    }


    private List<Long> idlist( String ids){

        List<Long >list=new ArrayList<>();
        if(!"".equals(ids) && ids!=null){
            String [] idarr =ids.split(",");
            for(String  id:idarr){
                list.add(new Long(id));
            }
        }
        return list;
    }

编辑博客时对标签的处理 ,这里是在博客实体类Blog中处理的,加上@Transient注解的字段不会被添加到数据库中。

    @Transient
    private String Tagids;

    // 将标签id 的数组 转换成字符串
    public void init(){
        this.Tagids=toTagids(this.getTags());
    }

    public String toTagids(List<Tag>tagarr){
        if(!tagarr.isEmpty()){
            boolean flag=false;
            StringBuffer sb=new StringBuffer();
            for(Tag tag:tagarr){
                if(flag){
                    sb.append(",");
                }else{
                    flag=true;
                }
                sb.append(tag.getId());
            }
            return sb.toString();
        }
        return Tagids;
    }

因为新增和编辑共用一个页面所以不管是保存为草稿,还是新增或者编辑最后都是调用同一个方法,不过编辑除了修改博客本身的内容,只需要再修改更新时间就行,不用修改创建时间和阅读次数,所以这里做了一个判断。

    @Transactional
    @Override
    public Blog saveBlog(Blog blog) {
        if(blog.getId()<1){  //说明id不存在  是新增博客
            blog.setCreatTime(new Date());
            blog.setUpdateTime(new Date());
            blog.setViews(0);
        }else {
            blog.setUpdateTime(new Date());
        }
        return blogRepository.save(blog);
    }

列表展示页面

 博客的列表展示页面和标签/分页的列表展示页面基本相似,但是多了一个动态查询功能。可以根据单独的标签/分类进行查询,也可以综合起来查询。

前端核心代码

        <div  class="ui secondary segment form">
            <!--     定义一个隐含域        -->
             <input type="hidden" name="page">
             <div class="inline fields ">
                 <div class="field">
                     <input type="text" name="title" placeholder="标题">
                 </div>
                 <div class="field">
                    <div class="ui selection dropdown">
                        <input type="hidden" name="typeId">
                        <i class="dropdown icon"></i>
                        <div class="default text">分类</div>
                        <div class="menu">
                            <div th:each="type : ${types}" th:data-value="${type.id}" 
                                 th:text="${type.name}" class="item" >1</div>
                        </div>
                    </div>
                </div>
                <div class="field">
                    <div class="ui checkbox">
                        <input type="checkbox" id="recommende" name="recommende">
                        <label for="recommende">推荐</label>
                    </div>
                </div>
                <div class="field">
                    <button type="button" id="search-btn" class="ui mini teal basic             
                         button"><i class="search icon"></i>搜索</button>
                </div>
             </div>
         </div>

分页

                    <tfoot>
                    <tr>
                        <th colspan="7">
                            <div class="ui mini pagination menu"             
                                         th:if="${page.totalPages>1}">
                                <a onclick="page(this)" th:attr="data-    
                     page=${page.number}-1" class=" item" th:unless="${page.first}" >上一页</a>
                                <a  onclick="page(this)"  th:attr="data-             
                     page=${page.number}+1" class=" item" th:unless="${page.last}">下一页</a>
                            </div>
                            <a href="#" th:href="@{/admin/blogs/input}" class="ui mini 
                                 right floated teal basic button">新增</a>
                        </th>
                    </tr>
                    </tfoot>
        function page(obj){ //给隐含域赋值
            $("[name='page']").val($(obj).data("page"));
            loadlist();
        }
        $('#search-btn').click(function(){
            $("[name='page']").val(0);
           loadlist();
        });
        function loadlist() {
            $("#table-container").load(/*[[@{/admin/blogs/search}]]*/"/admin/blogs/search",    
             {
                title : $("[name='title']").val(),
                id : $("[name='typeId']").val(),
                recommende : $("[name='recommende']").prop('checked'),
                page : $("[name='page']").val()
            });
        }

因为整个项目的分页功能不是一次性查出所有数据,然后整体做分页,而是每点击一次分页就查一次数据库。这里的分页和之前的不一样,当有条件搜索时,点击分页也应该满足搜索条件,负责下一页会出现不在查询条件中的数据。如果将搜索部分的条件也拼接到“{page.number}-1”这个后面,那代码可读性太差。因此在搜索部分添加了一个page的隐含域,让page可以动态的更新。th:attr="data-page=${page.number}-1"   

data-xxx 是自定义属性数据  th:attr是用thymeleaf模板进行解析。

后端核心代码

service层

    @Override
    public Page<Blog> listBlog(Pageable pageable, BlogVo blog) {
        return blogRepository.findAll(new Specification<Blog>() {
            @Override
            public Predicate toPredicate(Root<Blog> root, CriteriaQuery<?> query, 
                CriteriaBuilder cb) {
                List<Predicate>predicates=new ArrayList<>();
                if(!"".equals(blog.getTitle())&&(blog.getTitle()!=null)){
                    predicates.add(cb.like(root.         
                     <String>get("title"),"%"+blog.getTitle()+"%"));
                }
                if(blog.getId()!=null){
                    predicates.add(cb.equal(root.         
                     <Type>get("type").get("id"),blog.getId()));
                }
                if (blog.isRecommende()){
                    predicates.add(cb.equal(root.                 
                     <Boolean>get("recommende"),blog.isRecommende()));
                }
                // 相当于 sql 里面的where   传一个数组
                query.where(predicates.toArray(new Predicate[predicates.size()]));
                return null;
            }
        },pageable);
    }

这个地方为了方便条件查询,将三个条件放在了一个vo里面,然后通过Specification查询Specification查询来得到数据,Specification有三个参数,root就是你要查询的对象,CriteriaQuery 主要用于对查询结果的处理,包括groupBy、orderBy、having、distinct等操作。 CriteriaBuilder 主要用于各种条件查询、模拟sql函数等。

controller层

    @RequestMapping("/blogs/search")
    private String search(Model model , @PageableDefault(size = 2,sort = 
         {"updateTime"},direction = Sort.Direction.DESC) Pageable pageable, BlogVo blog){
        model.addAttribute("page",blogService.listBlog(pageable,blog));
        return "admin/blogs :: bloglist";//返回该页面下的  bloglist 的片段
    }
admin/blogs :: bloglist  这里通过Ajax请求结合thymeleaf模板里的fragment完成动态局部刷新。

前端核心代码

            <table  th:fragment="bloglist" class="ui compact table">
                    <thead>
                    <tr >
                        <th></th>
                        <th>标题</th>
                        <th>类型</th>
                        <th>推荐</th>
                        <th>状态</th>
                        <th>更新时间</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <tbody>
                    <tr th:each="blog,iterStat : ${page.content}">
                        <td th:text="${iterStat.count}">1</td>
                        <td th:text="${blog.title}">Java并发编程的艺术</td>
                        <td th:text="${blog.type.name}"> java</td>
                        <td th:text="${blog.recommende} ? '是' : '否'">是</td>
                        <td th:text="${blog.published} ? '发布' : '草稿'">是</td>
                        <td th:text="${blog.updateTime}">2017-10-02</td>
                        <td>
                            <a href="#" 
                 th:href="@{/admin/blogs/{id}/input(id=${blog.id})}"  class="ui mini positive basic button">编辑</a>
                            <a href="#"  
                 th:href="@{/admin/blogs/{id}/delete(id=${blog.id})}" class="ui mini negative basic button">删除</a>
                        </td>
                    </tr>

                    </tbody>
                    <tfoot>
                    <tr>
                        <th colspan="7">
                            <div class="ui mini pagination menu" 
                 th:if="${page.totalPages>1}">
                                <a onclick="page(this)" th:attr="data-             
         page=${page.number}-1" class=" item" th:unless="${page.first}" >上一页</a>
                                <a  onclick="page(this)"  th:attr="data-             
         page=${page.number}+1" class=" item" th:unless="${page.last}">下一页</a>
                            </div>
                            <a href="#" th:href="@{/admin/blogs/input}" class="ui mini 
                  right floated teal basic button">新增</a>
                        </th>
                    </tr>
                    </tfoot>
                </table>
th:fragment="bloglist"  这里就是定义的动态更新的区域。
        $('#search-btn').click(function(){
            $("[name='page']").val(0);
           loadlist();
        });
        function loadlist() {
            $("#table-container").load(/*[[@{/admin/blogs/search}]]*/"/admin/blogs/search", 
        {
                title : $("[name='title']").val(),
                id : $("[name='typeId']").val(),
                recommende : $("[name='recommende']").prop('checked'),
                page : $("[name='page']").val()
            });
        }

最后的删除和前面的一样,比较简单。

SpringBoot开发一个小而美的个人博客(五)分类、标签管理_舒克、舒克的博客-CSDN博客

SpringBoot开发一个小而美的个人博客(四)实体类构建、使用JPA建数据库表,实现后台登录_舒克、舒克的博客-CSDN博客

SpringBoot开发一个小而美的个人博客(三) 框架搭建_舒克、舒克的博客-CSDN博客_springboot开发个人博客

SpringBoot开发一个小而美的个人博客(二) 前端页面(二)_舒克、舒克的博客-CSDN博客

SpringBoot开发一个小而美的个人博客(一) 前端页面(一)_舒克、舒克的博客-CSDN博客

这篇写了很久,但感觉还是没有写好,没有讲清楚,还得多看看视频,加油加油

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值