• 交互与前端8 Tabulator+Flask开发日志005


    说明

    上次完成了6, 我觉得7有点鸡肋(因为我们总是在单元格内编辑,失去单元格焦点时就已经提交了),可以直接跳过。本次完成8,那么交互式表格的基本功能就完成了。

    • 1 启动本地Flask服务及组件测试
    • 2 依赖文件本地化
    • 3 在本地建立远端数据库的句柄
    • 4 页面载入时加载参数进行表格初始化(前端一个模板、后端一个视图)
    • 5 页面载入并初始化一个可读表格(暂时不用分页)-> 这个是为了展示一些数据,同时不想这些数据被编辑
    • 6 页面载入并初始化一个可行内编辑的表格 -> 这个是应对希望被编辑(Update),但是不允许增删的情况,通过焦点操作
    • 7 页面载入并初始化一个可行内编辑,同时可以增加和删除数据的表格(单行) -> 这个是对于日常编辑来说正常的操作,通过焦点来单行的直接操作
    • 8 页面载入并初始化一个行内编辑,可以增加和删除表格(多行)的表格-> 这个用来应对有较大批量增删改的操作,操作的变化会先计入缓存,通过按钮来批量提交后台同步
    • 9 远程服务的多级下拉框筛选功能(数据库、表以及模式),模式可以有 Latest, First, Random几种,用来满足日常的需求。
    • 10 远程服务下拉框变动触发表格载入,后面就和本地测试是一样的了(数据库句柄相同的)
    • 11 为表格增加适当的筛选和样式等
    • 12 建立元数据表,映射不同数据库服务的句柄、描述等,页面载入时将从元数据表中读取所有的句柄信息和描述信息,然后页面会基于这些信息查询当前可访问的库和表。从而完成在一个页面连接所有的数据库。包括云服务器数据库、公网数据库集群、算网数据库以及集群等。这个用途是用来快速的浏览数据,在调试和运维时很有用。

    内容

    1 结果

    大约3个小时左右,完成了功能的开发和验证

    虽然方法可能不是最优雅的,但是可以在这么短的时间内达到可用的效果,还是很让我满意的。前端这个事让我挂意了很久,虽然理论上是简单的,真的要实现起来总是觉得很麻烦。

    这样,我基本上可以不用数据库工具软件了,这个会极大提高生产效率。

    总体上,这次的功能增加了两个视图函数,略微修改了一个视图函数

    add_a_row: 增加一个新行,主要是分配一个id给到前端的表格,这样后面的新增行就是更新了。

    del_a_row: 删除行本质上也是更新,通过is_enable来控制。

    效果这样:
    在这里插入图片描述

    • 1 通过 Load Data 来加载表格(这个可以通过页面载入时自动加载)
    • 2 点击Add Blank Row 会在首行增加一个新行
    • 3 点击任意单元格就可以进行编辑
    • 4 通过前面的tickbox进行选择,然后再点删除就可以批量删除

    注意这里的删除是从操作层面上感知的,数据库中仍然存在对应的数据。到了数据库,操作就比较轻松了:

    • 1 可以有脚本定期删除
    • 2 也可以设置时间过期索引,在一段时间后让数据库自己删除

    2 前端页面的一些主要修改

    2.1 与视图函数的交互

    表格增加一列tickbox

    var table = new Tabulator("#example-table", {
     	height:205, // set height of table (in CSS or here), this enables the Virtual DOM and improves render speed dramatically (can be any valid css height value)
     	data:[], //assign data to table
         layout:"fitColumns", //fit columns to width of table (optional)
         addRowPos: "selected",
         columns:[ //Define Table Columns
         {formatter:"rowSelection", titleFormatter:"rowSelection", hozAlign:"center", headerSort:false, cellClick:function(e, cell){
            cell.getRow().toggleSelect();
          }},
    	 	{title:"Name", field:"name", width:150,editor:MyCellEdit01},
    	 	{title:"Habit", field:"habit",editor:MyCellEdit01},
             {title:"Create Time", field:"create_time", sorter:"date", hozAlign:"center"},
             {title:"Update Time", field:"update_time", sorter:"date", hozAlign:"center"},
         ],
         rowClick:function(e, row){
            	_curRow = row;
        	}
    });
    

    编辑函数做修改,增加了更新时间显示

    var MyCellEdit01 = function(cell, onRendered, success, cancel){
        //cell - the cell component for the editable cell
        //onRendered - function to call when the editor has been rendered
        //success - function to call to pass thesuccessfully updated value to Tabulator
        //cancel - function to call to abort the edit and return to a normal cell
    
        //create and style input
        // var cellValue = luxon.DateTime.fromFormat(cell.getValue(), "dd/MM/yyyy").toFormat("yyyy-MM-dd"),
        var cellValue = cell.getValue()
        input = document.createElement("input");
        row = cell.getRow()
        $(row.getElement()).css("background-color",'')
        $(row.getElement()).css("color", "")
        // input.setAttribute("type", "date");
    
        input.style.padding = "4px";
        input.style.width = "100%";
        input.style.boxSizing = "border-box";
    
        input.value = cellValue;
    
        onRendered(function(){
            input.focus();
            input.style.height = "100%";
            input.style.background ='yellow'
        });
    
        function onChange(){
            if(input.value != cellValue){
                // the_id = cell.getData().id
                // alert(the_id )
                // success(alert(input.value));
                row_id = cell.getData().id
                colname = cell.getColumn().getField()
                // row = cell.getRow()
                // row.style.background ='red'
    
                filter_dict = {}
                attr_dict = {}
                filter_dict['tid'] = row_id
                attr_dict[colname] = input.value
                attr_dict['is_enable'] = 1
                attr_dict['update_time']= getDatetime()
    
    
                // alert(colname)
                para_dict = {}
                para_dict['filter_dict'] = filter_dict 
                para_dict['attr_dict'] = attr_dict
                row.update({'update_time' :attr_dict['update_time']})
    
                post_json('/update_a_cell/', para_dict, $(cell.getElement()))
                
                success(input.value);
                // cellValue = input.value
            }else{
                cancel();
            }
        }
    
        //submit new value on blur or change
        input.addEventListener("blur", onChange);
    
        //submit new value on enter
        input.addEventListener("keydown", function(e){
            if(e.keyCode == 13){
                onChange();
            }
    
            if(e.keyCode == 27){
                cancel();
            }
        });
    
        return input;
    };
    

    在创建新行时,和后端要有数据交互,主要获得新的id。要通过函数获得结果,要把ajax改为同步async:false,,这样可以获得结果然后让调用数据请求的函数使用。

    // 单元格提交一个数据
    function post_json_addrow(url, para_dict, target_obj){
    
    let data = null;
    $.ajax({
                url:url,
                type:'POST',
                async:false,
                data:JSON.stringify(para_dict),
                dataType:'json',
                contentType:'application/json',
                success:function(res_data){
                    data_status = res_data.status ;
                    data = res_data.data
    
                    if(data_status==true){
    
                        $(target_obj).css("background-color", "green")
                        $(target_obj).css("color", "white")
                    }
                    else{
                        $(target_obj).css("background-color", "red")
                            $(target_obj).css("color", "white")
                    }
                    
                    
    
                },
                error:function(){
                    $(target_obj).css("background-color", "red")
                            $(target_obj).css("color", "white")
                                        }
    
            },'json')
    return data
    }
    

    单元格的提交,根据后台的状态更新单元格的颜色

    function post_json(url, para_dict,target_obj){
    
    $.ajax({
                url:url,
                type:'POST',
                data:JSON.stringify(para_dict),
                dataType:'json',
                contentType:'application/json',
                success:function(res_data){
                    data_status = res_data.status ;
                    // alert(data_status)
                    // // alert('success')
                    if(data_status==true){
    
                        $(target_obj).css("background-color", "green")
                        $(target_obj).css("color", "white")
                    }
                    else{
                        $(target_obj).css("background-color", "red")
                        $(target_obj).css("color", "white")
                    }
                    
                    
    
                },
                error:function(){
                        //  $('#is_human_correct_old_model').css("background-color","red")
                        $(target_obj).css("background-color", "red")
                        $(target_obj).css("color", "white")
                                        }
    
            },'json')
    }
    

    根据要删除的行在后台执行。

    function post_json_del(url, para_dict){
    
    $.ajax({
                url:url,
                type:'POST',
                data:JSON.stringify(para_dict),
                dataType:'json',
                contentType:'application/json',
                success:function(res_data){
                    data_status = res_data.status ;
                    // alert(data_status)
                    // // alert('success')
                    if(data_status==true){
    
                    }
                    else{
                        alert('删除有问题')
    
                    }
                    
                    
    
                },
                error:function(){
                        //  $('#is_human_correct_old_model').css("background-color","red")
                        alert('删除有问题')
                                        }
    
            },'json')
    }
    
    

    2.2 使用tabulator本身的函数监听变化

    这个还是很方便的,表格自身的改变和展示会根据操作做变化

    document.getElementById("ajax-trigger").addEventListener("click", function(){
        table.setData("/get_test_data_wmongo/");})
    
    
    //Add row on "Add Row" button click
    document.getElementById("add-row").addEventListener("click", function(){
        table.addRow({})})
    
    //Delete row on "Delete Row" button click
    document.getElementById("del-row").addEventListener("click", function(){
        // rowCount = table.getDataCount();
        // alert(rowCount)
        rows = table.getRows("selected")
        rows.forEach(function(row){
            alert(row.getData().id)
            table.deleteRow(row.getData().id)
            }) 
        ;})
    

    3 总结

    • 1 使用tabulator初始化表格后,增加监听函数
    • 2 监听函数将增删改变化和后端视图进行交互
    • 3 后端视图将变化同步到数据库并给前端反馈

    进度

    • 1 启动本地Flask服务及组件测试
    • 2 依赖文件本地化
    • 3 在本地建立远端数据库的句柄
    • 4 页面载入时加载参数进行表格初始化(前端一个模板、后端一个视图)
    • 5 页面载入并初始化一个可读表格(暂时不用分页)-> 这个是为了展示一些数据,同时不想这些数据被编辑
    • 6 页面载入并初始化一个可行内编辑的表格 -> 这个是应对希望被编辑(Update),但是不允许增删的情况,通过焦点操作
    • 7 页面载入并初始化一个可行内编辑,同时可以增加和删除数据的表格(单行) -> 这个是对于日常编辑来说正常的操作,通过焦点来单行的直接操作
    • 8 页面载入并初始化一个行内编辑,可以增加和删除表格(多行)的表格-> 这个用来应对有较大批量增删改的操作,操作的变化会先计入缓存,通过按钮来批量提交后台同步
    • 9 远程服务的多级下拉框筛选功能(数据库、表以及模式),模式可以有 Latest, First, Random几种,用来满足日常的需求。
    • 10 远程服务下拉框变动触发表格载入,后面就和本地测试是一样的了(数据库句柄相同的)
    • 11 为表格增加适当的筛选和样式等
    • 12 建立元数据表,映射不同数据库服务的句柄、描述等,页面载入时将从元数据表中读取所有的句柄信息和描述信息,然后页面会基于这些信息查询当前可访问的库和表。从而完成在一个页面连接所有的数据库。包括云服务器数据库、公网数据库集群、算网数据库以及集群等。这个用途是用来快速的浏览数据,在调试和运维时很有用。

    接下来就简单了(之前已经实现过),使用多级下拉框来选择不同的服务器、数据库和表。唯一一个要注意的就是不同表的主键问题:大部分主键是不同名的。当然也不是所有表都需要/允许编辑。

    约定:每个可编辑的数据表都有一个整型的主键id,名字叫task_id,这个我已经有一个可手动开启的worker来长期运行。所以如果要将某个表转为可编辑状态时,启动这个worker去守护这张表就可以了。

    可编辑与不可编辑的原则

    一般来说,原始数据,或者中间状态的数据是不可编辑的。

    而元数据,例如任务的运行状态,数据的标签(对错等),以及服务的启停等,这些可以通过前端控制。

  • 相关阅读:
    转载 | 自动驾驶数据服务市场占有率居首,这家公司是如何做到的?
    从ASM看jacoco运行原理
    Python实用脚本【二】
    【3D目标检测】KITTI数据集介绍
    初探C++ CRTP(奇异的递归模板模式)
    分享这几个好用的文字识别软件,教你快速识别
    git:二、git的本地配置+工作区域和文件状态+git add/commit/log +git reset回退版本
    走进HBase
    使用 Apache Camel 和 Quarkus 的微服务(二)
    聊聊对Andorid的FileProvider的理解
  • 原文地址:https://blog.csdn.net/yukai08008/article/details/127082554