一. 组件
1. 多页签函数
1.1 多页签的子页签在一定条件下进行隐藏
viewMoel.execute('updateViewMeta',{ code: '子页签的cGroupCode', visible: false })
1.2 多页签中子页签的表格中放大器隐藏
viewModel.get('btnMax_b7ae8ce3cd76495bb8c3f7717c8d8490').setVisible(false); 其中btnMax是固定的,_后面跟的是 多页签 的cGroupCode
1.3 点击按钮使本页面一个子页签打开
viewModel.execute('updateViewMeta',{activeKey: 'fb016a0082924140bc6a11f919558acc'}) activeKey是子页签的cGroupCode
1.4 多页签切换
gridModel.on('beforeTabActiveKeyChange',function(args){console.log(arg)})
2. 列表
2.1 表格设置高度
viewModel.getGridModel().setState('fixedHeight', 300);
和扩展属性 fixedHeight 配置 数字 值 可以一起使用
2.2 设置表格行上下拖拽
gridModel.setRowDraggable(true);
2.3 表格取消复现框
var gridModel = viewModel.getGridModel();
//设置取消复现框
gridModel.setState("showCheckBox",false);
//或者
gridModel.setShowCheckbox(false);
2.4 子表列隐藏
viewModel.getGridModel().setColumnState( '字段名' , 'visible' , false )
或
viewModel.getGridModel().setColumnState( '字段名' , 'bShowIt' , false )
viewModel.getGridModel().setColumnState( '字段名' , 'bIsNull' , true)
2.5 列表加颜色标注
viewModel.getGridModel().on('afterSetDataSource',function(data){
data.forEach((item,index)=>{
if(item.b_if_finish === '2'){
viewModel.getGridModel().setCellState(index,"b_if_finish","style",{color:'red'});
}else{
viewModel.getGridModel().setCellState(index,"b_if_finish","style",{color:'black'});
}
})
})
2.6 使用代码触发表格的效验
let validate = viewModel.getGridModel().validate();
触发表格的效验设置,成功返回true,失败返回false
2.7 获取表格中的已选种行号
viewModel.getGridModel().getSelectedRowIndexes();
2.8 子表中设置数据
viewModel.getGridModel().setDataSource([]);
2.9 行编辑表高度渲染
viewModel.on('afterMount',function(data){
//点后边第一个是 自己设置的自定义样式名 rsdagClass
loadStyle(".rsdagClass>.wui-tabs>.wui-tabs-content>.wui-tabs-tabpane>.yonRow>.fixedDataTableLayout_main{height:410px !important;}")
//定义方法
function loadStyle(params){
let headobj = document.getElementsByTagName('head')[0];
let style = document.createElement('style');
style.type = 'text/css';
headobj.appendChild(style);
style.sheet.insertRule(params,0);
}
})
2.10 隐藏表头列的排序、锁定、定位、筛选小图标:
注意:代码需要放在afterLoadMeta事件中才有效
viewModel.on('afterLoadMeta', function(data){
let gridModel = viewModel.get('lmhr_tbm_work_month1List');
// 隐藏表头排序
gridModel._set_data('multiSort',false);
// 隐藏表头锁定列
gridModel._set_data('unLock',true);
// 隐藏表头锁定定位
gridModel._set_data('unLookUp',true);
// 隐藏表头筛选
gridModel._set_data('unColFilter',true);
});
2.11 列表页日期搜索框 比较符是区间的(左开右闭)
viewModel.on('beforeSearch',function(args){
args.isExtend = true;
let conditions = args.params.condition;
let arr = conditions.commonVOs;
for(let i = 0 ; i < arr.length ;i++){
let itemName = arr[i].itemName;
if(itemName == 'd_apply_date'){
conditions.commonVOs.value1 = conditions.commonVOs.value1.substring(0,10);
}
}
})
3. js方法
3.1 返回强制刷新(尽量别用,后续问题多多)
window.location.reload()
3.2 根据id赋值名称
let item = viewModel.get('staffNew_name');
cb.utils.triggerReferBrowse(item, [{ field:'id', op:'eq',value1:'1603446359739858947'}]);
3.3 数组去重
let len = arr.length;
for(let i = 0; i < len ; i++){
for(let j = i + 1; j < len ; j++){
if(arr[i].id == arr[j].id){
arr.splice(j,1)
len--
j--
}
}
}
3.4 弹框提示 - 可以修改持续时间
cb.utils.alert({
title : '版本生成失败',
type : 'error',
duration : 7 ,
mask : true,
onClose : function(){}
})
3.5 Loading旋转
function Loading(){
var hook = React.useState(true);
stop = hook[1];
return React.createElement(TinperNext.Spin,{spinning:hook[0]});
}
//开启
ReactDOM.render(React.createElement(Loading),document.createElement('div'));
//关闭
stop();
3.6 把装有id的数组转化为可以拼接sql的字符串
let idArr = "(";
for(let i = 0 ; I < arr.length ; i++){
if(i == arr.length - 1){
idArr = idArr + "'" + arr[i] + "'";
}else{
idArr = idArr + "'" + arr[i] + "',";
}
}
idArr = idArr + ")";
3.7 审批后跳转到主页
viewModel.on('afterWorkflow',() => {
let serviceCode = viewModel.getServiceCode();
let result = cb.rest.invokeFunction("LMHR_ORG.backApiFunc.getMainOriginUrl",{},function(err,res){},viewModel,{async:false});
if(result.result != undefined && result.result.res != undefied && result.result.res.length > 0){
let obj = result.result.res[0];
if(serviceCode == undefined){
//审批后跳转到主页
top.location.href = obj.value;
}
}
})
3.8 关闭自动加载
第一种:
viewModel.on('customInit', function (data) {
viewModel.getParams().autoLoad = false;
});
第二种:
viewModel.on('beforeSearch', function(args) {
// 标准写法
args.isExtend = true;
let conditions = args.params.condition;
conditions.simpleVOs = [{
"logicOp": "and",
"conditions": [{
"field": "1",
"op": "eq",
"value1": "2"
}]
}];
});
3.9 修改输入框为密码框
viewModel.on("afterMount",function(data){
// 976e9acd是cBillNo的值,v_password是字段名
document.getElementById("976e9acd|v_password").getElementsByTagName("input")[0].type = 'password';
// 隐藏右上角的x
document.getElementsByClassName("close dnd-cancel")[0].style.display = 'none';
})
3.10 树表详情页面beforeSave事件可以让上级分类值改变
//将数据保存为js对象
const res = JSON.parse(data.data.data);
//上级分类赋值为输入框内容
res.parent = viewmodel.get('parent').getValue();
//js对象转为json字符串
const res2 = JSON.stringify(res);
//赋值
data.data.data = res2;
3.11 列表页定义值,详情也做判断
列表页
viewModel.on('customInit',function(data){
viewModel.getParams().view_flag = "flag";
})
详情页
let parentParams = viewModel.getParams().parentParams;
if(parentParams != null && parentParams != undefined){
var view_flag = parentParams.view_flag;
if(!cb.utils.isEmpty(view_flag) && view_flag == "flag"){
//做判断
}
}
3.12 判断数组中是否有该元素并添加改元素
let array = [];
if(array.indexOf(row.d_date) === -1){
array.push(row.d_date);
}
3.13 获取搜索框的值
let firstDept = viewModel.getCache('FilterViewModel').get('c_first_dept').getFromModel().getValue();
3.14 后端函数中获取当前时间
const date = new Date();
const nowdate1 = date.getFullYear() +"-"
+(date.getMonth()+1 >= 10 ? (date.getMonth() +1) : '0' + (date.getMonth() +1)) +"-"
+(date.getDate() >= 10 ? date.getDate() : '0' + date.getDate()) + " "
+(date.getHours() >= 10 ? date.getHours() : '0' + date.getHours()) + ":"
+(date.getMinutes() >= 10 ? date.getMinutes() : '0' + date.getMinutes()) + ":"
+(date.getSeconds() >= 10 ? date.getSeconds() : '0' + date.getSeconds());
//date.getMonth()获取的月份的值是[0,11],所以需要加1来获取实际月份;
3.15 刷新
viewModel.execute('refresh')
3.16 页面添加自定义全局参数
首先,在列表页或详情页添加自己的自定义参数,可以直接写:
viewModel.getParams().isAlreadyCheck = false;
let check = viewModel.getParams().isAlreadyCheck;
这样在本页面添加的就是全局参数,只要页面不关闭,就可以一直存在;
其次,打开模态框的时候,可以在params里传参,使用时依然可以用上面的方式获取:
const code= viewModel.get('code').getValue();
let event={
billtype:'VoucherList',
billno:'84410631List',
params:{
mode:'browse', //必传,所打开页面的页面状态
code:code //自定义的参数
}
};
cb.loader.runCommandLine('bill',event,viewModel);
注意:这种代码跳转页面不仅适用于模态框,也可以跳转列表或详情页。
最后,列表页跳转详情页传参有两种方式:
第一种:在列表页添加参数,详情页使用如下代码即可获取:
let parentParams = viewModel.getParams().parentParams();
第二种:使用双击事件覆盖默认的双击跳转,需要在最后return false;
并且在双击事件中使用上面的跳转模态框的方式打开详情页,并传递参数。
3.17 获取卡片页面的单据状态
let currentState = viewModel.getParams().mode;
3.18 获取单元格值的方法--抽取出来的
function myGetCellValue(rowIndex,cellName){
let value = viewModel.getGridModel().getCellValue(rowIndex,cellName);
return value;
}
3.19 设置单元格的方法--抽取出来的
function mySetCellValue(rowIndex,cellName,value){
viewModel.getGridModel().setCellValue(rowIndex,cellName,value);
}
3.20 数据保存后修改子页签名称和子表行修改颜色
先看效果图
代码
function event(){
function loadStyle(params) {
var headobj = document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(params));
headobj.appendChild(style);
}
//下边这小段只有颜色可以改
loadStyle(`.meta-table .public_fixedDataTableRow_bg-yellow .public_fixedDataTableCell_main {background: #FFDEAD}`);
let g1 = viewModel.get('lm**_evo_title_b1List');
let g2 = viewModel.get('lm**_evo_title_b2List');
let g3 = viewModel.get('lm**_evo_title_b3List');
...
let args = [];
let rows1 = g1.getRows();
let rows2 = g2.getRows();
let rows3 = g3.getRows();
...
if(rows1.length > 0){
args.push({cGroupCode:'子页签容器编码',caption:'出国信息 *'});
for(let i = 0 ;i < rows1.length ; i++){
let w5 = rows1[i].字段名称;
if(w5 == '2'){
//只有i可改
g1.setRowState(i,'className','bg-yellow');
}
}
}
if(rows2.length > 0){
args.push({cGroupCode:'子页签容器编码',caption:'学习经历 *'});
for(let i = 0 ;i < rows2.length ; i++){
let w5 = rows2[i].字段名称;
if(w5 == '2'){
//只有i可改
g2.setRowState(i,'className','bg-yellow');
}
}
}
...
if(args.length > 0){
viewModel.execute('updateTabsCaption',args);
}
}
3.21 打印显示照片(照片没存在本地,本地存储的是一个id值)
//每次点击打印按钮会触发
viewModel.on('beforePreview',function(data){
let f_photo = document.getElementById("单据编码|照片字段").getElementsByTagName('img')[0].currentSrc;
if(f_photo != undefined && f_photo != ""){
f_photo = f_photo.replace("downThumb=true","downThumb=false");
let object = {id:viewModel.get('id').getValue(),"存储路径的字段名":f_photo};
//下边更新进库就行了->不写了
}
})
4. 属性
4.1 搜索区默认方案隐藏方法:查询区的扩展属性中增加
"bHideFilterScheme": true
4.2 设置文本框输入长度
yya,get('new1').setState('iMaxLength',5)
4.3 页面字段不可编辑
viewModel.get('字段').setState('bCanModify',false);
viewModel.get('字段').setState('disable',true);
4.4 设置是否必填
viewModel.get('字段').setState('bIsNull',false);
gridModel.setCellState(rowIndex,"字段",'bIsNull',true);//不必填
4.5 按钮跳转行编辑页自动进入编辑态
viewModel.get("btnEdit").execute("click");
4.6 单卡页面修改左上角编码显示为名称:
在CardTableLeftHeader中添加一个按钮,打开按钮的编辑器,
修改‘cControlType’和‘uitype’的值为‘pagetitle’
4.7 卡片的组件改名有两种方式:
第一种不需要设置自定义样式名,只需要用‘单据编码|字段别名’
document.getElementById("898847a0|c_code_v_code")
.getElementsByTagName("label")[0].innerHTML = "员工编码";
document.getElementById("898847a0|c_code_v_code")
.getElementsByClassName("label-control")[0].setAttribute("title","员工编码");
label-control的上面这行代码不写也能生效
4.8 卡片页添加大标题
//如何像word一样能给卡片页添加一个居中显示的大标题呢,是能做到的,首先你需要添加一个描述文本类型的虚字段,可以设置默认值,也可以根据条件触发主动设置你想要的内容
viewModel.get('item82re').setValue(
`<p style="text-align:center;" size='0' _root="undefined"
__ownerID="undefined" __hash="undefined" __altered="false">
<span
style="font-size:28px">
<strong>《离职审批表》</strong>
</span>
</p>`
);
5. 参照
5.1 搜索框一级部门筛选
树的筛不了,树形参照改为列表参照
viewMoel.on('afterMount',function(data){
var filterViewModelInfo = viewModel.getCache('FilterViewModel');
//设置搜索框一级部门数据过滤
filterViewModelInfo.on('afterInit',function(args){
filterViewModelInfo.get('c_unit').getFromModel().on('beforeBrowse',function(data){
let myFilter ={"isExtend":true,simpleVOs:[]};
myFilter.simpleVOs.push({
field:"level",
op:"eq",
value1:'1.00'
});
filterViewModelInfo.get('c_unit').getFromModel().setFilter(myFilter);
})
})
})
5.2 参照创建出来了,名字就固定了,使用时,单页面修改其名称的代码
viewModel.get('deptstaff_new1').on('afterInitVm', function (args) {
//获取参照弹窗的模型
let referViewModel = args.vm;
debugger;
referViewModel.on('afterGetRefMeta', function (data) {
console.log(data);
debugger;
data.refEntity.name = '1111'
});
});
5.3 详情页参照打开前筛选
viewModel.get('c_apply_person_v_name') && viewModel.get('c_apply_person_v_name').on('beforeBrowse',function(data){
var myFilter = {"isExtend": true,simpleVOs:[]};
myFilter.simpleVOs.push({
field:'lmhr_xx_xgzxxList.b_ifjob',
op:'eq',
value1:'1'
})
viewModel.get('c_apply_person_v_name').setFilter(myFilter);
})
5.4 详情页子表参照打开前筛选
viewModel.get('lmhr_xx_ruku_register_bList') && viewModel.get('lmhr_xx_ruku_register_bList').getEditRowModel().get('c_code_v_code').on('beforeBrowse',function(data){
var myFilter = {"isExtend": true,simpleVOs:[]};
myFilter.simpleVOs.push({
field:'lmhr_xx_xgzxxList.b_ifjob',
op:'eq',
value1:'1'
})
viewModel.get('lmhr_xx_ruku_register_bList').getEditRowModel().get('c_code_v_code').setFilter(myFilter);
})
5.5 详情页子表参照弹出前进行筛选
viewModel.getGridModel().getEditRowModel().get('c_dept_v_name').on('beforeBrowse',function(data){
var gridModel = viewModel.getGridModel();
//获取一级部门
let result = cb.rest.invokeFunction("xxxx_CM_demand.getOneDept",
{},function(err,res) {},viewModel,{async:false});
let deptArr = [];
if(result.result != undefined && result.result.res != undefined && result.result.res.length > 0){
let arr = result.result.res;
for(let i = 0 ; i < arr.length ; i++){
deptArr.push(arr[i].id);
}
if(deptArr.length > 0){
//部门过滤
var myFilter = {"isExtend":true,simpleVOs:[]};
myFilter.simpleVOs.push({
field : 'id' ,
op : "in" ,
value1 : deptArr
});
gridModel.getEditRowModel().get('c_dept_v_name').setFilter(myFilter);
}else{
cb.utils.alert("获取一级部门数据失败",'error');
}
}
deptArr = [];
})
6. 枚举
6.1 枚举筛选
列表页
let gridModel = viewModel.getGridModel();
let oldData = gridModel.getEditRowModel().get('c_type).__data.dataSource;
let newData = [{value:'2',text:'注销',nameType:'string'},{value:'3',text:'撤销',nameType:'string'}];
gridModel.getEditRowModel().get('c_type').setDataSource(newData);
详情页
//获取枚举原来的数据
let oldData = viewModel.get('enum_code').__data.dataSource
//自定义处理数据
let newData = 获取需要的数据;
//从新设置数据源
viewModel.get('enum_code').setDataSource(newData)
7.事件
7.1 调用按钮
viewModel.get('button38jd').execute('click');
7.2 列表页搜索前事件,符合要求的会进行显示
viewModel.on('beforeSearch',funtion(data){
data.isExtend = true
let conditions = data.params.condition;
conditions.simpleVos = [{
"logicOp":"or" ,
"conditions":[{
"field":"c_ziduanming",
"op":"eq",
"value1":'1'
},{
"field":"c_ziduanming",
"op":"eq",
"value1":'2'
}]
}];
});
7.3 树形表详情页默认带入上级
viewModel.on("modeChange",function(data){
if("add" == data){
debugger;
let parent = viewModel.originalParams.parent;
if(typeof parent == "undefined" || null == parent){
return;
}
let parentName = viewModel.originalParams.parent.v_name;
let parentId = viewModel.originalParams.parent.id;
viewModel.get('parent').setValue(parentId);
viewModel.get('parent_name').setValue(parentName);
}
});
7.4 左树右表列表页限制节点选择:
viewModel.on('beforeAdd',function(args){
debugger;
const parent = args.params.parent;
if(typeof parent == "undefind" || null == parent){
return false;
}else{
const type = parent.orgs_type;
if("dept" != type){
return false;
}
}
});
8. 注意事项
8.1 树形表配置树形链接查询才可以通过树表数据双击进入详情
8.2 主子表导入时主表手工码等于子表中主表手工码
8.3 树形表列表页的系统导入添加脚手架相关的配置
如果树形表的导入报错缺少配置,需要在脚手架的properties配置文件中添加对应的属性配置:
treeFieldMappingConfig=[{"billNo":"2b172bb3","treeParentField":"parent","treeField":"id"},{"billNo":"a477d4f8","treeParentField":"parent","treeField":"id"}]
billNo的值对应树形表单卡页面的页面编码
treeParentField和treeField是固定写法
8.4 bip的坑
8.41 YonSql的默认查询上限是5000条,如果想查询所有数据,需要先查询对应数据的条数,然后将limit条件拼上去,就会覆盖YonSql默认的limit条件;
8.42 YonSql 查询使用case when的时候,语法要写全,else也要加上,数据库支持没有else时返回NULL,但是YonSql不支持,底层的查询与结果封装返回走的是java代码,没有else的时候未判断它是否存在就直接获取else的值,所以会报空指针的问题;
8.43 查询区的日期框,如果格式调整为年月,设计器直接默认是区间查询,如果想直接匹配,则打开该查询框的编辑器,将compareLogic字段的value由between改为eq即可;
8.44 YonSql的批量插入,不管是分批for循环插入,还是一次性全部插入,上限大概100条,有一个类似脚本执行时间或者数据库连接维持时间来控制,对于大批量数据的新增是不支持的,只能从后端脚手架里走接口保存了;(其实应该是每个表上限100条)
8.45 YonSql中使用left join的时候,where条件使用‘!=’,它左右两边的字段不能一个是left join 左边的表字段,一个是右边的表字段,如果两边的字段都使用left join左边唯一主表的字段,或者左边字段任意,右边是固定值,这两种情况都好使,如果左边是唯一主表的字段,右边是另一个表的字段,YonSql对于这个条件的处理是使其失效,就当这个条件不存在。如果遇到需要这种情况的sql,只能是把所有数据查询出来自己去做判断;
8.46 前端脚本直接调用脚手架接口的时候,如果后端脚手架的返回是自己定义的json数据,proxy的回调函数中只需要一个参数去接收,而且返回值外面会被包一层error,如果是用ResponseUtils.renderJson(response, ResultMessage.success("部门反撤销成功"))脚手架的返回方式,它会在自定义的返回值外面包一层code、message、data,此时前端proxy回调函数使用6个参数接收;
8.47 可以在BIP的前端脚本卡一个bug,本来proxy调用脚手架接口需要最新代码部署到流水线,但是前端脚本的代码最终就是浏览器页面的代码,可以使用原生JS代替proxy直接调用本地的后端服务实现前后端联调,等测试完好使再把代码改回proxy调用。
8.48 针对系统的导入规则链写后端脚本时,脚本的位置只能放在checkUniqueRule之后,saveBillRule之前;
8.49 表格数据在编辑态的时候如果要删除,要么选中行通过删除按钮删除,要么走gridModel的deleteRow()方法或deleteAllRows()方法,clear或setDataSource等方法无法清除之前的数据;
8.52 BIP页面的组件,第一次生成的时候所有字段都是必传,删了重加的组件‘必传’属性默认改为false了,这样的话在保存后事件中就无法从返回的数据中获取该字段,需要自己手动去修改‘必传’属性为true;
8.51 一个页面如果修改为模态框之后就无法再修改回正常的页面了,只能作为模态框使用,所以在是否做成模态框的问题上需要谨慎考虑;
8.52.左树右表的节点,如果列表页面双击点不进去详情页,正常情况都配置好依然不好使,可以看看左树是不是将创建人修改人拖出来了,控制台打印的_get_data错误十有八九是这个东西引起的,它的本质是用户参照,说明是列表页的用户参照有问题,一般详情页的参照是没有问题的;
8.53 如果后期在一主多子的多页签添加子页签,不要在左边的层级点击右键添加组件,直接在卡片页多页签的位置点击‘添加子页签’
二.主子表详情页面导入导出功能简单实现
2.1 xslx.core.min.js 导入
// 导入按钮
viewModel.get('button36pi') && viewModel.get('button36pi').on('click', function (data) {
let secScript = document.createElement("script");
secScript.setAttribute("type", "text/javascript");
document.body.insertBefore(secScript, document.body.lastChild);
//调用两个方法
f();
selected();
})
function selected(){
document.getElementById('file_input-import').click();
}
//旋转方法
function Loading(){
var hook = React.useState(true);
stop = hook[1];
return React.createElement(TinperNext.Spin,{spining:hook[0]});
}
function f(){
let temp = document.getElementById('file_input-import');
if(null != temp){
document.getElementById('file_input-import').remove();
}
let fileInput= document.createElement("input");
fileInput.id = 'file_input_import';
fileInput.type = 'file';
fileInput.style = 'display:none';
document.body.insertBefore(fileInput, document.body.lastChild);
document.getElementById("file_input_import").addEventListener('change',function(e){
let files = e.target.files;
if(files.length == 0 )return;
let filesData = files[0];
let fileName = filesData.name;
//判断导入模版是否准确
//包含为true
if(!fileName.includes("模版名")){
cb.utils.alert("导入模版错误" + fileName ,'error');
return false;
}
if(!isStrEmpty(fileName) && (("xls" == (fileName.substring(fileName.lastIndexOf(".") + l))) || ("xlsx" == (fileName.substring(fileName.lastIndexOf(".") + l))))){
readWorkbookFromLocalFile(filesData ,function(workbook){
readworkbook(workbook);
})
}else{
cb.utils.alert('文件类型错误','error');
fileInput.remove();
return false;
}
});
//读取本地excel文件
function readWorkbookFromLocalFile(file, callback) {
var reader = new FileReader();
reader.onload = function(e) {
cb.requireInner(["/iuap-yonbuilder-runtime/opencomponentsystem/public/AT16F65E4A1D400004/xlsxjs?domainKey=developplatform"], function(a) {
var localData = e.target.result;
var workbook = a.getXlsx({flag:'1',localData:localData});
if(callback) callback(workbook);
reader.abort();
})
};
reader.readAsBinaryString(file);
}
//读取excel里面数据,进行缓存
function readWorkbook(workbook) {
//开始旋转
ReactDOM.render(React.createElement(Loading),document.createElement('div'));
cb.requireInner(["/iuap-yonbuilder-runtime/opencomponentsystem/public/AT16F65E4A1D400004/xlsxjs?domainKey=developplatform"], function(a) {
var sheetNames = workbook.SheetNames; // 工作表名称集合
sheetNames.forEach(sheet => {
//获取每个sheet页的数据
var workbookDatas = a.getXlsx({flag:'2',workbook:workbook.Sheets[sheet]});
if(sheet == "sheet页名"){
for(let i = 0 ;i < arr[0].length ;i++){
let start = arr[0][i].开始时间;
let end = arr[0][i].结束时间;
viewModel.getGridModel().appendRow({'begindate':start,'enddate':end});
}
}
})
//关闭Loading
stop();
});
fileInput.remove();
}
}
2.2 导入调用的方法
//字符串判空 - 前端脚本用
function isStrEmpty(str){
if(typeof str == "undefined" || null == str || "" == str || "" == str.trim()){
return true;
}else{
return false;
}
}
//字符串判空 - api和后端脚本用
function isObjEmpty(str){
if(typeof str == "undefined" || null == str || "" == str || "" == trim(str)){
return true;
}else{
return false;
}
}
2.3 xslx.full.min.js 导出
// 导出--单击
viewModel.get('button42ci') && viewModel.get('button42ci').on('click', function (data) {
let b1Arr = [{
"年度": "文本,必填",
"积分": "文本,必填",
"备注": "文本",
"注意 : 前两行不可以做任何改动" : ''
}]
let secScript = document.createElement("script");
secScript.setAttribute("type", "text/javascript");
document.body.insertBefore(secScript, document.body.lastChild);
let fileOutput= document.createElement("input");
fileOutput.id = 'file_input_export';
fileOutput.type = 'file';
fileOutput.style = 'display:none';
document.body.insertBefore(fileOutput, document.body.lastChild);
let inputElement = document.getElementById("file_input_export");
inputElement.addEventListener("change", handleFiles, false);
function handleFiles() {
var selectedFile = document.getElementById("file_input_export").files[0];
var reader = new FileReader();
reader.readAsText(selectedFile,"UTF-8");
reader.onload = function(){
arrExcel = JSON.parse(this.result);
};
}
function jsonToExcel () {
var filename = "自定义文件_模版.xlsx";
var ws_name1 = "年度考核";
let waArr = [{
"ws_name1" : ws_name1 ,
"bArr1" : b1Arr
}]
cb.requireInner(["/iuap-yonbuilder-runtime/opencomponentsystem/public/AT16F65E4A1D400004/dsfsdf?domainKey=developplatform"],
function(a) {
var workbook = a.getFull({flag:'1',waArr :waArr ,filename:filename});
console.log(workbook)
})
}
jsonToExcel();
});
调用前端公共函数,把数据传入公共函数中,处理完后返回结果,公共函数内把参数传入方法中 把js 代码包裹返回结果是处理完的数据
三. 调用方面
3.1 前端调用api函数
let result = cb.utils.invokeFunction('脚本路径',{"参数":参数值},function(err,res){},viewModel,{async:false});
注意:async的值为false时,此时脚本的调用是同步的,只有等结果返回之后才能执行之后的代码,如果改为true,调用脚本之后代码会继续执行,结果只能在回调函数中处理,此时可以进行脚本调试,可以打断点进入到调用的脚本中;
3.2 一个api函数insert主子表
var object = {orgVO: "2362172988248320",orgVO_name: "开心",bustype_name: "测试",name:"张三",age:10,money:10,childTableList:[{good:"10",num:1,"total":1}]};
var res = ObjectStore.insert("GT64965AT75.GT64965AT75.parentTable",object,"0a2c44bf");
3.3 调用后端函数
//常用的是在审批后触发后端函数
详情页面 -> 命令管理 -> 动作 -> audit动作 -> 执行函数选择自己写的那个函数
3.4 调用后端流水线接口
3.4.1 前端事件直接调用接口(常用的)
//使用原生JS代码直接调用本地服务进行联调
/*let url = 'http://localhost:8080/****/allocateDept';
let xhr = new XMLHttpRequest(); //适配浏览器见收藏博客
xhr.open("POST",url,true);
xhr.setRequestHeader('Content-Type','application/json;charset=UTF-8');
xhr.setRequestHeader('Access-Control-Allow-Origin','*');
xhr.send(JSON.stringify(param));
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
debugger;
let result = xhr.responseText;
console.log(result);
//let data = JSON.parse(result);
xhr = null;
viewModel.communication({type:'modal',payload:{data:false}});
parentViewModel.execute('refresh');
}
}*/
//流水线调用接口
const proxy = viewModel.setProxy({
saveLog:{
url:'/****/allocateDept',
method:'POST',
options:{
mask:true,
async:true
}
}
})
proxy.saveLog(data,function(err,res){
console.log("res",res);
})
注意:这种方式直接调用本地服务可能会产生跨域的拦截,具体的原理之后再探究,
主要的解决方法就是在后端服务的配置类加上以下的代码:
@Bean
public CorsConfiguration buildConfig(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",buildConfig());
return new CorsFilter(source);
}
3.4.2 前端函数调用流水线的接口 ->按钮触发的
let options = {
method:'POST',
headers:{
'Accept':'application/json',
'Content'-Type':'application/json'
},
body:JSON.stringify({
data:'1'
})
}
fetch("https://dev-zhongtai.xxxxx.cn/xxxx-be/ZzbImportExportExportAction/ZzbExport",options
).then(res=>{console.log(res)})
3.4.3 后端函数或api函数,调用流水线的接口
使用的达梦数据库,表中id无自增,流水线中需要自行获取id
//准备租户id和创建人id,新增时需要 和表中的 CREATOR ,TENANT_ID , YTENANT_ID 对应
let appCon = JSON.parse(AppContext());
let currentUser = appCon.currentUser;
let creator = currentUser.id;
let tenant = currentUser.tenantId;
//上边的 不执行新增库操作可 不传, obj 是自己的数组类型的数据
let body = {
creator : creator,
tenantId : tenant,
obj : obj
}
// throw new Error(JSON.stringify(body));
let header = {}
var strResponse = postman("post","http://dev-zhongtai.xxxxxx.cn/xxxx-be/fileConsult/insertBatchData",JSON.stringify(header),JSON.stringify(body));
let objResponse = JSON.parse(strResponse);
//判断有错的话就抛异常
if(objResponse.code != '200'){
throw new Error(objResponse.message);
}
3.5 openLinker 连接接口
var userId = request.userId;
//通过用户ID查询员工信息
let base_path = "https://yonbip.diwork.com/iuap-api-gateway/yonbip/digitalModel/staffQry/getStaff";
var body = {
"userId": [
userId
]
};
//请求数据
let apiResponse = openLinker("post", base_path, "GT102500AT53", JSON.stringify(body));
var result = JSON.parse(apiResponse);
// throw new Error(apiResponsejaon);
var queryCode = result.code;
if (queryCode !== "200") {
throw new Error('查询用户对应人员错误 ' + result.message);
}
3.6 api函数或后端函数,直接传token,省略调后端设置权限了
let body = {}
let appCon = JSON.parse(AppContext());
let token = appCon.token;
let header = {
"yht_access_token":token
};
3.7 关于文件上传接口的前端调用
文件上传接口,如果走前端脚本cb.util的封装调用proxy或者ajax,走的是node服务的转发,具体再往后不知道走没走网关或者nginx,能确定的是这种方式的调用,在转发的过程中表单multipart/form-data请求会被改写为application/json,导致后端服务接口解析失败,所以在前端脚本中,就需要写最原始的XMLHttpRequest走另一种方式的调用,这种调用和后端脚本中的postman调用一样,都是域名+后端服务二级域名+接口url,这个是最直接的调用,openApi最后一步填写后端调用地址的时候也是用的这种方式,后端服务二级域名不是领域名,应该是领域名+‘-be’构成的,具体代码如下:
viewModel.get('button19pe') && viewModel.get('button19pe').on('click', function(data) {
// 批量照片上传--单击
fileMulti();
selected();
});
function selected() {
document.getElementById('fileinput').click();
}
function fileMulti() {
debugger;
let xxx = document.getElementById("fileinput");
if(null != xxx){
xxx.remove();
}
let Input = document.createElement('input');
Input.id = 'fileinput';
Input.type = 'file'; //文件选择
Input.multiple = 'multiple'; //多选
Input.accept = 'image/*'; //设置筛选
// Input.style = 'display:none';
document.body.insertBefore(Input, document.body.lastChild)
//给文件iput注册改变事件
document.getElementById("fileinput").addEventListener('change', function(e) {
debugger
let files = document.getElementById("fileinput").files;
let fd = new FormData();
//fd.enctype = 'multipart/form-data';
for(let i = 0; i < files.length; i++){
fd.append('files',files[i]);
}
let xhr = new XMLHttpRequest();
let prefix = window.location.origin;
xhr.open("POST",prefix + "/lmhr-be/fileUpLoad/imageBatchUploadUpdatePsnMsg");
xhr.send(fd);
xhr.onload = function(){
let res = cb.utils.parseCStyle(xhr.responseText);
if(res.code == 200){
cb.utils.alert(res.message);
}
}
});
}
3.8 审批过程中记录各个活动的详细信息
甲方要求把本来点审批按钮就能看到的审批信息输出为页面的一个子表信息,一个是方便看,一个是为了配打印模板进行打印,所以还能怎么办呢,只能宠着人家了呗。还是调审批流的系统接口,获取数据进行写入,注意这里需要设置两处触发方式,因为BIP触发审批有两种方式,一种是点进卡片页点击审批,一种是在首页的消息中心点击审批同意,对应的两种方式分别是:在前端脚本添加审批后事件和在审批的规则链添加后端脚本
方式一:
前端脚本代码
viewModel.on('afterWorkflow',function(args){
debugger;
let id = viewModel.get('id').getValue();
let res = cb.rest.invokeFunction('LMHR_TRN.deploy.approveMsgSave',{id:id},function(err,res){},viewModel,{async:false});
let serviceCode = viewModel.getServiceCode();
//查询参数表
let result = cb.rest.invokeFunction("LMHR_ORG.backApiFunc.getMainOriginUrl",
{},function(err, res) {}, viewModel, {async: false});
//查询结果不为空
if(result.result != undefined && result.result.res != undefined && result.result.res.length > 0){
let obj = result.result.res[0];
if(serviceCode == undefined){
//审批后跳主页
top.location.href = obj.value;
//后面这部分是甲方希望单据审批完之后直接回到主页,还能怎么办,宠呗
}
}
});
API脚本approveMsgSave:
let AbstractAPIHandler = require('AbstractAPIHandler');
class MyAPIHandler extends AbstractAPIHandler {
execute(request){
let id = request.id;
let appCon = JSON.parse(AppContext());
let token = appCon.token;
let tenantId = appCon.currentUser.tenantId;
let header = {
"yht_access_token":token,
"tenantId":tenantId,
"tenantAppSource":"lmhr"
};
let body = {
//76e1a30d_ 这玩意,卡片页页面编码
"businessKey":"76e1a30d_" + id
};
let sqlUrl = "select * from LMHR_BD.LMHR_BD.lmhr_bd_sys_param where p_code = 'mainOriginUrl'";
let resUrl = ObjectStore.queryByYonQL(sqlUrl);
let urlPrefix = "";
if (resUrl != null && resUrl != undefined && resUrl.length > 0) {
urlPrefix = resUrl[0].value;
}
let url = urlPrefix + "/iuap-apcom-workflow/approve-component/api/v1/approve/load";
//调用接口查询到审批后的流程信息
let response = postman("post", url, JSON.stringify(header), JSON.stringify(body));
//根据主表id查询子表记录的所有审批信息并封装Map
let childSql = "select * from LMHR_TRN.LMHR_TRN.lmhr_trn_deploy_approve_msg where lmhr_trn_deploy_id = '"+ id +"'";
let childRes = ObjectStore.queryByYonQL(childSql);
let childMap = new Map();
if(childRes.length > 0){
for(let i = 0; i < childRes.length; i++){
childMap.set(childRes[i].task_id,childRes[i]);
}
}
//遍历审批任务,如果当前任务不在Map中,就将任务信息写入子表
let approveMsg = JSON.parse(response);
let historicTasks = approveMsg.data.historicTasks;
for(let i = 0; i < historicTasks.length; i++){
let obj = {};
obj.process_definition_id = approveMsg.data.processDefinitionId;
obj.ziduan1 = approveMsg.data.businessKey;
obj.lmhr_trn_deploy_id = id;
let task = historicTasks[i];
let task_id = task.id;
if(null == childMap.get(task_id)){
//封装审批信息并插入子表
obj.process_instance_id = task.processInstanceId;
obj.task_id = task_id;
obj.ziduan3 = task.name;
let taskComments = task.taskComments;
if(null != taskComments){
obj.commentmsg = taskComments[0].message;
}
obj.assignee = task.assignee + "";
obj.assignee_name = task.assigneeParticipant.name;
obj.ziduan2 = task.assigneeParticipant.code;
let endTime = task.endTime;
if(null != endTime){
let startDate = new Date(endTime);
obj.start_time = startDate.getFullYear()
+"-" +(startDate.getMonth()+1 >= 10 ? (startDate.getMonth() +1) : '0' + (startDate.getMonth() +1))
+"-" +(startDate.getDate() >= 10 ? startDate.getDate() : '0' + startDate.getDate());
}
//你猜这里为什么不把结束时间放到结束时间的字段里,而是放在开始时间的字段中呢
// let endTime = task.endTime;
// if(null != endTime){
// let endDate = new Date(endTime);
// obj.end_time = endDate.format("yyyy-MM-dd");
// }
ObjectStore.insert("LMHR_TRN.LMHR_TRN.lmhr_trn_deploy_approve_msg",obj,"f5c3f587");
}else{
let child = childMap.get(task_id);
let taskComments = task.taskComments;
if(null != taskComments){
child.commentmsg = taskComments[0].message;
}
let endTime = task.endTime;
if(null != endTime){
let startDate = new Date(endTime);
child.start_time = startDate.getFullYear()
+"-" +(startDate.getMonth()+1 >= 10 ? (startDate.getMonth() +1) : '0' + (startDate.getMonth() +1))
+"-" +(startDate.getDate() >= 10 ? startDate.getDate() : '0' + startDate.getDate());
}
let childWrapper = new Wrapper();
childWrapper.eq("id",child.id);
ObjectStore.update("LMHR_TRN.LMHR_TRN.lmhr_trn_deploy_approve_msg", child, childWrapper, "f5c3f587");
}
}
//throw new Error(response);
return {res:JSON.parse(response)};
}
}
exports({"entryPoint":MyAPIHandler});
方式二:
规则链后端脚本:添加audit动作规则链,一般放在refreshTsRule之前
let AbstractTrigger = require('AbstractTrigger');
class MyTrigger extends AbstractTrigger {
execute(context,param){
let appCon = JSON.parse(AppContext());
let token = appCon.token;
let tenantId = appCon.currentUser.tenantId;
let header = {
"yht_access_token":token,
"tenantId":tenantId,
"tenantAppSource":"lmhr"
};
let id = param.data[0].id;
//throw new Error(id);
let body = {
"businessKey":"76e1a30d_" + id
};
let sqlUrl = "select * from LMHR_BD.LMHR_BD.lmhr_bd_sys_param where p_code = 'mainOriginUrl'";
let resUrl = ObjectStore.queryByYonQL(sqlUrl);
let urlPrefix = "";
if (resUrl != null && resUrl != undefined && resUrl.length > 0) {
urlPrefix = resUrl[0].value;
}
let url = urlPrefix + "/iuap-apcom-workflow/approve-component/api/v1/approve/load";
let response = postman("post", url, JSON.stringify(header), JSON.stringify(body));
//throw new Error(response);
//根据主表id查询子表记录的所有审批信息并封装Map
let childSql = "select * from LMHR_TRN.LMHR_TRN.lmhr_trn_deploy_approve_msg where lmhr_trn_deploy_id = '"+ id +"'";
let childRes = ObjectStore.queryByYonQL(childSql);
let childMap = new Map();
if(childRes.length > 0){
for(let i = 0; i < childRes.length; i++){
childMap.set(childRes[i].task_id,childRes[i]);
}
}
//遍历审批任务,如果当前任务不在Map中,就将任务信息写入子表
let approveMsg = JSON.parse(response);
let historicTasks = approveMsg.data.historicTasks;
//throw new Error(JSON.stringify(historicTasks));
for(let i = 0; i < historicTasks.length; i++){
let obj = {};
obj.process_definition_id = approveMsg.data.processDefinitionId;
obj.ziduan1 = approveMsg.data.businessKey;
obj.lmhr_trn_deploy_id = id;
let task = historicTasks[i];
let task_id = task.id;
if(null == childMap.get(task_id)){
//封装审批信息并插入子表
obj.process_instance_id = task.processInstanceId;
obj.task_id = task_id;
obj.ziduan3 = task.name;
let taskComments = task.taskComments;
if(null != taskComments){
obj.commentmsg = taskComments[0].message;
}
obj.assignee = task.assignee + "";
obj.assignee_name = task.assigneeParticipant.name;
obj.ziduan2 = task.assigneeParticipant.code;
let endTime = task.endTime;
if(null != endTime){
let startDate = new Date(endTime);
obj.start_time = startDate.getFullYear()
+"-" +(startDate.getMonth()+1 >= 10 ? (startDate.getMonth() +1) : '0' + (startDate.getMonth() +1))
+"-" +(startDate.getDate() >= 10 ? startDate.getDate() : '0' + startDate.getDate());
}
// let endTime = task.endTime;
// if(null != endTime){
// let endDate = new Date(endTime);
// obj.end_time = endDate.format("yyyy-MM-dd");
// }
注意:后端脚本的调试只能通过抛异常来查看信息
//throw new Error(JSON.stringify(obj));
ObjectStore.insert("LMHR_TRN.LMHR_TRN.lmhr_trn_deploy_approve_msg",obj,"f5c3f587");
}else{
let child = childMap.get(task_id);
let taskComments = task.taskComments;
if(null != taskComments){
child.commentmsg = taskComments[0].message;
}
let endTime = task.endTime;
if(null != endTime){
let startDate = new Date(endTime);
child.start_time = startDate.getFullYear()
+"-" +(startDate.getMonth()+1 >= 10 ? (startDate.getMonth() +1) : '0' + (startDate.getMonth() +1))
+"-" +(startDate.getDate() >= 10 ? startDate.getDate() : '0' + startDate.getDate());
}
let childWrapper = new Wrapper();
childWrapper.eq("id",child.id);
ObjectStore.update("LMHR_TRN.LMHR_TRN.lmhr_trn_deploy_approve_msg", child, childWrapper, "f5c3f587");
}
}
return {};
}
}
exports({"entryPoint":MyTrigger});
四.模态框
4.0 目标页面设计器中添加
先在UI设计器-层级,在xxx-页面模板节点右击,选择打开编辑器。
添加
"templateType": "modal",
(即卡片页面的页面模板节点)
4.1 页面主动跳转
let id = viewModel.get("id").getValue();
let event = {
billtype:'VoucherList',
billno:'3fa665bbList',
params:{
mode:'browse',
id:id
}
}
cb.loader.runCommandLine("bill",event,viewModel);
4.2 当前模态框关闭
viewModel.communication({type:'modal',payload:{data:false}});
4.3 模态框父页面刷新(放在关闭之前)
let parentViewModel = viewModel.getCache('parentViewModel');
parentViewModel.execute('refresh');
4.4 模态框去掉右上角的叉号
document.getElementsByClassName("close dnd-cancel")[1].style.display = 'none';
4.5 弹页面
1、在页面中配置一个cControlType=modal的容器,容器中配置想要显示的组件
2、调用方法弹出容器区域
3、支持指定弹窗宽度,在cGrupCode对应容器的cStyle中配置width属性;当width值大于1时为px单位,如:800(是指800px);当width值小于1时为百分比,如:0.8(是指定宽度占比80%)
viewModel.communication({
type: 'modal',
payload: {
mode: 'inner',
groupCode: '容器区域cGroupCode',
viewModel: viewModel,
data: { ... }
}
})
五. 事件
5.1 搜索前事件
viewModel.on('beforeSearch', function(data) {})
5.2 单元格值改变后事件
viewModel.get('****List').on('afterCellValueChange',function(data){})
5.3 保存前效验前
viewModel.on('beforeValidate',function(data){})
5.4 页面状态改变事件
viewModel.on('modeChange',function(mode){})
5.5 监听审批动作
viewModel.on('afterWorkflowBeforeQueryAsync',function(args){})
5.6 审批后事件
viewModel.on('afterWorkflow',() =>{})
5.7 数据加载后
viewModel.on('afterLoadData',function(data){})
5.8 值改变后事件
viewModel.on('afterValueChange',function(data){})
5.9 组件加载后事件
viewModel.on('afterMount',function(data){})
5.10 选择后事件
viewModel.get('****List').on('afterSelect',function(data){})
5.11 取消选中后
viewModel.get('****List').on('afterUnSelect',function(data){})
5.12 全选后事件
viewModel.get('****List').on('afterSelectAll',function(data){})
5.13 取消全选后事件
viewModel.get('****List').on('afterUnSelectAll',function(data){})
5.14 编辑前事件
viewModel.on('beforeEdit',function(args){})
5.15 保存后事件
viewModel.on('afterSave',function(args){})
5.16 保存前事件
viewModel.on('beforeSave',function(args){})
5.17 规则加载后事件
viewModel.on('afterRule',function(event){})
5.18 页面初始化
viewModel.on('customInit',function(data){})
5.19 增行后事件
viewModel.on('afterAddRow',function(param){})
5.20 设置数据源前事件
viewModel.get('****List').on('beforeSetDataSource',function(data){})
5.21 加载html后事件
viewModel.on('afterLoadMeta', function(data){
5.22 参照打开前事件
viewModel.get('参照').on('beforeBrowse', function(data){