页面树结构
转至元数据结尾
转至元数据起始

需求来源

之前的案例中介绍了EChart可视化控件,可以实现个性化数据可视化,但是用户往往不希望看到一个静止不动的结果,而是希望在DWF后端收集数据到前端以后,将前端界面上展示的效果更新,如下面所示:

虽然,前面的案例中介绍了使用订阅控件在后端实现定期刷新的方法,这种方法的轮询逻辑是在服务器上运行的。

如果只是希望前端能够通过脚本快速获得数据并进行显示,使用订阅控件就显得难以操作,因此,本文介绍一种前端定期刷新的简便的方式,通过定时拉取数据完成动态数据显示的目的。

图-定时刷效果

背景知识

在介绍具体操作之前,先了解一些背景知识。

如何设置定时器

首先,在定时刷新之前,需要学习一些背景知识,其中第一个是如何设置定时器,由于DWF使用的是JavaScript,所以直接利用以下两个方法设置定时器即可。

  • setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
  • clearInterval() :在指定的毫秒数后调用函数或计算表达式。

setInterval返回一个定时器变量,可以作为clearInterval的参数,终止定时器。

第一个参数是一个函数,说明到达时间点的时候具体执行什么样的逻辑。

第二个参数是一个以毫秒为单位的周期,1000毫秒=1秒。

如下面的代码所示,其定时运行10秒以后,自动停止,其中:

var i = 0;
var timer = setInterval(function () {
    i++;
    if(i === 10) {
         clearInterval(timer);
    }
}, 1000);

激活控件的事件

其次,在DWF基础培训中已经介绍了控件和控件上面的事件这一概念,例如:按钮控件上存在“单击事件”,文本框控件上存在“值变化事件”,这些事件上可以绑定操作,当交互发生的时候操作就会激活。

在DWF中,也可以通过前端脚本模拟事件调用,例如:模拟按钮单击事件,具体方法是通过调用invokeEvent实现,如下所示:

var btn = this.getAddinById("Operation1");
btn.invokeEvent("单击事件");

上述代码表示得到表单中代号“Operation1”的按钮,然后通过invokeEvent模拟触发“单击事件”,invokeEvent函数的参数对应画布右侧,选项区控件事件清单中事件的中文名。

需要特别,特别,特别注意的是:不要进行在事件的脚本中利用invokeEvent嵌套调用,避免出错。

技术路线

由于控件的事件可以通过脚本激活,前端刷新的技术路线就非常明确了,建立一个针对表单的刷新操作,启动定时器后在函数中实现invokeEvent模拟刷新:

第一步、在表单上基于DWF控件实现一个展示表单,利用动态数字框,EChart控件等展示数据。

第二步、在该表单上设置一个刷新按钮,绑定单击事件,利用脚本从后端获取数据,到前端利用控件的函数动态刷新。

第三步、在表单初始化的时候启动定时器,或者设置另外的按钮,手动启动的按钮启动定时器,每当到达定时器指定的时间间隔,则调用刷新按钮的invokeEvent刷新,完成动态变化

接下来,以教学使用的搅拌车管理系统为背景,实现按照每2秒刷新,从后台统计搅拌车总报警数和总工单数的仪表盘的例子:

第一步、建立基础表单

按照下面的方式拖放一个用于定期刷新的表单,放入两个动态数字框用于统计总报警数和车辆数,一个操作按钮用于手动刷新,两个最顶上的操作按钮用于启动和停止定时器。

图1-基础表单

第二步、设置按钮绑定事件

在手动刷新数字按钮的单击事件上绑定一个操作命名为:totalRefresh,选择implement动作,用前端脚本实现:

图2 手动刷新操作对话框

操作的前端脚本实现如下:

this.callServer().then(res => {
    var result = JSON.parse(res.data.data);
    var addinTotalNumber = this.getAddinById("DynamicDigitalLabel1");
    var addinTotalAlarm = this.getAddinById("DynamicDigitalLabel2");
    addinTotalNumber.setValue(result.totalNumber);
    addinTotalAlarm.setValue(result.totalAlarm);
})

脚本的含义是,首先通过this.callServer()启动服务器端对应的后端脚本,成功后将以字符串为基础的结果转化为JSON对象,再利用动态数字框DynamicDigitalLabel1和DynamicDigitalLabel2实现刷新。

操作的后端脚本实现如下:

//从数据库中获取车辆的总数和车辆报警总数
var queryTotal = this.em.createNativeQuery("select count(*) as total from plt_cus_asset");
var queryAlarm = this.em.createNativeQuery("select sum(plt_alarmcount) as alarm from plt_cus_asset");
//执行查询获取返回结果
var resultTotal = queryTotal.getSingleResult();
var resultAlarm = queryAlarm.getSingleResult();
//将查询结果转换为字符串
var res = {
    totalNumber : resultTotal.toString(),
    totalAlarm : resultAlarm.toString()
}
//最后传输到前端等待处理
this.res = JSON.stringify(res);

脚本一开始通过this.em建立了两条sql语句获得单个取值,之后转换为JSON字符串返回给前端脚本使用。

第三步、启动停止定时器

上述工作完成后,即可针对“启动”和“停止”按钮启动和停止计时器,在定时器到达时间限制时通过invokeEvent启动手动刷新按钮模拟刷新。

其中启动定时器脚本为:

var btnTimer = this.getAddinById("Operation2");
var btnLabel = this.getAddinById("Operation5");
btnTimer.timer = setInterval(function () {
    btnLabel.invokeEvent("单击事件")
}, 1000);

其中,btnTimer表示启动按钮对应控件的id为"Operation2",btnLabel表示“手动刷新数字”对应控件的id为“Operation5”,为了保存定时器的句柄,直接将setInterval的返回值作为属性附在btnTimer之上。

