• 【Java】表白墙终章-飞流直下的“甜言蜜语”-瀑布流式布局


    飞流直下三千尺!

    在这里插入图片描述

    【JavaEE】表白墙终章-飞流直下的“甜言蜜语”-瀑布流式布局

    1. 效果前后对比

    改进之前:

    1. 用户之间无差别
    2. 消息先来先到

    在这里插入图片描述

    改进后:

    1. 添加导航栏左上角的身份显示(头像 + 用户名)
    2. 采用瀑布流的方式去排列消息
    3. 消息以卡片的形式显示(头像 + 用户名 + 日期 + 甜言蜜语)
    4. 先来后到
      • 最新发布的排在最前

    在这里插入图片描述

    2. 瀑布流式布局原理思想

    • 瀑布流布局的前提就是,待排序的div元素都是等宽不限高的~

    在这里插入图片描述

    流程:

    1. 获取一个div元素盒子的宽
    2. 获取底板(父元素)的宽
    3. 计算底板最多排列的列数:n
    4. 将前四个div元素排在首行
    5. 从已排列的每一列中找到最矮的一列,将接下来的元素安插上去(通过绝对定位,即通过提供的坐标,安插到底板的固定位置)
    6. 重复5操作,直到全部排序好

    动图演示:

    在这里插入图片描述

    越界了怎么办?

    • 加滚动条就行了~

    细节:

    • 滚动条不能加在底板上,应该加在底板的至少上一级元素

    原因:

    • 刚才在计算列数的时候,是没有减掉滚动条的宽度的,也就是说列数可能是多了1列,导致出现了“水平滚动条”,也就是说“竖直滚动条”挤压了原有元素,会导致一些排版重叠问题!

    3. 约定前后端接口

    之前的“甜言蜜语”只有简单的正文部分,而现在,它需要有更多的属性:
    在这里插入图片描述

    再原有基础上,应该增加三个属性

    1. image,头像(与用户名绑定)
    2. date,日期(可按时间逆排序)
    3. username,用户名(确认用户信息)

    所以,我们需要数据库的一张新的表:

    在这里插入图片描述

    因此,我们需要更改后端代码关于json构造的部分!

    4. 后端代码

    在这里插入图片描述

    查询的表

    • message => vindicate
    • 注意修改!

    4.1 修改Love类的定义

    class Love {
        public String from;
        public String to;
        public String love;
        public String username;
        public String image;
        public String date;
    
        public Love() {
            //没有这个一定不行!!!
            //因为后续json构建Love对象需要用到无参的构造方法
        }
    
        public Love(String from, String to, String love, String username, String image, Timestamp date) {
            this.from = from;
            this.to = to;
            this.love = love;
            this.username = username;
            this.image = image;
            String strn = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
            this.date = strn;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    注意:

    1. jdbc操作数据库,既可以将满足格式的 String类或者 Timestamp类载入数据库datetime类型字段中
    2. jdbc操作数据库,将 datetime类型的字段提取出来的时候,只能提取为 Date或者 Timestamp类(本文选择后者)

    在这里插入图片描述

    • 这样子,我们在表白墙里看到的就是日期格式了~
      • 当然,日期还有格式还有很多种,这个类还能满足“yyyy/MM/dd HH-mm-ss”、“HH/mm/ss yyyy.MM.dd”等等等…
      • 但是这里,由于后续我将数据载入数据库是用的严格符合格式的字符串的方式,所以选择“yyyy-MM-dd HH:mm:ss”的格式~
      • 还有更多的类表达日期的格式,感兴趣的可以去了解了解,但是我建议,用的时候查就行了

    4.2 修改doPost方法

    • 客户端发来的post请求,将客户端json格式的body转化为Love类,载入数据库

    当然,客户端那里发过来的post正文是不包含,image、username和date的,所以构造的Love类对象的这三个属性的值为null~

    @WebServlet("/love")
    public class ShowLove extends HttpServlet {
        
        private ObjectMapper objectMapper = new ObjectMapper();
        
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            Love love = objectMapper.readValue(req.getInputStream(), Love.class);
            HttpSession session = req.getSession();
            Timestamp time = new Timestamp(System.currentTimeMillis());
            String strn = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time);
            love.date = strn;
            love.username = (String)session.getAttribute("username");
            love.image = Save.getImage(love.username);
            save(love);
            //默认返回的就是200的空报文
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    然后就是调用save方法,将love对象载入数据库

    4.3 修改save方法

    private void save(Love love){
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/Loves?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("mmsszsd666");//这是俺的微信号,欢迎添加,相互学习!
        try {
            Connection connection = dataSource.getConnection();
            String sql = "insert into vindicate values(?, ?, ?, ?, ?, ?);";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, love.from);
            preparedStatement.setString(2, love.to);
            preparedStatement.setString(3, love.love);
            preparedStatement.setString(4, love.image);
            preparedStatement.setString(5, love.date);
            preparedStatement.setString(6, love.username);
            preparedStatement.executeUpdate();
    
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    4.4 修改doGet方法

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //如果有输入操作互动的操作,在这里refresh是不合理的,因为写着写着就刷新了,体验不好
        // 所以在这里设置refresh没用!
    
        //转换为json字符串!
        List<Love> list =  load();
        String result = objectMapper.writeValueAsString(list);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(result);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这段方法没有更改,主要是load方法更改了

    4.5 修改load方法

    private List<Love> load(){
    
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/Loves?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("mmsszsd666");//这是俺的微信号,欢迎添加,相互学习!
        List<Love> list = new ArrayList<>();
        try {
            Connection connection = dataSource.getConnection();
            String sql = "select * from vindicate order by date desc;";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
    
            //这里的Set并不是,对象为Love的Set集合,而是一个迭代器!
            ResultSet set = preparedStatement.executeQuery();
            //迭代他(是next方法而不是hasnext)
    
            while(set.next()) {
                String from = set.getString("from");
                String to = set.getString("to");
                String love = set.getString("love");
                String username = set.getString("username");
                if(username.length() > 9) {
                    username = username.substring(0, 9) + "...";
                }
                String image = set.getString("image");
                Timestamp date = set.getTimestamp("date");
                list.add(new Love(from, to, love, username, image, date));
            }
    
            set.close();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述

    接下来就是本文重点的前端的瀑布流布局实现了

    5. 前端瀑布流实现

    在这里插入图片描述

    5.1 规定“甜言蜜语”的层级结构

    在这里插入图片描述

    在这里插入图片描述

    pic的div块,就是我们的后端数据载入的对象了~
    在这里插入图片描述

    在这里插入图片描述

    T为信息头,头像显示、用户名显示以及日期显示

    B为正文,“甜言蜜语”显示

    在这里插入图片描述

    在这里插入图片描述

    T的左侧显示头像,右侧显示用户名和日期

    5.2 css修饰

    在pursue.css里增加

    5.2.1 底板div:a
    #a {
        height: calc(100% - 66.67px);
        width: 100%;
    
        /* overflow: auto; */
        position: relative;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 将滚动条注释掉,交给其上一级:article

    在这里插入图片描述

    • 设置属性position为:relative,相对定位 => 与绝对定位对应,如果没有这个属性,我们提供的坐标,其是按照整个页面来定位的
      • 而设置这个属性后,是以此div来定位的(坐标系平移,原点改变)
    5.2.2 div:box
    • 这个盒子代表了一条信息的占位空间,主要作用是设立内边距,与其他信息块分离
    • 这个div极为主要
    .box {
        /* 用padding,而不是margin,因为我们计算的时候,我们不希望算上外边距(盒子大小,js获得高度的时候还得加上margin,复杂多了) */
        padding: 15px 0 0 15px;
        height: auto;
    
        /* 浮动属性 */
        float: left;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 只设置上,与左的内边距为15px

      • padding:top right bottom left(顺时针)
    • 设置浮动属性:float

      • 子元素div是块级元素,独占一行,设置这个属性后,取消块级性质,并且左排列,越界换行,但是这并不能起到瀑布流的效果
        1. 如果接下来的元素可以安插到其中一列,且总长度超过其原最长高,则会补充到对应列
        2. 若1不满足,则重起一行
    5.2.3 div:pic
    .pic {
        background-color: rgb(255, 255, 255);
        width: 150px;
        height: auto;
        /* 边距 */
        padding: 10px;
        border: 1px solid #ccc;
        border-radius: 5px;
    
        /* 阴影 */
        box-shadow: 0 0 5px rgb(0, 0, 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 底色为白
    2. 宽度固定为150px
    3. 高不限制,根据子元素决定
    4. 设置内边距为10px,不会导致子元素紧贴边界
    5. 设置灰色边框和黑色阴影~
    5.2.4 div:T
    .pic .T {
        width: 150px;
        height: 30px;
        display: flex;
        justify-content: space-between;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    消息头的width撑破了限制无所谓~

    • 高度固定为30px => 头像为30px × 30px

    设置为弹性布局,并使子元素space-between(根据实际留出间隙)

    • 并且T的左和右是不换行的~
    5.2.5 div:Tleft
    .Tleft {
        width: 30px;
        height: 30px;
        background-image: url(https://img1.baidu.com/it/u=4205447136,2730860147&fm=253&fmt=auto&app=138&f=JPEG?w=300&h=300);
        border: 1px solid rgb(0, 0, 0);
        border-radius: 15px;
        background-repeat: no-repeat;
        background-position: center center;
        background-size: cover;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 设置半径为15px的圆形
    2. 图片为默认头像
    3. 黑色边框
    4. 设置属性是的图片充满圆
    5.2.6 div:Tright
    .Tright {
        width: 110px;
        height: 30px;
    }
    .Tright h4 {
        line-height: 15px;
        font-size: 15px;
        color: rgb(128, 128, 128);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 宽度设置为110px,不会导致文字和图片太紧凑
    2. 高度设置为30px~
    3. h4标签设置为15px(因为有两个),刚好与div:T等高(字为灰色)
      • 日期可能会超出限制,不过无所谓,在正文的一开始加个换行错开就行了~
    5.2.7 div:B
    .B {
        width: 150px;
        height: auto;
        word-wrap: break-word;
        padding-right: 10px;
        padding-top: 5px;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 宽度为150px
    2. 高度由正文决定
    3. 根据单词超出限制换行
      • 没有这个设置会导致B的高度始终为一个文字的高度
    4. 微调文字布局

    5.3 JS实现瀑布流式布局

    5.3.1 修改getLoves函数
    • 由于前后端交互接口的改变,这里也需要做一些调整~
    function getLoves() {
        jQuery.ajax({
            type: "GET",
            url: "love",
            success: function (body) {
                var boxArray = [];
                //body就是数组
                for (var word of body) {
                    var result =
                        "

    " + word.from + "想对" + word.to + "说“" + word.love + "”

    "
    ; var aB = jQuery("
    "
    ); aB.attr("class", "B"); aB.append(result); var aTleft = jQuery("
    "
    ); aTleft.attr("class", "Tleft"); console.log(word.image); aTleft.css("background-image", "url(" + word.image + ")"); var aTright = jQuery("
    "
    ); aTright.attr("class", "Tright"); aTright.append( "

    " + word.username + ":

    " + word.date + "

    "
    ); var aT = jQuery("
    "
    ); aT.attr("class", "T"); aT.append(aTleft); aT.append(aTright); var aPic = jQuery("
    "
    ); aPic.attr("class", "pic"); aPic.append(aT); aPic.append(aB); var aBox = jQuery("
    "
    ); aBox.attr("class", "box"); aPic.appendTo(aBox); aBox.appendTo(jQuery("#a")); boxArray.push(aBox); } waterFall(boxArray); }, }); }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在这里插入图片描述

    构造box的时候要细心哦,要契合我们的层级结构!

    5.3.2 waterFall函数-瀑布流排列
    function waterFall(boxes) {
        // 将a下的全部box取出来
        var oParent = jQuery("#a");
        
        // 计算显示的列数(页面的宽/box的宽)
        var oBoxWeight = boxes[0].outerWidth(); // console.log(oBoxWeight);
        var cols = Math.floor(jQuery("#a").width() / oBoxWeight);
        // 设置a的宽度
        oParent.css("width", cols * oBoxWeight + "px");
        oParent.css("margin", "0 auto");
    
        // 存放第该列(位置正确摆放的多个div高度)的高度的数组
        // 第一次,则是第一行每个div的高度
        var hArr = [];
        for (var i = 0; i < boxes.length; i++) {
            if (i < cols) {
                hArr.push(boxes[i].outerHeight());
            } else {
                var minH = Math.min.apply(null, hArr); //top
                var index = getMinhIndex(hArr, minH);
                boxes[i].attr(
                    "style",
                    "position: absolute; top: " +
                    minH +
                    "px; left: " +
                    oBoxWeight * index +
                    "px;"
                );
                //处理盒子重叠
                hArr[index] += boxes[i].outerHeight();
            }
        }
        // left -> 左边三个div的宽,top-> 最小高
    }
    function getMinhIndex(arr, val) {
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] == val) {
                return i;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在这里插入图片描述
    在这里插入图片描述

    瀑布流布局完成!

    5.4 定时刷新

    • 我们可以通过setInterval函数,设置一个函数多久定时调用一次
    function refresh() {
        jQuery("#a").empty();
        getLoves();
    }
    setInterval(refresh, 5000);//5000ms
    
    • 1
    • 2
    • 3
    • 4
    • 5

    现在我们自动刷新的时机为:

    1. 点击发送
    2. 定时刷新

    6. 测试

    手机端:
    在这里插入图片描述

    电脑端:

    在这里插入图片描述


    文章到此结束!谢谢观看
    可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆

    表白墙完结啦啦啦啦啦✿✿ヽ(°▽°)ノ✿

    可能以后会追加一系列的功能,敬请期待

    不久后将上线!

    本文代码位置:六月代码/showLove/src/main · 游离态/马拉圈2023年6月 - 码云 - 开源中国 (gitee.com)


  • 相关阅读:
    工业数字化转型 — 工业现场总线
    1-10、信息 / 个人信息 / 数字化 / 数字经济 / 生产要素 / 数据要素 / 数据 / 公共数据 / 企业数据 / 个人数据
    Java通过反射机制获取数据类对象的属性及方法
    前端面试一面 汇量科技
    第 361 场 LeetCode 周赛题解
    用DIV+CSS技术设计的环保主题网站(web前端网页制作课作业)
    代码随想录算法训练营29期|day55 任务以及具体安排
    如何设计一个好的游戏剧情(Part 1:主题的设定)
    Docker Compose
    OpenWrt下安装Mosquitto
  • 原文地址:https://blog.csdn.net/Carefree_State/article/details/131145983