精工动态看板

数据流向

静态数据

系统中静态数据,由底层到顶层,可分为如下四个层级:

数据源

动态看板运行时的数据来源配置。

定义了数据源的标识,以及Java后台获取数据源的方式。

常见的数据源获取方式包括:UDP端口/数据库查询/TCP连接/ICE连接等。

系统运行时,Java后端会抹平数据源的差异,使用统一的格式发往页面进行显示。

页面元素

看板的构成组件。

页面元素需要维护自身的大小/位置/展现形式。不同种类的页面元素有不同的配置方式。

向上而言,由多个页面元素可以拼成完整的看板,向下而言,页面元素负责获得数据源的数据,并可以定义数据的处理规则。

看板实体

页面元素的集合,用户可见的看板。

维护自身的尺寸,看板内的chart风格。

在运行时以看板为单位逐个生成可视页面。

在拖拽系统中,编辑状态的看板既是画板,页面元素既是最小绘图单位。参加chartEz

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为例解释组件生成的方式

可以看到,一个页面元素创建的基本流程就是

  1. 创建dom对象

  2. 设置对象的大小,位置,自定义样式

  3. 将对象加入到看板中

  4. 执行after脚本(对于chart对象,会在chart创建前执行before脚本,可以完成数据初始化工作)

  5. 进行数据源绑定

//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共同发往后端,完成事件注册。

Last updated