因为,btnTimer上具有了定时器对象,可以在停止按钮上完成停止如下:

var btn = this.getAddinById("Operation2");
clearInterval(btn.timer);

注意:如果需要一打开表单即可看到定时器启动效果,那么可以直接将启动操作绑定在表单初始化事件之上。上述工作完毕以后,可以看到如下的效果:

配合可视化实现刷新

在定时器启动以后,配合Echart控件可以实现更加生动的展示效果,这里展示了一个仪表盘和一个滚动折线图的效果

仪表盘控件刷新

仪表盘的Echart控件,来源于Echart官网,https://echarts.apache.org/examples/zh/editor.html?c=gauge-speed

对应的代码如下:

option = {
    series: [{
        type: 'gauge',
        progress: {
            show: true,
            width: 18
        },
        axisLine: {
            lineStyle: {
                width: 18
            }
        },
        axisTick: {
            show: false
        },
        splitLine: {
            length: 15,
            lineStyle: {
                width: 2,
                color: '#999'
            }
        },
        axisLabel: {
            distance: 25,
            color: '#999',
            fontSize: 20
        },
        anchor: {
            show: true,
            showAbove: true,
            size: 25,
            itemStyle: {
                borderWidth: 10
            }
        },
        title: {
            show: false
        },
        detail: {
            valueAnimation: true,
            fontSize: 80,
            offsetCenter: [0, '70%']
        },
        data: [{
            value: 70
        }]
    }]
};

其刷新操作的客户端脚本如下:

var echart = this.getAddinById("EChart1");

this.callServer().then(res => {
    var newdata = JSON.parse(res.data.data);
    echart.chart.setOption({
        series: [{
            data: newdata
        }]
    });
})

其含义是从服务器端获取一个数值,并将其设置到EChart控件上,在Echart控件中的chart属性代表Echart实例,详细概念可以参阅 案例(一)用扩展控件Echarts实现高级可视化

而后端代码则返回一个字符串用于表示即将显示的读数:

var ramdomVal = Math.floor((Math.random()*100)+1);
var data = [{
    value: ramdomVal
}];
this.res = JSON.stringify(data);


折线图控件刷新

折线图稍微复杂一些,该案例来自于:https://echarts.apache.org/examples/zh/editor.html?c=dynamic-data2

但是需要进行一些修改,将其定时器,随机生成数字的逻辑拆除,之后生成随机数字的逻辑放到操作的服务器脚本中。

function randomData() {
    now = new Date(+now + oneDay);
    value = value + Math.random() * 21 - 10;
    return {
        name: now.toString(),
        value: [
            [now.getFullYear(), now.getMonth() + 1, now.getDate()].join('/'),
            Math.round(value)
        ]
    };
}

var data = [];
var now = +new Date(1997, 9, 3);
var oneDay = 24 * 3600 * 1000;
var value = Math.random() * 1000;
for (var i = 0; i < 1000; i++) {
    data.push(randomData());
}

option = {
    title: {
        text: '动态数据 + 时间坐标轴'
    },
    tooltip: {
        trigger: 'axis',
        formatter: function (params) {
            params = params[0];
            var date = new Date(params.name);
            return date.getDate() + '/' + (date.getMonth() + 1) + '/' + date.getFullYear() + ' : ' + params.value[1];
        },
        axisPointer: {
            animation: false
        }
    },
    xAxis: {
        type: 'time',
        splitLine: {
            show: false
        }
    },
    yAxis: {
        type: 'value',
        boundaryGap: [0, '100%'],
        splitLine: {
            show: false
        }
    },
    series: [{
        name: '模拟数据',
        type: 'line',
        showSymbol: false,
        hoverAnimation: false,
        data: data
    }]
};

之后刷新的前端脚本,主要是通过this.callServer(date)将折线最后一点的时间戳传入后端,读入一个新的数据点,将这些的滑动窗口移位,之后将新的数据点放入到数组之中。

var echart = this.getAddinById("EChart2");
var option = echart.chart.getOption(); //获取当前Echart控件的选项
var data = option.series[0].data; //获取选项中的数组
var lastDay = data[data.length - 1]; //取得最后一个元素对应日期

this.callServer(lastDay).then(res => { //此时this.callServer携带参数调用
    var result = JSON.parse(res.data.data);
    data.shift(); //数组移位
    data.push(result); //补充元素
    echart.chart.setOption({
        series: [{
            data: data
        }]
    });
})

而后端脚本则产生一个下一天的随机数:

var now = new Date(this.customData.name); // this.customData是前端传递的参数入口
var oneDay = 24 * 3600 * 1000;
var value = Math.random() * 1000;

function randomData() {
    now = new Date(+now + oneDay);
    value = value + Math.random() * 21 - 10;
    return {
        name: now.toString(),
        value: [
            [now.getFullYear(), now.getMonth() + 1, now.getDate()].join('/'),
            Math.round(value)
        ]
    };
}

this.res = JSON.stringify(randomData());

小结

本文主要介绍了一种利用定时器触发事件的方式实现动态刷新的方法,主要涉及到下面的一些要点:

第一、setInterval和clearInterval:表示设置缺省的定时器时间间隔。

第二、invokeEvent("事件名称"),用脚本触发特定控件上的事件,其中事件的名字和控件事件配置选项卡上的中文名一致。

第三、this.callServer(参数),可以在前端脚本中调用后端脚本,并且将结果通过res回调函数的参数带回。

第四、this.res,后端返回结果的中间变量,注意前后端需要利用JSON.stringify和JSON.parse进行解析。

第五、如果需要实现高级可视化,可以利用Echart控件,并通过chart属性获取Echart实例,之后设置数据即可刷新,即:echart.chart.setOption()

  • 无标签