Flask+LayUI开发手记(一):LayUI表格的前端数据分页展现

news2024/9/20 18:53:04

        用数据表格table展示系统数据,是LayUI的基本功能,编码十分简单,就是通过table.render()渲染,把属性配置好就OK了,十分方便,功能也十分强大。

       不过,在实现时,把table的有个功能却理解错了,就是分页。

        table.render()的分页属性,主要包括三个,page、limit和limits,page是逻辑值,设置为true是分页,limit是每页显示行数,limits是设置面显示行数的多条选项。应该说,这几个属性都十分清晰,按说明配好后,果然分页控制栏就显示出来了,真不错。

       原来以为只要在服务端把需要的数据生成好传到前端来,分页就算完成了,但做了一段时间就发现不对了。开始做无外乎就是用户、角色、权限编辑,记录都很少,用不上分页,等做日志显示时一下就看出错误了,分页设的16条,怎么系统在一个页面把全部100多条数据都展示了。

       然后仔细研究才发现,这三个分页属性设置好,只是相当于打了了前端控制的开关,界面上显示出分页流程控件,可以进行分页切换操作,但数据展示内容,前端不管,还是要后端做处理的。table会在每次点击一次页面切换时,就向后端提交一次数据请求,请求中含有page和limit属性,后端按这两个参数把当前页的数据记录生成传下来。

       我是一直不喜欢这种每换一次页就要向后端提数据请求的实现逻辑的。一页才展示10几条记录,每次换页都要去取数据,来来回回的,性能不好,对后端压力有些太多了。而且,大部分数据展示功能,总共也就是几百条数据,完全可以一次把数据取到前端来,只在前端做处理就可以了。这样无疑大大减轻后端服务器的压力,而且控制上也比较简单是吧。

       也因此,我把系统需要进行分页展示的功能分成两种模式实现,一是前端数据分页,二是后端数据分页 。前端数据分页,即一次性提取后台数据到前端,由前端实现全部的分页数据处理控制,后端数据分页,即前端展示分页操作栏,所有的分页操作均提交到后端进行数据请求,由后端生成分页数据传给前端。

       下面这些程序,就是前端数据分页功能的实现,程序主要分为三个部分,前端html、JS实现、后端数据处理服务。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>CMS系统-登录日展示</title>
    <link rel="stylesheet" href="/static/layui/css/layui.css"  media="all">
</head>
<body>

<table id="admin_log" lay-filter="admin_log" style="margin-top:-15px;"></table>

<script type="text/html" id="toolBar">
    <div class="layui-btn-container">
        <div class="layui-inline"  style="display:inline;margin-right:10px;">
            <label class="layui-btn-sm">日期范围</label>
            <div class="layui-input-inline">
                <input type="text" id="bdate" placeholder="开始日期" autocomplete="off" class="layui-input layui-btn-sm">
            </div>
            <label class="layui-btn-sm" style="display:inline;">-</label>
            <div class="layui-input-inline">
                <input type="text" id="edate" placeholder="结束日期" autocomplete="off" class="layui-input layui-btn-sm">
            </div>
            <div class="layui-input-inline" style="padding-left:10px;padding-top:8px">
                <button id="btn_search" type="button" class="layui-btn layui-btn-normal layui-btn-sm" lay-event="search">
                    <i class="layui-icon layui-icon-search"></i>查询
                </button>
           </div>
        </div>
    </div>
</script>
<script type="text/html" id="linetoolBar">
    <a lay-event="detail" title="查看细节" ><i class="layui-icon layui-icon-form"></i></a>
</script>

</body>
</html>

      前端 HTML页面相当简单,包括三个部分,table、toolBar、linetoolBar。table部分定义了一个总的表格容器,其具体内容由table.render进行渲染。toolBar是数据表格的头部工具栏,linetoolBar是数据表格的行工具栏内容。这三部分有了,基本的展示框架也就出来了。

       具体的还要看JavaScript中的处理,程序如下:

