数据流向
静态数据
系统中静态数据,由底层到顶层,可分为如下四个层级:
数据源
动态看板运行时的数据来源配置。
定义了数据源的标识,以及Java后台获取数据源的方式。
常见的数据源获取方式包括:UDP端口/数据库查询/TCP连接/ICE连接等。
系统运行时,Java后端会抹平数据源的差异,使用统一的格式发往页面进行显示。
页面元素
看板的构成组件。
页面元素需要维护自身的大小/位置/展现形式。不同种类的页面元素有不同的配置方式。
向上而言,由多个页面元素可以拼成完整的看板,向下而言,页面元素负责获得数据源的数据,并可以定义数据的处理规则。
看板实体
页面元素的集合,用户可见的看板。
维护自身的尺寸,看板内的chart风格。
在运行时以看板为单位逐个生成可视页面。
在拖拽系统中,编辑状态的看板既是画板,页面元素既是最小绘图单位。参加chartEz
管理数据
维护用户登录,看板展示等信息。
页面渲染
看板展示页面为showOne.html
主要页面逻辑在js/showPage.js
下面将整个页面的展现分为几个具体步骤
动态约束
在页面中定义了约束设定的表单,当用户点击看板时可以弹出进行设置
<!-- 弹出层菜单,用作动态condition -->
<div id="my-lay" hidden="hidden">
<form class="layui-form" action="" style="padding: 20px 20px">
<div class="layui-form-item">
<label class="layui-form-label">选择用户</label>
<div class="layui-input-block">
<select id="conditionUser" name="user" lay-search>
<option value ="">请选择</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">选择角色</label>
<div class="layui-input-block">
<select id="conditionRole" name="role" lay-search>
<option value ="">请选择</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">选择车间</label>
<div class="layui-input-block">
<select id="conditionCj" name="cj" lay-search lay-filter="chejian">
<option value ="">请选择</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">选择产线</label>
<div class="layui-input-block">
<select id="conditionCx" name="cx" lay-search>
<option value ="">请选择</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">日期</label>
<div class="layui-input-block">
<input type="text" class="layui-input" name="date" id="date">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">开始日期</label>
<div class="layui-input-block">
<input type="text" class="layui-input" name="startTime" id="startTime">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">结束日期</label>
<div class="layui-input-block">
<input type="text" class="layui-input" name="endTime" id="endTime">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">订单号</label>
<div class="layui-input-block">
<input type="text" name="ddh" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">工单号</label>
<div class="layui-input-block">
<input type="text" name="gdh" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="setCondition">确定</button>
</div>
</div>
</form>
</div>
页面加载时,通过接口获取约束的可选项。例如用户的列表,产线的列表等。
callAjax('condition/all', {}, function(res) {
allConditions = res
var userSelect = document.getElementById('conditionUser')
res.ConditionUser.forEach(user => {
var option = document.createElement('option')
option.innerHTML = user.username
option.setAttribute('value', user.userid)
userSelect.appendChild(option)
})
var roleSelect = document.getElementById('conditionRole')
res.ConditionRole.forEach(role => {
var option = document.createElement('option')
option.innerHTML = role.rolename
option.setAttribute('value', role.roleid)
roleSelect.appendChild(option)
})
var cjSelect = document.getElementById('conditionCj')
res.ConditionCheJian.forEach(chejian => {
var option = document.createElement('option')
option.innerHTML = chejian.deptname
option.setAttribute('value', chejian.deptcode)
cjSelect.appendChild(option)
})
form.render()
initPage()
})
当约束条件发生变化时,记录当前的约束条件,并且重置看板页,重新完成动态SQL的计算,事件触发等操作。
//表单提交
form.on('submit(setCondition)', function(data) {
//设置查询条件
condition.userId = "'" + data.field.user + "'"
condition.roleId = "'" + data.field.role + "'"
condition.cjId = "'" + data.field.cj + "'"
condition.cxId = "'" + data.field.cx + "'"
condition.date = "'" + data.field.date + "'"
condition.startTime = "'" + data.field.startTime + "'"
condition.endTime = "'" + data.field.endTime + "'"
condition.ddh = "'" + data.field.ddh + "'"
condition.gdh = "'" + data.field.gdh + "'"
layer.closeAll()
initPage()
return false
})
约束设置完毕后执行的initPage方法,既是页面生成的方法。
页面生成
通过约束计算本页的uuid,保证了即使是相同的页面,当查询条件不同时,也会向后台注册不同的事件
//重置容器
uuid = obHash(condition)
charts = {}
socketInfo = {}
dbs = {}
通过看板组生成多块看板(不重要,略过)
渲染看板组件
根据看板ID获得页面元素表记录,通过不同的load方法进行组件的生成。
//循环每一个看板,向其中添加数据
for (var x = 0; x < Object.keys(dbs).length; x++) {
//因为要讲看板id带进入,所以搞一个闭包
(function(dbId) {
//所有元素都是平级,所以按顺序填充即可
//首先处理html
callAjax('html/list', {
db_id: dbId
}, function(res) {
loadHtml(dbId, res.data)
})
//处理chart
callAjax('chart/list', {
db_id: dbId
}, function(res) {
loadChart(dbId, res.data)
})
//处理table
callAjax('table/list', {
db_id: dbId
}, function(res) {
loadTable(dbId, res.data)
})
//处理image
callAjax('img/list', {
db_id: dbId
}, function(res) {
loadImage(dbId, res.data)
})
})(Object.keys(dbs)[x])
}
以loadHtml为例解释组件生成的方式
可以看到,一个页面元素创建的基本流程就是
执行after脚本(对于chart对象,会在chart创建前执行before脚本,可以完成数据初始化工作)
//html插入工具方法
function loadHtml(dbId, htmlList) {
for (var x = 0; x < htmlList.length; x++) {
(function(x) {
var config = JSON.parse(htmlList[x].config)
var style = JSON.parse(htmlList[x].style)
//创建对象
var div = document.createElement('div')
//设置定位
div.style.position = 'absolute'
div.style.top = config.top + 'px'
div.style.left = config.left + 'px'
div.style.width = config.width + 'px'
div.style.height = config.height + 'px'
setStyle(div, style)
document.getElementById('db_' + dbId).appendChild(div)
var self = $(div)
eval(htmlList[x].after)
//如果存在数据源,则设置事件与回调
if (htmlList[x].ds_id) {
var eventId = uuid + 'html_' + htmlList[x].id
var call_back = htmlList[x].call_back
//新建监听
socket.on(eventId, function(res) {
eval(call_back)
callAjax('data/backLife', {
eventId: eventId
}, function() {})
})
//通知服务器建立事件
callAjax('data/byid', {
id: htmlList[x].ds_id
}, function(res) {
var sqls = res.data.sql_stmt.split(';')
var result = ""
sqls.forEach(function(sql) {
sql = sql.replace(/\n/g, ' ')
result += eval(sql) + ';'
})
socketInfo[eventId] = result
callAjax('data/addEvent', {
eventId: eventId,
sqlArr: result
}, function() {})
})
}
})(x)
}
}
动态数据
详细解释当页面元素进行数据源绑定时,到底发生了什么。
先看代码:
var eventId = uuid + 'html_' + htmlList[x].id
var call_back = htmlList[x].call_back
//新建监听
socket.on(eventId, function(res) {
eval(call_back)
callAjax('data/backLife', {
eventId: eventId
}, function() {})
})
//通知服务器建立事件
callAjax('data/byid', {
id: htmlList[x].ds_id
}, function(res) {
var sqls = res.data.sql_stmt.split(';')
var result = ""
sqls.forEach(function(sql) {
sql = sql.replace(/\n/g, ' ')
result += eval(sql) + ';'
})
socketInfo[eventId] = result
callAjax('data/addEvent', {
eventId: eventId,
sqlArr: result
}, function() {})
eventId是事件向后端注册的标识符。由uuid和页面元素的类型+id组合形成。
uuid由页面约束经过hash计算得到。
因此我们可以得知,当两个页面打开的看板相同,而且页面约束也相同时。他们会向后端发送同样的eventId。既是完全相同的事件不会重复注册。
页面中的socket根据eventId建立监听,当后端通过webSocket推送数据时,页面既可以执行用户配置的回调。同时使用data/backLife接口刷新后端事件的TTL。
之后,页面会通过接口请求该数据源的模板。通过页面中的约束将模板变为实际可执行的SQL语句。
将可执行的SQL和eventId共同发往后端,完成事件注册。