<script src="/static/layui/layui.js"></script>
<script>
layui.use(['jquery','layer','table','laydate'], function(){
	var $=layui.jquery
        ,layer=layui.layer
        ,table=layui.table
        ,laydate = layui.laydate;

    var recData = null;
    var recCount = null;

    initTableData(0);

    // op 操作标志 0:渲染 1:重载
    function initTableData(op) {
        $.post('{{url_for("sysadm.admin_log")}}'
            ,{
                bdate:$('#bdate').val(),
                edate:$('#edate').val()
            }
            ,function(rs){
                if(rs.code == 0){
                    recData = rs.data;
                    recCount = rs.count;
                    if (op ==0) 
                        table_render();
                    else 
                        table_reload(1);
                    layer.msg(rs.msg,function(){});
                }else{
                    layer.msg(rs.msg,function(){});
                    return false;
                }
            }
            ,'json'
        );
    }

    function table_render() {
        table.render({
            elem: '#admin_log'
            ,height: 'full'
            ,data: recData
            ,toolbar: '#toolBar'
            ,method: 'POST'
            ,page: true //开启分页
            ,limits: [16, 20, 30, 40, 50]           
            ,limit : 16 
            ,even : true
            ,size : 'sm' 
            ,cols: [[ 
            { type: 'checkbox', fixed: 'left' }
            ,{field: 'id', title: 'ID', width:40, sort: true, fixed: 'left'}
            ,{field: 'opr_cd', title: '操作', width:40, fixed: 'left'}
            ,{field: 'operate', title: '内容', width:260}
            ,{field: 'username', title: '操作员', width:80, sort: true}
            ,{field: 'ip', title: '客户端IP', width:80}
            ,{field: 'add_time', title: '操作时间', width: 220}
            ,{fixed: 'right', width:200, align:'center', toolbar: '#linetoolBar'}
            ]]
        });

        b_date = laydate.render({
            elem: '#bdate'
        });

        e_date = laydate.render({
            elem: '#edate'
        });

        //表头工具栏事件
        table.on('toolbar(admin_log)', function (obj) {
            switch (obj.event) {
                case 'search':
                    initTableData(1);
                    break;
            };
        });

        //table行内工具栏事件
        table.on('tool(admin_log)', function (obj) {    //obj是指这张表中的数据
            row = obj.data;                             //将这张表中的数据赋给row这个变量
            rid = row.id;
            switch(obj.event) {
                case 'detail':
                    adminlog_detail("查看细节",rid);
                    break;
            }
        });
    }

    function adminlog_detail(title,rid){
        url = '{{url_for("sysadm.admin_log_detail",id="")}}' + rid;
        layer.open({
            type: 2,  //layer提供了5种层类型。可传入的值有:0(信息框,默认)1(页面层)2(iframe层)3(加载层)4(tips层)
            title:title,
            area: ['660px', '460px'],   //宽高
            skin: 'layui-layer-rim',    //样式类名
            content:  url, //查看细节页面
            btn:['关闭'],
            yes: function(index, layero){
                layer.closeAll();
            },
        });
    }

    function table_reload(cpage) {
        table.reload('admin_log', {
            data : recData,
            page: { curr: cpage },
        },true);
    }

});

</script>

        在数据分页展示中,最主要的思路是先把数据一次性传到前端,之后,前端进行分页展示处理。所以,处理包括三个阶段,第一、数据获取,第二、渲染数据表格,第三、数据检索并重载。

       第一步数据获取,是向后端发post请求获取数据下传,这个服务器的python程序不用改动,继续按规定格式下发全量数据。

       第二步渲染数据表格,这块与原来最大的区别是数据源配置发生了变化。LayUI数据表格的数据源有两种模式,一是配url属性,二是配data属性。一般实现都是配url属性,从后端路由或者是JSON文件中取数据。data方式,我只用过一次,就是调试render时,模拟生成了几条数据,配在前端变量里作为数据源。这样调试可以避免前后端通讯的干扰因素,先熟悉table的各种技术细节。好在已经用过了,要做前端分页控制,就得用data数据源模式(主要还是借鉴网上别人的经验),将后端传下来的数据结构中的data赋给前端变量,然后配置成数据源,一切OK。

       这一步还出现了个小问题。开始,我把第一步数据获取initTableData()和第二步表格渲染table_render(),以串行模式编排的,也就是先取数赋本地变量再渲染表格。但运行时界面却不显示表格内容,调试发现recData变量在initTableData中有数,但在table_render渲染时居然是空值,按照程序流程不该如此呀。上网查了一下,原来是post异步通讯闹的怪,也就是说执行post后流程并不会等待结果返回,而是接着往下执行后面的程序。

       先获取数据再表格渲染,这一流程必须是串行的,所以post异步通讯的处理方法必须改,必须能串行。改可以有两种方法,第一种是将post改为同步通讯,也就是程序流程阻塞在这个点,等接收到数据返回后再往下执行,但post没有设置同步的属性,要改就要改为ajax提交,这个倒也不难。不过,还有更好的办法,还是把表格渲染的程序放到post的成功后回调函数中来执行,这一样可以实行同步顺序的功能。应该说,第二种方法更简单更方便。

       表格渲染,除了表格渲染render外,还要对头部及行内工具栏的按钮动作进行功能设置,这两个设置必须在表格渲染之后完成,同样属于表格渲染的一部分。合并在一起形成table_render(),都在获取数据后一起执行。

       把这些都配置完成,执行一下,数据展示出来了。而且,data数据源模式下,table会自动完成分页展示控制,不需要再象url数据源模式下,还需要各种特殊处理了。这是真方便。

       第三步是表格检查重载。头部工具栏中有一个检索功能,就是输入启始日期和终止日期,作为登录日志的查询条件。检索按钮实际上相当于表格数据初始化的再次重入,所以,执行了与第一次数据初始化同样的函数,只是入口属性设置为1。这时,需要从后端重新获取数据,但前端不必重新渲染,只要调用table_reload()进行重载就可以。

       前端的功能完成了,下面就是后端程序了。

@bp.route('/admin_log/',methods=['GET','POST'])
@login_required
#@admin_auth
def admin_log():
    if request.method == 'GET':
        return render_template('admin/admin_log.html.j2')
    else :
        logging.debug('Admin Log POST....')
        bdate = request.values.get('bdate');
        edate = request.values.get('edate')
        filtstr = '1=1 '
        if bdate :
            filtstr += ' and add_time >= "' + bdate +'"'
        if edate :
            filtstr += ' and add_time <= "' + edate + '"'
        logging.debug('filter : ' + filtstr)
        adminlog = db.session.query(Admin_Log).filter(text(filtstr)).order_by(Admin_Log.add_time.desc()).all()
        rnum = len(adminlog)
        alist = []
        for ilog in adminlog:
            rdata = dict(id=ilog.id,opr_cd=ilog.opr_cd,admin_id=ilog.admin_id,username=ilog.username,
                         operate = ilog.operate,ip=ilog.ip,add_time=ilog.add_time.strftime('%Y-%m-%d %H:%M:%S'))
            alist.append(rdata)
        rsdata = {
            "code": 0,
            "msg": "查询登录日志数据成功",
            "count": rnum,
            "data":alist
        }
        return jsonify(rsdata)

@bp.route('/admin_log_detail/',methods=['GET'])
def admin_log_detail():
    if request.method == 'GET':
        rid= request.values.get('id')
        if rid == None:
            return render_template('admin/admin_log_dtl.html.j2')
        else:
            ilog = db.session.query(Admin_Log).filter_by(id=rid).first()
            rdata = dict(id=ilog.id,opr_cd=ilog.opr_cd,admin_id=ilog.admin_id,username=ilog.username,
                         operate = ilog.operate,ip=ilog.ip,add_time=ilog.add_time.strftime('%Y-%m-%d %H:%M:%S'))
            rsdata = {
                "success": 1,
                "msg": "取登录日志数据成功" + rid,
                "data":rdata
            }
            logging.debug(str(rsdata))
            return render_template('admin/admin_log_dtl.html.j2',rsdata=rsdata)

        后端数据处理就是遵循flask的标准规范了,在get请求时,调用admin_log.html进行渲染,然后页面的JS程序会向后端发出post请求获取数据。后端在POST分支里取数并生成回传数据文件。回传数据文件的按规定组成,包括四部分code、count、msg和data。这个处理比较标准,不再细述了。

       最后的展示样式如下

       后端一次获取数据传到前端,由前端独立完成数据处理,其实只适合数据查询的应用场景。因为,一次把数据全传到前端的模式,意味着系统内出现两套数据,一套前端缓冲数据,一套后端原本数据,如果功能中需要增删改数据,如何保持前后端数据的一致性,就成了控制上的大问题。当然这种场景也是应用很广泛,专门做个前端数据分页模式也有很大用处。

       但后端数据分页模式也是有很多场景应用的。比如增删改查功能,与其却考虑前后端数据同步,还不如每次切换页就向服务端请求数据简单直接,系统所有处理都只去管理后端数据即可。应该说后端数据分页的模式,应用范围更为广泛,而且程序操作流程及逻辑都比前端分页更为清晰,也是layUI table主推的模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1987104.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WPF MVVM实现TreeView层级显示

最近在写一个小工具的时候&#xff0c;遇到TreeView的层级显示&#xff0c;刚好我又用了MVVM模式&#xff0c;所以这里做个总结。 以前我是直接绑定XML数据到TreeView的&#xff0c;使用的XmlDataProvider&#xff0c;这次的数据是直接来自数据库的。 用到的都是Hierarchical…

Element学习(入门)(1)

1、Element官网&#xff1a;https://element.eleme.cn/#/zh-CN 2、来源与用处 3、Element的快速入门 &#xff08;1&#xff09; &#xff08;2&#xff09;在入口文件&#xff08;main.js&#xff09;中引入 &#xff08;3&#xff09; 4、快捷键ctrlc&#xff0c;在当前的项目…

【SpringBoot】自定义注解 I18n <约定式>国际化 (源码分享直接Copy)

0. 已做全新升级版 链接&#xff1a;【SpringBoot】自定义注解终极升级版&#xff1c;i18n国际化&#xff1e;方案源码Copy 链接&#xff1a;【SpringBoot】自定义注解终极升级版&#xff1c;i18n国际化&#xff1e;方案源码Copy 链接&#xff1a;【SpringBoot】自定义注解终…

【电子电路学习笔记】——模电笔记

关于专栏&#xff1a;本专栏用于分享学习电子电路过程中记录的笔记。模电部分使用的教材是高等教育出版社&#xff0c;华成英主编的《模拟电子技术基础&#xff08;第六版&#xff09;》&#xff08;其他版本的内容差不多&#xff0c;建议使用最新版教材&#xff09;&#xff1…

【完全二叉树的权值】

题目 代码1&#xff08;队列&#xff09; #include<bits/stdc.h> using namespace std; #define x first #define y second typedef pair<int, int> PII;const int N 1e510; int n; long long a[N]; queue<PII> q; long long sum[25]; int main() {memset(…

Leetcode—233. 数字 1 的个数【困难】

2024每日刷题&#xff08;152&#xff09; Leetcode—233. 数字 1 的个数 算法思想 参考自k神 实现代码 class Solution { public:int countDigitOne(int n) {long digit 1;long high n / 10;long low 0;long cur n % 10;long ans 0;while(high ! 0 || cur ! 0) {if(cu…

Python Web开发之“基于flask的轻量级Web应用”

参考文章1&#xff1a;https://cloud.tencent.com/developer/article/2373503 参考文章2&#xff1a;基于Flask的自定义网站设计与实现&#xff08;代码全文讲解V1.0&#xff09;_flask框架制作网页-CSDN博客 参考文章3&#xff1a;PythonFlaskMysqL设计网页 - 李明惠 - 博客…

Candance Allegro 入门教程笔记:如何绘制PCB封装库?

文章目录 一、PCB封装库的组成元素二、使用Padstack Edictor制作封装焊盘引脚三、PCB Editor软件创建贴片封装&#xff08;STM32F103T8U6 QFN36 为例&#xff09;3.1、新建PCB封装对象3.2、计算引脚启始坐标3.3、添加焊盘Pad路径3.4、放置焊盘引脚3.5、绘制装配线3.6、放置字符…

sqli-labs-php7-master\Less-1

1&#xff0c;进入mysql数据库 mysql -u root -p 接着&#xff1a; show databases; use security; select * from where id1 LIMIT 0,1; 函数的基本用法 system_user() #当前系统用户 user() #当前登录用户 current_user() #当前登录用…

MOS场效应管常见损坏原因

造成损坏的原因可能有几个: 1.缺少保护电路&#xff0c;一般来说电路中应适当设置保护电路&#xff0c;以吸收电路中的瞬间高压&#xff0c;浪涌电压保护关键元件。 2.参数选取不合理&#xff0c;没有余地&#xff1b;场效应管的耐压&#xff0c;电流都应该流有一定的余地&…

【网络】网络层

网络层 一、前置知识二、IP协议1、协议头格式2、网段划分3、特殊的IP地址&#xff1a;4、IP地址数量限制5、私有IP地址和公网IP地址6、浅谈运营商7、路由8、IP分片 一、前置知识 1、首先要对每台主机要有一个唯一标识符&#xff0c;所以要有源ip地址和目的ip地址来标识目的主机…

linux包管理工具与软件安装

目录 TAR工具的使用 常用选项&#xff1a; 对文件进行打包 查看文件包 向包文件里放添加文件 解包到当前路径 解包到指定路径 zip工具的使用 gzip压缩 bzip2压缩 gzip解压缩 bzip2解压缩 查看压缩文件内有哪些文件 将当前目录下压缩文件解压到指定目录下 软件安…

OpenGL ES->工作机制

渲染流程 渲染目的&#xff1a;输入3D立体坐标&#xff0c;输出绘制后的2D平面像素工作流程&#xff1a;顶点着色器->图元装配->几何着色器->光栅化->片段着色器->测试与混合&#xff0c;整个工作流程被封装在GPU内部&#xff0c;无法改变。运行在CPU的代码调用…

word加密文档忘记密码要如何打开

我们在日常工作中&#xff0c;经常需要使用word来编写文档&#xff0c;有时为了保证资料的安全性&#xff0c;会给word文档加密。虽然这样可以保障安全&#xff0c;但时间间隔一长就容易忘记密码&#xff0c;word又没有密码重置功能&#xff0c;忘记密码就很麻烦&#xff01;那…

Jenkins 部署Vue项目指引: Vue项目本地跨域代理 、解决ERR_UNSAFE_PORT

文章目录 引言I Jenkins 部署Vue项目配置插件安装系统配置NodeJS安装目录和别名设置新建任务(通用类型)构建环境Build Steps(构建步骤)II nginx部署站点(端口和站点目录的映射)查找Nginx配置文件端口和站点目录的映射III Vue项目本地跨域代理,屏蔽掉后端服务API的网关IP…

设计模式-领域逻辑模式-数据源架构模式

行数据入口&#xff08;Row Data Gateway&#xff09; 充当数据源中单条记录入口的对象。每行一个实例 运行机制 行数据入口和单条记录极为相似&#xff0c;数据库中的每一列变成了一个域。适用于事务脚本只能设置单独的查找方法对象&#xff0c;对行数据入口进行操作。如果行…

golang判断某个文件内容是否是二进制文件方法, LimitReader, 获取文件大小,字符串0写入后的byte数据为48, byte零值

go语言中判断某个文件是否是二进制文件的方法&#xff0c; 通过LimitReader读取指定大小的数据后对数据进行判断&#xff0c; 这里有一个很有趣的知识点就是 字符串0在写入文件后&#xff0c;再通过io read读取后的byte数据他在内存中显示的可不是0 而是变成了 48, 十六进制 0x…

Java Web——第二天

什么是JavaScript? JavaScript(简称:JS) 是一门跨平台、面向对象的脚本语言。是用来控制网页行为的&#xff0c;它能使网页可交互 JavaScript和Java是完全不同的语言&#xff0c;不论是概念还是设计。但是基础语法类似 JavaScript在1995年由 Brendan Eich 发明&#xff0c;…

8月6日Spring Boot学习笔记

MyBatis动态SQL 动态 SQL 大大减少了编写代码的工作量&#xff0c;更体现了 MyBatis 的灵活性、高度可配置性和可维护性。 if标签 <if test"判断条件">SQL语句</if> 当判断条件为 true 时&#xff0c;才会执行所包含的 SQL 语句。 choose、when和otherw…

我在杭州的Day30_进程间通信(IPC)——20240805

一、相关练习 1.使用有名管道实现&#xff0c;一个进程用于给另一个进程发消息&#xff0c;另一个进程收到消息后&#xff0c;展示到终端上&#xff0c;并且将消息保存到文件上一份 1.1> 01homework.c #include <myhead.h>int main(int argc, const char *argv[]) …