筋斗云前端框架-Web应用桌面版
此框架实现与筋斗云服务端接口的无缝整合。在界面上以jquery-easyui库为基础展示列表、Tab页等。
参考应用 web/store.html - 商户管理端应用。
设计模式:列表页与详情页。
以订单对象Order为例:为订单对象增加“列表页”和“详情页”。
列表页应包含分页功能,默认只显示“未完成”订单。
点击列表中一项(一个订单),可显示详情页,即订单详情,并可进行查找、更新等功能。
@key #my-pages 包含所有页面、对话框定义的容器。
@key my-obj DOM属性,标识服务端对象
@key my-initfn DOM属性,标识页面或对话框的初始化函数,首次显示页面/对话框时调用。
列表页使用逻辑页面定义如下(放在div#my-pages之下),它最终展示为一个tab页:
<div id="my-pages" style="display:none">
...
<script type="text/html" id="tpl_pageOrder">
<div class="pageOrder" title="订单管理" my-initfn="initPageOrder">
<table id="tblOrder" style="width:auto;height:auto">
<thead><tr>
<th data-options="field:'id', sortable:true, sorter:intSort">订单号</th>
<th data-options="field:'userPhone', sortable:true">用户联系方式</th>
<th data-options="field:'createTm', sortable:true">创建时间</th>
<th data-options="field:'status', jdEnumMap: OrderStatusMap, formatter:Formatter.enum(OrderStatusMap), styler:Formatter.enumStyler({PA:'Warning'}), sortable:true">状态</th>
<th data-options="field:'dscr', sortable:true">描述</th>
<th data-options="field:'cmt'">用户备注</th>
</tr></thead>
</table>
</div>
</script>
</div>
注意:
详情页展示为一个对话框,也将它也放在 div#my-pages 下。定义如下(此处为展示原理已简化):
<script type="text/html" id="tpl_dlgOrder">
<div id="dlgOrder" my-obj="Ordr" my-initfn="initDlgOrder" title="用户订单" style="width:520px;height:500px;">
<form method="POST">
订单号:<input name="id" disabled></td>
订单状态:
<select name="status">
<option value=""> </option>
<option value="CR">未付款</option>
<option value="PA">待服务(已付款)</option>
<option value="ST">正在服务</option>
<option value="RE">已服务(待评价)</option>
<option value="RA">已评价</option>
<option value="CA">已取消</option>
</select>
用户备注:<textarea name="cmt" rows=3 cols=30></textarea>
</form>
<div>
</script>
注意:
@see showObjDlg
@see showDlg
以上定义了订单对象的列表页和详情页,围绕对象"Order", 按规范,我们定义了以下名字:
<a href="#pageOrder" class="easyui-linkbutton" icon="icon-ok">订单管理</a><br/><br/>
打开页面后,页面的生存周期如下:
@key event-pagecreate,pageshow,pagedestroy 页面事件
@key wui-pageName 属性:页面名
@key .wui-page 页面类
订单列表页的初始化,需要将列表页(代码中jpage)、列表(代码中jtbl)与详情页(代码中jdlg)关联起来,实现对话增删改查各项功能。
function initPageOrder()
{
var jpage = $(this);
var jtbl = jpage.find("#tblOrder");
var jdlg = $("#dlgOrder");
// 注意:此处定义显示哪些缺省操作按钮:
// r-refresh/刷新, f-find/查找, s-set/更新。参考 WUI.dg_toolbar.
// 如果不定义则所有操作按钮都展示。
jtbl.jdata().toolbar = "rfs";
// 当天订单
var query1 = {cond: "createTm between '" + new Date().format("D") + "' and '" + new Date().addDay(1).format("D") + "'"};
// var query1 = WUI.getQueryParam({createTm: new Date().format("D") + "~" + new Date().addDay(1).format("D")});
// 显示待服务/正在服务订单
var query2 = {cond: "status='CR' OR status='PA' OR status='ST'"};
// var query2 = WUI.getQueryParam({status: "CR,PA,ST"});
function getTodoOrders()
{
WUI.reload(jtbl, null, query2);
}
function getTodayOrders()
{
WUI.reload(jtbl, null, query1);
}
var btn1 = {text: "今天订单", iconCls:'icon-search', handler: getTodayOrders};
var btn2 = {text: "所有未完成", iconCls:'icon-search', handler: getTodoOrders};
var dgOpt = {
// 设置查询接口
url: WUI.makeUrl(["Ordr", "query"], {res:"*,createTm,userPhone"}),
// 设置缺省查询条件
queryParams: query1,
// 设置工具栏上的按钮,默认有增删改查按钮,"export"表示"导出到Excel"的按钮,btn1, btn2是自定义按钮,"-"表示按钮分隔符。
toolbar: WUI.dg_toolbar(jtbl, jdlg, "export", "-", btn1, btn2),
// 双击一行,应展示详情页对话框
onDblClickRow: WUI.dg_dblclick(jtbl, jdlg)
};
jtbl.datagrid(dgOpt);
}
@see showPage
@see dg_toolbar
@see dg_dblclick
@see makeUrl
@key example-dialog-event
默认对话框中由于设定了底层对象(my-obj)及属性关联(form中带name属性的组件,已关联对象属性),因而可自动显示和提交数据。
特别地,某些属性不宜直接展示,例如属性“人物头像”,服务器存储的是图片id(picId),而展示时应显示为图片而不是一个数字;
或者如“权限列表”属性,服务器存储的是逗号分隔的一组权限比如"emp,mgr",而展示时需要为每项显示一个勾选框。
这类需求就需要编码控制。
相关事件:
@see beforeshow show 对话框中form显示前后
对话框类名:
@see .wui-dialog
function initDlgOrder()
{
var jdlg = $(this);
jdlg.on("beforeshow", onBeforeShow)
.on("show", onShow)
.on("validate", onValidate)
.on("retdata", onRetData);
function onBeforeShow(ev, formMode, opt) {
// beforeshow用于设置字段是否隐藏、是否可编辑;或是设置opt(即WUI.showDlg的opt)。
var objParam = opt.objParam;
var forAdd = formMode == FormMode.forAdd;
var forSet = formMode == FormMode.forSet;
jdlg.find(".notForFind").toggle(formMode != FormMode.forFind);
// WUI.toggleFields也常用于控制jfrm上字段显示或jtbl上列显示
var type = opt.objParam && opt.objParam.type;
var isMgr = g_data.hasRole("mgr"); // 最高管理员
var isAdm = g_data.hasRole("mgr,emp"); // 管理员
WUI.toggleFields(jfrm, {
type: !type,
status: !type || type!="公告",
atts: isAdm
});
// 根据权限控制字段是否可编辑。注意:find模式下一般不禁用。
if (formMode != FormMode.forFind) {
$(frm.empId).prop("disabled", !isMgr);
$(frm.status).prop("disabled", forAdd || !isAdm);
$(frm.code).prop("disabled", !isAdm);
}
}
function onShow(ev, formMode, initData) {
// 常用于add模式下设置初值,或是set模式下将原值转换并显示。
// initData是列表页中一行对应的数据,框架自动根据此数据将对应属性填上值。
// 如果界面上展示的字段无法与属性直接对应,可以在该事件回调中设置。
// hiddenToCheckbox(jdlg.find("#divPerms"));
if (forAdd) {
$(frm.status).val("CR");
}
else if (forSet) {
// 显示成表格
jdlg.find("#tbl1").datagrid(...);
}
}
function onValidate(ev, formMode, initData, newData) {
// 在form提交时,所有带name属性且不带disabled属性的对象值会被发往服务端。
// 此事件回调可以设置一些界面上无法与属性直接对应的内容。
// 额外要提交的数据可放在隐藏的input组件中,或(v5.1)这里直接设置到newData对象中。
// checkboxToHidden(jdlg.find("#divPerms"));
}
function onRetData(ev, data, formMode) {
var formMode = jdlg.jdata().mode;
if (formMode == FormMode.forAdd) {
alert('返回ID: ' + data);
}
}
}
在onBeforeShow中一般设置字段是否显示(show/hide/toggle)或只读(disabled),以及在forAdd/forFind模式时为opt.data设置初始值(forSet模式下opt.data已填上业务数据);
之后框架用opt.data数据填充相应字段,如需要补填或修改些字段(比如显示图片),可在onShow中处理,也可以直接在onBeforeShow中用setTimeout来指定,如:
function onBeforeShow(ev, formMode, opt) {
// ... 根据formMode等参数控制某些字段显示隐藏、启用禁用等...
var frm = jdlg.find("form")[0];
var isFind = formMode == FormMode.forFind;
frm.type.disabled = !isFind;
// 这里可以对opt.data赋值,但不要直接为组件设置值,因为接下来组件值会被opt.data中的值覆盖。
setTimeout(onShow);
function onShow() {
// 这里可根据opt.data直接为input等组件设置值。便于使用onBeforeShow中的变量
}
}
@see checkboxToHidden (有示例)
@see hiddenToCheckbox
@see imgToHidden
@see hiddenToImg (有示例)
框架中,对象列表通过easyui-datagrid来展现。
注意:由于历史原因,我们没有使用datagrid中的编辑功能。
参考:http://www.jeasyui.net/plugins/183.html
教程:http://www.jeasyui.net/tutorial/148.html
@key datagrid.formatter
@key datagrid.styler
示例一:显示名称及颜色
订单状态字段定义为:
- status: 订单状态. Enum(CR-新创建,RE-已服务,CA-已取消).
在显示时,要求显示其中文名称,且根据状态不同,显示不同的背景颜色。
在table中设置formatter与styler选项:
<div class="pageOrder" title="订单管理" my-initfn="initPageOrder">
<table id="tblOrder" style="width:auto;height:auto" title="订单列表">
<thead><tr>
<th data-options="field:'id', sortable:true, sorter:intSort">订单号</th>
...
<th data-options="field:'status', jdEnumMap: OrderStatusMap, formatter:Formatter.enum(OrderStatusMap), styler:Formatter.enumStyler({PA:'Warning', RE:'Disabled', CR:'#00ff00', null: 'Error'}), sortable:true">状态</th>
</tr></thead>
</table>
</div>
formatter用于控制Cell中的HTML标签,styler用于控制Cell自己的CSS style, 常用于标记颜色.
在JS中定义:
var OrderStatusMap = {
CR: "未付款",
RE: "已服务",
CA: "已取消"
};
Formatter = $.extend(WUI.formatter, Formatter);
上面Formatter.enum及Formatter.enumStyler是框架预定义的常用项,也可自定义formatter或styler,例:
var OrderColumns = {
status: function (value, row) {
if (! value)
return;
return OrderStatusMap[value] || value;
},
statusStyler: function (value, row) {
var colors = {
CR: "#000",
RE: "#0f0",
CA: "#ccc"
};
var color = colors[value];
if (color)
return "background-color: " + color;
},
...
}
注意:
@see formatter 通用格式化函数
一些其它示例:
var Formatter = {
// 显示数值
number: function (value, row) {
return parseFloat(value);
},
// 订单编号,显示为一个链接,点击就打开订单对话框该订单。
orderId: function (value, row) {
if (value) {
return WUI.makeLinkTo("#dlgOrder", row.orderId, value);
}
},
// 显示状态的同时,设置另一个本地字段,这种字段一般以"_"结尾,表示不是服务器传来的字段,例如
// <th data-options="field:'hint_'">提醒事项</th>
status: function (value, row) {
if (value) {
if (value == "PA") {
row.hint_ = "请于2小时内联系";
}
return StatusMap[value] || value;
}
}
};
@see makeLinkTo 生成对象链接,以便点击时打开该对象的详情对话框。
@key datagrid.sortable
@key datagrid.sorter
使用sortable:true指定该列可排序(可点击列头排序),用sorter指定排序算法(缺省是字符串排序),例如:
<th data-options="field:'name', sortable:true">姓名</th>
<th data-options="field:'id', sortable:true, sorter:intSort">编号</th>
<th data-options="field:'score', sortable:true, sorter:numberSort">评分</th>
框架提供了intSort,numberSort这些函数用于整数排序或小数排序。也可以自定义函数。示例:
function intSort(a, b)
{
return parseInt(a) - parseInt(b);
}
注意:
@see intSort numberSort
如果打开数据表就希望按某一列排序,可设置:
jtbl.datagrid({
...
sortName: 'id',
sortOrder: 'desc'
});
手工点击列标题栏排序,会自动修改这两个属性。
在添加数据时,如果当前sortOrder是倒序,则新数据显示在表格当前页的最前面,否则显示在最后。
框架对datagrid还做了以下缺省设置:
默认开启datagrid的分页功能。每页缺省显示20条数据。可通过datagrid选项自行重新定义,如:
jtbl.datagrid({
...
pageSize: 20,
pageList: [20,30,50] // 在分页栏中可以选择分页大小
});
如果需要禁用分页,可以设置:
jtbl.datagrid({
url: WUI.makeUrl("Ordr.query", {"pagesz": -1}), // -1表示取后端允许的最大数量
pagination: false, // 禁用分页组件
...
});
(支持版本5.0)
除了默认地增删改查,还可为数据表添加标准的“导出Excel”操作,可自动按表格当前的显示字段、搜索条件、排序条件等,导出表格。
只需在dg_toolbar函数的参数中加上"export"(表示导出按钮),如:
jtbl.datagrid({
url: WUI.makeUrl("User.query"),
toolbar: WUI.dg_toolbar(jtbl, jdlg, "export"),
onDblClickRow: WUI.dg_dblclick(jtbl, jdlg)
});
导出字段由jtbl对应的表格的表头定义,如下面表格定义:
<table id="tblOrder" style="width:auto;height:auto" title="订单列表">
...
<th data-options="field:'id'">编号</th>
<th data-options="field:'status'">状态</th>
<th data-options="field:'hint_'">友情提示</th>
</table>
它生成的res参数为"id 编号, status 状态"。"hint_"字段以下划线结尾,它会被当作是本地虚拟字段,不会被导出。
table上的title属性可用于控制列表导出时的默认文件名,如本例导出文件名为"订单列表.xls"。
如果想导出表中没有显示的列,可以设置该列为隐藏,如:
<th data-options="field:'userId', hidden:true">用户编号</th>
@key jdEnumMap datagrid中th选项, 在导出文件时,枚举变量可显示描述
对于枚举字段,可在th的data-options用formatter:WUI.formatter.enum(map)
来显示描述,在导出Excel时,需要设置jdEnumMap:map
属性来显示描述,如
<th data-options="field:'status', jdEnumMap: OrderStatusMap, formatter: WUI.formatter.enum(OrderStatusMap)">状态</th>
OrderStatusMap在代码中定义如下
var OrderStatusMap = {
CR: "未付款",
PA: "待服务"
}
它生成的res参数为"id 编号, status 状态=CR:未付款;PA:待服务"。筋斗云后端支持这种res定义方式将枚举值显示为描述。
@see dg_toolbar 指定列表上的操作按钮
@see getExportHandler 自定义导出Excel功能
@see getQueryParamFromTable 根据当前datagrid状态取query接口参数
HINT: 点“导出”时会直接下载文件,看不到请求和调用过程,如果需要调试导出功能,可在控制台中设置 window.open=$.get 即可在chrome中查看请求响应过程。
easyui-datagrid已适配筋斗云协议调用,底层将发起callSvr调用请求(参考dgLoader)。
此外,增加支持url_
属性,以便初始化时不发起调用,直到调用"load"/"reload"方法时才发起调用:
jtbl.datagrid({
url_: WUI.makeUrl("Item.query", {res:"id,name"}), // 如果用url则会立即用callSvr发起请求。
...
});
// ...
jtbl.datagrid("load", {cond: "itemId=" + itemId});
jtbl.datagrid("reload");
如果接口返回格式不符合,则可以使用loadData方法:
// 接口 Item.get() -> {item1=[{srcItemId, qty}]}
callSvr("Item.get", {res:"item1"}, function (data) {
jtbl.datagrid("loadData", data.item1); // 是一个对象数组
});
datagrid默认加载数据要求格式为{total, rows}
,框架已对返回数据格式进行了默认处理,兼容筋斗云协议格式(参考dgLoadFilter)。
var rows = [ {id:1, name:"name1"}, {id:2, name:"name2"} ];
jtbl.datagrid("loadData", {tota:2, rows: rows});
// 还支持以下三种格式
jtbl.datagrid("loadData", rows);
jtbl.datagrid("loadData", {h: ["id","name"], d: [ [1, "name1"], [2, "name2"]}); // 筋斗云query接口默认返回格式。
jtbl.datagrid("loadData", {list: rows}); // 筋斗云query接口指定fmt=list参数时,返回这种格式
@key treegrid
后端数据模型若要支持树类型,须在表中有父节点字段(默认为fatherId), 即可适配treegrid. 典型的表设计如下:
@Dept: id, code, name, fatherId, level
在初始化页面时, 与datagrid类似: pageItemType.js
var dgOpt = {
// treegrid查询时不分页. 设置pagesz=-1. (注意后端默认返回1000条, 可设置放宽到10000条. 再多应考虑按层级展开)
url: WUI.makeUrl("ItemType.query", {pagesz: -1}),
toolbar: WUI.dg_toolbar(jtbl, jdlg),
onDblClickRow: WUI.dg_dblclick(jtbl, jdlg)
// treeField: "code" // 树表专用,表示在哪个字段上显示折叠,默认为"id"
// fatherField: "id" // 树表专用,WUI扩展字段,表示父节点字段,默认为"fatherId"
};
// 用treegrid替代常规的datagrid
jtbl.treegrid(dgOpt);
如果数据量非常大, 可以只显示第一层级, 展开时再查询.
仅需增加初始查询条件(只查第一级)以及一个判断是否终端结点的回调函数isLeaf (否则都当作终端结点将无法展开):
var dgOpt = {
queryParams: {cond: "fatherId is null"},
isLeaf: function (row) {
return row.level>1;
},
...
};
jtbl.treegrid(dgOpt);
[通过非id字段关联父节点的情况]
比如通过fatherCode字段关联到父节点的code字段:
@Dept: id, code, fatherCode
则可以指定idField, 这样调用:
var dgOpt = {
idField: "code",
fatherField: "fatherCode",
...
};
jtbl.treegrid(dgOpt);
在对话框中按快捷键"Ctrl-F"可进入查询模式。
详情页提供通用查询,如:
手机号: <input name="phone">
注册时间: <input name="createTm">
可在手机号中输入"137*",在注册时间中输入">=2017-1-1 and <2018-1-1" (或用 "2017-1-1~2018-1-1"),这样生成的查询参数为:
{ cond: "phone like '137%' and (createTm>='2017-1-1' and createTm<'2018-1-1')" }
@see getQueryCond 查询条件支持
@see getQueryParam 生成查询条件
@key .wui-find-field 用于查找的字段样式
可设置该样式来标识哪些字段可以查找。一般设置为黄色。
@key .notForFind 指定非查询条件
不参与查询的字段,可以用notForFind类标识,如:
登录密码: <input class="notForFind" type="password" name="pwd">
@key .wui-notCond 指定独立查询条件
如果查询时不想将条件放在cond参数中,可以设置wui-notCond类标识,如:
状态: <select name="status" class="my-combobox wui-notCond" data-options="jdEnumList:'0:可用;1:禁用'"></select>
如果不加wui-notCond类,生成的查询参数为:{cond: "status=0"}
;加上后,生成查询参数如:{status: 0}
.
(v5.3)
在对话框中三击(2秒内)字段标题栏,可快速按查询该字段。Ctrl+三击为追加过滤条件。
(v7)也可以在对话框的字段标题上点右键,在菜单中选择“查询该字段”。
@key wui-find-hint 控制查询条件的生成。(v5.5)
示例:
视频代码 <input name="code" wui-find-hint="s">
当输入'126231-191024'时不会当作查询126231到191024的区间。
示例:下拉框中显示员工列表 (Choose-from-list / 关联选择框)
@see jQuery.fn.mycombobox
@see hiddenToImg (有示例)
@see imgToHidden
@see hiddenToCheckbox
@see checkboxToHidden (有示例)
例如设计有商品表Item, 每个商品属于特定的商户:
@Item: id, storeId, name
- storeId: Integer. 商品所属商户编号。
也就是说,一个商户对应多个商品。要展现商品,可将它放在商户层次之下。
可以这样设计用户操作:在商户列表上增加一个按钮“查看商品”,点击后打开一个新的列表页,显示该商户的商品列表。
定义两个列表页:
<div class="pageStore" title="商户列表" my-initfn="initPageStore">
</div>
<div class="pageItem" title="商户商品" my-initfn="initPageItem">
</div>
为这两个列表页定义初始化函数:
// 商户列表页
function initPageStore()
{
...
var btn1 = {text: "查看商品", iconCls: "icon-search", handler: showItemPage};
jtbl.datagrid({
...
toolbar: WUI.dg_toolbar(jtbl, jdlg, btn1),
});
function showItemPage() {
var row = WUI.getRow(jtbl);
if (row == null)
return;
// !!! 调用showPage显示新页 !!!
var pageFilter = {cond: {storeId: row.id}};
WUI.showPage("pageItem", {title: "商户商品-" + row.name, pageFilter: pageFilter});
// 要使每个商户都打开一个商品页面而不是共享一个页面,必须保证第二个参数(页面标题)根据商户不同而不一样。
}
}
注意:
WUI.showPage的第二参数既可以是简单的页面标题(title),也可以是复杂的showPage选项(showPageOpt),下面两个调用是相同的:
WUI.showPage("pageItem", "商户商品-" + row.name, ...);
WUI.showPage("pageItem", {title: "商户商品-" + row.name}, ...);
显然,title随着商户名称不同而不同,这保证了不同商户打开的商品页面不会共用。
而showPageOpt.pageFilter参数会自动加到数据表调用后端的query接口参数中,对列表页进行过滤。
@see showPage
@key .wui-fixedField 固定值字段
(v6) 当打开商户关联的商品列表页时,点开商品明细对话框,商户这个字段(storeId)固定为pageFilter.cond
中storeId的值,不可修改。这称为固定值字段(fixedField)。
也可以通过WUI.showDlg/WUI.showObjDlg的opt.fixedFields参数来指定固定值字段(值为field=>value映射),或是通过jdlg设置对话框参数如jdlg.objParam.fixedFields={storeId: row.id}
。
v5.3引入了wui-fixedField类设置在字段上,v6起已不建议使用。以下仅用于兼容:
当打开对话框时, 标识为.wui-fixedField类的字段会自动从传入的opt.objParam中取值, 如果取到值则将自己设置为只读.
<select name="storeId" class="my-combobox wui-fixedField" data-options="ListOptions.Store()"></select>
@key example-dialog
以群发短信功能为例。
假定服务端已有以下接口:
sendSms(phone, content)
phone:: 手机号
content:: 发送内容
注意:每个带name属性的组件对应接口中的参数。
<div id="dlgSendSms" title="群发短信" style="width:500px;height:300px;">
<form method="POST">
手机号:<input name="phone" class="easyui-validatebox" required>
发送内容: <textarea rows=5 cols=30 name="content" class="easyui-validatebox" required></textarea>
</form>
</div>
在form中带name属性的字段上,可以设置class="easyui-validatebox"对输入进行验证。
可以调用WUI.showDlg,写一个显示对话框的函数:
function showDlgSendSms()
{
WUI.showDlg("#dlgSendSms", {
url: WUI.makeUrl("sendSms"),
onOk: function (data) { // (v6.0) 可以直接用字符串 'close'
var jdlg = $(this);
WUI.closeDlg(jdlg);
app_show('操作成功!');
}
});
}
在showDlg的选项url中指定了接口为"sendSms"。操作成功后,显示一个消息。
(v5.5) 新的编程惯例,建议使用定义对话框接口的方式,写在主应用(如store.js)的接口区域,如:
// 把showDlgSendSms换成DlgSendSms.show
var DlgSendSms = {
show: function () {
// 同showDlgSendSms
}
};
@see showDlg
@see app_show
点击菜单项显示对话框:
<a href="javascript:;" onclick="DlgSendSms.show()" class="easyui-linkbutton" icon="icon-ok">群发短信</a><br/><br/>
可以通过my-initfn属性为对话框指定初始化函数。复杂对话框的逻辑一般都写在初始化函数中。习惯上命令名initDlgXXX,如:
<div id="dlgSendSms" title="群发短信" style="width:500px;height:300px;" my-initfn="initDlgSendSms">
function initDlgSendSms() {
var jdlg = $(this);
// 处理对话框事件
jdlg.on("beforeshow", onBeforeShow)
.on("validate", onValidate);
// 处理内部组件事件
jdlg.find("#btn1").click(btn1_click);
...
}
示例:显示某订单,可设置属性并保存。
假如已做好订单html模板 dlgOrder.html, 调用它:
var orderId = 99;
callSvr("Ordr.get", {id: orderId}, function (data) {
WUI.showDlg("#dlgOrder", {
modal: false, // 非模态框
data: data, // 初始化数据
forSet: true, // 与data选项合用,如果无数据修改则不提交
dialogOpt: {maximized: true}, // 打开时最大化
// reload: true, // 每次都重新加载(测试用)
url: WUI.makeUrl("Ordr.set", {id: orderId}), // 提交的URL,form中的内容通过POST请求发到后端。由于设置了opt.forSet=true, 只会提交修改部分。
onOk: 'close'
})
});
由于是典型的对象操作,一般用前述章节的对象对话框来做。上例相当于:
var orderId = 99;
WUI.showObjDlg("#dlgOrder", FormMode.forSet, {id: orderId});
(v5.1)
可以通过showObjDlg(jdlg, mode, opt)中的opt参数,或jdlg.objParam来给对话框传参。
在对话框的beforeshow事件处理中,可通过opt.objParam拿到参数,如:
function initPageBizPartner() {
var jdlg = $("#dlgSupplier");
// 设置objParam参数供对话框使用。
jdlg.objParam = {type: "C", obj: "Customer", title: "客户"}; // opt.title参数可直接设置对话框的标题。参考showObjDlg.
jtbl.datagrid(toolbar: dg_toolbar(jtbl, jdlg, ...));
// 点表格上的菜单或双击行时会调用 WUI.showObjDlg
}
function initDlgBizPartner() {
// ...
jdlg.on("beforeshow", onBeforeShow);
function onBeforeShow(ev, formMode, opt) {
// opt.objParam 中包含前面定义的type, obj, 以及id, mode等参数。
}
}
设计有客户(Customer)和供应商(Supplier)两个虚拟的逻辑对象,它们物理底层都是业务伙伴对象(BizPartner)。
现在只设计一个页面pageBizPartner和一个对话框dlgBizPartner。
菜单中两项:
默认pageBizPartner是供应商,如果要显示为"客户"页,需要明确调用showPage。
<a href="#pageBizPartner">供应商</a>
<a href="javascript:;" onclick="WUI.showPage('pageBizPartner', '客户', ['C']);">客户</a>
在initPageBizPartner函数中,为对话框传递参数objParam:
type = type || "S";
var obj = "type=="S"? "Supplier": "Customer";
jdlg.objParam = {type: type, obj: obj};
// ...
在对话框的beforeshow事件处理中,根据opt.objParam.type确定标题栏:
jdlg.on("beforeshow", function (ev, formMode, opt) {
opt.title = opt.objParam.type == "C"? "客户": "供应商";
});
(v5.1)
@key .wui-readonly 只读对话框类名
设置是否为只读对话框只要加上该类:
jdlg.addClass("wui-readonly");
jdlg.removeClass("wui-readonly");
jdlg.toggleClass("wui-readonly", isReadonly);
只读对话框不可输入(在style.css中设定pointer-events为none),点击确定按钮后直接关闭。
注意:在dialog beforeshow事件中,不应直接设置wui-readonly类,因为框架之后会自动设置,导致前面设置无效。正确做法是设置opt.objParam.readonly=true
,示例:
jdlg.on("beforeshow", onBeforeShow);
function onBeforeShow(ev, formMode, opt)
{
var objParam = opt.objParam;
var ro = (formMode == FormMode.forSet && !!opt.data.usedFlag);
// beforeshow中设置对话框只读
objParam.readonly = ro;
}
disabled:不可添加或更新该字段,但可查询(即forAdd/forSet模式下只显示不提交,forFind时可设置和提交),例如编号字段、计算字段。示例:
<input name="id" disabled>
<input name="userName" disabled>
readonly:不可手工添加、更新和查询(但可通过代码设置)。示例:
<input name="total" readonly>
@key wui-script
@key options.pageFolder
允许将逻辑页、对话框的html片段和js片段放在单独的文件中。以前面章节示例中订单对象的列表页(是一个逻辑页)与详情页(是一个对话框)为例:
先在文件page/pageOrder.html中定义逻辑页
<div title="订单管理" wui-script="pageOrder.js" my-initfn="initPageOrder">
<table id="tblOrder" style="width:auto;height:auto">
...
</table>
</div>
注意:
在html文件的div中可以添加style样式标签:
<div>
<style>
table {
background-color: #ddd;
}
</style>
<table>...</table>
</div>
注意:其中定义的样式(比如这里的table)只应用于当前页面或对话框,因为框架会在加载它时自动限定样式作用范围。
在文件page/pageOrder.js中定义逻辑:
function initPageOrder()
{
var jpage = $(this);
...
}
这时,就可以用 WUI.showPage("#pageOrder")来显示逻辑页了。
注意:逻辑页的title字段不能和其它页中title重复,否则这两页无法同时显示,因为显示tab页时是按照title来标识逻辑页的。
动态加载页面时,先加载逻辑页html和js文件,再将逻辑页插入应用程序并做系统初始化(如增强mui组件或easyui组件等),然后调用页面的用户初始化函数。
若希望在系统初始化之前做一些操作,应放在用户初始化函数之外。
例如,初始化过程中的服务调用使用批处理:
functio initPageOrder()
{
...
}
WUI.useBatchCall();
在文件page/dlgOrder.html中定义对话框UI:
<div wui-script="dlgOrder.js" my-obj="Ordr" my-initfn="initDlgOrder" title="用户订单" style="width:520px;height:500px;">
<form method="POST">
...
</form>
<div>
注意:
在文件page/dlgOrder.js中定义js逻辑:
function initDlgOrder()
{
var jdlg = $(this);
...
}
这时,就可以用 WUI.showObjDlg("#dlgOrder")来显示逻辑页了。
(v5.2)
列表页支持两种批量操作模式。
服务端应支持{obj}.setIf(cond)
及{obj}.delIf(cond)
接口。
定义一个逻辑页面,可以在#my-pages下直接定义,也可以在单独的文件中定义,还可以在一个模板中定义,如:
<script type="text/html" id="tpl_pageOrder">
<div class="pageOrder" title="订单管理" my-initfn="initPageOrder">
...
</div>
</script>
模板用script标签定义,其id属性必须命名为tpl_{逻辑页面名}
。
这样就定义了逻辑页pageOrder,且在用到时才加载。与从外部文件加载类似,可以不设置class="pageOrder",框架会自动处理。
定义对话框也类似:
<script type="text/html" id="tpl_dlgOrder">
<div id="dlgOrder" my-obj="Ordr" my-initfn="initDlgOrder" title="用户订单" style="width:520px;height:500px;">
...
</div>
</script>
定义了对话框dlgOrder,这个id属性也可以不设置。
模板用script标签定义,其id属性必须命名为tpl_{对话框名}
。
注意:
如果将script标签制作的页面模板内嵌在主页中,可能会造成加载时闪烁。
在chrome中,在easyui-layout之后定义任意script标签(哪怕是空内容),会导致加载首页时闪烁,标题栏是黑色尤其明显。
测试发现,将这些个script模板放在head标签中不会闪烁。
这个特性可用于未来WEB应用编译打包。
@key wui-deferred
(v5.5)
如果页面或对话框依赖一个或一组库,且这些库不想在主页面中用script默认加载,这时可以使用wui-deferred
属性。
页面或对话框初始化函数wui-initfn将在该deferred对象操作成功后执行。
示例:想在工艺对话框上使用mermaid库显示流程图,该库比较大,只在这一处使用,故不想在应用入口加载。
可在app.js中添加库的加载函数:
var m_dfdMermaid;
function loadMermaidLib()
{
if (m_dfdMermaid == null)
m_dfdMermaid = WUI.loadScript("lib/mermaid.min.js");
return m_dfdMermaid;
}
在对话框html中用wui-deferred引入依赖库:
<form my-obj="Flow" title="工艺" ... wui-deferred="loadMermaidLib()">
在对话框模块(初始化函数)中就可以直接使用这个库了:
function initDlgFlow()
{
...
mermaid.render("graph", def, function (svg) {
jdlg.find(".graph").html(svg);
});
}
以下参考文档介绍WUI模块提供的方法/函数(fn)、属性/变量(var)等,示例如下:
@fn showPage(pageName, title?, paramArr?) 一个函数。参数说明中问号表示参数可缺省。
@var options 一个属性。
@class batchCall(opt?={useTrans?=0}) 一个JS类。
@key example-dialog key表示一般关键字。前缀为"example-"用于示例讲解。
@key .wui-page 一个CSS类名"wui-page",关键字以"."开头。
@key #wui-pages 一个DOM对象,id为"wui-pages",关键字以"#"开头。
对于模块下的fn,var,class这些类别,如非特别说明,调用时应加WUI前缀,如
WUI.showPage("#pageOrder");
var opts = WUI.options;
var batch = new WUI.batchCall();
batch.commit();
以下函数可不加WUI前缀:
intSort
numberSort
callSvr
callSvrSync
app_alert
app_confirm
app_show
参考wui-name.js模块。
@key .wui-fullscreen
生成指定区间的随机整数。示例:
var i = randInt(1, 10); // 1-10之间的整数,包含1或10
生成随机字符串,包含字母或数字,不包含易混淆的0或O。示例:
var dynCode = randChr(4); // e.g. "9BZ3"
解析url编码格式的查询字符串,返回对应的对象。
if (location.search) {
var queryStr = location.search.substr(1); // "?id=100&name=abc&val=3.14"去掉"?"号
var args = parseQuery(queryStr); // {id: 100, name: "abc", val: 3.14}
}
注意:
如果值为整数或小数,则会转成相应类型。如上例中 id为100,不是字符串"100".
将字符串转成boolean值。除"0", "1"外,还可以支持字符串 "on"/"off", "true"/"false"等。
重新加载当前页面,但不要#hash部分。
日期对象格式化字符串。
@param fmt 格式字符串。由以下组成:
yyyy - 四位年,如2008, 1999
yy - 两位年,如 08, 99
mm - 两位月,如 02, 12
dd - 两位日,如 01, 30
HH - 两位小时,如 00, 23
MM - 两位分钟,如 00, 59
SS - 两位秒,如 00, 59
支持这几种常用格式:
L - 标准日期时间,相当于 "yyyy-mm-dd HH:MM:SS"
D - 标准日期,相当于 "yyyy-mm-dd"
T - 标准时间,相当于 "HH:MM:SS"
示例:
var dt = new Date();
var dtStr1 = dt.format("D"); // "2009-10-20"
var dtStr2 = dt.format("yyyymmdd-HHMM"); // "20091020-2038"
将纯时间字符串生成一个日期对象。
var dt1 = parseTime("10:10:00");
var dt2 = parseTime("10:11");
将日期字符串转为日期时间格式。其效果相当于new Date(Date.parse(dateStr))
,但兼容性更好(例如在safari中很多常见的日期格式无法解析)
示例:
var dt1 = parseDate("2012-01-01");
var dt2 = parseDate("2012/01/01 20:00:09");
var dt3 = parseDate("2012.1.1 20:00");
支持时区,时区格式可以是"+8", "+08", "+0800", "Z"这些,如
parseDate("2012-01-01T09:10:20.328+0800");
parseDate("2012-01-01T09:10:20Z");
为日期对象加几天/小时等。参数n为整数,可以为负数。
@param sInterval Enum. 间隔单位. d-天; m-月; y-年; h-小时; n-分; s-秒
示例:
var dt = new Date();
dt.add("d", 1); // 1天后
dt.add("m", 1); // 1个月后
dt.add("y", -1); // 1年前
dt.add("h", 3); // 3小时后
dt.add("n", 30); // 30分钟后
dt.add("s", 30); // 30秒后
@see Date.diff
计算日期到另一日期间的间隔,单位由sInterval指定(具体值列表参见Date.add).
var dt = new Date();
...
var dt2 = new Date();
var days = dt.diff("d", dt2); // 相隔多少天
@see Date.add
从tm到tm1的时间差描述,如"2分钟前", "3天前"等。
tm和tm1可以为时间对象或时间字符串
根据时间段描述得到[起始时间,结束时间)
,注意结束时间是开区间(即不包含)。
假设今天是2015-9-9 周三:
getTmRange("本周", "2015-9-9") -> ["2015-9-7"(本周一), "2015-9-14")
getTmRange("上周") -> ["2015-8-31", "2015-9-7") // 或"前1周"
getTmRange("本月") -> ["2015-9-1", "2015-10-1")
getTmRange("上月") -> ["2015-8-1", "2015-9-1")
getTmRange("今年") -> ["2015-1-1", "2016-1-1") // 或"本年"
getTmRange("去年") -> ["2014-1-1", "2015-1-1") // 或"上年"
getTmRange("本季度") -> ["2015-7-1", "2015-10-1") // 7,8,9三个月
getTmRange("上季度") -> ["2015-4-1", "2015-7-1")
getTmRange("上半年") -> ["2015-1-1", "2015-7-1")
getTmRange("下半年") -> ["2015-7-1", "2016-1-1")
getTmRange("今天") -> ["2015-9-9", "2015-9-10") // 或"本日"
getTmRange("昨天") -> ["2015-9-8", "2015-9-9") // 或"昨日"
getTmRange("前1周") -> ["2015-8-31"(上周一),"2015-9-7"(本周一))
getTmRange("前3月") -> ["2015-6-1", "2015-9-1")
getTmRange("前3天") -> ["2015-9-6", "2015-9-9")
getTmRange("近1周") -> ["2015-9-3","2015-9-10")
getTmRange("近3月") -> ["2015-6-10", "2015-9-10")
getTmRange("近3天") -> ["2015-9-6", "2015-9-10") // "前3天"+今天
dscr可以是
"近|前|上" N "个"? "小时|日|周|月|年|季度"
"本|今" "小时|日/天|周|月|年|季度"
注意:"近X周"包括今天(即使尚未过完)。
示例:快捷填充
<td>
<select class="cboTmRange">
<option value ="本月">本月</option>
<option value ="上月">上月</option>
<option value ="本周">本周</option>
<option value ="上周">上周</option>
<option value ="今年">今年</option>
<option value ="去年">去年</option>
</select>
</td>
var txtTmRange = jdlg.find(".cboTmRange");
txtTmRange.change(function () {
var range = WUI.getTmRange(this.value);
if (range) {
WUI.setFormData(jfrm, {tm1: range[0], tm2: range[1]}, {setOnlyDefined: true});
}
});
// 初始选中
setTimeout(function () {
txtTmRange.change();
});
设置cookie值。如果只是为了客户端长时间保存值,一般建议使用 setStorage.
@see getCookie
@see delCookie
@see setStorage
使用localStorage存储(或使用sessionStorage存储, 如果useSession=true)。
value可以是简单类型,也可以为数组,对象等,后者将自动在序列化后存储。
如果设置了window.STORAGE_PREFIX, 则键值(name)会加上该前缀.
示例:
setStorage("id", "100");
var id = getStorage("id");
delStorage("id");
示例2:存储对象:
window.STORAGE_PREFIX = "jdcloud_"; // 一般在app.js中全局设置
var obj = {id:10, name:"Jason"};
setStorage("obj", obj); // 实际存储键值为 "jdcloud_obj"
var obj2 = getStorage("obj");
alert(obj2.name);
@var STORAGE_PREFIX 本地存储的键值前缀
如果指定, 则调用setStorage/getStorage/delStorage时都将自动加此前缀, 避免不同项目的存储项冲突.
@see getStorage
@see delStorage
取storage中的一项。
默认使用localStorage存储,如果useSession=true,则使用sessionStorage存储。
如果浏览器不支持Storage,则使用cookie实现.
@see setStorage
@see delStorage
@param rs= {h=[header], d=[ @row ]} rs对象(RowSet)
@return arr=[ %obj ]
rs对象用于传递表格,包含表头与表内容。
函数用于将服务器发来的rs对象转成数组。
示例:
var rs = {
h: ["id", "name"],
d: [ [100, "Tom"], [101, "Jane"] ]
};
var arr = rs2Array(rs);
// 结果为
arr = [
{id: 100, name: "Tom"},
{id: 101, name: "Jane"}
];
@see rs2Hash
@see rs2MultiHash
@param rs= {h, d} rs对象(RowSet)
@return hash= {key => %obj}
示例:
var rs = {
h: ["id", "name"],
d: [ [100, "Tom"], [101, "Jane"] ]
};
var hash = rs2Hash(rs, "id");
// 结果为
hash = {
100: {id: 100, name: "Tom"},
101: {id: 101, name: "Jane"}
};
key可以为一个函数,返回实际key值,示例:
var hash = rs2Hash(rs, function (o) {
return "USER-" + o.id;
});
// 结果为
hash = {
"USER-100": {id: 100, name: "Tom"},
"USER-101": {id: 101, name: "Jane"}
};
key函数也可以返回[key, value]数组:
var hash = rs2Hash(rs, function (o) {
return ["USER-" + o.id, o.name];
});
// 结果为
hash = {
"USER-100": "Tom",
"USER-101": "Jane"
};
@see rs2Array
数据分组(group by).
@param rs= {h, d} rs对象(RowSet)
@return hash= {key => [ %obj ]}
示例:
var rs = {
h: ["id", "name"],
d: [ [100, "Tom"], [101, "Jane"], [102, "Tom"] ]
};
var hash = rs2MultiHash(rs, "name");
// 结果为
hash = {
"Tom": [{id: 100, name: "Tom"}, {id: 102, name: "Tom"}],
"Jane": [{id: 101, name: "Jane"}]
};
key也可以是一个函数,返回实际的key值,示例,按生日年份分组:
var rs = {
h: ["id", "name", "birthday"],
d: [ [100, "Tom", "1998-10-1"], [101, "Jane", "1999-1-10"], [102, "Tom", "1998-3-8"] ]
};
// 按生日年份分组
var hash = rs2MultiHash(rs, function (o) {
var m = o.birthday.match(/^\d+/);
return m && m[0];
});
// 结果为
hash = {
"1998": [{id: 100, name: "Tom", birthday: "1998-10-1"}, {id: 102, name: "Tom", birthday:"1998-3-8"}],
"1999": [{id: 101, name: "Jane", birthday: "1999-1-10"}]
};
key作为函数,也可返回[key, value]:
var hash = rs2MultiHash(rs, function (o) {
return [o.name, [o.id, o.birthday]];
});
// 结果为
hash = {
"Tom": [[100, "1998-10-1"], [102, "1998-3-8"]],
"Jane": [[101, "1999-1-10"]]
};
@see rs2Hash
@see rs2Array
将字符串代表的压缩表("v1:v2:v3,...")转成对象数组。
e.g.
var users = "101:andy,102:beddy";
var varr = list2varr(users);
// varr = [["101", "andy"], ["102", "beddy"]];
var arr = rs2Array({h: ["id", "name"], d: varr});
// arr = [ {id: 101, name: "andy"}, {id: 102, name: "beddy"} ];
var cmts = "101\thello\n102\tgood";
var varr = list2varr(cmts, "\t", "\n");
// varr=[["101", "hello"], ["102", "good"]]
将对象数组转成字符串代表的压缩表("v1:v2:v3,...")。
示例:
var objarr = [
{id:100, name:'name1', qty:2},
{id:101, name:'name2', qty:3}
];
var list = objarr2list(objarr, ["id","qty"]);
// 返回"100:2,101:3"
var list2 = objarr2list(objarr, function (e, i) { return e.id + ":" + e.qty; });
// 结果同上
整数排序. 用于datagrid column sorter:
<th data-options="field:'id', sortable:true, sorter:intSort">编号</th>
小数排序. 用于datagrid column sorter:
<th data-options="field:'score', sortable:true, sorter:numberSort">评分</th>
取符合条件(fn)的对象,一般可使用$.closest替代
示例:
var url = "http://xxx/api.php";
if (a)
url = appendParam(url, "a=" + a);
if (b)
url = appendParam(url, "b=" + b);
appendParam(url, $.param({a:1, b:3}));
支持url中带有"?"或"#",如
var url = "http://xxx/api.php?id=1#order";
appendParam(url, "pay=1"); // "http://xxx/api.php?id=1&pay=1#order";
示例:
var url = "http://xxx/api.php?a=1&b=3&c=2";
var url1 = deleteParam(url, "b"); // "http://xxx/api.php?a=1&c=2";
var url = "http://server/jdcloud/m2/?logout#me";
var url1 = deleteParam(url, "logout"); // "http://server/jdcloud/m2/?#me"
当前应用运行在微信中。
当前应用运行在IOS平台,如iphone或ipad中。
当前应用运行在安卓平台。
如果str符合整数或小数,则返回相应类型。
对模板做字符串替换
var tpl = "<li><p>{name}</p><p>{dscr}</p></li>";
var data = {name: 'richard', dscr: 'hello'};
var html = applyTpl(tpl, data);
// <li><p>richard</p><p>hello</p></li>
设置延迟执行。当delayCnt=1时与setTimeout效果相同。
多次置于事件队列最后,一般3次后其它js均已执行完毕,为idle状态
e.g.
var str = kvList2Str({"CR":"Created", "PA":"Paid"}, ';', ':');
// str="CR:Created;PA:Paid"
解析key-value列表字符串,返回kvMap。
示例:
var map = parseKvList("CR:新创建;PA:已付款", ";", ":");
// map: {"CR": "新创建", "PA":"已付款"}
var map = parseKvList("CR:新创建;PA:已付款", ";", ":", true);
// map: {"新创建":"CR", "已付款":"PA"}
sep和sep2支持使用正则式,以下处理结果相同:
var map = parseKvList("CR:新创建;PA:已付款", /[; ]/, /[:=]/);
var map2 = parseKvList("CR=新创建 PA=已付款", /[; ]/, /[:=]/);
Q("abc") -> 'abc'
Q("a'bc") -> 'a\'bc'
将文本或图片转成html,常用于将筋斗云后端返回的图文内容转成html在网页中显示。示例:
var item = {id: 1, name: "商品1", content: "商品介绍内容", pics: "100,102"};
var html = MUI.text2html(item.content, item.pics);
jpage.find("#content").html(html);
文字转html示例:
var html = MUI.text2html("hello\nworld");
生成html为
<p>hello</p>
<p>world</p>
支持简单的markdown格式,如"# ","## "分别表示一二级标题, "- "表示列表(注意在"#"或"-"后面有英文空格):
# 标题1
内容1
# 标题2
内容2
- 列表1
- 列表2
函数可将图片编号列表转成img列表,如:
var html = MUI.text2html(null, "100,102");
生成
<img src="../api.php/att?thumbId=100">
<img src="../api.php/att?thumbId=102">
var a = {a: 1};
WUI.extendNoOverride(a, {b: 'aa'}, {a: 99, b: '33', c: 'bb'});
// a = {a: 1, b: 'aa', c: 'bb'}
与toFixed相比,它返回float数组,不带多余的0位。
myround(114957.15999999, 4) = 114957.16
myround(114957.15999999, 1) = 114957.2
定义JS模块。这是一个全局函数。
定义一个模块:
jdModule("jdcloud.common", JdcloudCommon);
function JdcloudCommon() {
var self = this;
// 对外提供一个方法
self.rs2Array = rs2Array;
function rs2Array(rs)
{
return ...;
}
}
获取模块对象:
var mCommon = jdModule("jdcloud.common");
var arr = mCommon.rs2Array(rs);
返回模块映射列表。
var moduleMap = jdModule(); // 返回 { "jdcloud.common": JdcloudCommon, ... }
支持出错自动重连的websocket client. 默认连接同域下的jdserver。
/jdserver
,也可指定全路径,如 ws://oliveche.com/jdserver
示例:
// 返回websocket proxy对象
var ws = jdPush("tighten", function (msg, ws) {
console.log(msg)
// 发送消息
ws.send({ac:"hello"}); // 发送JS对象会自动转JSON发送。
}, opt);
// 关闭
ws.close();
开发环境可配置代理服务器连接jdserver,比如使用Apache,在web根目录下配置.htaccess:
(确保已打开wstunnel模块且允许htaccess文件)
# 连接已部署的websocket server
rewriterule ^jdserver ws://oliveche.com/jdserver [P,L]
# 或连本地websocket server (注意最后/不可少)
# rewriterule ^jdserver ws://127.0.0.1:8081/ [P,L]
jdserver同时支持http和websocket,建议设置为:(注意顺序)
rewriterule ^jdserver/(.+) http://127.0.0.1:8081/$1 [P,L]
rewriterule ^jdserver ws://127.0.0.1:8081/ [P,L]
或
rewriterule ^jdserver/(.+) http://oliveche.com/jdserver/$1 [P,L]
rewriterule ^jdserver ws://oliveche.com/jdserver/ [P,L]
这样,可以使用基于HTTP的push接口给别的客户端发消息:
callSvr("/jdserver/push", $.noop, {
app: "app1",
user: "*", // 表示发给app1中所有人。也可指定`user1`, `user1,user2`, `user*`这样
msg: {
ac: "msg1",
data: "hello"
}
});
// 返回推送人次数量,示例:2
上面通过msg字段发送js对象。
注意jdPush默认只处理json消息: handleMsg(jsonObj),其它消息只在控制台打印;
想处理其它消息,可调用jdPush时设置opt.dataType='text'
取在线用户:
callSvr("/jdserver/getUsers", { app: "app1" });
// 返回示例: ['user1', 'user2' ]
示例:websocket连接jdserver,然后给自己发消息
jdPush('app1'); // 默认用户为g_data.userInfo.id
callSvr('/jdserver/push', {app: 'app1', user: '*', msg: 'hi'});
// 在控制台可收到hi
将字符串分为两块。替代JS默认的split函数limit参数的错误设计。
var str = "lot Lot ID";
var kv = str.split2(" "); // ["lot", "Lot ID"]
// str.split(" ", 2); // ["lot", "Lot"] 不合要求
取DOM对象中带name属性的子对象的内容, 放入一个JS对象中, 以便手工调用callSvr.
注意:
与setFormData配合使用时, 可以只返回变化的数据.
jf.submit(function () {
var ac = jf.attr("action");
callSvr(ac, fn, getFormData(jf));
});
在dialog的onValidate/onOk回调中,由于在显示对话框时自动调用过setFormData,所以用getFormData只返回有修改变化的数据。如果要取所有数据可设置参数doGetAll=true:
var data = WUI.getFormData(jfrm, true);
这也只返回非disabled的组件,如果包括disabled组件的值也需要,可以用
var data = WUI.getFormData(jfrm, "all");
如果在jo对象中存在有name属性的file组件(input[type=file][name]),或指定了属性enctype="multipart/form-data",则调用getFormData会返回FormData对象而非js对象,
再调用callSvr时,会以"multipart/form-data"格式提交数据。一般用于上传文件。
示例:
<div>
课程文档
<input name="pdf" type="file" accept="application/pdf">
</div>
或传统地:
<form method="POST" enctype='multipart/form-data'>
课程文档
<input name="pdf" type="file" accept="application/pdf">
</form>
如果有多个同名组件(name相同,且非disabled状态),最终值将以最后组件为准。
如果想要以数组形式返回所有值,应在名字上加后缀"[]",示例:
行统计字段: <select name="gres[]" class="my-combobox fields"></select>
行统计字段2: <select name="gres[]" class="my-combobox fields"></select>
列统计字段: <select name="gres2" class="my-combobox fields"></select>
列统计字段2: <select name="gres2" class="my-combobox fields"></select>
取到的结果示例:
{ gres: ["id", "name"], gres2: "name" }
@see setFormData
专门取虚拟字段的值。例如:
<select name="whId" class="my-combobox" data-options="url:..., jd_vField:'whName'"></select>
用WUI.getFormData可取到{whId: xxx}
,而WUI.getFormData_vf遍历带name属性且设置了jd_vField选项的控件,调用接口getValue_vf(ji)来取其显示值。
因而,为支持取虚拟字段值,控件须定义getValue_vf接口。
<input name="orderType" data-options="jd_vField:'orderType'" disabled>
注意:与getFormData不同,它不忽略有disabled属性的控件。
@see defaultFormItems
表单对象遍历。对表单jo(实际可以不是form标签)下带name属性的控件,交给回调cb处理。
注意:
对于checkbox,设置时根据val确定是否选中;取值时如果选中取value属性否则取value-off属性。
缺省value为"on", value-off为空(非标准属性,本框架支持),可以设置:
<input type="checkbox" name="flag" value="1">
<input type="checkbox" name="flag" value="1" value-off="0">
@param cb (ji, name, it) it.getDisabled/setDisabled/getValue/setValue/getShowbox
当cb返回false时可中断遍历。
示例:
WUI.formItems(jdlg.find(".my-fixedField"), function (ji, name, it) {
var fixedVal = ...
if (fixedVal || fixedVal == '') {
it.setReadonly(true);
var forAdd = beforeShowOpt.objParam.mode == FormMode.forAdd;
if (forAdd) {
it.setValue(fixedVal);
}
}
else {
it.setReadonly(false);
}
});
@fn getFormItem (ji)
获取对话框上一个组件的访问器。示例,设置名为orderId的组件值:
var ji = jdlg.find("[name=orderId]"); // 或者用 var ji = $(frm.orderId);
var it = WUI.getFormItem(ji); // 或者用 var it = ji.gn()
it.setValue("hello"); // 类似于ji.val("hello"),但支持各种复杂组件
还可以用更简洁的jo.gn,以及支持用链式风格的调用:
var it = jdlg.gn("orderId");
it.val("hello").disabled(true); // 等价于 it.setValue("hello"); it.setDisabled(true);
@see jQuery.fn.gn (name?)
@var getFormItemExt
可通过扩展WUI.getFormItemExt[新类型]
来为表单扩展其它类型控件。示例:
WUI.getFormItemExt["myinput"] = function (ji) {
if (ji.hasClass("myinput"))
return new MyInputFormItem(ji);
}
function MyInputFormItem(ji) {
WUI.FormItem.call(this, ji);
}
MyInputFormItem.prototype = $.extend(new WUI.FormItem(), {
getValue: function () {
return this.ji.val();
}
});
按名字访问组件(gn表示getElementByName),返回访问器(iterator),用于对各种组件进行通用操作。
示例:
var it = jdlg.gn("orderId");
var v = it.val(); // 取值,相当于 jdlg.find("[name=orderId]").val(); 但兼容my-combobox, wui-combogrid, wui-subobj等复杂组件。
it.val(100); // 设置值
// it.val([100, "ORDR-100"]); // 对于combogrid, 可以传数组,同时设置value和text
var isVisible = it.visible(); // 取是否显示
it.visible(false); // 设置隐藏
var isDisabled = it.disabled();
it.disabled(true);
var isReadonly = it.readonly();
it.readonly(true);
如果未指定name,则以jQuery对象本身作为组件返回访问器。所以也可以这样用:
var jo = jdlg.find("[name=orderId]");
var it = jo.gn(); // name可以省略,表示取自身
it.val(100);
用于为带name属性的DOM对象设置内容为data[name].
要清空所有内容, 可以用 setFormData(jo), 相当于增强版的 form.reset().
注意:
@param opt {setOrigin?=false, setOnlyDefined?=false}
@param opt.setOrigin 为true时将data设置为数据源, 这样在getFormData时, 只会返回与数据源相比有变化的数据.
缺省会设置该DOM对象数据源为空.
@param opt.setOnlyDefined 设置为true时,只设置form中name在data中存在的项,其它项保持不变;而默认是其它项会清空。
对象关联的数据源, 可以通过 jo.data("origin") 来获取, 或通过 jo.data("origin", newOrigin) 来设置.
示例:
<div id="div1">
<p>订单描述:<span name="dscr"></span></p>
<p>状态为:<input type=text name="status"></p>
<p>金额:<span name="amount"></span>元</p>
</div>
Javascript:
var data = {
dscr: "筋斗云教程",
status: "已付款",
amount: "100"
};
var jo = $("#div1");
var data = setFormData(jo, data);
$("[name=status]").html("已完成");
var changedData = getFormData(jo); // 返回 { dscr: "筋斗云教程", status: "已完成", amount: "100" }
var data = setFormData(jo, data, {setOrigin: true});
$("[name=status]").html("已完成");
var changedData = getFormData(jo); // 返回 { status: "已完成" }
$.extend(jo.data("origin_"), changedData); // 合并变化的部分到数据源.
@see getFormData
@param fnOK 加载成功后的回调函数
@param ajaxOpt 传递给$.ajax的额外选项。
默认未指定ajaxOpt时,简单地使用添加script标签机制异步加载。如果曾经加载过,可以重用cache。
注意:再次调用时是否可以从cache中取,是由服务器的cache-control决定的,在web和web/page目录下的js文件一般是禁止缓存的,再次调用时会从服务器再取,若文件无更改,服务器会返回304状态。
这是因为默认我们使用Apache做服务器,在相应目录下.htaccess中配置有缓存策略。
如果指定ajaxOpt,且非跨域,则通过ajax去加载,可以支持同步调用。如果是跨域,仍通过script标签方式加载,注意加载完成后会自动删除script标签。
返回defered对象(与$.ajax类似),可以用 dfd.then() / dfd.fail() 异步处理。
常见用法:
动态加载一个script,异步执行其中内容:
loadScript("1.js", onload); // onload中可使用1.js中定义的内容
loadScript("http://otherserver/path/1.js"); // 跨域加载
加载并立即执行一个script:
loadScript("1.js", {async: false});
// 可立即使用1.js中定义的内容
注意:如果是跨域加载,不支持同步调用($.ajax的限制),如:
loadScript("http://oliveche.com/1.js", {async: false});
// 一旦跨域,选项{async:false}指定无效,不可立即使用1.js中定义的内容。
示例:在菜单中加一项“工单工时统计”,动态加载并执行一个JS文件:
store.html中设置菜单:
<a href="javascript:;" onclick="WUI.loadScript('page/mod_工单工时统计.js')">工单工时统计</a>
在page/mod_工单工时统计.js
文件中写报表逻辑,mod
表示一个JS模块文件,示例:
function show工单工时统计()
{
DlgReportCond.show(function (data) {
var queryParams = WUI.getQueryParam({createTm: [data.tm1, data.tm2]});
var url = WUI.makeUrl("Ordr.query", { res: 'id 工单号, code 工单码, createTm 生产日期, itemCode 产品编码, itemName 产品名称, cate2Name 产品系列, itemCate 产品型号, qty 数量, mh 理论工时, mh1 实际工时', pagesz: -1 });
WUI.showPage("pageSimple", "工单工时统计!", [url, queryParams, onInitGrid]);
});
}
show工单工时统计();
如果JS文件修改了,点菜单时可以实时执行最新的内容。
如果要动态加载script,且使用后删除标签(里面定义的函数会仍然保留),建议直接使用$.getScript
,它等同于:
loadScript("1.js", {cache: false});
[小技巧]
在index.js/app.js等文件中写代码,必须刷新整个页面才能加载生效。
可以先把代码写在比如 web/test.js 中,这样每次修改后不用大刷新,直接在chrome控制台上加载运行:
WUI.loadScript("test.js")
等改好了再拷贝到真正想放置这块代码的地方。修改已有的框架中函数也可以这样来快速迭代。
执行JS代码,返回JS对象,示例:
source='{a: 1, b:"hello", c: function () {} }'
前面允许有单行注释,如
source='// comment\n {a: 1, b:"hello", c: function () {} }'
JS代码一般以花括号开头。在用于DOM属性时,也允许没有花括号的这种写法,如:
<div data-options="a:1,b:'hello',c:true"></div>
上例可返回 {a:1, b:'hello', c:true}
.
也支持各种表达式及函数调用,如:
<div data-options="getSomeOption()"></div>
更复杂的,如果代码不只是一个选项,前面还有其它JS语句,则返回最后一行语句的值,
最后一行可以是变量、常量、函数调用等,但不可以是花括号开头的选项,对选项须用括号包起来:
function hello() { }
({a: 1, b:"hello", c: hello })
或:
function hello() { }
var ret={a: 1, b:"hello", c: hello }
ret
传入的参数变量ctx可以在代码中使用,用于框架与扩展代码的交互,如:
{a: 1, b:"hello", c: ctx.fn1() }
执行出错时错误显示在控制台。调用者可回调处理错误。
evalOptions(src, null, function (ex) {
app_alert("文件" + url + "出错: " + ex);
});
注意:受限于浏览器,若使用try { eval(str) } catch(ex) {}
结构会导致原始的错误行号丢失,
为了避免大量代码中有错误时调试困难,在大于1行代码时,不使用try-catch,而是直接抛出错误。
从远程获取JSON结果.
注意: 与$.getJSON不同, 本函数不直接调用JSON.parse解析结果, 而是将返回当成JS代码使用eval执行得到JSON结果再回调fnOK.
示例:
WUI.loadJson("1.js", function (data) {
// handle json value `data`
});
1.js可以是返回任意JS对象的代码, 如:
// 可以有注释
{
a: 2 * 3600,
b: "hello",
// c: {}
d: function () {
},
}
复杂地,可以定义变量、函数,注意最后返回值对象要用小括号把返回对象括起来,如:
var schema = {
title: "菜单配置",
};
function onReady() {
}
({
schema: schema,
onReady: onReady
})
建议用模块方式来写,即用(function() { ... return ... })()
把定义内容包起来,这样变量和函数不会污染全局空间:
(function () {
var schema = ...
function onReady() {...}
return {
schema: schema,
onReady: onReady
}
})();
不处理结果时, 则该函数与$.getScript效果类似.
@param options 参考$.ajax参数
@options.async 默认为异步,设置{async:false}
表示同步调用。
@see evalOptions
动态加载css文件, 示例:
WUI.loadCss("lib/bootstrap.min.css");
设置日期框, 如果输入了非法日期, 自动以指定日期(如未指定, 用当前日期)填充.
setDateBox($("#txtComeDt"), function () { return genDefVal()[0]; });
设置时间框, 如果输入了非法时间, 自动以指定时间(如未指定, 用当前时间)填充.
setTimeBox($("#txtComeTime"), function () { return genDefVal()[1]; });
用于简化异步编程. 可将不易读的回调方式改写为易读的顺序执行方式.
var dfd = $.getScript("http://...");
function onSubmit()
{
dfd.then(function () {
foo();
bar();
});
}
可改写为:
function onSubmit()
{
if (waitFor(dfd)) return;
foo();
bar();
}
生成"#112233"形式的颜色值.
rgb(255,255,255) -> "#ffffff"
将jquery取到的颜色转成16进制形式,如:"rgb(4, 190, 2)" -> "#04be02"
示例:
var color = rgb2hex( $(".mui-container").css("backgroundColor") );
和使用$.data()差不多,更好用一些. 例:
$(o).jdata().hello = 100;
$(o).jdata({hello:100, world:200});
通过限定图片大小来压缩图片,用于图片预览和上传。
不支持IE8及以下版本。
函数cb的回调参数: picData={b64src,blob,w,h,w0,h0,quality,name,mimeType,size0,size,b64size,info}
b64src为base64格式的Data URL, 如 "data:image/jpeg;base64,/9j/4AAQSk...", 用于给image或background-image赋值显示图片;
可以赋值给Image.src:
var img = new Image();
img.src = picData.b64src;
或
$("<div>").css("background-image", "url(" + picData.b64src + ")");
blob用于放到FormData中上传:
fd.append('file', picData.blob, picData.name);
其它picData属性:
[预览和上传示例]
HTML:
<form action="upfile.php">
<div class="img-preview"></div>
<input type="file" /><br/>
<input type="submit" >
</form>
用picData.b64src来显示预览图,并将picData保存在img.picData_属性中,供后面上传用。
var jfrm = $("form");
var jpreview = jfrm.find(".img-preview");
var opt = {maxSize:1280};
jfrm.find("input[type=file]").change(function (ev) {
$.each(this.files, function (i, fileObj) {
compressImg(fileObj, function (picData) {
$("<img>").attr("src", picData.b64src)
.prop("picData_", picData)
.appendTo(jpreview);
//$("<div>").css("background-image", "url("+picData.b64src+")").appendTo(jpreview);
}, opt);
});
this.value = "";
});
上传picData.blob到服务器
jfrm.submit(function (ev) {
ev.preventDefault();
var fd = new FormData();
var idx = 1;
jpreview.find("img").each(function () {
// 名字要不一样,否则可能会覆盖
fd.append('file' + idx, this.picData_.blob, this.picData_.name);
++idx;
});
$.ajax({
url: jfrm.attr("action"),
data: fd,
processData: false,
contentType: false,
type: 'POST',
// 允许跨域调用
xhrFields: {
withCredentials: true
},
success: cb
});
return false;
});
参考:JIC.js (https://github.com/brunobar79/J-I-C)
TODO: 用完后及时释放内存,如调用revokeObjectURL等。
触发含有异步操作的事件,在异步事件完成后继续。兼容同步事件处理函数,或多个处理函数中既有同步又有异步。
返回Deferred对象,或false表示要求取消之后操作。
@param ev 事件名,或事件对象$.Event()
示例:以事件触发方式调用jo的异步方法submit:
var dfd = WUI.triggerAsync(jo, 'submit');
if (dfd === false)
return;
dfd.then(doNext);
function doNext() { }
jQuery对象这样提供异步方法:triggerAsync会用事件对象ev创建一个dfds数组,将Deferred对象存入即可支持异步调用。
jo.on('submit', function (ev) {
var dfd = $.ajax("upload", ...);
if (ev.dfds)
ev.dfds.push(dfd);
});
将array转成tree. 注意它会修改arr(添加children属性),但返回新的数组。
var ret = WUI.makeTree([
{id: 1},
{id: 2, fatherId: 1},
{id: 3, fatherId: 2},
{id: 4, fatherId: 1}
]);
结果:
ret = [
{id: 1, children: [
{id: 2, fatherId: 1, children: [
{id: 3, fatherId: 2},
],
{id: 4, fatherId: 1}
]
]
返回一个属性做eval后的js值。
示例:读取一个对象值:
var opt = evalAttr(jo, "data-opt");
<div data-opt="{id:1, name:\"data1\"}"><div>
考虑兼容性,也支持忽略括号的写法,
<div data-opt="id:1, name:\"data1\""><div>
读取一个数组:
var arr = evalAttr(jo, "data-arr");
<div data-arr="['aa', 'bb']"><div>
读取一个函数名(或变量):
var fn = evalAttr(jo, "mui-initfn");
<div mui-initfn="initMyPage"><div>
@see evalOptions
中止之后的调用, 直接返回.
直接返回. 用法:
throw new DirectReturn();
可直接调用app_abort();
框架自动在脚本出错时弹框报错,并上报服务端(syslog).
可通过WUI.options.skipErrorRegex忽略指定错误。
第一次调用,根据jo上设置的data-options属性及指定的defVal初始化,或为{}
。
存到jo.prop("muiOptions")上。之后调用,直接返回该属性。
通过指定ctx, 可以为属性计算时添加可访问接口, 示例: (eol1Sn属性是需要取下面input中的值)
<button type="button" class="btnPlcNotify" data-options="newItemFlag:1, eol1Sn:ctx.eolSn">Move In from eol-1</button>
<input id="eolSn" value="sn1">
取值:
var data = WUI.getOptions($(this), null, {
eol1Sn: jpage.find("eolSn").val()
}
当然ctx也可以做成个funciton比如:
<button type="button" class="btnPlcNotify" data-options="newItemFlag:1, eol1Sn:ctx('#eolSn').val()">Move In from eol-1</button>
<input id="eolSn" value="sn1">
var data = WUI.getOptions($(this), null, function (ref) {
return jpage.find(ref);
});
@see evalAttr
@var queryHint 查询用法提示
@param kvList {key=>value}, 键值对,值中支持操作符及通配符。也支持格式 [ [key, value] ], 这时允许key有重复。
根据kvList生成BPQ协议定义的{obj}.query的cond参数。
例如:
var kvList = {phone: "13712345678", id: ">100", addr: "上海*", picId: "null"};
WUI.getQueryCond(kvList);
有多项时,每项之间以"AND"相连,以上定义将返回如下内容:
"phone='13712345678' AND id>100 AND addr LIKE '上海*' AND picId IS NULL"
示例二:
var kvList = [ ["phone", "13712345678"], ["id", ">100"], ["addr", "上海*"], ["picId", "null"] ];
WUI.getQueryCond(kvList); // 结果同上。
设置值时,支持以下格式:
支持and/or查询,但不支持在其中使用括号:
以下表示的范围相同:
{k1:'1-5,7-10', k2:'1-10 and <>6'}
符号优先级依次为:"-"(类似and) ","(类似or) and or
在详情页对话框中,切换到查找模式,在任一输入框中均可支持以上格式。
(v5.5) value支持用数组表示范围(前闭后开区间),特别方便为起始、结束时间生成条件:
var cond = getQueryCond({tm: ["2019-1-1", "2020-1-1"]}); // 生成 "tm>='2019-1-1' AND tm<'2020-1-1'"
var cond = getQueryCond({tm: [null, "2020-1-1"]}); // 生成 "tm<'2020-1-1'"。数组中任一值为null或''都一样会被忽略。
var cond = getQueryCond({tm: [null, null]); // 返回空串''
@see getQueryParam
@see getQueryParamFromTable 获取datagrid的当前查询参数
@see doFind
(v5.5) 支持在key中包含查询提示。如"code/s"表示不要自动猜测数值区间或日期区间。
比如输入'126231-191024'时不会当作查询126231到191024的区间。
(v6) 日期、时间字段查询时,可使用WUI.getTmRange
函数支持的时间区间如"今天","本周","本月", "今年", "近3天(小时|周|月|季度|年)”,"前3天(小时|周|月|季度|年)”等。
@see wui-find-hint
根据键值对生成BQP协议中{obj}.query接口需要的cond参数.
即 {cond: WUI.getQueryCond(kvList) }
示例:
WUI.getQueryParam({phone: '13712345678', id: '>100'})
返回
{cond: "phone='13712345678' AND id>100"}
@see getQueryCond
@see getQueryParamFromTable 获取datagrid的当前查询参数
连续5次点击某处,每次点击间隔不超过2s, 执行隐藏动作。
例:
// 连续5次点击当前tab标题,重新加载页面. ev为最后一次点击事件.
var self = WUI;
self.doSpecial(self.tabMain.find(".tabs-header"), ".tabs-selected", function (ev) {
self.reloadPage();
self.reloadDialog(true);
// 弹出菜单
//jmenu.menu('show', {left: ev.pageX, top: ev.pageY});
return false;
});
连续3次点击对话框中的字段标题,触发查询:
WUI.doSpecial(jdlg, ".wui-form-table td", fn, 3);
复制到剪贴板。
出错时,取出错调用的上下文信息。
ctx: {ac, tm, tv, ret}
设置为true禁用batchCall, 仅用于内部测试。
当前batchCall对象,用于内部调试。
在后端接口尚无法调用时,可以配置MUI.mockData做为模拟接口返回数据。
调用callSvr时,会直接使用该数据,不会发起ajax请求。
mockData={ac => data/fn}
mockData中每项可以直接是数据,也可以是一个函数:fn(param, postParam)->data
例:模拟"User.get(id)"和"User.set()(key=value)"接口:
var user = {
id: 1001,
name: "孙悟空",
};
MUI.mockData = {
// 方式1:直接指定返回数据
"User.get": [0, user],
// 方式2:通过函数返回模拟数据
"User.set": function (param, postParam) {
$.extend(user, postParam);
return [0, "OK"];
}
}
// 接口调用:
var user = callSvrSync("User.get");
callSvr("User.set", {id: user.id}, function () {
alert("修改成功!");
}, {name: "大圣"});
实例详见文件 mockdata.js。
在mockData的函数中,可以用this变量来取ajax调用参数。
要取HTTP动词可以用this.type
,值为GET/POST/PATCH/DELETE之一,从而可模拟RESTful API.
可以通过MUI.options.mockDelay设置模拟调用接口的网络延时。
@see options.mockDelay
模拟数据可直接返回[code, data]格式的JSON数组,框架会将其序列化成JSON字符串,以模拟实际场景。
如果要查看调用与返回数据日志,可在浏览器控制台中设置 MUI.options.logAction=true,在控制台中查看日志。
如果设置了MUI.callSvrExt,调用名(ac)中应包含扩展(ext)的名字,例:
MUI.callSvrExt['zhanda'] = {...};
callSvr(['token/get-token', 'zhanda'], ...);
要模拟该接口,应设置
MUI.mockData["zhanda:token/get-token"] = ...;
@see callSvrExt
也支持"default"扩展,如:
MUI.callSvrExt['default'] = {...};
callSvr(['token/get-token', 'default'], ...);
或
callSvr('token/get-token', ...);
要模拟该接口,可设置
MUI.mockData["token/get-token"] = ...;
@key ajaxOpt.jdFilter 禁用返回格式合规检查.
以下调用, 如果1.json符合[code, data]
格式, 则只返回处理data部分; 否则将报协议格式错误:
$.ajax("1.json", {dataType: "json"})
$.get("1.json", null, console.log, "json")
$.getJSON("1.json", null, console.log)
对于ajax调用($.ajax,$.get,$.post,$.getJSON等), 若明确指定dataType为"json"或"text", 且未指定jdFilter为false,
则框架按筋斗云返回格式即[code, data]
来处理只返回data部分, 不符合该格式, 则报协议格式错误.
以下调用未指定dataType, 或指定了jdFilter=false, 则不会应用筋斗云协议格式:
$.ajax("1.json")
$.get("1.json", null, console.log)
$.ajax("1.json", {jdFilter: false}) // jdFilter选项明确指定了不应用筋斗云协议格式
@param ctx {ac, tm, tv?, tv2?, noLoadingImg?}
@param rv BQP协议原始数据,如 "[0, {id: 1}]",一般是字符串,也可以是JSON对象。
@return data 按接口定义返回的数据对象,如 {id: 1}. 如果返回值===RV_ABORT,调用函数应直接返回,不回调应用层。
注意:如果callSvr设置了noex:1
选项,则当调用失败时返回false。
取服务端接口URL对应的目录。可用于拼接其它服务端资源。
相当于dirname(MUI.options.serverUrl);
例如:
serverUrl为"../jdcloud/api.php" 或 "../jdcloud/",则MUI.baseUrl返回 "../jdcloud/"
serverUrl为"http://myserver/myapp/api.php" 或 "http://myserver/myapp/",则MUI.baseUrl返回 "http://myserver/myapp/"
生成对后端调用的url.
var params = {id: 100};
var url = MUI.makeUrl("Ordr.set", params);
注意:函数返回的url是字符串包装对象,可能含有这些属性:{makeUrl=true, action?, params?}
这样可通过url.action得到原始的参数。
支持callSvr扩展,如:
var url = MUI.makeUrl('zhanda:login');
(deprecated) 为兼容旧代码,action可以是一个数组,在WUI环境下表示对象调用:
WUI.makeUrl(['Ordr', 'query']) 等价于 WUI.makeUrl('Ordr.query');
在MUI环境下表示callSvr扩展调用:
MUI.makeUrl(['login', 'zhanda']) 等价于 MUI.makeUrl('zhanda:login');
特别地, 如果action是相对路径, 或是'.php'文件, 则不会自动拼接WUI.options.serverUrl:
callSvr("./1.json"); // 如果是callSvr("1.json") 则url可能是 "../api.php/1.json"这样.
callSvr("./1.php");
@see callSvrExt
@param ac String. action, 交互接口名. 也可以是URL(比如由makeUrl生成)
@param params Object. URL参数(或称HTTP GET参数)
@param postParams Object. POST参数. 如果有该参数, 则自动使用HTTP POST请求(postParams作为POST内容), 否则使用HTTP GET请求.
@param fn Function(data). 回调函数, data参考该接口的返回值定义。
@param userOptions 用户自定义参数, 会合并到$.ajax调用的options参数中.可在回调函数中用"this.参数名"引用.
常用userOptions:
指定contentType和设置自定义HTTP头(headers)示例:
var opt = {
contentType: "text/xml",
headers: {
Authorization: "Basic aaa:bbb"
}
};
callSvr("hello", $.noop, "<?xml version='1.0' encoding='UTF-8'?><return><code>0</code></return>", opt);
想为ajax选项设置缺省值,可以用callSvrExt中的beforeSend回调函数,也可以用$.ajaxSetup,
但要注意:ajax的dataFilter/beforeSend选项由于框架已用,最好不要覆盖。
@see callSvrExt[].beforeSend (opt) 为callSvr选项设置缺省值
@return deferred对象,在Ajax调用成功后回调。
例如,
var dfd = callSvr(ac, fn1);
dfd.then(fn2);
function fn1(data) {}
function fn2(data) {}
在接口调用成功后,会依次回调fn1, fn2. 在回调函数中this表示ajax参数。例如:
callSvr(ac, function (data) {
// 可以取到传入的参数。
console.log(this.key1);
}, null, {key1: 'val1'});
(v5.4) 支持失败时回调:
var dfd = callSvr(ac);
dfd.fail(function (data) {
console.log('error', data);
console.log(this.ctx_.ret); // 和设置选项{noex:1}时回调中取MUI.lastError.ret 或 this.lastError相同。
});
@key callSvr.noex 调用接口时忽略出错,可由回调函数fn自己处理错误。
当后端返回错误时, 回调fn(false)
(参数data=false). 可通过 MUI.lastError.ret 或 this.lastError 取到返回的原始数据。
示例:
callSvr("logout");
callSvr("logout", api_logout);
function api_logout(data) {}
callSvr("login", api_login);
function api_login(data) {}
callSvr("info/hotline.php", {q: '大众'}, api_hotline);
function api_hotline(data) {}
// 也可使用makeUrl生成的URL如:
callSvr(MUI.makeUrl("logout"), api_logout);
callSvr(MUI.makeUrl("logout", {a:1}), api_logout);
callSvr("User.get", function (data) {
if (data === false) { // 仅当设置noex且服务端返回错误时可返回false
// var originalData = MUI.lastError.ret; 或
// var originalData = this.lastError;
return;
}
foo(data);
}, null, {noex:1});
@see lastError 出错时的上下文信息
框架会自动在ajaxOption中增加ctx_属性,它包含 {ac, tm, tv, tv2, ret} 这些信息。
当设置MUI.options.logAction=1时,将输出这些信息。
callSvr支持FormData对象,可用于上传文件等场景。示例如下:
@key example-upload
HTML:
file: <input id="file1" type="file" multiple>
<button type="button" id="btn1">upload</button>
JS:
jpage.find("#btn1").on('click', function () {
var fd = new FormData();
$.each(jpage.find('#file1')[0].files, function (i, e) {
fd.append('file' + (i+1), e);
});
callSvr('upload', api_upload, fd);
function api_upload(data) { ... }
});
@key callSvrExt
当调用第三方API时,也可以使用callSvr扩展来代替$.ajax调用以实现:
例:合作方接口使用HTTP协议,格式如(以生成token调用为例)
http://<Host IP Address>:<Host Port>/lcapi/token/get-token?user=用户名&password=密码
返回格式为:{code, msg, data}
成功返回:
{
"code":"0",
"msg":"success",
"data":[ { "token":"xxxxxxxxxxxxxx" } ]
}
失败返回:
{
"code":"4001",
"msg":"invalid username or password",
"data":[]
}
callSvr扩展示例:
MUI.callSvrExt['zhanda'] = {
makeUrl: function(ac, param) {
// 只需要返回接口url即可,不必拼接param
return 'http://hostname/lcapi/' + ac;
},
dataFilter: function (data) {
if ($.isPlainObject(data) && data.code !== undefined) {
if (data.code == 0)
return data.data;
if (this.noex)
return false;
app_alert("操作失败:" + data.msg, "e");
}
else {
app_alert("服务器通讯协议异常!", "e"); // 格式不对
}
}
};
在调用时,ac参数使用"{扩展名}:{调用名}"的格式:
callSvr('zhanda:token/get-token', {user: 'test', password: 'test123'}, function (data) {
console.log(data);
});
旧的调用方式ac参数使用数组,现在已不建议使用:
callSvr(['token/get-token', 'zhanda'], ...);
@key callSvrExt[].makeUrl (ac, param)
根据调用名ac生成url, 注意无需将param放到url中。
注意:
对方接口应允许JS跨域调用,或调用方支持跨域调用。
@key callSvrExt[].dataFilter (data) = null/false/data
对调用返回数据进行通用处理。返回值决定是否调用callSvr的回调函数以及参数值。
callSvr(ac, callback);
callback(data)
.callback
.callSvr(ac, callback, postData, {noex:1})
,表示由应用层回调函数来处理出错: callback(false)
。当返回false时,应用层可以通过MUI.lastError.ret
来获取服务端返回数据。
@see lastError 出错时的上下文信息
(支持版本: v3.1)
如果要修改callSvr缺省调用方法,可以改写 MUI.callSvrExt['default']。示例:
MUI.callSvrExt['default'] = {
makeUrl: function(ac) {
return '../api.php/' + ac;
},
dataFilter: function (data) {
var ctx = this.ctx_ || {};
if (data && $.isArray(data) && data.length >= 2 && typeof data[0] == "number") {
if (data[0] == 0)
return data[1];
if (this.noex)
{
return false;
}
if (data[0] == E_NOAUTH) {
// 如果支持自动重登录
//if (MUI.tryAutoLogin()) {
// $.ajax(this);
//}
// 不支持自动登录,则跳转登录页
MUI.popPageStack(0);
MUI.showLogin();
return;
}
else if (data[0] == E_AUTHFAIL) {
app_alert("验证失败,请检查输入是否正确!", "e");
return;
}
else if (data[0] == E_ABORT) {
console.log("!!! abort call");
return;
}
logError();
app_alert("操作失败:" + data[1], "e");
}
else {
logError();
app_alert("服务器通讯协议异常!", "e"); // 格式不对
}
function logError()
{
console.log("failed call");
console.log(ctx);
}
}
};
@key callSvrExt[].beforeSend (opt) 为callSvr或$.ajax选项设置缺省值
如果有ajax选项想设置,可以使用beforeSend回调,例如POST参数使用JSON格式:
MUI.callSvrExt['default'] = {
beforeSend: function (opt) {
// 示例:设置contentType
if (opt.contentType == null) {
opt.contentType = "application/json;charset=utf-8";
}
// 示例:添加HTTP头用于认证
if (g_data.auth) {
if (opt.headers == null)
opt.headers = {};
opt.headers["Authorization"] = "Basic " + g_data.auth;
}
}
}
可以从opt.ctx中取到{ac, ext, noex, dfd}等值(如opt.ctx.ac),可以从opt.url中取到{ac, params}值。
如果要设置请求的HTTP headers,可以用opt.headers = {header1: "value1", header2: "value2"}
.
更多选项参考jquery文档:jQuery.ajax的选项。
接口示例:更新订单
PATCH /orders/{ORDER_ID}
调用成功仅返回HTTP状态,无其它内容:"200 OK" 或 "204 No Content"
调用失败返回非2xx的HTTP状态及错误信息,无其它内容,如:"400 bad id"
为了处理HTTP错误码,应设置:
MUI.callSvrExt["default"] = {
beforeSend: function (opt) {
opt.handleHttpError = true;
},
dataFilter: function (data) {
var ctx = this.ctx_;
if (ctx && ctx.status) {
if (this.noex)
return false;
app_alert(ctx.statusText, "e");
return;
}
return data;
}
}
$.ajaxSetup({error: fn})
,$.ajax({error: fn})
,它会覆盖框架的处理.如果接口在出错时,返回固定格式的错误对象如{code, message},可以这样处理:
MUI.callSvrExt["default"] = {
beforeSend: function (opt) {
opt.handleHttpError = true;
},
dataFilter: function (data) {
var ctx = this.ctx_;
if (ctx && ctx.status) {
if (this.noex)
return false;
if (data && data.message) {
app_alert(data.message, "e");
}
else {
app_alert("操作失败: 服务器错误. status=" + ctx.status + "-" + ctx.statusText, "e");
}
return;
}
return data;
}
}
调用接口时,HTTP谓词可以用callSvr的userOptions中给定,如:
callSvr("orders/" + orderId, fn, postParam, {type: "PATCH"});
这种方式简单,但因调用名ac是变化的,不易模拟接口。
如果要模拟接口,可以保持调用名ac不变,像这样调用:
callSvr("orders/{id}", {id: orderId}, fn, postParam, {type: "PATCH"});
于是可以这样做接口模拟:
MUI.mockData = {
"orders/{id}": function (param, postParam) {
var ret = "OK";
// 获取资源
if (this.type == "GET") {
ret = orders[param.id];
}
// 更新资源
else if (this.type == "PATCH") {
$.extend(orders[param.id], postParam);
}
// 删除资源
else if (this.type == "DELETE") {
delete orders[param.id];
}
return [0, ret];
}
};
不过这种写法需要适配,以生成正确的URL,示例:
MUI.callSvrExt["default"] = {
makeUrl: function (ac, param) {
ac = ac.replace(/\{(\w+)\}/g, function (m, m1) {
var ret = param[m1];
assert(ret != null, "缺少参数");
delete param[m1];
return ret;
});
return "./api.php/" + ac;
}
}
支持Promise/Deferred编程风格:
var dfd = callSvr("...");
dfd.then(function (data) {
console.log(data);
})
.catch(function (err) {
app_alert(err);
})
.finally(...)
支持catch/finally等Promise类接口。接口逻辑失败时,dfd.reject()触发fail/catch链。
支持await编程风格,上例可写为:
// 使用await时callSvr调用失败是无法返回的,加{noex:1}选项可让失败时返回false
var rv = callSvr("...", $.noop, null, {noex:1});
if (rv === false) {
// 失败逻辑 dfd.catch. 取错误信息用WUI.lastError={ac, tm, tv, ret}
console.log(WUI.lastError.ret)
}
else {
// 成功逻辑 dfd.then
}
// finally逻辑
示例:
let rv = await callSvr("Ordr.query", {res:"count(*) cnt", fmt:"one"})
let cnt = rv.cnt
(v5.5) 如果ac是调用相对路径, 则直接当成最终路径, 不做url拼接处理:
callSvr("./1.json"); // 如果是callSvr("1.json") 则实际url可能是 "../api.php/1.json"这样.
callSvr("../1.php");
相当于调用
$.ajax("../1.php", {dataType: "json", success: callback})
或
$.getJSON("../1.php", callback);
注意下面调用未指定dataType, 不会按筋斗云协议格式处理:
$.ajax("../1.php", {success: callback})
在回调中获取this.xhr_对象(即$.ajax()返回对象, 详细可参考$.ajax手册), 通过它的getResponseHeader()方法取响应头, status属性来获取状态码, 示例
callSvr("User.query", function (data) {
console.log("status", this.xhr_.status);
var v = this.xhr_.getResponseHeader("X-Powered-By"); // header名字不分区大小写,用"x-powered-by"也可以
console.log(v);
});
当然也可以通过返回的dfd对象来操作:
rv = callSvr("User.query");
rv.then(function (data) {
console.log("status", this.xhr_.status);
});
@see $.ajax
@return data 原型规定的返回数据
同步模式调用callSvr.
@see callSvr
该方法已不建议使用。上传文件请用FormData。
@see example-upload callSvr
@param $iframe 一个隐藏的iframe组件.
@param callOpt 用户自定义参数. 参考callSvr的同名参数. e.g. {noex: 1}
一般对后端的调用都使用callSvr函数, 但像上传图片等操作不方便使用ajax调用, 因为要自行拼装multipart/form-data格式的请求数据.
这种情况下可以使用form的提交和一个隐藏的iframe来实现类似的调用.
先定义一个form, 在其中放置文件上传控件和一个隐藏的iframe. form的target属性设置为iframe的名字:
<form data-role="content" action="upload" method=post enctype="multipart/form-data" target="ifrUpload">
<input type=file name="file[]" multiple accept="image/*">
<input type=submit value="上传">
<iframe id='ifrUpload' name='ifrUpload' style="display:none"></iframe>
</form>
然后就像调用callSvr函数一样调用setupCallSvrViaForm:
var url = MUI.makeUrl("upload", {genThumb: 1});
MUI.setupCallSvrViaForm($frm, $frm.find("iframe"), url, onUploadComplete);
function onUploadComplete(data)
{
alert("上传成功");
}
批量调用。将若干个调用打包成一个特殊的batch调用发给服务端。
注意:
示例:
var batch = new MUI.batchCall();
callSvr("Family.query", {res: "id,name"}, api_FamilyQuery);
callSvr("User.get", {res: "id,phone"}, api_UserGet);
batch.commit();
以上两条调用将一次发送到服务端。
在批处理中,默认每条调用是一个事务,如果想把批处理中所有调用放到一个事务中,可以用useTrans选项:
var batch = new MUI.batchCall({useTrans: 1});
callSvr("Attachment.add", api_AttAdd, {path: "path-1"});
callSvr("Attachment.add", api_AttAdd, {path: "path-2"});
batch.commit();
在一个事务中,所有调用要么成功要么都取消。
任何一个调用失败,会导致它后面所有调用取消执行,且所有已执行的调用会回滚。
参数中可以引用之前结果中的值,引用部分需要用"{}"括起来,且要在opt.ref参数中指定哪些参数使用了引用:
MUI.useBatchCall();
callSvr("..."); // 这个返回值的结果将用于以下调用
callSvr("Ordr.query", {
res: "id,dscr",
status: "{$-1.status}", // 整体替换,结果可以是一个对象
cond: "id>{$-1.id}" // 部分替换,其结果只能是字符串
}, api_OrdrQuery, {
ref: ["status", "cond"] // 须在ref中指定需要处理的key
});
特别地,当get/post整个是一个字符串时,直接整体替换,无须在ref中指定,如:
callSvr("Ordr.add", $.noop, "{$-1}", {contentType:"application/json"});
以下为引用格式示例:
{$1} // 第1次调用的结果。
{$-1} // 前1次调用的结果。
{$-1.path} // 取前一次调用结果的path属性
{$1[0]} // 取第1次调用结果(是个数组)的第0个值。
{$1[0].amount}
{$-1.price * $-1.qty} // 可以做简单的数值计算
如果值计算失败,则当作"null"填充。
综合示例:
MUI.useBatchCall();
callSvr("Ordr.completeItem", $.noop, {itemId:1})
callSvr("Ordr.completeItem", $.noop, {itemId:2, qty:2})
callSvr("Ordr.calc", $.noop, {items:["{$1}", "{$2}"]}, {contentType:"application/json", ref:["items"] });
callSvr("Ordr.add", $.noop, "{$3}", {contentType:"application/json"});
@see useBatchCall
@see disableBatch
@see m_curBatch
之后的callSvr调用都加入批量操作。例:
MUI.useBatchCall();
callSvr("Family.query", {res: "id,name"}, api_FamilyQuery);
callSvr("User.get", {res: "id,phone"}, api_UserGet);
可指定多少毫秒以内的操作都使用批处理,如10ms内:
MUI.useBatchCall(null, 10);
如果MUI.disableBatch=true, 该函数不起作用。
@see batchCall
@see disableBatch
是否批量操作模式(即是否按住Ctrl键操作)。
用于列表中选择一行来操作,返回该行数据。如果未选则报错,返回null。
var row = WUI.getRow(jtbl);
if (row == null)
return;
判断是treegrid还是datagrid。
示例:
var datagrid = WUI.isTreegrid(jtbl)? "treegrid": "datagrid";
var opt = jtbl[datagrid]("options");
刷新数据表,或用指定查询条件重新查询。
url和queryParams都可以指定查询条件,url通过makeUrl指定查询参数,它是基础查询一般不改变;
queryParams在查询对话框做查询时会被替换、或是点Ctrl-刷新时会被清除;如果doAppendFilter为true时会叠加之前的查询条件。
在明细对话框上三击字段标题可查询,按住Ctrl后三击则是追加查询模式。
临时reload一下,完事后恢复原url
@param rowData 通过原始数据指定行,可通过WUI.getRow(jtbl)获取当前所选择的行数据。
rowData如果未指定,则使用当前选择的行。
示例:
var row = WUI.getRow(jtbl);
if (row == null)
return;
...
WUI.reloadRow(jtbl, row);
如果要刷新整个表,可用WUI.reload(jtbl)。
刷新整个页面可用WUI.reloadPage(),但一般只用于调试,不用于生产环境。
返回Deferred对象,表示加载完成。
force: 1
会刷新该页面, 按住ctrl键打开页面时激活该功能。@alias showPage (pageName, title?, paramArr?)
新页面以title作为id。
注意:每个页面都是根据pages下相应pageName复制出来的,显示在一个新的tab页中。相同的title当作同一页面。
初始化函数由page上的my-initfn属性指定。
page定义示例:
<div id="my-pages" style="display:none">
<div class="pageHome" title="首页" my-initfn="initPageHome"></div>
</div>
page调用示例:
WUI.showPage("pageHome");
WUI.showPage("pageHome", "我的首页"); // 默认标题是"首页",这里指定显示标题为"我的首页"。
WUI.showPage("pageHome", {title: "我的首页"}); // 同上
(v5.4) 如果标题中含有"%s",将替换成原始标题,同时传参到initPage:
WUI.showPage("pageHome", {title: "%s-" + cityName, cityName: cityName}); //e.g. 显示 "首页-上海"
title用于唯一标识tab,即如果相同title的tab存在则直接切换过去。除非:
(v5.5) 如果标题以"!"结尾, 则每次都打开新的tab页,(v7)等价于指定选项showPageOpt.force:1
:
WUI.showPage("pageHome", "我的首页!");
WUI.showPage("pageHome", {title: "我的首页", force:1}); // 同上
(v7) 按住Ctrl点击菜单项打开页面, 也会刷新式打开, 相当于force:1
选项
按住Shift点击菜单项, 强制打开新页面, 相当于forceNew:1
选项;
如果菜单项是链接页面(指定href为一个url), 则会在新窗口打开(默认是在tab页内即pageIframe中打开).
@key pageIframe
jdcloud-ganlan中内置在tab页打开独立网页(注意被开页面CSP设置及其后端跨域设置)功能,如:
WUI.showPage("pageIframe", "官网", ["http://yibo.ltd"])
// 也可使用绝对或相对路径
WUI.showPage("pageIframe", "官网", ["/"])
它在store.html中包含了pageIframe的页面定义,在uimeta.js中定义了页面函数。
示例
var pageFilter = {cond: {status: "在职"}};
WUI.showPage("pageEmployee", {title: "员工", pageFilter: pageFilter});
它直接影响页面中的datagrid的查询条件。
选项_pageFilterOnly
用于影响datagrid查询只用page filter的条件。
var pageFilter = { cond: {status: "在职"}, _pageFilterOnly: true };
WUI.showPage("pageEmployee", {title: "员工", pageFilter: pageFilter});
注意:旧应用中通过paramArr[1]来指定pageFilter, 目前兼容该用法,但不推荐使用:
WUI.showPage("pageEmployee", "员工", [null, pageFilter]); // 旧写法,不推荐使用
// 等价于
WUI.showPage("pageEmployee", {title: "员工", pageFilter: pageFilter});
showPage返回deferred/promise对象,表示页面加载完成。所以如果某些操作要依赖于页面完成,可以用:
var dfd = WUI.showPage(...);
dfd.then(fn);
// fn中的操作需要依赖上面页面。
或
await WUI.showPage();
fn();
一般与系统页面pageTab合用,pageTab可显示一个或多个tabs,然后把新页面显示在指定tabs中。示例:
await WUI.showPage("pageTab", {title: "员工!", tabs:"40%,60%"});
WUI.showPage("pageEmployee", {title: "管理员", pageFilter:{cond: {perms:"~mgr"}}, target:"员工_1"});
WUI.showPage("pageEmployee", {title: "非管理员", pageFilter:{cond: {perms:"!~mgr"}}, target:"员工_2"});
注意:下面两个页面target要依赖于pageTab页面,所以需要加await等待pageTab页面加载完成。
@see pageTab
动态加载的逻辑页(或对话框)具有该属性,值为源文件名。
返回showPage时传入的参数,是一个对象:{title, ...}。
如果jpage不是页面,则返回null。
@key PAGE_FILTER
取页面的过滤参数,由框架自动处理.
返回showPage原始过滤参数或null。注意不要修改它。
如果指定name,则取page filter中的该过滤字段的值。示例:假如page filter有以下4种:
{ cond: { type: "A" }}
{ cond: "type='A'"}
{ cond: {type: "A OR B"} }
{ cond: {type: "~A*"} }
则调用getPageFilter(jpage, "type")时,仅当第1种才返回"A";
其它几种的cond值要么不是对象,要么不是相等条件,均返回null。
对话框保存,相当于点击确定按钮。如果noCloseDlg=true,则不关闭对话框(即“应用”保存功能)。
复制并添加对象。
对象对话框在更新模式下,按Ctrl-D或右键对话框空白处选择“再次新增”,可复制当前对象,进入添加模式。
支持对子表进行复制,注意仅当子表是允许添加的才会被复制(wui-subobj组件,且设置了valueField和dlg选项)。
(由于子表支持懒加载,当运行“再次新增”时,会自动加载所有子表数据后再复制)
在对话框进入新增模式后,编号(id)会被清除,其它字段保留,
如果对话框编写过新增时的逻辑,比如自动填写或disable某些字段,这些逻辑会生效。
如果字段在新增时是disabled状态,比如“添加时间”等字段,尽管它仍显示为旧数据且无法修改,
但在添加时它并不会提交,所以没有影响。
如果要定制复制时的逻辑,可以使用下面事件:
@key event-duplicate (ev, data) 对话框上复制添加对象
如果要定制添加行为,可在对话框的duplicate事件中修改数据。示例:
jdlg.on("duplicate", function (ev, data) {
// 可修改data, 若有子表则是有相应的数组字段
// 主表id,子表id及关联字段在这里可以访问,之后会被框架自动删除,此处无须处理
console.log(data);
});
判断是否为手机小屏显示. 宽度低于640当作小屏处理.
@param jdlg 可以是jquery对象,也可以是selector字符串或DOM对象,比如 "#dlgOrder".
注意:当对话框动态从外部加载时,jdlg=$("#dlgOrder") 一开始会为空数组,这时也可以调用该函数,且调用后jdlg会被修改为实际加载的对话框对象。
@param opt ?={url, buttons, noCancel=false, okLabel="确定", cancelLabel="取消", modal=true, reset=true, validate=true, data, onOk, onSubmit, title}
[{text: '下一步', iconCls:'icon-ok', handler: btnNext_click}]
。如果是对象对话框(showObjDlg)调用过来,会自动带上以下选项:
示例1:静态加载(在web/store.html中的my-pages中定义),通过对话框的id属性标识。
<div id="my-pages" style="display:none">
<div id="dlgLogin" title="商户登录">
...
</div>
<div>
加载:WUI.showDlg($("#dlgLogin"))
。对话框顶层DOM用div或form都可以。用form更简单些。
除了少数内置对话框,一般不建议这样用,而是建议从外部文件动态加载(模块化)。
示例2:从内部模板动态加载,模板id须为"tpl_{对话框id}",对话框上不用指定id
<script type="text/template" id="tpl_dlgLogin">
<div title="商户登录">
...
</div>
</script>
加载:WUI.showDlg($("#dlgLogin"))
或WUI.showDlg("#dlgLogin")
。
比示例1要好些,但一般也不建议这样用。目前是webcc编译优化机制使用该技术做发布前常用对话框的合并压缩。
示例3:从外部模板动态加载,模板是个文件如web/page/dlgLogin.html,对话框上不用指定id
<div title="商户登录">
...
</div>
加载:WUI.showDlg($("#dlgLogin"))
或WUI.showDlg("#dlgLogin")
。
这是目前使用对话框的主要方式。
示例4:不用HTML,直接JS中创建DOM:
var jdlg = $('<div title="商户登录">Hello World</div>');
WUI.showDlg(jdlg);
适合编程动态实现的对话框。参考使用更简单的WUI.showDlgByMeta或WUI.showDlg的meta选项。
对话框有两种编程模式,一是通过opt参数在启动对话框时设置属性及回调函数(如onOk),另一种是在dialog初始化函数中处理事件(如validate事件)实现逻辑,有利于独立模块化。
对话框显示时会触发以下事件:
事件beforeshow
事件show
opt.onShow(formMode, data)
对于自动提交数据的对话框(设置了opt.url),提交数据过程中回调函数及事件执行顺序为:
事件validate; // 提交前,用于验证或设置提交数据。返回false或ev.preventDefault()可取消提交,中止以下代码执行。
opt.onSubmit(jdlg, data); // 提交前,验证或设置提交数据,返回false将阻止提交。
... 框架通过callSvr自动提交数据,如添加、更新对象等。
opt.onOk(data); // 提交且服务端返回数据后。data是服务端返回数据。
事件retdata; // 与onOk类似。
对于手动提交数据的对话框(opt.url为空),执行顺序为:
事件validate; // 用于验证、设置提交数据、提交数据。
opt.onOk(); // 同上. 回调函数中this为jdlg.
注意:
调用此函数后,对话框将加上以下CSS Class:
@key .wui-dialog 标识WUI对话框的类名。
示例:显示一个对话框,点击确定后调用后端接口。
WUI.showDlg("#dlgCopyTo", {
modal: false,
reset: false,
url: WUI.makeUrl("Category.copyTo", {cond: ...}),
onSubmit: ..., // 提交前验证,返回False则取消提交
onOk: function (retdata) {
var jdlgCopyTo = this; // this是当前对话框名
// 自动提交后处理返回数据retdata
}
});
用户名:<input name="uname">
.callSvr(url, 回调onOk(retdata), POST内容为WUI.getFormData(dlg))
。如果不使用url选项,也可实现如下:
WUI.showDlg("#dlgCopyTo", {
modal: false,
reset: false,
onOk: function () {
var jdlgCopyTo = this; // this是当前对话框名
var data = WUI.getFormData(jdlgCopyTo);
callSvr("Category.copyTo", {cond: ...}, function (retdata) { ... }, data);
}
});
如果要做更多的初始化配置,比如处理对话框事件,则使用初始化函数机制,即在对话框DOM上设置my-initfn
属性:
<div title="复制到" my-initfn="initDlgCopyTo"> ... </div>
初始化函数示例:
function initDlgCopyTo()
{
var jdlg = $(this);
jdlg.on("beforeshow", onBeforeShow)
.on("validate", onValidate);
function onBeforeShow(ev, formMode, opt) {
}
function onValidate(ev, mode, oriData, newData) {
}
}
函数showObjDlg()会调用本函数显示对话框,称为对象型对话框,用于对象增删改查,它将以下操作集中在一起。
打开窗口时,会设置窗口模式(formMode):
(v7) 按住Shift双击一行、或点击查询/新增/修改按钮,会打开新的对话框,而不是复用默认对话框。
注意:
var formMode = jdlg.jdata().mode;
来获取当前对话框模式。初始数据与对话框中带name属性的对象相关联,显示对话框时,带name属性的DOM对象将使用数据opt.data自动赋值(对话框show事件中可修改),在点“确定”按钮提交时将改动的数据发到服务端(validate事件中可修改),详见
@see setFormData getFormData
操作对话框时会发出以下事件供回调:
create - 对话框创建(在my-initfn执行后)
beforeshow - 对话框显示前。常用来处理对话框显示参数opt或初始数据opt.data.
show - 显示对话框后。常用来设置字段值或样式,隐藏字段、初始化子表datagrid或隐藏子表列等。
validate - 用于提交前验证、补齐数据等。返回false可取消提交。(v5.2) 支持其中有异步操作.
retdata - 服务端返回结果时触发。用来根据服务器返回数据继续处理,如再次调用接口。
注意:
@key event-create (ev)
对话框初始化函数执行后触发。
@key event-beforeshow (ev, formMode, opt)
显示对话框前触发。
注意:每次调用showDlg()都会回调,可能这时对话框已经在显示。
@key event-show (ev, formMode, initData)
对话框显示后事件,用于设置DOM组件。
注意如果在beforeshow事件中设置DOM,对于带name属性的组件会在加载数据时值被覆盖回去,对它们在beforeshow中只能通过设置opt.data来指定缺省值。
@key event-validate (ev, formMode, initData, newData)
initData为初始数据,如果要验证或修改待提交数据,应直接检查form中相应DOM元素的值。如果需要增加待提交字段,可加到newData中去。示例:添加参数: newData.mystatus='CR';
(v5.2) validate事件支持返回Deferred对象支持异步操作.
示例: 在提交前先弹出对话框询问. 由于app_alert是异步对话框, 需要将一个Deferred对象放入ev.dfds数组, 告诉框架等待ev.dfds中的延迟对象都resolve后再继续执行.
jdlg.on("validate", onValidate);
function onValidate(ev, mode, oriData, newData)
{
var dfd = $.Deferred();
app_alert("确认?", "q", function () {
console.log("OK!");
dfd.resolve();
});
ev.dfds.push(dfd.promise());
}
常用于在validate中异步调用接口(比如上传文件).
@key event-retdata (ev, data, formMode)
form提交后事件,用于处理返回数据
以下事件将废弃:
@key event-initdata (ev, initData, formMode) 加载数据前触发。可修改要加载的数据initData, 用于为字段设置缺省值。将废弃,改用beforeshow事件。
@key event-loaddata (ev, initData, formMode) form加载数据后,一般用于将服务端数据转为界面显示数据。将废弃,改用show事件。
@key event-savedata (ev, formMode, initData) 对于设置了opt.url的窗口,将向后台提交数据,提交前将触发该事件,用于验证或补足数据(修正某个)将界面数据转为提交数据. 返回false或调用ev.preventDefault()可阻止form提交。将废弃,改用validate事件。
@see example-dialog-event 在对话框中使用事件
对话框上有name属性的组件在显示对话框时会自动清除(除非设置opt.reset=false或组件设置有noReset属性)。
@key .my-reset 标识在显示对话框时应清除
对于没有name属性(不与数据关联)的组件,可加该CSS类标识要求清除。
例如,想在forSet模式下添加显示内容, 而在forFind/forAdd模式下时清除内容这样的需求。
<div class="my-reset">...</div>
@key [noReset]
某些字段希望设置后一直保留,不要被清空,可以设置noReset属性,例如:
<input type="hidden" name="status" value="PA" noReset>
wui-dialog可以使用底层easyui-dialog/easyui-window/easyui-panel的选项或方法,参考:
示例:打开时最大化,关闭对话框时回调事件:
var dialogOpt = {
maximized: true,
onClose:function(){
console.log("close");
}
};
jfrm.on("beforeshow",function(ev, formMode, opt) {
opt.dialogOpt = dialogOpt;
})
(v6) 除了直接指定opt.dialogOpt,还可以直接通过data-options来设置,示例:
不显示折叠、最大化按钮,自定义类名"loginPanel"
<div id="dlgLogin" title="登录" data-options="cls:'loginPanel',collapsible:false,maximizable:false" style="width:350px;height:210px;">
...
</div>
(v5.3引入,v6修改) 该机制可用于自定义表(UDT, 对话框动态生成)。
如 dlgUDT_inst_A 与 dlgUDT_inst_B 会共用dlgUDT对话框模板,只要用"inst"分隔对话框模板文件和后缀名。
WUI.showDlg("dlgUDT_inst_A"); // 自动加载page/dlgUDT.html文件
若涉及重用其它模块中的页面或对话框,请参考 WUI.options.moduleExt
(v6) 该机制可用于为已有对话框动态追加字段(比如用于用户自定义字段UDF),或是只用纯JS而不用html来创建对话框。
示例:为对话框dlgReportCond追加两个输入字段。
var itemArr = [
// title, dom, hint?
{title: "状态", dom: '<select name="status" class="my-combobox" data-options="jdEnumMap:OrderStatusMap"></select>'},
{title: "订单号", dom: "<textarea name='param' rows=5></textarea>", hint: '每行一个订单号'}
];
WUI.showDlg("#dlgReportCond", {
meta: itemArr,
onOk: function (data) {
console.log(data)
},
// metaParent: "table:first" // 也可指定插入点父结点
});
通过指定opt.meta动态生成字段,这些字段默认放置于对话框中的第一个table下。
一般详情对话框DOM模型均为"
注意由于对话框有id,只会创建一次。之后再调用也不会再创建。如果希望能创建多的对话框互不影响,可以用"#dlgReportCond_inst_A"这种方式指定是用它的一个实例。
示例2:动态创建一个登录对话框
var jdlg = $('<form title="商户登录"><table></table></form>');
var meta = [
{title: "用户名", dom: '<input name="uname" class="easyui-validatebox" required>', hint: "字母开头或11位手机号"},
{title: "密码", dom: '<input type="password" name="pwd" class="easyui-validatebox" required>'}
];
WUI.showDlg(jdlg, {
meta: meta,
onOk: function (data) {
console.log(data); // 根据meta中每个带name项的输入框生成:{uname, pwd}
callSvr("login", function () {
app_show("登录成功");
WUI.closeDlg(jdlg);
}, data);
}
});
可以没有title/dom,只用hint用于逻辑说明,示例:
var meta = [
{hint:'查询<code>工单实际开工日期</code>在指定日期区间的所有受EC到影响的工件'},
{title: "序列号", dom: '<input name="code">'},
...
];
WUI.showDlg("#dlgReportCond", {
meta: meta,
onOk: function (data) { ... }
});
@see showDlgByMeta
@see showObjDlg
WUI.showDlg的简化版本,通过直接指定组件创建对话框。返回动态创建的jdlg。
示例:
var meta = [
// title, dom, hint?
{title: "接口名", dom: "<input name='ac' required>", hint: "示例: Ordr.query"},
{title: "参数", dom: "<textarea name='param' rows=5></textarea>", hint: '示例: {cond: {createTm: ">2020-1-1"}, res: "count(*) cnt", gres: "status"}'}
];
WUI.showDlgByMeta(meta, {
title: "通用查询",
modal: false,
onOk: function (data) {
app_alert(JSON.stringify(data));
}
});
@see showDlg 参考opt.meta选项
基于列表的批量处理逻辑:(v6支持基于查询条件的批量处理逻辑,见下面opt.queryParam)
对表格jtbl中的多选数据进行批量处理,先调用$obj.query(cond)
接口查询符合条件的数据条数(cond条件根据jtbl上的过滤条件及当前多选项自动得到),弹出确认框(opt.acName
可定制弹出信息),
确认后调用ac(cond)
接口对多选数据进行批量处理,处理完成后回调opt.onBatchDone
,并刷新jtbl表格。
其行为与框架内置的批量更新、批量删除相同。
@param ac 对象接口名, 如"Task.setIf"/"Task.delIf",也可以是函数接口,如"printSn"
@param opt.acName 操作名称, 如"更新"/"删除"/"打印"等, 一个动词. 用于拼接操作提示语句.
@param opt.data 调用支持批量的接口的POST参数
opt.data也可以是一个函数dataFn(batchCnt),参数batchCnt为当前批量操作的记录数(必定大于0)。
该函数返回data或一个Deferred对象(该对象适时应调用dfd.resolve(data)做批量操作)。dataFn返回false表示不做后续处理。
@return 如果返回false,表示当前非批量操作模式,或参数不正确无法操作。
为支持批量操作,服务端须支持以下接口:
// 对象obj的标准查询接口:
$obj.query($queryParam, res:"count(*) cnt") -> {cnt}
// 批量操作接口ac, 接受过滤查询条件(可通过$obj.query接口查询), 返回实际操作的数量.
$ac($queryParam)($data) -> $cnt
其中obj, ac, data(即POST参数)由本函数参数传入(data也可以是个函数, 返回POST参数), queryParam根据表格jtbl的选择行或过滤条件自动生成.
基于列表的批量操作,完成时会自动刷新表格, 无须手工刷新. 在列表上支持以下批量操作方式:
基于多选: 按Ctrl/Shift在表上选择多行,然后点操作按钮(如"删除"按钮, 更新时的"确定"按钮),批量操作选中行;生成过滤条件形式是{cond: "id IN (100,101)"}
,
基于表格当前过滤条件: 按住Ctrl键(进入批量操作模式)点操作按钮, 批量操作表格过滤条件下的所有行. 若无过滤条件, 自动以{cond: "id>0"}
做为过滤条件.
@param batchOpMode 定制批量操作行为, 比如是否需要按Ctrl激活批量模式, 未按Ctrl时如何处理未选行或单选行。
{cond:"id=100"}
简单来说, 默认模式对单个记录不处理, 返回false留给调用者处理; 模式2是对单个记录也按批量处理; 模式1是无须按Ctrl键就批量处理.
工件列表页(pageSn)中,点"打印条码"按钮, 打印选中的1行或多行的标签, 如果未选则报错. 如果按住Ctrl点击, 则按表格过滤条件批量打印所有行的标签.
显然, 这是一个batchOpMode=2的操作模式, 调用后端Sn.print
接口, 对一行或多行数据统一处理,在列表页pageSn.js中为操作按钮指定操作:
// function initPageSn
var btn1 = {text: "打印条码", iconCls:'icon-ok', handler: function () {
WUI.batchOp("Sn", "printSn", jtbl, {
acName: "打印",
batchOpMode: 2
});
}};
jtbl.datagrid({
...
toolbar: WUI.dg_toolbar(jtbl, jdlg, "export", btn1),
});
后端应实现接口printSn(cond)
, 实现示例:
function api_printSn() {
// 通过query接口查询操作对象内容.
$param = array_merge($_GET, ["res"=>"code", "fmt"=>"array" ]);
$arr = callSvcInt("Sn.query", $param);
addLog($rv);
foreach ($arr as $one) {
// 处理每个对象
}
// 应返回操作数量
return count($arr);
}
@param opt.queryParam
(v6) 基于查询条件的批量处理,即指定opt.queryParam,这时jtbl参数传null,与表格操作无关,只根据指定条件查询数量和批量操作。
注意jtbl和opt.queryParam必须指定其一。参见下面示例4。
在列表页上添加操作按钮,pageXXX.js:
// 点按钮打开批量上传对话框
var btn1 = {text: "批量设置", iconCls:'icon-add', handler: function () {
WUI.showDlg("#dlgUpload", {modal:false, jtbl: jtbl}); // 注意:为对话框传入特别参数jtbl即列表的jQuery对象,在batchOp函数中要使用它。
}};
jtbl.datagrid({
...
toolbar: WUI.dg_toolbar(jtbl, jdlg, "export", btn1),
});
对批量设置页面上调用接口,dlgUpload.js:
var jtbl;
jdlg.on("validate", onValidate)
on("beforeshow", onBeforeShow);
function onBeforeShow(ev, formMode, opt) {
jtbl = opt.jtbl; // 记录传入的参数
}
function onValidate(ev, mode, oriData, newData)
{
WUI.batchOp("Item", "batchSetItemPrice", jtbl, {
batchOpMode: 1, // 无须按Ctrl键, 一律当成批量操作
data: WUI.getFormData(jfrm),
onBatchDone: function () {
WUI.closeDlg(jdlg);
}
});
}
注意:对主表字段的设置都可在通用的详情对话框上解决(若要批量设置子表,也可通过在set/setIf接口里处理虚拟字段解决)。一般无须编写批量设置操作。
在安装任务列表页上,点"批量上传"按钮, 打开上传文件对话框(dlgUpload), 选择上传并点击"确定"按钮后, 先上传文件, 再将返回的附件编号批量更新到行记录上.
先选择操作模式batchOpMode=1, 点确定按钮时总是批量处理.
与示例2不同,上传文件是个异步操作,可为参数data传入一个返回Deferred对象(简称dfd)的函数(onGetData)用于生成POST参数,
以支持异步上传文件操作,在dfd.resolve(data)之后才会执行真正的批量操作.
pageTask.js:
// 点按钮打开批量上传对话框
var btn2 = {text: "批量上传附件", iconCls:'icon-add', handler: function () {
WUI.showDlg("#dlgUpload", {modal:false, jtbl: jtbl}); // 注意:为对话框传入特别参数jtbl即列表的jQuery对象,在batchOp函数中要使用它。
}};
dlgUpload.js:
var jtbl;
jdlg.on("validate", onValidate)
on("beforeshow", onBeforeShow);
function onBeforeShow(ev, formMode, opt) {
jtbl = opt.jtbl; // 记录传入的参数
}
function onValidate(ev, mode, oriData, newData)
{
WUI.batchOp("Task", "Task.setIf", jtbl, {
batchOpMode: 1, // 无须按Ctrl键, 一律当成批量操作
data: onGetData,
onBatchDone: function () {
WUI.closeDlg(jdlg);
}
});
}
// 一定batchCnt>0. 若batchCnt=0即没有操作数据时, 会报错结束, 不会回调该函数.
function onGetData(batchCnt)
{
var dfd = $.Deferred();
app_alert("批量上传附件到" + batchCnt + "行记录?", "q", function () {
var dfd1 = triggerAsync(jdlg.find(".wui-upload"), "submit"); // 异步上传文件,返回Deferred对象
dfd1.then(function () {
var data = WUI.getFormData(jfrm);
if ($.isEmptyObject(data)) {
app_alert("没有需要更新的内容。");
return false;
}
dfd.resolve(data);
});
});
return dfd.promise();
}
@see triggerAsync 异步事件调用
上面函数中处理异步调用链,不易理解,可以简单理解为:
if (confirm("确认操作?") == no)
return;
jupload.submit();
return getFormData(jfrm);
示例:在工单列表页,批量为工单中的所有工件打印跟踪码。
工单为Ordr对象,工件为Sn对象。注意:此时是操作Sn对象,而非当前Ordr对象,所以不传jtbl,而是直接传入查询条件.
WUI.batchOp("Sn", "printSn", null, {
acName: "打印",
queryParam: {cond: "orderId=80"},
data: {tplId: 10}
});
后端批量打印接口设计为:
printSn(cond, tplId) -> cnt
上例中,查询操作数量时将调用接口callSvr("Sn.query", {cond: "orderId=80", res: "count(*) cnt"})
,
在批量操作时调用接口callSvr("printSn", {cond: "orderId=80"}, $.noop, {tplId: 10})
。
取处于最上层的对话框。如果没有,返回jo.size() == 0
@param pageName 如未指定,表示当前页。
删除一个页面。一般用于开发过程,在修改外部逻辑页后,调用该函数删除页面。此后载入页面,可以看到更新的内容。
注意:对于内部逻辑页无意义。
@var g_data.doUpload
(v7) 为便于在reload页面时定制逻辑,在unload页面时会设置g_data.doUnload变量为true,可以页面pagedestory事件中检测处理,如:
jpage.on("pagedestroy", onPageDestroy);
function onPageDestroy() {
if (g_data.doUnload) {
console.log('!!! unload page');
}
}
meta页面(或二次开发的页面)支持在更新meta后,点“刷新页面”重新加载meta,就是使用此机制。
重新加载当前页面。一般用于开发过程,在修改外部逻辑页后,调用该函数可刷新页面。
@alias reloadDialog
删除指定对话框jdlg,如果不指定jdlg,则删除当前激活的对话框。一般用于开发过程,在修改外部对话框后,调用该函数清除以便此后再载入页面,可以看到更新的内容。
WUI.reloadDialog(jdlg);
WUI.reloadDialog();
WUI.reloadDialog(true); // 重置所有外部加载的对话框(v5.1)
注意:
权限检查回调,支持以下场景:
页面上的操作(按钮)
canDo(页面标题, 按钮标题);// 返回false则不显示该按钮
对话框上的操作
canDo(对话框标题, "对话框"); // 返回false表示对话框只读
canDo(对话框标题, 按钮标题); // 返回false则不显示该按钮
特别地:如果对话框或页面上有wui-readonly类,则额外优先用permSet2来检查:
canDo(对话框, 按钮标题, null, {只读:true}); // 返回false则不显示该按钮
topic可理解为数据对象(页面、对话框对应的数据模型),cmd可理解为操作(增加、修改、删除、只读等,常常是工具栏按钮)。
通过permSet2参数可指定额外权限。
判断逻辑示例:canDo("工艺", "修改")
如果指定有"工艺.修改",则返回true,或指定有"工艺.不可修改",则返回false;否则
如果指定有"修改",则返回true,或指定有"不可修改", 则返回 false; 否则
如果指定有"工艺.只读" 或 "只读",则返回false; 否则
如果指定有"工艺",则返回true,或指定有"不可工艺", 则返回 false; 否则返回默认值。
判断逻辑示例:canDo("工艺", null)
如果指定有"工艺",则返回true,或指定有"不可工艺", 则返回 false; 否则默认值
判断逻辑示例:canDo(null, "修改")
如果指定有"修改",则返回true,或指定有"不可修改", 则返回 false; 否则
如果指定有"只读",则返回false; 否则返回默认值。
默认值逻辑:
如果指定了默认值defaultVal,则返回defaultVal,否则
如果指定有"不可*",则默认值为false,否则返回 true
(注意:如果未指定"*"或"不可*",最终是允许)
特别地,对于菜单显示来说,顶级菜单的默认值指定是false,所以如果未指定""或"不可"则最终不显示;
而子菜单的默认值则是父菜单是否允许,不指定则默认与父菜单相同。
建议明确指定默认值,采用以下两种风格之一:
风格一:默认允许,再逐一排除
* 不可删除 不可导出 不可修改
风格二:默认限制,再逐一允许
不可* 工单管理
要限制菜单项的话,先指定"不可*",再加允许的菜单项,这样如果页面中链接其它页面或对话框,则默认是无权限的。
否则链接对象默认是可编辑的,存在漏洞。
TODO:通过设置 WUI.options.canDo(topic, cmd) 可扩展定制权限。
默认情况下,所有菜单不显示,其它操作均允许。
如果指定了"*"权限,则显示所有菜单。
如果指定了"不可XX"权限,则topic或cmd匹配XX则不允许。
@key wui-perm
示例:假设有菜单结构如下(不包含最高管理员专有的“系统设置”)
主数据管理
企业
用户
运营管理
活动
公告
积分商城
只显示“公告”菜单项:
公告
只显示“运营管理”菜单组:
运营管理
显示除了“运营管理”外的所有内容:
* 不可运营管理
其中*
表示显示所有菜单项。
显示所有内容(与管理员权限相同),但只能查看不可操作
* 只读
“只读”权限排除了“新增”、“修改”等操作。
特别地,“只读”权限也不允许“导出”操作(虽然导出是读操作,但一般要求较高权限),假如要允许导出公告,可以设置:
* 只读 公告.导出
显示“运营管理”,在列表页中不显示“删除”、“导出”按钮:
运营管理 不可删除 不可导出
显示“运营管理”,在列表页中,不显示“删除”、“导出”按钮,但“公告”中显示“删除”按钮:
运营管理 不可删除 不可导出 公告.删除
或等价于:
运营管理 不可导出 活动.不可删除 积分商城.不可删除
显示“运营管理”和“主数据管理”菜单组,但“主数据管理”下面内容均是只读的:
运营管理 主数据管理 企业.只读 用户.只读
假如在“活动”页面中链接了“用户”对话框(或“活动”页面上某操作按钮会打开“用户”页面),即使该角色只有“活动”权限而没有“用户”的权限,也能正常打开用户对话框或页面并修改用户。
这是一个潜在安全漏洞,在配置权限时应特别注意。
这样设计是因为用户权限主要是针对菜单项的,而且可以只指定到父级菜单(表示下面子菜单均可显示);这样就导致对未指定的权限,也无法判断是否可用(因为可能是菜单子项),目前处理为默认可用(可通过权限不可*
来指定默认不可用)。
以指定运营管理下所有功能为例,解决办法:
简单的处理方式是对于所有链接的内容,分别加入黑名单,如特别指定“不可用户”(或指定“用户.只读”),这时链接的对话框或页面将以只读模式打开(对话框不可设置,页面无操作按钮)。最终权限指定为运营管理 不可用户
不可*
,然后再精确指定该角色可用的所有权限(通常是列举所有子菜单项供打勾选择)。最终权限指定为不可* 活动 公告 积分商城
。这时应注意:根据对话框中jo部分内容查询,结果显示到表格(jtbl)中。
jo一般为对话框内的form或td,也可以为dialog自身。
查询时,取jo内部带name属性的字段作为查询条件。如果有多个字段,则生成AND条件。
如果查询条件为空,则不做查询;但如果指定jtbl的话,则强制查询。
jtbl未指定时,自动取对话框关联的表格;如果未关联,则不做查询。
doAppendFilter=true时,表示追加过滤条件。
@see .wui-notCond 指定独立查询条件
@param jdlg 可以是jquery对象,也可以是selector字符串或DOM对象,比如 "#dlgOrder". 注意:当对话框保存为单独模块时,jdlg=$("#dlgOrder") 一开始会为空数组,这时也可以调用该函数,且调用后jdlg会被修改为实际加载的对话框对象。
@param opt.id String. 对话框set模式(mode=FormMode.forSet)时可设置,表示从后端加载数据,set/del如缺省则从关联的opt.jtbl中取, add/find时不需要
@param opt.jtbl Datagrid. 指定对话框关联的列表(datagrid),用于从列表中取值,或最终自动刷新列表。 -- 如果dlg对应多个tbl, 必须每次打开都设置
@param opt.obj String. (v5.1) 对象对话框的对象名,如果未指定,则从my-obj属性获取。通过该参数可动态指定对象名。
@param opt.offline Boolean. (v5.1) 不与后台交互。
@param opt.readonly String. (v5.5) 指定对话框只读。即设置wui-readonly类。
showObjDlg底层通过showDlg实现,(v5.5)showObjDlg的opt会合并到showDlg的opt参数中,同时showDlg的opt.objParam将保留showObjDlg的原始opt。在每次打开对话框时,可以从beforeshow回调事件参数中以opt.objParam方式取出.
以下代码帮助你理解这几个参数的关系:
function showObjDlg(jdlg, mode, opt)
{
opt = $.extend({}, jdlg.objParam, opt);
var showDlgOpt = $.extend({}, opt, {
...
objParam: opt
});
showDlg(jdlg, showDlgOpt);
}
jdlg.on("beforeshow", function (ev, formMode, opt) {
// opt即是上述showDlgOpt
// opt.objParam为showObjDlg的原始opt,或由jdlg.objParam传入
});
@param opt.title String. (v5.1) 指定对话框标题。
@param opt.data Object. (v5.5) 为对话框指定初始数据,对话框中name属性匹配的控件会在beforeshow事件后且show事件前自动被赋值。
param opt.onOk Function(retData) (v6) 与showDlg的onOk参数一致。在提交数据后回调,参数为后端返回数据,比如add接口返回新对象的id。
注意:如果是forSet模式的对话框,即更新数据时,只有与原始数据不同的字段才会提交后端。
其它参数可参考showDlg函数的opt参数。
@key objParam 对象对话框的初始参数。
(v5.1)
此外,通过设置jdlg.objParam,具有和设置opt参数一样的功能,常在initPageXXX中使用,因为在page中不直接调用showObjDlg,无法直接传参数opt.
示例:
var jdlg = $("#dlgSupplier");
jdlg.objParam = {type: "C", obj: "Customer"};
showObjDlg(jdlg, FormMode.forSet, {id:101});
// 等价于 showObjDlg(jdlg, FormMode.forSet, {id:101, obj: "Customer", type: "C"});
在dialog的事件beforeshow(ev, formMode, opt)中,可以通过opt.objParam取出showObjDlg传入的所有参数opt。
(v5.3) 可在对象对话框的初始化函数中使用 initDlgXXX(opt),注意:非对象对话框初始化函数的opt参数与此不同。
@param opt.onCrud Function(). (v5.1) 对话框操作完成时回调。
一般用于点击表格上的增删改查工具按钮完成操作时插入逻辑。
在回调函数中this对象就是objParam,可通过this.mode获取操作类型。示例:
jdlg1.objParam = {
offline: true,
onCrud: function () {
if (this.mode == FormMode.forDel) {
// after delete row
}
// ... 重新计算金额
var rows = jtbl.datagrid("getData").rows, amount = 0;
$.each(rows, function(e) {
amount += e.price * e.qty;
})
frm.amount.value = amount.toFixed(2);
// ... 刷新关联的表格行
// opt.objParam.reloadRow();
}
};
jtbl.datagrid({
toolbar: WUI.dg_toolbar(jtbl, jdlg1), // 添加增删改查工具按钮,点击则调用showObjDlg,这时objParam生效。
onDblClickRow: WUI.dg_dblclick(jtbl, jdlg1),
...
});
在dialog逻辑中使用objParam:
function initDlgXXX() {
// ...
jdlg.on("beforeshow", onBeforeShow);
function onBeforeShow(ev, formMode, opt) {
var objParam = opt.objParam; // {id, mode, jtbl?, offline?...}
}
}
(v7) 通过jdlg.prop('objParam')可取到对象对话框的参数。
@param opt.reloadRow () 可用于刷新本对话框关联的表格行数据
事件参考:
@see showDlg
@param jdlg 可以是对话框的jquery对象,或selector如"#dlgOrder".
设置easyui-datagrid上toolbar上的按钮。缺省支持的按钮有r(refresh), f(find), a(add), s(set), d(del), 可通过以下设置方式修改:
// jtbl.jdata().toolbar 缺省值为 "rfasd"
jtbl.jdata().toolbar = "rfs"; // 没有a-添加,d-删除.
// (v5.5) toolbar也可以是数组, 如 ["r", "f", "s", "export"]; 空串或空数组表示没有按钮.
如果要添加自定义按钮,可通过button_lists一一传递.
示例:添加两个自定义按钮查询“今天订单”和“所有未完成订单”。
function getTodayOrders()
{
var queryParams = WUI.getQueryParam({comeTm: new Date().format("D")});
WUI.reload(jtbl, null, queryParams);
}
// 显示待服务/正在服务订单
function getTodoOrders()
{
var queryParams = {cond: "status=" + OrderStatus.Paid + " or status=" + OrderStatus.Started};
WUI.reload(jtbl, null, queryParams);
}
var btn1 = {text: "今天订单", iconCls:'icon-search', handler: getTodayOrders};
var btn2 = {text: "所有未完成", iconCls:'icon-search', handler: getTodoOrders};
// 默认显示当天订单
var queryParams = WUI.getQueryParam({comeTm: new Date().format("D")});
var dgOpt = {
url: WUI.makeUrl(["Ordr", "query"]),
queryParams: queryParams,
pageList: ...
pageSize: ...
// "-" 表示按钮之间加分隔符
toolbar: WUI.dg_toolbar(jtbl, jdlg, btn1, "-", btn2),
onDblClickRow: WUI.dg_dblclick(jtbl, jdlg)
};
jtbl.datagrid(dgOpt);
特别地,要添加导出数据到Excel文件的功能按钮,可以增加参数"export"作为按钮定义:
导入可以用"import", 快速查询可以用"qsearch" (这两个以扩展方式在jdcloud-wui-ext.js中定义),复制可以用"dup":
var dgOpt = {
...
toolbar: WUI.dg_toolbar(jtbl, jdlg, "import", "export", "dup", "-", btn1, btn2, "qsearch"),
}
@var toolbar-dup 复制
(v7) 复制一行或多行数据,调用后端{Obj}.dup(id)接口。
@see toolbar-qsearch 模糊查询
如果想自行定义导出行为参数,可以参考WUI.getExportHandler
@see getExportHandler 导出按钮设置
按钮的权限(即是否显示)取决于wui-perm和text属性。优先使用wui-perm。系统内置的常用的有:"新增", "修改", "删除", "导出"
下面例子,把“导入”特别设置为内置的权限“新增”,这样不仅不必在角色管理中设置,且设置了“只读”等权限也可自动隐藏它。
var btnImport = {text: "导入", "wui-perm": "新增", iconCls:'icon-ok', handler: function () {
DlgImport.show({obj: "Ordr"}, function () {
WUI.reload(jtbl);
});
}};
支持定义扩展,比如importOrdr:
// ctx = {jtbl, jp, jdlg} // jp是jpage或jdlg,为上一层容器。jdlg是表格关联的对话框,
// 注意jdlg在调用时可能尚未初始化,可以访问 jdlg.selector和jdlg.objParam等。
dg_toolbar.importOrdr = function (ctx) {
return {text: "导入", "wui-perm": "新增", iconCls:'icon-ok', handler: function () {
DlgImport.show({obj: "Ordr"}, function () {
WUI.reload(jtbl);
});
}}
};
这时就可以直接这样来指定导入按钮(便于全局重用):
WUI.dg_toolbar(jtbl, jdlg, ..., "importOrdr")
@key event-dg_toolbar (ev, jtbl, jdlg) 定制列表按钮事件
示例:为订单列表增加一个“关联商品”按钮
$(document).on("dg_toolbar", ".wui-page.pageOrder", pageOrder_onToolbar);
// 用于二次开发,更成熟的写法像这样: UiMeta.on("dg_toolbar", "售后工单", function (ev, buttons, jtbl, jdlg) { ... })
function pageOrder_onToolbar(ev, buttons, jtbl, jdlg) {
// var jpage = $(ev.target);
// console.log(jpage);
var btnLinkToItem = {text: "关联商品", iconCls: "icon-redo", handler: function () {
var row = WUI.getRow(jtbl);
if (row == null)
return;
var pageFilter = { cond: {id: row.itemId} };
PageUi.show("商品", "关联商品-订单"+row.id, pageFilter);
}};
buttons.push(btnLinkToItem);
}
@key example-setToolbarMenu 动态修改数据表上的工具栏菜单状态
在dg_toolbar事件中,如果想指定datagrid选项,比如要实现需求:
单击一行时,自动根据当前行状态,设置菜单项是否disabled(比如非CR状态时,“删除”菜单不可点击):
jtbl.prop("datagridOpt", {
onClickRow: function (rowIndex, rowData) {
var val = (rowData.status == 'CR');
WUI.setToolbarMenu(jtbl, {删除: val});
// 因为框架可能扩展过datagrid一些功能,加上这句比较稳妥
$.fn.datagrid.defaults.onClickRow(rowIndex, rowData);
}
});
@see setToolbarMenu
@key datagridOpt 二次开发中datagrid选项设置
设置数据表工具栏上的菜单项,禁用或启用:
WUI.setToolbarMenu(jtbl, {删除: false, 导出: true}); // 菜单项“删除”(即菜单标题)显示为禁用,“导出”显示为可用。
也可以通过回调函数(或lambda)操作该菜单项:
WUI.setToolbarMenu(jtbl, {
删除: ji => ji.linkbutton("disable"), // 禁用,等同于设置false
导出: ji => ji.linkbutton("enable"), // 启用,等同于设置true
// 导入: ji => ji.toggle(true)
导入: function (ji) {
var show = ...;
ji.toggle(Show); // 显示或隐藏
}
});
@param jdlg 可以是对话框的jquery对象,或selector如"#dlgOrder".
设置双击datagrid行的回调,功能是打开相应的dialog
页面中的a[href]字段会被框架特殊处理:
<a href="#pageHome">首页</a>
<a href="http://baidu.com">百度</a>
为数据表添加导出Excel菜单,如:
jtbl.datagrid({
url: WUI.makeUrl("User.query"),
toolbar: WUI.dg_toolbar(jtbl, jdlg, {text:'导出', iconCls:'icon-save', handler: WUI.getExportHandler(jtbl) }),
onDblClickRow: WUI.dg_dblclick(jtbl, jdlg)
});
默认是导出数据表中直接来自于服务端的字段,并应用表上的查询条件及排序。
也可以通过设置param参数手工指定,如:
handler: WUI.getExportHandler(jtbl, "User.query", {res: "id 编号, name 姓名, createTm 注册时间", orderby: "createTm DESC"})
注意:由于分页机制影响,会设置参数{pagesz: -1}以便在一页中返回所有数据,而实际一页能导出的最大数据条数取决于后端设置(默认1000,参考后端文档 AccessControl::$maxPageSz)。
会根据datagrid当前设置,自动为query接口添加res(输出字段), cond(查询条件), fname(导出文件名), orderby(排序条件)参数。
若是已有url,希望从datagrid获取cond, fname等参数,而不要覆盖res参数,可以这样做:
var url = WUI.makeUrl("PdiRecord.query", ...); // makeUrl生成的url具有params属性,为原始查询参数
var btnExport = {text:'导出', iconCls:'icon-save', handler: WUI.getExportHandler(jtbl, null, {res: url.params.res || null}) };
@see getQueryParamFromTable 获取datagrid的当前查询参数
@alias getParamFromTable
根据数据表当前设置,获取查询参数。
可能会设置{cond, orderby, res, fname}参数。但如果param中明确指定了比如res参数,则不会覆盖该参数。
res参数从列设置中获取,如"id 编号,name 姓名", 特别地,如果列对应字段以"_"结尾,不会加入res参数。
(v5.2)
如果表上有多选行,则导出条件为cond="t0.id IN (id1, id2)"这种形式。
fname自动根据当前页面的title以及datagrid当前的queryParam自动拼接生成。
如title是"检测记录报表", queryParam为"tm>='2020-1-1' and tm<='2020-7-1",则生成文件名fname="检测记录报表-2020-1-1-2020-7-1".
@see getExportHandler 导出Excel
取datagrid/treegrid数据表的字段映射表(不含以"_"结尾的特殊字段)。
取datagrid/treegrid数据表的列信息数组(不含以"_"结尾的特殊字段)。
如果指定一个处理方法fn(col), 则返回fn返回数据组装为列数组.
取datagrid关联信息. 返回字段标记?的须显式指定,如:
var dg = WUI.getDgInfo(jtbl); // {opt, url, ...}
var dg = WUI.getDgInfo(jtbl, {res: null}); // 多返回res字段
var data = jtbl[dg.datagrid]("getData"); // 相当于jtbl.datagrid(...), 但兼容treegrid调用。
datagrid: "datagrid"或"treegrid"
列表中显示附件(支持多个), 每个附件一个链接,点击后可下载该附件。(使用服务端att接口)
显示图片(支持多图), 每个图可以有预览, 点击后在新页面打开并依次显示所有的图片.(使用服务端pic接口)
@alias Formatter.pics
相当于picx({thumb:1, preview:3})。显示图片列表预览,点击链接显示图片列表。
@alias Formatter.pics1
相当于picx({thumb:1})。不显示图片列表预览,点击链接显示图片列表。
显示flag类的值,示例:
<th data-options="field:'clearFlag', sortable:true, formatter:Formatter.flag("已结算", "未结算"), styler:Formatter.enumStyler({1:'Disabled',0:'Warning'}, 'Warning')">结算状态</th>
注意flag字段建议用Formatter.enum和jdEnumMap,因为在导出表格时,只用flag的话,导出值还是0,1无法被转换,还不如定义一个Map来的更清晰。
@see datagrid.formatter
@see Formatter.enum
将字段的枚举值显示为描述信息。示例:
<th data-options="field:'status', jdEnumMap: OrderStatusMap, formatter: WUI.formatter.enum(OrderStatusMap)">状态</th>
如果状态值为"CR",则显示为"未付款". 全局变量OrderStatusMap在代码中定义如下(一般在web/app.js中定义)
var OrderStatusMap = {
CR: "未付款",
PA: "待服务"
}
常用的YesNoMap是预定义的0-否,1-是
映射,示例:
<th data-options="field:'clearFlag', sortable:true, jdEnumMap:YesNoMap, formatter:Formatter.enum(YesNoMap), styler:Formatter.enumStyler({1:'Disabled',0:'Warning'}, 'Warning')">已结算</th>
特别地,可以为null指定值:
<th data-options="field:'name', sortable:true, formatter:Formatter.enum({null:'(默认)'})">页面名</th>
@see datagrid.formatter
@see Formatter.enumStyler
为列表的单元格上色,示例:
<th data-options="field:'status', jdEnumMap: OrderStatusMap, formatter:Formatter.enum(OrderStatusMap), styler:Formatter.enumStyler({PA:'Warning', RE:'Disabled', CR:'#00ff00', null: 'Error'}), sortable:true">状态</th>
颜色可以直接用rgb表示如'#00ff00',或是颜色名如'red'等,最常用是用系统预定的几个常量'Warning'(黄), 'Error'(红), 'Info'(绿), 'Disabled'(灰).
缺省值可通过defaultColor传入。
如果是根据其它字段来判断,使用field选项指定字段,示例: 显示statusStr字段,但根据status字段值来显示颜色(默认'Info'颜色)
<th data-options="field:'statusStr', styler:Formatter.enumStyler({PA:'Warning'}, 'Info', 'status'), sortable:true">状态</th>
@see datagrid.styler
@see Formatter.enumFnStyler 比enumStyler更强大
为列表的单元格上色,示例:
<th data-options="field:'id', sortable:true, sorter:intSort, styler:Formatter.enumFnStyler({'v<10': 'Error', 'v>=10&&v<20': 'Warning'}, 'Info')">编号</th>
每个键是个表达式(其实是个函数),特殊变量v和row分别表示当前列值和当前行。缺省值可通过defaultColor传入。
@see Formatter.enumStyler
以进度条方式显示百分比,传入数值value为[0,1]间小数:
<th data-options="field:'progress', formatter: Formatter.progress">工单进度</th>
如果要定制颜色等样式,可以加个styler,如
<th data-options="field:'progress', formatter: Formatter.progress, styler: OrdrFormatter.progressStyler">工单进度</th>
通过为cell指定一个class来控制内部进度条样式:
progressStyler: function (value, row) {
var st = ... // 'info', 'error'
return {class: st}; // 直接返回字符串表示styler; 也可以返回 {class, style}这样
}
然后通过CSS类来改写进度条颜色:
<style>
.info .progressbar-value {
background-color: rgb(190, 247, 190) !important;
}
.error .progressbar-value {
background-color: rgb(253, 168, 172) !important;
}
</style>
常常应用定义Formatter变量来扩展WUI.formatter,如
var Formatter = {
userId: WUI.formatter.linkTo("userId", "#dlgUser"), // 显示用户名(value),点击后打开用户明细框
storeId: WUI.formatter.linkTo("storeId", "#dlgStore", true), // 显示"商户id-商户名", 点击后打开商户明细框
orderStatus: WUI.formatter.enum({CR: "新创建", CA: "已取消"}) // 将CR,CA这样的值转换为显示文字。
};
Formatter = $.extend(WUI.formatter, Formatter);
可用值:
enum({CR:"创建", CA:"取消"})
。enumList({emp:"员工", mgr:"经理"})
,则会将"emp"和"emp,mgr"分别解析为"员工", "员工,经理"flag("禁用","启用")
,也可以用enum,如enum({0:"启用",1:"禁用"})
在datagrid中使用:
<th data-options="field:'createTm', sortable:true, formatter:Formatter.dt">创建时间</th>
<th data-options="field:'amount', sortable:true, sorter: numberSort, formatter:Formatter.number">金额</th>
<th data-options="field:'userName', sortable:true, formatter:Formatter.linkTo('userId', '#dlgUser')">用户</th>
<th data-options="field:'status', sortable:true, jdEnumMap: OrderStatusMap, formatter: Formatter.orderStatus">状态</th>
<th data-options="field:'done', sortable:true, formatter: Formatter.flag()">已处理</th>
为datagrid扩展属性,用于在数据表底部显示统计值。
默认统计逻辑是当前页面内累加,如:
var dgOpt = {
url: WUI.makeUrl("Contract.query"),
showFooter: true, // 指定要显示底部统计
sumFields: ["amount", "invoiceAmount", "recvAmount"], // 指定哪些列加统计值
...
});
jtbl.datagrid(dgOpt);
如果想跨页统计,即显示所有页数据的统计(在当前查询条件下),需要为query调用添加statRes参数,如:
var dgOpt = {
url: WUI.makeUrl("Contract.query", {
// 指定返回amount, invoiceAmount两个统计列
statRes: "SUM(amount) amount, SUM(invoiceAmount) invoiceAmount",
}),
showFooter: true, // 指定要显示底部统计
sumFields: ["amount", "invoiceAmount", "recvAmount"], // 注意:此时amount,invoiceAmount由于在statRes中指定,是跨页数据统计,而recvAmount未在statRes中指定,则只统计当前显示页。
...
});
jtbl.datagrid(dgOpt);
扩展属性quickAutoSize。当行很多且列很多时,表格加载极慢。如果是简单表格(全部列都显示且自动大小,没有多行表头等),可以用这个属性优化。
在pageSimple中默认quickAutoSize为true。
var dgOpt = {
...
pageSize: 200, // 默认一页20,改为200后,默认性能将显著下降; 设置为500后,显示将超过10秒
pageList: [200, 500, 1000],
quickAutoSize: true // WUI对easyui-datagrid的扩展属性,用于大量列时提升性能. 参考: jquery.easyui.min.js
};
jtbl.datagrid(dgOpt);
其原因是easyui-datagrid的autoSizeColumn方法有性能问题。当一页行数很多时可尝试使用quickAutoSize选项。
CSS类, 可定义无数据提示的样式
表头左上角右键菜单功能。
扩展示例:
WUI.GridHeaderMenu.items.push('<div id="showObjLog">操作日志</div>');
WUI.GridHeaderMenu.showObjLog = function (jtbl) {
var row = WUI.getRow(jtbl);
if (!row)
return;
...
WUI.showPage("pageObjLog", "操作日志!", [null, param]);
};
显示高级查询对话框。在表头左上角右键菜单中有“高级查询”对话框。
身份证校验,示例:
var rv = WUI.checkIdCard("310115200809090927"); // false
var rv = WUI.checkIdCard("310115200809090928"); // true
为form中的组件加上该类,可以限制输入类型,如:
<input name="amount" class="easyui-validatebox" data-options="validType:'number'" required>
validType还支持:
validType:'equalTo[\'#txtNewPwd\']'
。它会找在同一个form下的指定组件与其比较。注意:
required选项定义是否允许为空。它可以写在data-options中如required:true;validType:'number'
,也可以单独以属性方式来写。
建议使用属性方式来写,这样在对话框上会自动添加"*"号标识。
checkEmpty:true
选项(这是jdcloud扩展easyui的选项)。其它自定义规则(或改写上面规则),可通过下列方式扩展:
$.extend($.fn.validatebox.defaults.rules, {
workday: {
validator: function(value) {
return value.match(/^[1-7,abc]+$/);
},
message: '格式例:"1,3a,5b"表示周一,周三上午,周五下午.'
}
});
@key EXT_LINK_BUTTON
datagrid options中的toolbar,我们使用代码指定方式,即
var btnFind = {text:'查询', iconCls:'icon-search', handler: function () {
showObjDlg(ctx.jdlg, FormMode.forFind, {jtbl: ctx.jtbl});
};
jtbl.datagrid({ ...
toolbar: WUI.dg_toolbar(jtbl, jdlg, ... btnFind)
});
缺点是它只能使用默认的linkbutton组件(在easyui里写死了)。
此处进行hack,增加class属性,让它支持splitbutton/menubutton,示例:
var jmneu = $('#mm').menu({
onClick: function (item) {
console.log(item.id);
}
});
var btnFind = {text:'查询', class: 'splitbutton', iconCls:'icon-search', handler: ..., menu: jmenu};
在wui-dialog上,对于form下直接放置的table,一般用于字段列表排列,框架对它添加类wui-form-table并自动对列设置百分比宽度,以自适应显示。
在非对话框上,也可手工添加此类来应用该功能。
标识应用当前是否正在与服务端交互。一般用于自动化测试。
应用参数。
URL参数会自动加入该对象,例如URL为 http://{server}/{app}/index.html?orderId=10&dscr=上门洗车
,则该对象有以下值:
g_args.orderId=10; // 注意:如果参数是个数值,则自动转为数值类型,不再是字符串。
g_args.dscr="上门洗车"; // 对字符串会自动进行URL解码。
框架会自动处理一些参数:
@see parseQuery URL参数通过该函数获取。
应用全局共享数据。
在登录时,会自动设置userInfo属性为个人信息。所以可以通过 g_data.userInfo==null 来判断是否已登录。
TODO: remove
设置应用的基本路径, 应以"/"结尾.
{appName=user, title="客户端", onShowLogin, pageHome="pageHome", pageFolder="page"}
用于模块扩展。有两个回调函数选项:
// 定制模块的页面路径
WUI.options.moduleExt.showPage = function (name) {
// name为showPage或showDlg函数调用时的页面/对话框;返回实际页面地址;示例:
var map = {
"pageOrdr__Mes.html": "page/mes/pageOrdr.html",
"pageOrdr__Mes.js": "page/mes/pageOrdr.js",
};
return map[name] || name;
}
// 定制模块的接口调用地址
WUI.options.moduleExt.callSvr = function (name) {
// name为callSvr调用的接口名,返回实际URL地址;示例:
var map = {
"Ordr__Mes.query" => "../../mes/api/Ordr.query",
"Ordr__Item.query" => "../../mes/api/Item.query"
}
return map[name] || name;
}
详细用法案例,可参考:筋斗云开发实例讲解 - 系统复用与微服务方案。
通讯加密。默认为1(开启),在后端接口返回当前是测试模式时,会改为0(关闭)。
也可以在chrome控制台中直接修改,如MUI.options.xparam=0。
URL参数加密后在URL参数中仅保留xp={加密值}, 即使没有URL参数也会强制加上xp=1以标识开启了加密;
注意URL加密值每次会不一样,这将破坏浏览器的缓存机制,作为特例,对于以att/pic结尾的接口(如"att", "debugPic"等接口,按惯例为返回文件或图片的接口,一般支持缓存),在makeUrl/callSvc中加密时每次URL保持一致。
设置WUI.options.xparam=2也会让每次加密后的URL相同。
POST参数也将整体加密,且在contentType中会加上";xparam=1"标记。
带缩略图的图片编号保存风格。
系统支持通过URL参数lang指定语言,如指定英文版本:http://myserver/myapp/web/store.html?lang=en
如果未指定lang参数,则根据html的lang属性来确定语言,如指定英文版:
<html lang="en">
默认为开发语言(lang="dev"),以中文为主。英文版下若想切换到开发语言,可以用http://myserver/myapp/web/store.html?lang=dev
g_args.lang中保存着实际使用的语言。
自带英文语言翻译文件lib/lang-en.js,当lang=en时加载它。可扩展它或以它为模板创建其它语言翻译文件。
语言翻译文件中,设置全局变量LANG,将开发语言翻译为其它语言。
系统会自动为菜单项、页面标题、列表表头标题、对话框标题等查找翻译。
其它DOM组件若想支持翻译,可手工添加CSS类lang,如:
<div><label class="lang"><input type="checkbox" value="mgr">最高管理员</label></div>
<a href="javascript:;" onclick="logout()" class="logout"><span class="lang"><i class="icon-exit"></i>退出系统</span></a>
或在代码中,使用WUI.enhanceLang(jo)来为DOM组件支持翻译,或直接用T(str)翻译字符串。
注意lang类或enhanceLang函数不能设置组件下子组件的文字,可先取到文字组件再设置如WUI.enhanceLang(jo.find(".title"))
。
@fn T (s, defVal?) 字符串翻译
T函数用于将开发语言翻译为当前使用的语言。
@key .lang DOM组件支持翻译
@fn enhanceLang (jo) DOM组件支持翻译
@param type 对话框类型: "i": info, 信息提示框; "e": error, 错误框; "w": warning, 警告框; "q"(与app_confirm一样): question, 确认框(会有"确定"和"取消"两个按钮); "p": prompt, 输入框
@param fn Function(text?) 回调函数,当点击确定按钮时调用。当type="p" (prompt)时参数text为用户输入的内容。
@param opt Object. 可选项。 timeoutInterval表示几秒后自动关闭对话框。defValue用于输入框(type=p)的缺省值.
使用jQuery easyui弹出提示对话框.
示例:
// 信息框,3s后自动点确定
app_alert("操作成功", function () {
WUI.showPage("pageGenStat");
}, {timeoutInterval: 3000});
// 错误框
app_alert("操作失败", "e");
// 确认框(confirm, 确定/取消)
// 仅当点击“确定”按钮才会进入回调函数。如果想处理取消事件,可使用opt.onCancel()回调
app_alert("立即付款?", "q", function () {
WUI.showPage("#pay");
});
// 提示输入框(prompt)
// 仅当点击“确定”按钮且输入值非空时才会进入回调函数。可使用opt.defValue指定缺省值
app_alert("输入要查询的名字:", "p", function (text) {
callSvr("Book.query", {cond: "name like '%" + text + "%'"});
});
@param fn Function(isOk). 用户点击确定或取消后的回调。
使用jQuery easyui弹出确认对话框.
使用jQuery easyui弹出对话框.
@param value 0-100间数值.
显示进度条对话框. 达到100%后自动关闭.
注意:同一时刻只能显示一个进度条。
生成一个链接的html代码,点击该链接可以打开指定对象的对话框。
示例:根据订单号,生成一个链接,点击链接打开订单详情对话框。
var orderId = 101;
var html = makeLinkTo("#dlgOrder", orderId, "订单" + orderId);
(v5.1)
示例:如果供应商(obj=Supplier)和客户(obj=Customer)共用一个对话框BizPartner,要显示一个id=101的客户,必须指定obj参数:
var html = makeLinkTo("#dlgBizPartner", 101, "客户-101", "Customer");
点击链接将调用
WUI.showObjDlg("#dlgBizPartner", FormMode.forSet, {id: 101, obj: "Customer"};
@param onHandleLogin Function(data). 调用后台login()成功后的回调函数(里面使用this为ajax options); 可以直接使用WUI.handleLogin
@param reuseCmd String. 当session存在时替代后台login()操作的API, 如"User.get", "Employee.get"等, 它们在已登录时返回与login相兼容的数据. 因为login操作比较重, 使用它们可减轻服务器压力.
@return Boolean. true=登录成功; false=登录失败.
该函数一般在页面加载完成后调用,如
function main()
{
$.extend(WUI.options, {
appName: APP_NAME,
title: APP_TITLE,
onShowLogin: showDlgLogin
});
WUI.tryAutoLogin(WUI.handleLogin, "Employee.get");
}
$(main);
该函数同步调用后端接口。如果要异步调用,请改用tryAutoLoginAsync函数,返回Deferred对象,resolve表示登录成功,reject表示登录失败。
@param userInfo 调用login/Employee.get等接口返回的用户信息数据。
处理login相关的操作, 如设置g_data.userInfo, 保存自动登录的token等等.
(v5.5) 如果URL中包含hash(即"#pageIssue"这样),且以"#page"开头,则登录后会自动打开同名的列表页(如"pageIssue"页面)。
@var dfdLogin
用于在登录完成状态下执行操作的Deferred/Promise对象。
示例:若未登录,则在登录后显示消息;若已登录则直接显示消息
WUI.dfdLogin.then(function () {
app_show("hello");
});
一般在进入页面时,同步地调用后端initClient接口,获取基本配置信息。
此后可通过g_data.initClient取这些配置。
若指定param参数(JS对象,如{token: 123}
),则作为POST参数调用initClient接口.
@param dontReload 如果非0, 则注销后不刷新页面.
注销当前登录, 成功后刷新页面(除非指定dontReload=1)
返回logout调用的deferred对象
关闭指定idx的标签页。如果未指定idx,则关闭当前标签页.
返回当前激活的逻辑页jpage,注意可能为空: jpage.size()==0。
标签页组件。为jquery-easyui的tabs插件,可以参考easyui文档调用相关命令进行操作,如关闭当前Tab:
var jtab = WUI.tabMain.tabs("getSelected");
var idx = WUI.tabMain.tabs("getTabIndex", jtab);
WUI.tabMain.tabs("close", idx);
注:要关闭当前Tab,可以直接用WUI.tabClose().
页面上方标题栏的右键菜单
扩展示例:
WUI.PageHeaderMenu.items.push('<div id="reloadUiMeta">刷新Addon</div>');
WUI.PageHeaderMenu.reloadUiMeta = function () {
UiMeta.reloadUiMeta();
}
@fn jQuery.fn.mycombobox (opt?)
@key .my-combobox 关联选择框
@var ListOptions 定义关联选择框的数据源
@param opt.force ?=false 如果为true, 则调用时强制重新初始化。默认只初始化一次。
关联选择框组件。
用法:先定义select组件:
<select name="empId" class="my-combobox" data-options="valueField: 'id', ..."></select>
通过函数参数opt或组件属性data-options可设置选项: { url, formatter(row), loadFilter(data), valueField, textField, jdEnumMap/jdEnumList }
初始化:
var jo = $(".my-combobox").mycombobox();
(v6) 使用参数opt直接指定参数,比使用data-options指定参数优先级高:
var jo = $(".my-combobox").mycombobox({
url: WUI.makeUrl("dbinst"),
// dbinst返回数组示例`["aa","bb"]`, 转为`[{name:"aa"}, {name:"bb"}]`才可正常显示. i
// 数组每1项须是个对象,前两个字段分别用作value和text,如果只有1个字段,则value和text均是这个字段;也可通过valueField和textField分别指定字段名。
loadFilter: function (data) {
return $.map(data, function (e) {
return {name: e};
});
}
});
注意:使用WUI.showPage或WUI.showDlg显示的逻辑页或对话框中如果有my-combobox组件,会自动初始化,无须再调用上述代码。
操作:
特性:
注意:
@param opt {url, jdEnumMap/jdEnumList, formatter, textField, valueField, loadFilter, urlParams, isLoaded_, url_, emptyText}
@param opt.url 动态加载使用的url,或一个返回URL的函数(这时会调用opt.url(opt.urlParams)得到实际URL,并保存在opt.url_中)
所以要取URL可以用
var opt = WUI.getOptions(jo);
url = opt.url_ || opt.url;
@param opt.emptyText 设置首个空行(值为null)对应的显示文字。
例如,想显示所有员工(Employee)的下拉列表,绑定员工编号字段(id),显示是员工姓名(name):
分派给 <select name="empId" class="my-combobox" data-options="url:WUI.makeUrl('Employee.query', {res:'id,name',pagesz:-1})"></select>
(v6)也可以用input来代替select,组件会自动处理.
注意查询默认是有分页的(页大小一般为20条),用参数{pagesz:-1}
使用服务器设置的最大的页大小(-1表示使用后端默认的pagesz,后端可使用maxPageSz参数调节)。
为了精确控制返回字段与显示格式,data-options可能更加复杂,习惯上定义一个ListOptions变量包含各种下拉框的数据获取方式,便于多个页面上共享,像这样:
<select name="empId" class="my-combobox" data-options="ListOptions.Emp()"></select>
var ListOptions = {
// ListOptions.Emp()
Emp: function () {
var opts = {
url: WUI.makeUrl('Employee.query', {
res: 'id,name,uname',
cond: 'storeId=' + g_data.userInfo.storeId,
pagesz:-1
}),
formatter: function (row) { return row.name + '(' + row.uname + ')'; }
};
return opts;
},
...
};
返回对象的前两个字段被当作值字段(valueField)和显示字段(textField),上例中分别是id和name字段。
如果返回对象只有一个字段,则valueField与textField相同,都是这个字段。
如果指定了formatter,则显示内容由它决定,textField此时无意义。
可以显式指定这两个字段,如:
var opts = {
valueField: "id",
textField: "name",
url: ...
}
示例2:下拉框绑定User.city字段,可选项为该列已有的值:
<select name="city" class="my-combobox" data-options="ListOptions.City()"></select>
var ListOptions = {
City: function () {
var opts = {
url: WUI.makeUrl('User.query', {
res: 'city',
cond: 'city IS NOT NULL'
distinct: 1,
pagesz:-1
})
};
return opts;
},
...
};
(v5.2) url还可以是一个函数。如果带一个参数,一般用于动态列表或级联列表。参考后面相关章节。
也支持通过key-value列表用jdEnumMap选项或jdEnumList选项来初始化下拉框,如:
订单状态: <select name="status" class="my-combobox" data-options="jdEnumMap:OrderStatusMap"></select>
或者:
订单状态: <select name="status" class="my-combobox" data-options="jdEnumList:'CR:未付款;CA:已取消'"></select>
或者:(key-value相同时, 只用';'间隔)
订单状态: <select name="status" class="my-combobox" data-options="jdEnumList:'未付款;已取消'"></select>
其中OrderStatusMap定义如下:
var OrderStatusMap = {
"CR": "未付款",
"CA": "已取消"
};
jdEnumList可以是键值对,如"CR:未付款;CA:已取消"(或写成"CR=未付款 CA=已取消"),与上面OrderStatusMap相同。
也可以简化为一组值,如"未付款;已取消"(也可写成"未付款 已取消"),它实际表示的是:{"未付款": "未付款", "已取消": "已取消"}
@see parseEnumList
另一个例子:在返回列表后,可通过loadFilter修改列表,例如添加或删除项:
<select name="brandId" class="my-combobox" data-options="ListOptions.Brand()" ></select>
JS代码ListOptions.Brand:
var ListOptions = {
...
// ListOptions.Brand()
Brand: function () {
var opts = {
url:WUI.makeUrl('queryBrand', {res: "id,name", pagesz:-1}),
loadFilter: function(data) {
data.unshift({id:'0', name:'所有品牌'});
return data;
}
};
return opts;
}
};
更简单地,这个需求还可以通过同时使用jdEnumMap和url来实现:
var ListOptions = {
...
// ListOptions.Brand()
Brand: function () {
var opts = {
url:WUI.makeUrl('queryBrand', {res: "id,name", pagesz:-1}),
jdEnumMap: {0: '所有品牌'}
};
return opts;
}
};
注意:jdEnumMap指定的固定选项会先出现。
(v6) url选项使用函数,之后调用loadOptions方法刷新
示例:在安装任务明细对话框(dlgTask)中,根据品牌(brand)过滤显示相应的门店列表(Store).
var ListOptions = {
// 带个cond参数,为query接口的查询条件参数,支持 "brand='xxx'" 或 {brand: 'xxx'}两种格式。
Store: function (cond) {
var opts = {
valueField: "id",
textField: "name",
url: WUI.makeUrl('Store.query', {
res: 'id,name',
cond: cond,
pagesz: -1
},
formatter: function (row) { return row.id + "-" + row.name; }
};
return opts;
}
};
在明细对话框HTML中不指定options而是代码中动态设置:
<form>
品牌 <input name="brand">
门店 <select name="storeId" class="my-combobox"></select>
</form>
对话框初始化函数:在显示对话框或修改品牌后刷新门店列表
function initDlgTask()
{
...
$(frm.brand).on("change", function () {
if (this.value) {
// 用setOption动态修改设置。注意trigger函数第二个参数须为数组,作为参数传递给用on监听该事件的处理函数。
$(frm.storeId).trigger("setOption", [ ListOptions.Store({brand: this.value}) ]);
}
});
function onShow() {
$(frm.brand).trigger("change");
}
}
(v6) 示例:根据type决定下拉列表用哪个,通过setOption来设置。
function onBeforeShow(ev, formMode, opt) {
var type = opt.objParam && opt.objParam.type || opt.data && opt.data.type;
var comboOpt = type == "入库" ? { jdEnumMap: MoveTypeMap } :
type == "出库"? { jdEnumMap: MoveTypeMap2 } : null
jdlg.find("[name=moveType]").trigger("setOption", [comboOpt]);
}
如果setOption给的参数是null,则忽略不处理。
(v5.2起, v6前) url选项使用函数,之后调用loadOptions方法刷新:
var ListOptions = {
Store: function () {
var opts = {
valueField: "id",
textField: "name",
// !!! url使用函数指定, 之后手工给参数调用loadOptions方法刷新 !!!
url: function (brand) {
return WUI.makeUrl('Store.query', {
res: 'id,name',
cond: "brand='" + brand + "'",
pagesz: -1
})
},
formatter: function (row) { return row.id + "-" + row.name; }
};
return opts;
}
};
在明细对话框HTML中:
<form>
品牌 <input name="brand">
门店 <select name="storeId" class="my-combobox" data-options="ListOptions.Store()"></select>
</form>
对话框初始化函数:在显示对话框或修改品牌后刷新门店列表
function initDlgTask()
{
...
$(frm.brand).on("change", function () {
if (this.value)
$(frm.storeId).trigger("loadOptions", this.value);
});
function onShow() {
$(frm.brand).trigger("change");
}
}
(v5.2引入, v6使用新方案) 与动态列表机制相同。
示例:缺陷类型(defectTypeId)与缺陷代码(defectId)二级关系:选一个缺陷类型,缺陷代码自动刷新为该类型下的代码。
在初始化时,如果字段有值,下拉框应分别正确显示。
在一级内容切换时,二级列表自动从后台查询获取。同时如果是已经获取过的,缓存可以生效不必反复获取。
双击仍支持刷新。
对话框上HTML如下:(defectId是用于提交的字段,所以用name属性;defectTypeId不用提交,所以用了id属性)
<select id="defectTypeId" class="my-combobox" data-options="ListOptions.DefectType()" style="width:45%"></select>
<select name="defectId" class="my-combobox" data-options="" style="width:45%"></select>
defectId上暂时不设置,之后传参动态设置。
其中,DefectType()与传统设置无区别,在Defect()函数中,应设置url为一个带参函数:
var ListOptions = {
DefectType: function () {
var opts = {
valueField: "id",
textField: "code",
url: WUI.makeUrl('Defect.query', {
res: 'id,code,name',
cond: 'typeId is null',
pagesz: -1
}),
formatter: function (row) { return row.code + "-" + row.name; }
};
return opts;
},
// ListOptions.Defect
Defect: function (typeId) {
var opts = {
valueField: "id",
textField: "code",
url: WUI.makeUrl('Defect.query', {
res: 'id,code,name',
cond: "typeId=" + typeId,
pagesz: -1
},
formatter: function (row) { return row.code + "-" + row.name; }
};
return opts;
}
}
在对话框上设置关联动作,调用setOption事件:
$(frm.defectTypeId).on("change", function () {
var typeId = $(this).val();
if (typeId)
$(frm.defectId).trigger("setOption", [ ListOptions.Defect(typeId) ]);
});
注意jQuery的trigger发起事件函数第二个参数须为数组。
对话框加载时,手工设置defectTypeId的值:
function onShow() {
$(frm.defectTypeId).val(defectTypeId).trigger("change");
}
假如某mycombobox组件查询Employee对象列表。当在Employee页面新建、修改、删除对象后,回到组件的页面,点击组件时将自动刷新列表。
(wui-combogrid也具有类似功能)
(v6) my-combobox继承easyui-validatebox,通过添加requried属性来指定必填项,显示时会在字段后自动加红色星号:
<select name="status" class="my-combobox" data-options="jdEnumMap:OrderStatusMap" required></select>
默认的select组件不支持设置必填选项(required属性,即使加easyui-validatebox类也不行;easyui-combobox可支持验证,但它默认并未限制选项必须在列表中),加上my-combobox类即可解决:
<select name="status" class="my-combobox" required>
<option value="CR">新创建</option>
<option value="RE">已完成</option>
</select>
旧用法(不建议使用):
<select name="status" class="my-combobox easyui-validatebox" data-options="jdEnumMap:OrderStatusMap, required:true"></select>
将字符串转换为键值对,专用于jdEnumList属性,常见风格示例:
a:1;b:2
a=1 b=2
都对应:
{a:"1", b:"2"}
若值中需要转义可以用URL编码,值中有空格一般用"+"替代(也可用"%20"),示例:
COUNT+总数 SUM+总和
它对应:
{"COUNT 总数": "COUNT 总数", "SUM 总和": "SUM 总和"}。
@see parseKvList
框架扩展功能或常用函数.
对话框上的上传文件组件。
用于在对象详情对话框中,展示关联图片字段。图片可以为单张或多张。
除显示图片外,也可以展示其它用户上传的文件,如视频、文本等。
预览图样式在style.css中由 .wui-upload img
定义。
点击一张预览图,如果有jqPhotoSwipe插件,则全屏显示图片且可左右切换;否则在新窗口打开大图。
示例:只需要标注wui-upload类及指定data-options,即可实现图片预览、上传等操作。
<table>
<tr>
<td>上传多图</td>
<td class="wui-upload">
<input name="pics">
</td>
</tr>
<tr>
<td>上传单图</td>
<td class="wui-upload" data-options="multiple:false">
<input name="picId">
</td>
</tr>
<tr>
<td>上传附件</td>
<td class="wui-upload" data-options="pic:false,fname:'attName'"> <!-- v5.5: 显示使用虚拟字段attName,须后端提供,也可以用 fname=1将显示内容保存到atts字段 -->
<input name="atts">
</td>
</tr>
<tr>
<td>上传单个附件</td>
<td class="wui-upload" data-options="multiple:false,pic:false,fname:'attName'">
<input name="attId">
</td>
</tr>
</table>
组件会自动添加预览区及文件选择框等,完整的DOM大体如下:
<div class="wui-upload">
<input name="atts">
<div class="imgs"></div>
<input type="file" multiple>
<p class="hint"><a href="javascript:;" class="btnEditAtts">编辑</a></p>
</div>
其中imgs为预览区,内部DOM结构为 .imgs - a标签 - img或p标签
。
在上传图片或文件后,会在imgs下创建<a>
标签,对于图片a标签里面是img标签,否则是p标签。
a标签上数据如下:
在a下的img标签上,有以下数据:
@param opt.multiple=true 设置false限制只能选一张图。
@param opt.nothumb=false 设置为true表示不生成缩略图,且不做压缩(除非指定maxSize参数,这时不生成缩略图,但仍然压缩)。
<td class="wui-upload" data-options="nothumb:true">...</td>
@param opt.pic=true 设置为false,用于上传视频或其它非图片文件
如果为false, 在.imgs区域内显示文件名链接而非图片。
@param opt.fname 是否显示文件名。默认为0。目前不用于图片,只用于附件(即pic=false时有效)。
(v5.5)
示例:用attId字段保存单个附件,并显示附件名
由于attId是数值字段,不可存额外字符串信息,所以不能设置fname=1。
这时在后端做一个虚拟字段如attName,格式为"{attId}:{fileName}":
protected $vcolDefs = [
[
"res" => ["concat(att.id,':',att.orgName) attName"],
"join" => "LEFT JOIN Attachment att ON att.id=t0.attId",
"default" => true
]
...
];
列表页中展示使用虚拟字段attName而非attId:
<th data-options="field:'attName', formatter:Formatter.atts">模板文件</th>
在详情对话框中指定fname为虚拟字段"att":
<td>模板文件</td>
<td class="wui-upload" data-options="multiple:false,pic:false,fname:'attName'">
<input name="attId">
</td>
示例:用atts存多个附件。
这时,可设置fname=1,即把文件名也存到atts字段。
也可以设置fname='attName'(虚拟字段),其格式为"{attId}:{filename},{attId2}:{filename2}",后端实现参考如下(使用find_in_set):
[
"res" => ["(SELECT group_concat(concat(att.id,':',att.orgName)) FROM Attachment att WHERE find_in_set(id, t0.atts)) attName"],
"default" => true
]
@param opt.manual=false 是否自动上传提交
默认无须代码即可自动上传文件。如果想要手工操控,可以触发submit事件,
示例:在dialog的validate事件中先确认提示再上传,而不是直接上传:
HTML:
<div class="wui-upload" data-options="manual:true">...</div>
JS:
jdlg.on("validate", onValidate);
function onValidate(ev)
{
var dfd = $.Deferred();
app_alert("确认上传?", "q", function () {
var dfd1 = WUI.triggerAsync(jdlg.find(".wui-upload"), "submit");
dfd1.then(doNext);
});
// dialog的validate方法支持异步,故设置ev.dfds数组来告知调用者等异步操作结束再继续
ev.dfds.push(dfd.promise());
function doNext() {
dfd.resolve();
}
}
@param opt.menu 设置右键菜单
在预览区右键单击会出现菜单,默认有“删除”菜单。
示例:商品可以上传多张照片,其中选择一张作为商品头像。
我们在右键中添加一个“设置为缺省头像”菜单,点击后将该图片圈出,将其图片编号保存到字段中。
数据结构为:
@Item: id, picId, pics
上面表Item中,picId为头像的图片ID,pics为逗号分隔的图片列表。
HTML: 在data-options中指定菜单的ID和显示文字。缺省头像将添加"active"类:
<style>
.wui-upload img.active {
border: 5px solid red;
}
</style>
<tr>
<td>门店照片</td>
<td class="wui-upload" data-options="menu:{mnuSetDefault:'设置为缺省头像'}">
<input name="pics">
<input type="input" style="display:none" name="picId">
</td>
</tr>
在右键点击菜单时,wui-upload组件会触发onMenu事件,其中参数item为当前预览项(.imgs区域下的a标签,在它下面才是img或p标签)
// 在initDlgXXX中:
jdlg.on("menu", onMenu);
function onMenu(ev, menuId, item)
{
if (menuId == "mnuSetDefault") {
var ja = $(item);
if (ja.attr("attId")) {
ja.closest(".wui-upload").find("img").removeClass("active");
var jimg = ja.find("img");
jimg.addClass("active");
frm = jdlg.find("form")[0];
frm.picId.value = jimg.attr("picId");
}
}
}
// 高亮显示选中的头像picId。
// 注意:要用jdlg而不是jfrm的show事件。否则wui-upload尚未初始化完成
jdlg.on("show", function (ev, formMode, initData) {
if (initData && initData.picId) {
jdlg.find(".wui-upload img[picId=" + initData.picId + "]").addClass("active");
}
});
@param opt.accept 指定可上传的文件类型
示例:上传单个音频文件,如m4a, mp3等格式。
<td>文件</td>
<td class="wui-upload" data-options="multiple:false,pic:false,accept:'audio/*'">
<input name="attId">
<p class="hint">要求格式m4a,mp3,wav; 采样率为16000</p>
</td>
@param opt.maxSize ?=1280 指定压缩后图片的最大长或宽
@param opt.quality ?=0.8 指定压缩质量, 一般不用修改.
示例:默认1280像素不够, 增加到2000像素:
<tr>
<td>图片</td>
<td class="wui-upload" data-options="maxSize:2000">
<input name="pics">
</td>
</tr>
示例:在dialog的beforeshow事件回调中,根据情况设置upload组件的选项,如是否压缩图片:
// function initDlgXXX
//
jdlg.on("beforeshow", onBeforeShow);
function onBeforeShow(ev, formMode, opt)
{
var objParam = opt.objParam;
var jo = jpage.find(".picId");
// 获取和动态设置选项:
var uploadOpt = WUI.getOptions(jo);
uploadOpt.nothumb = (objParam.type === "A");
}
opt.onGetData: Function(ret) 处理接口返回结果ret。
示例:调用upload1(resource, version) -> [{id, ..., url}]
接口。
该接口扩展了默认的upload接口,需要传入resource等参数,返回的url字段需要设置到form相应字段上。
// function initDlgVersion
var jo = jdlg.find(".uploadFile");
var uploadOpt = WUI.getOptions(jo);
uploadOpt.onGetQueryParam = function () {
return {
ac: "upload1",
resource: $(frm.resourceId).val(),
version: $(frm.name).val()
}
}
uploadOpt.onGetData = function (ret) {
var resUrl = ret[0].url;
$(frm.url).val(resUrl);
}
jdcloud传统风格是在上传图片后(upload接口),存储返回的小图编号(thumbId), 通过att(thumbId)访问大图。
WUI.options.useNewThumb=1时为新风格,即存储upload接口返回的大图编号(id),通过att(id, thumb=1)访问小图。
用于在对象详情对话框中,以一组复选框(checkbox)来对应一个逗号分隔式列表的字段。
例如对象Employee中有一个“权限列表”字段perms定义如下:
perms:: List(perm)。权限列表,可用值为: item-上架商户管理权限, emp-普通员工权限, mgr-经理权限。
现在以一组checkbox来在表达perms字段,希望字段中有几项就将相应的checkbox选中,例如值"emp,mgr"表示同时具有emp与mgr权限,显示时应选中这两项。
定义HTML如下:
<tr>
<td>权限</td>
<td class="wui-checkList">
<input type="hidden" name="perms">
<label><input type="checkbox" value="emp" checked>员工(默认)</label><br>
<label><input type="checkbox" value="item">上架商品管理</label><br>
<label><input type="checkbox" value="mgr">经理</label><br>
</td>
</tr>
wui-checkList块中包含一个hidden对象和一组checkbox. hidden对象的name设置为字段名, 每个checkbox的value字段设置为每一项的内部名字。
标签字段(labels)是空白分隔的一组词,每个词是一个标签(label)。
一般会在字段下方将常用标签列出供用户选择,点一下标签则追加到文本框中,再点一下删除该标签项。多个标签以空格分隔。
(v6) 可以设置选项opt.simple=true,这时如果单击一个标签,则直接填写(而不是追加)到文本框中,类似于单选。
示例1:列出各种类型,点一下类型标签就追加到对话框,再点一下会删除该项。
<tr>
<td>标签</td>
<td class="wui-labels">
<input name="label" >
<p class="hint">企业类型:<span class="labels" dfd="DlgStoreVar.onGetLabel()"></span></p>
<p class="hint">行业标签:<span class="labels">IT 金融 工业</span></p>
<p class="hint">位置标签:<span class="labels">一期 二期 三期 四期</span></p>
</td>
</tr>
具有CSS类"labels"的组件,内容以空白分隔的多个标签,如`IT 金融 工业"。
wui-labelTarget
的输入框)。dfd
属性是一个Deferred对象。labelMark
类(label太常用,没有用它以免冲突),默认已设置样式,可以为它自定义样式。本例中dfd属性"DlgStoreVar.onGetLabel()"是个函数调用,它返回的是一个Deferred对象,这样可以实现异步获取再设置标签列表,示例:(在dlgStore.js中)
function initDlgStore() {
// 按惯例,只被Xx页面使用的变量可放在dlgXx.js中的DlgXxVar中,而会被其它页面调用的变量则放在全局应用store.js中的DlgXx中(称为页面接口)。
window.DlgStoreVar = {
onGetLabel: function () {
var dfd = $.Deferred();
callSvr("Conf.query", {cond: "name='企业分类'", fmt: "one", res: "value"}, function (data) {
DlgStoreVar.dfdLabel.resolve(data.value);
})
return dfd;
}
};
...
}
上面的DlgStoreVar.onGetLabel也可以直接返回标签列表,如:
window.DlgStoreVar = {
onGetLabel: function () {
return "标签1 标签2";
}
}
在第一次打开页面时会调用onGetLabel函数,设置标签列表。
(v6) 如果想每次打开页面都调用,只需要稍加修改,直接为dfd指定函数DlgStoreVar.onGetLabel
(而不是函数调用后的返回值DlgStoreVar.onGetLabel()
):
<p class="hint">企业类型:<span class="labels" dfd="DlgStoreVar.onGetLabel"></span></p>
(v6) 示例1-1:可以为不良品(Fault)设置标签,可通过系统配置项"Cinf.faultLabel"来配置可用的标签,即后端提供Cinf.getValue({name: "faultLabel"})
接口获取可用标签列表(以空格分隔)
在dlgFault.html中设置标签字段:
<tr>
<td>标签</td>
<td class="wui-labels">
<input name="label">
<p class="hint"><span class="labels" dfd="DlgFaultVar.onGetLabel"></span></p>
</td>
</tr>
注意dfd属性直接指定为函数DlgFaultVar.onGetLabel
,也就是每次打开页面都会执行它:这意味着当修改系统配置项faultLabel
后,无须刷新系统,重新打开不良品对话框就可以点选新标签了。
在dlgFault.js中定义动态获取标签:
window.DlgFaultVar = {
onGetLabel: function () {
return callSvr("Cinf.getValue", {name: "faultLabel"});
}
}
注意:函数里比上例简化了很多,因为callSvr返回是就是Defered对象,而且由于Cinf.getValue
接口刚好返回的数据格式就是"标签1 标签2"字符串,所以不用再新建一个Defered对象在处理格式转换后做resolve了。
示例2:设置配置项,并配以说明和示例
<tr>
<td>配置项名称</td>
<td class="wui-labels">
<input name="name" class="easyui-validatebox" required>
<div class="hint">可选项和示例值:
<p class="easyui-tooltip" title="在移动端提交缺陷问题时,可从下拉列表中选择问题类型,就是在此处配置的,多个值以英文分号分隔。"><span class="labels">常见问题</span> 内饰;轮胎</p>
<p class="easyui-tooltip" title="多个值以英文空格分隔"><span class="labels">集市品类</span> 办公用品 书籍 卡票券</p>
<p class="easyui-tooltip" title="格式为`姓名,电话`"><span class="labels">会议室预订联系人</span> Candy,13917091068</p>
</div>
</td>
</tr>
示例3:(v6) 推荐项为单选(simple模式),且标签可以显示(text)与实际值(value)不同。
<tr>
<td>URL地址</td>
<td class="wui-labels" data-options="simple:true">
<input name="url" value="http://oliveche.com/mes/">
<p class="hint">
<span class="labels">生产环境|http://192.168.10.23/mes/ 测试|http://oliveche.com/mes/</span>
</p>
</td>
</tr>
管理端功能菜单,以"menu"作为id:
<div id="menu">
<div class="menu-expand-group">
<a class="expanded"><span><i class="fa fa-pencil-square-o"></i>主数据管理</span></a>
<div class="menu-expandable">
<a href="#pageCustomer">客户管理</a>
<a href="#pageStore">门店管理</a>
<a href="#pageVendor">供应商管理</a>
</div>
</div>
<!-- 单独的菜单项示例 -->
<a href="javascript:;" onclick="WUI.showDlg('#dlgImport',{modal:false})"><span><i class="fa fa-pencil-square-o"></i>批量导入</span></a>
<a href="javascript:;" onclick="showDlgChpwd()"><span><i class="fa fa-user-times"></i>修改密码</span></a>
</div>
菜单组由menu-expand-group标识,第一个a为菜单组标题,可加"expanded"类使其默认展开。
图标使用font awesome库,由<i class="fa fa-xxx"></i>
指定,图标查询可参考 http://www.fontawesome.com.cn/faicons/ 或 https://fontawesome.com/icons
fn(e) -> e
用于调整返回对象结构。返回主菜单的树型数组:[ {name, perm, @children} ],可通过opt.filter来修改返回结构。
可以用tree组件展示,如:
// 转为 [{text, children}] 结构
var tree = WUI.getMenuTree({
filter: function (e) {
var text = e.name;
if (e.name != e.perm)
text += "(" + e.perm + ")";
return { text: text, children: e.children }
}
});
jo.tree({
data: tree,
checkbox: true
});
可以用treegrid组件展示,如:
var tree = WUI.getMenuTree();
jtbl.treegrid({
data: tree,
idField: "name",
treeField: "name",
checkbox: true,
columns:[[
{title:'菜单项',field:'name',formatter: function (v, row) {
if (v == row.perm)
return v;
return v + "(" + row.perm + ")";
}},
]]
});
可搜索的下拉列表。
示例:在dialog上,填写门店字段(填写Id,显示名字),从门店列表中选择一个门店。
<form my-obj="Task" title="安装任务" wui-script="dlgTask.js" my-initfn="initDlgTask">
<tr>
<td>门店</td>
<td>
<input class="wui-combogrid" name="storeId" data-options="ListOptions.StoreGrid">
</td>
</tr>
</form>
选项定义如下:
ListOptions.StoreGrid = {
jd_vField: "storeName",
jd_dlgForAdd: "#dlgStore",
// jd_qsearch: "name", // 模糊匹配字段
panelWidth: 450,
width: '95%',
textField: "name",
columns: [[
{field:'id',title:'编号',width:80},
{field:'name',title:'名称',width:120}
]],
url: WUI.makeUrl('Store.query', {
res: 'id,name',
})
};
属性请参考easyui-combogrid相关属性。wui-combogrid扩展属性如下:
在wui-combogrid的输入框中输入后会自动进行模糊查询,展示匹配的数据列表;
选项jd_qsearch用于直接指定模糊查询的字段列表;如果不指定的话,则须由后端指定,否则无法模糊查询。
未指定jd_qsearch选项时,调用接口示例为callSvr("Store.query", {res:"id,name", q="张三"})
.
须由筋斗云后端指定qsearch字段,示例如下:(详细可参考后端手册, 搜索qsearch):
class AC2_Xxx
{
protected function onQuery() {
$this->qsearch("name,phone", param("q"));
}
}
指定jd_qsearch时,调用接口示例为: callSvr("Store.query", {res:"id,name", qserach:"name:张三"})
.
在选择一行并返回时,它会触发choose事件:
// 注意要取combogrid对象要用comboname! 而不是用 "[name=storeId]"(原始的input已经变成一个hidden组件,只存储值)
var jo = jdlg.find("[comboname=storeId]");
jo.on("choose", function (ev, row) {
console.log('choose row: ', row);
...
});
jo是easyui-combogrid,可以调用它的相应方法,如禁用和启用它:
jo.combogrid("disable");
jo.combogrid("enable");
特别逻辑:
示例2:简单的情况,选择时只用名字,不用id。
// var ListOptions 定义中:
// 只用name不用id
CateGrid: {
jd_vField: "category",
jd_showId: false,
// jd_qsearch: "name,fatherName",
panelWidth: 450,
width: '95%',
idField: "name",
textField: "name",
columns: [[
{field:'name',title:'类别',width:120},
{field:'fatherName',title:'父类别',width:120},
]],
url: WUI.makeUrl('Category.query', {
res: 'id,name,fatherName'
})
}
在dialog中:
<input class="wui-combogrid" name="category" data-options="ListOptions.CateGrid">
设置方法:
(v5.5) 与my-combobox类似,组件会在其它页面更新对象后自动刷新列表。
外部想要刷新组件列表,可以触发markRefresh事件:
jo = jdlg.find("[comboname=xxxId]");
jo.trigger("markRefresh", obj); // obj是可选的,若指定则仅当obj匹配组件对应obj时才会刷新。
(v6) 与my-combobox方法相同,重新设置选项:
jo = jdlg.find("[comboname=xxxId]");
jo.trigger("setOption", opt);
动态修改组件选项示例:
// 比如每打开对话框时,根据type动态显示列表。可在dialog的beforeShow事件中编码:
var cond = type == 'A'? xx: yy...;
// 取出选项,动态修改url
var jo = jdlg.find("[comboname=categoryId]");
jo.trigger("setOption", ListOptions.CategoryGrid(cond));
设置值:
jdlg.gn("userId").val(10);
jdlg.gn("userId").val([10, "用户1"]); // 如果传数组,则同时设置值和显示文本
设置状态:
jdlg.gn("userId").visible(true)
.readonly(true)
.disabled(true);
注意gn函数支持链式调用。
重置选项:
jdlg.gn("userId").setOption(ListOptions.UserGrid({phone: "~*9204"}));
当设置选项treeField后,显示为树表,示例:
// var ListOptions 定义中:
CateGrid: {
treeField: "name", // 在name字段上折叠
...
url: WUI.makeUrl('Category.query', {
res: 'id,name,fatherName',
pagesz: -1 // 树表应全部显示
})
}
设置选项multiple: true
后支持多选,combogrid和combotreegrid均支持。
多个选项将以逗号分隔的id值如来保存, 如2,3,4
; 此处id值由idField定义,不一定是数值。
可以通过FormItem的getValue/setValue方法(或gn的val方法)来存取值,值为逗号分隔的字符串(而不是数组)。
TODO:不支持jd_showId/jd_vField选项。因而在尚未加载过数据时(比如初次打开对话框时),会显示为值数字(id)而不是文本内容。
支持基于easyui-combo的表单扩展控件,如 combogrid/datebox/datetimebox等, 在使用WUI.getFormData时可以获取到控件值.
示例:可以在对话框或页面上使用日期/日期时间选择控件:
<input type="text" name="startDt" class="easyui-datebox">
<input type="text" name="startTm" data-options="showSeconds:false" class="easyui-datetimebox">
form提交时能够正确获取它们的值:
var d = WUI.getFormData(jfrm); // {startDt: "2019-10-10", startTm: "2019-10-10 10:10"}
而且在查询模式下,日期等字段也不受格式限制,可输入诸如"2019-10", "2019-1-1~2019-7-1"这样的表达式。
显示或隐藏datagrid的列。示例:
WUI.toggleCol(jtbl, 'status', false);
如果列不存在将出错。
根据type隐藏datagrid列表或明细页form中的项。示例:
function toggleItemFields(jo, type)
{
WUI.toggleFields(jo, {
type: !type,
status: !type || type!="公告",
tm: !type || type=="活动" || type=="卡券" || type=="停车券",
price: !type || type=="集市",
qty: !type || type=="卡券"
});
}
列表中调用,控制列显示:pageItem.js
var type = objParam & objParam.type; // 假设objParam是initPageXX函数的传入参数。
toggleItemFields(jtbl, type);
明细页中调用,控制字段显示:dlgItem.js
var type = objParam && objParam.type; // objParam = 对话框beforeshow事件中的opt.objParam
toggleItemFields(jfrm, type);
@key .wui-field
在隐藏字段时,默认是找到字段所在的行(tr)或标识wui-field
类的元素控制其显示或隐藏。示例:
<tr>
<td></td>
<label class="wui-field"><input name="forEnd" type="checkbox" value="1"> 结束打卡</label>
</td>
<td></td>
<td>
<label class="wui-field"><input name="repairFlag" type="checkbox" value="1"> 是否维修</label>
</td>
</tr>
这里一行有两组字段,以wui-field类来指定字段所在的范围。如果不指定该类,则整行(tr层)将默认当作字段范围。
JS控制:(dialog的onShow时)
WUI.toggleFields(jfrm, {
forEnd: formMode == FormMode.forSet && !frm.tm1.value,
repairFlag: g_args.repair
})
@key permission 菜单权限控制
前端通过菜单项来控制不同角色可见项,具体参见store.html中菜单样例。
<div class="perm-mgr" style="display:none">
<div class="menu-expand-group">
<a><span><i class="fa fa-pencil-square-o"></i>系统设置</span></a>
<div class="menu-expandable">
<a href="#pageEmployee">登录帐户管理</a>
...
</div>
</div>
</div>
系统默认使用mgr,emp两个角色。一般系统设置由perm-mgr控制,其它菜单组由perm-emp控制。
其它角色则需要在角色表中定义允许的菜单项。
根据用户权限,如"item,mgr"等,菜单中有perm-xxx类的元素会显示,有nperm-xxx类的元素会隐藏
示例:只有mgr权限显示
<div class="perm-mgr" style="display:none"></div>
示例:bx权限不显示(其它权限可显示)
<a href="#pageItem" class="nperm-bx">商品管理</a>
可通过 g_data.hasRole(roles) 查询是否有某一项或几项角色。注意:由于历史原因,hasRole/hasPerm是同样的函数。
var isMgr = g_data.hasRole("mgr"); // 最高管理员
var isEmp = g_data.hasRole("emp"); // 一般管理员
var isAdm = g_data.hasRole("mgr,emp"); // 管理员(两种都行)
var isKF = g_data.hasRole("客服");
自定义权限规则复杂,一般由框架管理,可以用canDo(对象, 权限)
函数查询,如:
var bval = WUI.canDo("客户管理"); // 查一个特定对象,名词性
var bval = WUI.canDo(null, "首件确认"); // 查一个特定权限,动词性。(最高)管理员或指定了"*"权限的话,则默认也允许。
var bval = WUI.canDo(null, "维修", false); // 查一个特定权限,动词性,缺省值设置false表示未直接设置就不允许,即使是(最高)管理员或指定了"*"权限也不允许。
WUI.canDo的底层实现是通过g_data.permSet[perm]
查询。
为fn生成一个名字。一般用于为a链接生成全局函数。
function onGetHtml(value, row) {
var fn = WUI.fname(function () {
console.log(row);
});
return '<a href="' + fn + '()">' + value + '</a>';
}
或:
function onGetHtml(value, row) {
return WUI.makeLink(value, function () {
console.log(row);
});
}
@see makeLink
生成一个A链接,显示text,点击运行fn.
用于为easyui-datagrid cell提供html.
<table>
...
<th data-options="field:'orderCnt', sortable:true, sorter:intSort, formatter:ItemFormatter.orderCnt">订单数/报名数</th>
</table>
定义formatter:
var ItemFormatter = {
orderCnt: function (value, row) {
if (!value)
return value;
return WUI.makeLink(value, function () {
var objParam = {type: row.type, itemId: row.id};
WUI.showPage("pageOrder", "订单-" + objParam.itemId, [ objParam ]);
});
},
};
@see fname
qsearch: 模糊查询,支持指定字段
@see toolbar-qsearch
@see toolbar-approve
工具栏-模糊查询。支持指定查询字段。
// pageXx.js function initPageXx
var dgOpt = {
...
toolbar: WUI.dg_toolbar(jtbl, jdlg, "qsearch"),
};
jtbl.datagrid(dgOpt);
它调用接口callSvr("Xx.query", {q: val})
需要后端支持查询,比如须指定在哪些字段查询:
protected function onQuery() {
$this->qsearch(["code", "category"], param("q"));
}
(v6) 新用法:也支持前端直接指定字段查询:
var dgOpt = {
...
// 表示在code或category两个字段中模糊查询。
toolbar: WUI.dg_toolbar(jtbl, jdlg, ["qsearch", "code,category"]),
};
jtbl.datagrid(dgOpt);
它将调用接口callSvr("Xx.query", {qsearch: "字段1,字段2:" + val})
。
参数选项:
依赖字段:
可选字段:
示例:显示审批菜单,在发起审批时,若未指定审批人则自动填写审批人
function canApprove_WO(row) {
return g_data.userInfo.id == row.approveEmpId || g_data.hasRole("mgr,售后审核");
}
var btnApprove = ["approve", {
obj: "WarrantyOrder", // 最终将调用"WarrantyOrder.set"接口
canApprove: function (row) {
return canApprove_WO(row);
},
// data: 待保存的新数据(data.approveFlag为新状态), row: 原数据, title: 当前菜单项名称,比如“发起审批”
onSet: function (row, data, title) {
// 发起审批时,自动填写审批人
if (data.approveFlag ==1 && row.approveEmpId == null) {
var empId = callSvrSync("Employee.query", {fmt: "one?", res: "id", role:"售后审核"});
if (empId)
data.approveEmpId = empId;
}
}
}];
var dgOpt = {
...
toolbar: WUI.dg_toolbar(jtbl, jdlg, btnApprove),
};
jtbl.datagrid(dgOpt);
做二次开发时,对话框上approveFlag设置示例:
{
disabled: e => canApprove_WO(e),
enumMap: ApproveFlagMap,
styler: Formatter.enumStyler(ApproveFlagStyler),
desc: "【售后审核】角色或【最高管理员】或指定审批人可审批"
}
对话框上approveEmpId设置示例:
{
disabled: e => canApprove_WO(e)
}
上面例子中用callSvrSync是使用同步方式来取数据的,更好的写法是直接用异步的callSvr。
onSet回调支持异步,
异步写法1:使用async/await。
onSet: async function (row, data) {
if (data.approveFlag ==1 && row.approveEmpId == null) {
var empId = await callSvr("Employee.query", {fmt: "one?", res: "id", role:"售后审核"});
if (empId)
data.approveEmpId = empId;
}
}
异步写法2:返回一个Deferred对象(callSvr函数刚好返回Deferred对象)
onSet: function (row, data) {
if (data.approveFlag ==1 && row.approveEmpId == null) {
var dfd = callSvr("Employee.query", {fmt: "one?", res: "id", role:"售后审核"});
dfd.then(function (empId) {
if (empId)
data.approveEmpId = empId;
});
return dfd;
}
}
示例:审批时弹出对话框,可输入备注(approveCmt)
在onSet中使用WUI.showDlgByMeta弹出自定义对话框,显然是异步操作,需要返回dfd对象。
function canApprove_WO(row) {
return g_data.userInfo.id == row.approveEmpId || g_data.hasRole("mgr,售后审核");
}
var btnApprove = ["approve", {
obj: "WarrantyOrder",
canApprove: function (row) {
return canApprove_WO(row);
},
// data: 待保存的新数据(data.approveFlag为新状态), row: 原数据, title: 当前菜单项名称,比如“发起审批”
onSet: function (row, data, title) {
var dfd = $.Deferred();
var meta = [
// title, dom, hint?
{title: "备注", dom: "<textarea name='approveCmt' rows=5></textarea>", hint: '选填,将添加到备注列表中'}
];
var jdlg = WUI.showDlgByMeta(meta, {
title: title,
onOk: async function (data1) {
// 发起审批时,自动填写审批人
if (data.approveFlag ==1 && row.approveEmpId == null) {
var empId = await callSvr("Employee.query", {fmt: "one?", res: "id", role:"售后审核"});
if (empId)
data.approveEmpId = empId;
}
if (data1.approveCmt) {
data.cmts = [
{text: data1.approveCmt}
];
}
dfd.resolve();
WUI.closeDlg(jdlg);
}
});
return dfd;
}
}];
选项:opt={obj, relatedKey, res?, dlg?/关联的明细对话框, datagrid/treegrid}
这些选项在dlg设置时有效:{valueField, readonly, objParam, toolbar, vFields}
<div class="wui-subobj" data-options="obj:'CusOrder', relatedKey:'cusId', valueField:'orders', dlg:'dlgCusOrder'">
<p><b>物流订单</b></p>
<table>
<thead><tr>
<th data-options="field:'tm', sortable:true">制单时间</th>
<th data-options="field:'status', sortable:true, jdEnumMap:CusOrderStatusMap, formatter:Formatter.enum(CusOrderStatusMap), styler:Formatter.enumStyler({CR:'Warning',CL:'Disabled'})">状态</th>
<th data-options="field:'amount', sortable:true, sorter:numberSort">金额</th>
</tr></thead>
</table>
</div>
选项说明:
opt.obj: 子表对象,与relatedKey字段一起自动生成子表查询,即{obj}.query
接口。
class AC2_CusOrder extends AccessControl
{
}
relatedKey: 关联字段. 指定两表(当前表与obj对应表)如何关联, 用于自动创建子表查询条件以及子表对话框的关联值设置(wui-fixedField)
值"cusId"与"cusId={id}"等价, 表示主表.id=CusOrder.cusId
.
可以明确指定被关联字段, 如relatedKey="name={name}" 表示主表.name=CusOrder.name
.
支持多个关联字段设置, 如relId={id} AND type={type}
.
支持in方式关联,如id in ({itemIds})
,其中itemIds字段为逗号分割的id列表,如"100,102"
以下字段仅当关联对话框(即dlg选项设置)后有效:
opt.valueField: 对应后端子表名称,在随主表一起添加子表时,会用到该字段。如果不指定,则不可在主表添加时一起添加。它对应的后端实现示例如下:
class AC2_Customer extends AccessControl
{
protected $subobj = [
"orders" => [ "obj" => "CusOrder", "cond" => "cusId=%d" ]
]
}
opt.readonly: 默认为false, 设置为true则在主表添加之后,不可对子表进行添加、更新或删除。
@see objParam
示例:在对话框dlgOrder上设置子表关联对话框dlgOrder1:
<div class="wui-subobj" id="tabOrder1" data-options="...">
注意:要在onBeforeShow中设置objParam,如果在onShow中设置就晚了:
jdlg.on("beforeshow", onBeforeShow)
function onBeforeShow(ev, formMode, opt)
{
var type = opt.objParam && opt.objParam.type;
var tab1Opt = WUI.getOptions(jdlg.find("#tabOrder1"));
tab1Opt.objParam = { type: type };
...
}
选项可以动态修改,如:
// 在dialog的beforeshow回调中:
var jsub = jdlg.find(".wui-subobj");
WUI.getOptions(jsub).readonly = !g_data.hasRole("emp,mgr");
示例:只留下删除和刷新:
<div ... class="wui-subobj" data-options="..., toolbar:'rd'"
示例:为子表定制一个操作按钮“取消”:
// function initPageXXX() 自定义个按钮
var btnCancelOrder = {text: "取消订单", iconCls:'icon-delete', handler: function () {
var row = WUI.getRow(jtbl);
if (row == null)
return;
callSvc("Ordr.cancel", {id: row.id}, function () {
app_show("操作完成");
WUI.reloadRow(jtbl, row);
})
}};
// 在dialog的beforeshow回调中:
var jsub = jdlg.find(".wui-subobj");
WUI.getOptions(jsub).toolbar = ["r", "f", "s", btnCancelOrder]
@see dg_toolbar
可以在validate事件中,对添加的子表进行判断处理:
function onValidate(ev, mode, oriData, newData)
{
if (mode == FormMode.forAdd) {
// 由于valueField选项设置为"orders", 子表数组会写在newDate.orders中
if (newData.orders.length == 0) {
WUI.app_alert("请添加子表项!", "w");
return false;
}
// 假如需要压缩成一个字符串:
// newData.orders = WUI.objarr2list(newData.orders, ["type", "amount"]);
}
}
注意:只有在主对象添加时可以检查子表。在更新模式下,子对象的更改是直接单独提交的,主对象中无法处理。
<div class="wui-subobj" data-options="obj:'CusOrder', relatedKey:'cusId', dlg:'dlgCusOrder'>
...
</div>
<div class="wui-subobj" data-options="obj:'CusOrder', relatedKey:'cusId', valueField:'orders', dlg:'dlgCusOrder', readonly: true">
...
</div>
示例:最简单的只读子表,只查看,也不关联对话框
<div class="wui-subobj" data-options="obj:'CusOrder', res:'id,tm,status,amount', relatedKey:'cusId'">
</div>
启用或禁用可通过事件发送指令:
jo.trigger("setOption", {disabled: boolDisabledVal}); // 会自动刷新UI
示例: 在物料明细对话框(dlgItem)中, 在Tabs组件中放置"组合"子表, 当下拉框选择"组合"时, 启用"组合"子表Tab页:
<select name="type">
<option value="">(无)</option>
<option value="P">组合</option>
<option value="U">拆卖</option>
</select>
<div class="easyui-tabs">
<!-- 注意 wui-subobj-item1 类定义了名字为 item1,以便下面setDlgLogic中引用。它一般和valueField相同,但valueField选项可能不存在 -->
<div class="wui-subobj wui-subobj-item1" data-options="obj:'Item1', valueField:'item1', relatedKey:'itemId', dlg:'dlgItem1'" title="组合">
...
</div>
</div>
设置"组合"页随着type选择启用禁用:
// 注意,subobj组件一般不设置name属性,而是通过定义CSS类`wui-subobj-{name}`类来标识名字,从而可以用 jdlg.gn("item1") 来找到它的通用接口。
WUI.setDlgLogic(jdlg, "item1", {
watch: "type",
disabled: function (e) {
return e.type != "P";
},
required: true
});
默认使用以下配置:
{
idField: "id", // 不建议修改
fatherField: "fatherId", // 指向父结点的字段,不建议修改
treeField: "id", // 显示树结点的字段名,可根据情况修改
}
子表以树表显示时,不支持分页(查询时自动设置参数pagesz=-1)。
@see treegrid
在添加主对象时,对子对象的添加、更新、删除操作不会立即操作数据库,而是将最终子对象列表与主对象一起提交(接口是主对象.add)。
我们称这时的子对象对话框为offline模式,它会带来一个问题,即子对象对话框上点确定后,子表列表中无法显示虚拟字段。
解决方案是:1. 在对话框中用jd_vField选项指定虚拟字段名,2. 在subobj选项中以vFields选项指定这些字段只显示而不最终提交到add接口中。
示例:InvRecord对象包含子表InvRecord1,字段定义为:
@InvRecord1: id, invId, whId, whId2, itemId
vcol: itemName, whName, whName2
打开对话框dlgInvRecord添加对象,再打开子表明细对话框dlgInvRecord1添加子表项。
在subobj组件中,通过选项vFields排除只用于显示而不向后端提交的虚拟字段,dlgInvRecord.html中:
<div class="wui-subobj" data-options="obj:'InvRecord1', relatedKey:'invId', valueField:'inv1', vFields:'itemName,whName,whName2', dlg:'dlgInvRecord1'" title="物料明细">
...子表与字段列表...
</div>
子表明细对话框中,为了在点击确定后将虚拟字段拷贝回subobj子表列表中,应通过data-options中指定jd_vField选项来指定虚拟字段名,如 dlgInvRecord1.html:
仓库 <select name="whId" class="my-combobox" required data-options="ListOptions.Warehouse()"></select> (Warehouse函数中已定义{jd_vField: 'whName'})
到仓库 <select name="whId2" class="my-combobox" required data-options="ListOptions.Warehouse({jd_vField:'whName2'})"></select> (覆盖Warehouse函数定义中的jd_vField选项)
物料 <input name="itemId" class="wui-combogrid" required data-options="ListOptions.ItemGrid()"> (ItemGrid函数中已定义{jd_vField: 'itemName'})
ListOptions中对下拉列表参数的设置示例:(store.js)
var ListOptions = {
...
Warehouse: function (opt) {
return $.extend({
jd_vField: "whName", // 指定它在明细对话框中对应的虚拟字段名
textField: "name", // 注意区别于jd_vField,textField是指定显示内容是url返回表中的哪一列
url: ...
}, opt);
},
ItemGrid: function () {
return {
jd_vField: "itemName",
textField: "name",
url: ...
}
},
}
注意带Grid结尾的选项用于wui-combogrid组件; 否则应用于my-combobox组件;
两者选项接近,wui-combogrid选项中应包含columns定义以指定下拉列表中显示哪些列,而my-combobox往往包含formatter选项来控制显示(默认是显示textField选项指定的列,设置formatter后textField选项无效)
上例中, 通过为组件指定jd_vField选项,实现在offline模式的子表对话框上点确定时,会自动调用WUI.getFormData_vf将虚拟字段和值字段拼到一起,返回并显示到表格中。
@see getFormData_vf
扩展: 若未指定onSelect回调, 默认行为: 点Tab发出tabSelect事件, 由Tab自行处理
禁用或启用easyui-tabs组件的某个Tab页.
which可以是Tab页的索引数或标题.
示例:
var jtabs = jdlg.find(".easyui-tabs");
WUI.toggleTab(jtabs, "组合物料", formData.type == "P");
@key .wui-picker-edit 手工编辑按钮
示例:输入框后添加一个编辑按钮,默认不可编辑,点按钮编辑:
<input name="value" class="wui-picker-edit">
特别地:
要查看该字段是否绝对只读(不显示picker按钮,不可手工编辑),可以通过gn函数:
var it = jo.gn(); // 或从对话框来取如 jdlg.gn("xxx")
var ro = it.readonly();
it.readonly(ro);
@key .wui-picker-help 帮助按钮
示例:输入框后添加一个帮助按钮:
<input name="value" class="wui-picker-help" data-helpKey="取消工单">
点击帮助按钮,跳往WUI.options.helpUrl指定的地址。如果指定data-helpKey,则跳到该锚点处。
可以多个picker一起使用。
帮助链接:(加wui-help类则点击可跳转,同时也支持用data-helpKey属性指定主题)
<a class="wui-help"><span><i class="fa fa-question-circle"></i>帮助</span></a>
显示一个按钮,用于隐藏(默认)或显示后面的内容。基于easyui-linkbutton创建,兼容该组件的options比如图标.
示例: 列对应: title=code,-,amount
<span class="wui-more" data-options="iconCls: 'icon-tip'">更多示例</span>
<pre>
映射方式对应: title=编码->code, Total Sum->amount&useColMap=1
根据code, 存在则更新: title=code,amount&uniKey=code
根据code, 批量更新: title=code,amount&uniKey=code!
带子表: title=code,amount,@order1.itemCode,@order1.qty&uniKey=code
</pre>
<span class="wui-more"><i class="fa fa-question-circle"></i> 代码示例</span>
<pre class="hint">
$env->get("地址", "value");
$env->set("地址", "value", "上海市XX区");
</pre>
@see .wui-help
(v6) 该函数已不建议使用。本来用于显示多组mycombobox/wui-combogrid组件,现成推荐直接用组件的setOption事件动态修改组件选项。
对话框上form内的一组控件中,根据type决定当前显示/启用哪一个控件。
需求:ItemStatusList定义了Item的状态,但当Item类型为“报修”时,其状态使用ItemStatusList_报修
,当类型为“公告”时,状态使用ItemStatusList_公告,其它类型的状态使用ItemStatusList
HTML: 在对话框中,将这几种情况都定义出来:
<select name="status" class="my-combobox" data-options="jdEnumList:ItemStatusList"></select>
<select name="status" class="my-combobox type-报修" style="display:none" data-options="jdEnumList:ItemStatusList_报修"></select>
<select name="status" class="my-combobox type-公告" style="display:none" data-options="jdEnumList:ItemStatusList_公告"></select>
注意:当type与“报修”时,它按class为"type-报修"来匹配,显示匹配到的控件(并添加active类),隐藏并禁用其它控件。
如果都不匹配,这时看第一条,如果它不带type-xxx
类,则使用第一条来显示,否则所有都不启用(隐藏、禁用)。
JS: 根据type设置合适的status下拉列表,当type变化时更新列表:
function initDlgXXX()
{
...
jdlg.on("beforeshow", onBeforeShow);
// 1. 根据type动态显示status
$(frm.type).on("change", function () {
var type = $(this).val();
WUI.showByType(jfrm.find("[name=status]"), type);
});
function onBeforeShow(ev, formMode, opt)
{
...
setTimeout(onShow);
function onShow() {
...
// 2. 打开对话框时根据type动态显示status
$(frm.type).trigger("change");
}
}
}
支持combogrid组件,注意combogrid组件用"[comboname]"而非"[name]"来找jQuery组件:
WUI.showByType(jdlg.find("[comboname=orderId]"), type);
HTML示例:
<tr>
<td class="orderIdTd">关联单据</td>
<td>
<input name="orderId" class="wui-combogrid type-生产领料 type-生产调拨 type-生产入库 type-生产退料" data-options="ListOptions.OrderGrid({type:'生产工单'})">
<input name="orderId" class="wui-combogrid type-销售" data-options="ListOptions.OrderGrid({type:'销售计划'})">
...
</td>
</tr>
支持组件状态被动态修改,比如添加模式时打开对话框就禁用该组件:
jdlg.find("[comboname=orderId]").combogrid({disabled: forAdd});
这时调用showByType后,active组件仍会保持disabled状态。类似的,如果调用者先隐藏了组件,则调用showByType后active组件也是隐藏的。
注意:对tr等包含输入框的块组件也可使用,但要求内部只有一个带name的输入组件,且各块的内部输入组件的name都相同。
<tr class="optional type-出库">...<input name="orderId">...</tr>
<tr class="optional type-入库">...<input name="orderId">...</tr>
JS控制:
WUI.showByType(jdlg.find("tr.optional"), type);
当一个块不显示时,其内部的带name的输入组件被设置disabled,提交时不会带该字段。
如果块内部包含多个输入框,或各块内的输入框的name不同,如果各块内所有输入框都默认显示、未禁用(也不会动态修改显示、禁用),这时也可以使用showByType,否则会有问题。
返回null-无差异,如果是对象与对象比较,返回差异对象,否则返回true表示有差异
var rv = diffObj({a:1, b:99}, {a:1, b:98, c:100});
// rv: {b:98, c:100}
var rv = diffObj([{a:1, b:99}], [{a:1, b:99}]);
// rv: null (无差异)
var rv = diffObj([{a:1, b:99}], [{a:1, b:99}, {a:2}]);
// rv: true
var rv = diffObj("hello", "hello");
// rv: null (无差异)
var rv = diffObj("hello", 99);
// rv: true
选项说明:
COUNT(*) 数量
["userPhone", "userName 用户", null, "status 状态=CR:新创建;RE:已完成"]
。["y 年", "m 月"]
将自动选中"时间-年"和"时间-月";tmField: 如果用到y,m,d等时间统计字段,应指定使用哪个时间字段进行运算。后端可能已经定义了时间字段(tmCols),指定该选项可覆盖后端的定义。
resFields: 用户可选择的字段列表(如res, cond中出现的字段)。如果指定(或指定了gres或gres2),则工具栏多出“统计”按钮,可供用户进一步设置。
注意gres,gres2,tmField中的字段会被自动加入,在resFields中指定或不指定都可以。
chartType: line-折线图(默认), bar-直方图, pie-饼图
pivotSumField: 统计列列名,默认为"合计"
{for: "xx报表"}
frozen:1
表示冻结1列。参考[pageSimple]中的冻结列。@see JdcloudStat.tmUnit
示例:
WUI.showDataReport({
ac: "Employee.query",
gres: ["depName 部门", "性别"],
gres2: ["职称", "学历"],
cond: WUI.getQueryCond({status: '在职'}),
detailPageName: "pageEmployee1",
title: "师资报表"
});
示例:订单月报
WUI.showDataReport({
ac: "Ordr.query",
res: "SUM(amount) 总和", // 不指定则默认为`COUNT(1) 总数`
tmField: "createTm 创建时间",
gres: ["y 年,m 月", "status 状态=CR:新创建;RE:已完成;CA:已取消"],
gres2: ["dscr 订单类别"],
cond: WUI.getQueryCond({createTm: ">=2020-1-1 and <2021-1-1", status: "CR,RE,CA"}), // 生成条件字符串
detailPageName: "pageOrder",
title: "订单月报",
// 定义用户可选的字段,定义它或gres/gres2会在工具栏显示“统计”按钮。注意不需要定义y,m等时间字段,它们由tmField自动生成。
resFields: "amount 金额, status 状态=CR:新创建;RE:已完成;CA:已取消, dscr 订单类别, userName 用户, userPhone 用户手机号, createTm 创建时间",
// showChart: true, // 显示统计图
// gres: ["y 年,m 月"],
// tmUnit: "y,m",
});
示例:订单状态占比, 显示饼图(当只有gres没有gres2,且没有tmUnit时,可自动显示饼图)
WUI.showDataReport({
ac: "Ordr.query",
res: "COUNT(1) 总数", // 不指定则默认为`COUNT(1) 总数`
gres: ["status 状态=CR:新创建;RE:已完成;CA:已取消"],
detailPageName: "pageOrder",
title: "订单状态占比",
// 定义用户可选的字段,定义它或gres/gres2会在工具栏显示“统计”按钮。注意不需要定义y,m等时间字段,它们由tmField自动生成。
resFields: "amount 金额, status 状态=CR:新创建;RE:已完成;CA:已取消, dscr 订单类别, userName 用户, userPhone 用户手机号, createTm 创建时间",
showChart: true, // 显示统计图
orderby: "总数 DESC"
});
示例:多个统计项:订单状态报表,同时统计数量和金额:
WUI.showDataReport({
ac: "Ordr.query",
res: "COUNT(1) 总数, SUM(amount) 总金额",
gres: ["status 状态=CR:新创建;RE:已完成;CA:已取消"],
// gres2: ["dscr 订单类别"], // 试试加上列统计项有何样式区别
detailPageName: "pageOrder",
title: "订单状态占比",
// showSum: true, // 自动添加行列统计
// 定义用户可选的字段,定义它会在工具栏显示“统计”按钮。注意不需要定义y,m等时间字段,它们由tmField自动生成。
resFields: "amount 金额, status 状态=CR:新创建;RE:已完成;CA:已取消, dscr 订单类别, userName 用户, userPhone 用户手机号, createTm 创建时间",
});
注意:统计图(showChart:true)目前只支持第一个统计项。
res中也可以加一些不用于统计的字段,统计列必须放在res最后面,系统会自动将res中最后若干个连续地以COUNT或SUM聚合的字段会当成统计列。
WUI.showDataReport({
ac: "Ordr.query",
res: "userName 用户, userPhone 手机号, createTm 创建时间, COUNT(1) 总数, SUM(amount) 总金额", // 自动识别2个统计列,调用query接口时设置参数`pivotCnt:2`
gres: ["status 状态=CR:新创建;RE:已完成;CA:已取消"],
gres2: ["dscr 订单类别"],
detailPageName: "pageOrder",
title: "订单状态占比",
showSum: true, // 自动添加行列统计
});
@see WUI.rs2Stat 图表数据转换
@see WUI.initChart 显示图表
@see pageSimple 通用列表页
示例:
// 各状态订单数: 柱图
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "status =CR:新创建;RE:已完成;CA:已取消",
res: "count(*) 总数",
}));
// 也可以与pageSimple列表页结合,同时显示列表页和统计图:
var url = WUI.makeUrl("Ordr.query", {
gres: "status 状态=CR:新创建;RE:已完成;CA:已取消",
res: "count(*) 总数",
});
var showChartParam = []; // 必须指定数组,即showDlgChart的后三个参数:[rs2StatOpt, seriesOpt, chartOpt]
WUI.showPage("pageSimple", "订单统计!", [url, null, null, showChartParam]);
// 各状态订单数: 饼图,习惯上应占比排序
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "status =CR:新创建;RE:已完成;CA:已取消",
res: "count(*) 总数",
orderby: "总数 DESC"
}), null, {
type: "pie"
});
// 订单年报
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "y",
res: "count(*) 总数",
}));
// 订单月报,横坐标为年月(两列)
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "y,m",
res: "count(*) 总数",
}), { // rs2StatOpt
xcol:[0,1]
});
// 订单月报,指定tmUnit,注意加orderby让时间排序,支持自动补齐缺少的时间
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "y,m",
res: "count(*) 总数",
orderby: "y,m"
}), { // rs2StatOpt
tmUnit: "y,m"
});
// 也可以与pageSimple列表页结合,同时显示列表页和统计图:
var url = WUI.makeUrl("Ordr.query", {
gres: "y 年,m 月",
res: "count(*) 总数",
orderby: "y,m"
});
var showChartParam = [ {tmUnit: "y,m"} ];
WUI.showPage("pageSimple", "订单统计!", [url, null, null, showChartParam]);
// 订单各月占比,显示为饼图,哪个月订单多则排在前
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "y,m",
res: "count(*) 总数",
orderby: "总数 DESC"
}), { // rs2StatOpt
xcol:[0,1]
}, { // seriesOpt
type: "pie"
}, { // chartOpt
title: { text: "订单各月分布" }
});
// 分状态订单月报
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "y,m,status =CR:新创建;RE:已完成;CA:已取消",
res: "count(*) 总数",
orderby: "y,m"
}), { // rs2StatOpt
tmUnit: "y,m"
});
// 同上,配置堆积柱状图,配置stack
WUI.showDlgChart(callSvr("Ordr.query", {
gres: "y,m,status =CR:新创建;RE:已完成;CA:已取消",
res: "count(*) 总数",
orderby: "y,m"
}), { // rs2StatOpt
tmUnit: "y,m"
}, { // seriesOpt
type: "bar",
stack: "X"
}, { // chartOpt
// swapXY: true // 横向柱状图
});
// 分用户订单月报
WUI.showDlgChart(callSvr("Ordr.query", {
cond: {createTm: ">=2010-1-1 and <2030-1-1"},
gres: "userId",
res: "userName,count(*) 总数",
orderby: "总数 DESC"
}), { // rs2StatOpt
xcol: 1
},{ // seriesOpt
type: 'pie', // 饼图
});
// 分用户订单月报
WUI.showDlgChart(callSvr("Ordr.query", {
cond: {createTm: ">=2020-1-1 and <2021-1-1"},
gres: "y,m,userId",
res: "userName,count(*) 总数",
orderby: "y,m"
}), { // rs2StatOpt
tmUnit: "y,m"
}, { // seriesOpt
type: 'bar',
});
系统默认会记录操作日志ObjLog,可在管理端展示:
菜单:系统设置-操作日志,结合查询框查找
<a href="#pageObjLog">操作日志</a>
可添加“日志”菜单到工具栏,按钮名为"objLog":
jtbl.datagrid({
...
toolbar: WUI.dg_toolbar(jtbl, jdlg, ..., 'objLog');
});
@key .wui-dialog-logic 指定对话框逻辑
设置对话框业务逻辑。
示例:对orderId组件,添加之后不可以修改:
// 一般可定义在initDlgXXX函数中
WUI.setDlgLogic(jdlg, "orderId", {
readonlyForSet: true
});
也可以定义在DOM对象上,如:
<input name="orderId" class="wui-dialog-logic" data-options="readonlyForSet:true">
如果是叠加在wui-combogrid组件上:
<input name="orderId" class="wui-combogrid wui-dialog-logic" data-options="$.extend(ListOptions.UserGrid(), readonlyForSet:true)">
logic中支持readonly选项,也支持readonlyForAdd/readonlyForSet这些特定模式的选项,它们既可以设置为一个值,也可以是一个函数fn(data, gn)
(或等价的lambda表达式):
gn("name").val()
取name字段值;参考[getFormItem]。如果同时指定了readonly和readonlyForAdd选项,则当添加模式时优先用readonlyForAdd选项,其它模式也类似。
这个特性同样适用于以下disabled/readonly/value选项,比如有disabledForAdd/readonlyForSet/valueForAdd等选项。
示例:添加时无须填写,但之后可修改:可设置添加时隐藏或只读
showForAdd: false
或
readonlyForAdd: true
注意:
示例:添加时自动填写
填当前日期:直接设置值
valueForAdd: new Date().format("D")
或指定lambda或函数:
valueForAdd: () => new Date().format("D")
(与直接指定值的区别是每次打开对话框时都会计算)
填当前操作员:
valueForAdd: () => g_data.userInfo.id
如果不允许改,则加上
readonlyForAdd: true
示例:计算字段(不提交)
disabled: true
value: e => e.x + "-" + e.y
由于后端没这个实体字段,所以不能提交。注意readonly和disabled在前端效果相似,但readonly字段会提交而disabled字段不提交。
示例:根据status值,如果为CR就显示orderId字段
WUI.setDlgLogic(jdlg, "orderId", {
show: e => e.status == "CR",
watch: "status" // 指定依赖字段,当它变化时刷新当前字段
});
上面用的是lambda写法,参数e是对话框初始数据。
如果浏览器不支持lambda,可以用函数形式:
WUI.setDlgLogic(jdlg, "orderId", {
show: function (e) {
return e.status == "CR";
},
watch: "status"
});
类似于readonly选项,选项show也支持showForAdd, showForSet, showForFind这些扩展选项。
上面如果只设置show选项,则只会在打开对话框时设置字段,加上watch选项指定依赖字段,则当依赖字段发生变化时也会动态刷新。
甚至可以依赖多字段,用逗号隔开即可,如单价(price)或数量(qty)变化后,更新总价(total):
WUI.setDlgLogic(jdlg, "total", {
value: e => e.price * e.qty,
watch: "price,qty" // 依赖多字段,以逗号分隔,注意不要多加空格
});
watch选项会刷新disabled/readonly/show/value系列选项中的表达式。
示例:商品字段(itemId)使用下拉数据表(wui-combogrid下拉框组件)显示,有id,name,price三列,如果从下拉列表中选择了一个商品,则自动用选中行的price列来更新对话框上的price字段:
WUI.setDlgLogic(jdlg, "price", {
value: e => e.eventData_itemId?.price,
watch: "itemId"
});
当选择商品后,可以从e.eventData_itemId数据中取到下拉列表的值(其中itemId是下拉列表对应的字段名),如e.eventData_itemId.price
;
e.eventData_itemId?.price
中的?.
是ES6写法,相当于e.eventData_itemId && e.eventData_itemId.price
(如果e.eventData_itemId有值时再取e.eventData_itemId.price,避免字段为空报错)。
类似地,如果是其它字段变化,则可以从e.eventData_{字段名}
中取值。my-combobox组件下拉时也是返回选择行对应的数据。
注意:如果有其它字段依赖了修改的值,比如上节例子中total字段依赖price,则此时会连锁地去更新total字段。
更自由地,可以用onWatch选项定义一个函数,当watch字段变化后执行,原型为:onWatch(e=当前对象数据, ev=扩展数据, gn=字段访问器)
,在ev参数中:
id,name,code
三列,它就以{id, name, code}
格式返回这三列数据。参数gn是一个函数,可以对任意字段进行处理,用gn(字段名)
可以取到该字段,然后可以链式调用通用接口,如gn("orderId").visible(true).disabled(false).readonly(true).val(100)
。
示例:当选择了一个工件(snId, 使用combogrid组件)后,自动填充工单(orderId, 使用combogrid组件)、工单开工时间(actualTm)等字段。
WUI.setDlgLogic(jdlg, "snId", {
watch: "snId",
onWatch: async function (e, ev, gn) {
console.log(ev);
// combogrid组件设置值可以用一个数组,同时设置value和text
gn("orderId").val([ev.data.orderId, ev.data.orderCode]);
// ev.data中没有现成数据,故再调用接口查一下
var rv = await callSvr("Ordr.get", {id: ev.data.orderId, res: "actualTm"})
gn("actualTm").val(rv.actualTm);
}
});
@see jQuery.fn.gn
可使用setOption(e, it, gn)
来设置组件。
示例:type="入库"时,下拉列表moveType字段显示入库选项,type="出库"时,显示出库选项
var MoveTypeMap_出库 = {
602: "销售出库冲销",
202: "成本中心",
222: "项目",
552: "报废冲销"
};
var MoveTypeMap_入库 = {
102: "生产入库冲销",
201: "成本中心领料"
}
// html中的组件: <input name="moveType" class="my-combobox">
WUI.setDlgLogic(jdlg, "moveType", {
setOption: function (e, it, gn) {
return {
jdEnumMap: e.type == "入库"? MoveTypeMap_入库: MoveTypeMap_出库
}
},
watch: "type"
});
也可以直接设置静态值:
// html中:<input class="wui-combogrid">
WUI.setDlgLogic(jdlg, "whId", {
setOption: ListOptions.WhGrid()
});
这与直接在html中设置是等价的:
<input class="wui-combogrid" data-options="ListOptions.WhGrid()">
required: true,
// 返回一个非空字符串,则表示验证失败,字符串即是错误信息
validate: v => /^\d{11}$/.test(v) || "手机号须11位数字"
注意验证选项对添加、更新有效,没有forAdd/forSet选项。
validate函数原型为:validate(value, it, gn)
示例:当status字段值为RE时,当前字段值不可为空:
{
validate: (v,it,gn) => {
if (gn("status").val() == "RE" && !v)
return "单据完成时[" + it.getTitle() + "]不可为空";
}
}
用gn取其它字段值;用it.getTitle()取当前字段标题。
注意:当字段未在对话框中显示,或是禁用状态时,或是只读状态时,不执行验证。
子表(wui-subobj)对象一样支持required/validate,在validate函数中传入值v是一个数组,如果没有填写则v.length为0。示例:
{
//required: true,
validate: function (value, it, gn) {
if (value.length < 2) {
return "明细表至少添加2行!";
}
}
}
此外,还可以设置validType选项,它与easyui-validatebox组件兼容。示例:
{
required: true,
validType: "email"
}
注意:validType与validate选项不可一起使用。
@see .easyui-validatebox
tmUnit指定时间维度分析的类型,目前支持以下维度:
"y,m" 年月
"y,m,d" 年月日
"y,m,d,h" 年月日时
"y,w" 年周
"y,q" 年季度
@param opt {gcol, xcol, ycol?, gtext?, maxSeriesCnt?, formatter?}
gcol/gtext, xcol, ycol可以是数字, 表示第几列; 也可以是数字数组, 表示若干列.
gtext也是列号或列号数组,与gcol合用,表示按gcol分组,最终“组名”结果按gtext列显示。
如果指定formatter,则“组名”再经formatter处理后显示。formatter(val): val是一个值或一个数组,由gcol/gtext决定。
如果指定opt.maxSeriesCnt,则分组最多maxSeriesCnt列,其它组则都归并到“其它”组。
示例: 按年-月统计各产品类别(cateId)的订单金额, 可以调用接口:
callSvr("Ordr.query", {gres: "y,m,cateId", res: "cateName,SUM(amount) sum"}, orderby: "y,m")
得到rs表格:
var rs = {
h: ["y","m","cateId","cateName","sum"],
d: [
[2019, 11, 1, "衣服", 20000],
[2019, 11, 2, "食品", 12000],
[2019, 12, 2, "食品", 15000],
[2020, 02, 1, "衣服", 19000]
]
}
即:
y m cateId cateName sum
------------
2019 11 1 衣服 20000
2019 11 2 食品 12000
2019 12 2 食品 15000
2020 02 1 衣服 19000
将分类cateId转到列上(但按cateName来显示):
var rs1 = pivot(rs, {
xcol: [0, 1], // 0,1列
gcol: 2, // 按该列转置
gtext: 3, // 表示第3列是第2列的显示内容, 也可以用函数function (row) { return row[3] }
ycol: 4, // 值列, 可以不指定, 缺省为最后一列.
})
得到结果:
rs1 = {
h: ["y","m","衣服","食品"],
d: [
[2019, 11, 20000, 12000],
[2019, 12, null, 15000],
[2020, 02, 19000, null]
]
}
即:
y m 衣服 食品
------------
2019 11 20000 12000
2019 12 null 15000
2020 02 19000 null
若xcol中只保留年:
var rs1 = pivot(rs, {
xcol: 0,
gcol: 2,
gtext: 3,
ycol:4
})
得到结果将变成这样:
rs1 = {
h: ["y","衣服","食品"],
d: [
[2019, 20000, 27000],
[2020, 19000, null]
]
}
若在xcol中保留分类, 将年-月转到列上:
var rs1 = pivot(rs, {
xcol: [2, 3], // 或只留3也可以
gcol: [0, 1],
ycol:4
})
得到结果:
rs1 = {
h: ["cateId","cateName","2019-11","2019-12","2020-2"],
d: [
[1, "衣服", 20000, null, 19000],
[2, "食品", 12000, 15000, null]
]
}
即:
cateId cateName 2019-11 2019-12 2020-02
------------
1 衣服 20000 0 19000
2 食品 12000 15000 0
ycol也可以指定多列, 用的比较少.
var rs1 = pivot(rs, {
xcol: [0, 1],
gcol: 3,
ycol: [4,4], // 为演示结果, 故意重复sum列. 实际可能为"订单总额","订单数"两列.
})
得到结果:
rs1 = {
h: ["y","m","衣服","食品"],
d: [
[2019, 11, [20000,20000], [12000,12000]],
[2019, 12, null, [15000,15000]],
[2020, 02, [19000,19000], null]
]
}
将query接口返回的数据,转成统计图需要的数据格式。
@param opt {xcol, ycol, gcol, gtext, maxSeriesCnt, tmUnit, formatter, formatterX, noTmInsert=0}
@param opt.xcol 指定X轴数据,可以是一列或多列,如0表示第0列, 值[0,1]表示前2列。可以没有x列,用空数组`[]`表示。
@param opt.ycol 指定值数据,可以是一列或多列。
@param opt.gcol 指定分组列。
@param opt.gtext 指定分组值对应的显示文本列。比如表中既有商品编号,又有商品名称,商品编号列设置为gcol用于分组,而商品名称列设置为gtext用于显示。
xcol,ycol,gcol,gtext,maxSeriesCnt参数可参考函数 WUI.pivot
@param opt.tmUnit 如果非空,表示按指定时间维度分析。参考[JdcloudStat.tmUnit]().
未指定tmUnit时,缺省xcol=0列(或1列,当有3列及以上且未指定ycol时,当成是xcol,xcoltext,ycol
三列格式),ycol=最后一列,gcol如需要则应手工指定
tmUnit用于指定时间字段: "y,m"-年,月; "y,m,d"-年,月,日; "y,w"-年,周; "y,m,d,h"-年,月,日,时; "y,q"-年,季度
若指定了tmUnit,则可以不指定xcol,gcol,ycol,而是由字段排列自动得到,详见"tmUnit使用举例"章节。
@param opt.formatter 对汇总数据列进行格式化,缺省取WUI.options.statFormatter[gcolNames]。Function(value).
@param opt.formatterX 对X轴数据进行格式化,缺省取WUI.options.statFormatter[xcolNames]。Function(value)。若opt.xcol是数组,则value也是数组。
@return statData { @xData, @yData=[{name=seriesName, data=@seriesData}] }
与echart结合使用示例可参考 initChart. 原理如下:
var option = {
...
legend: {
data: statData.yData
},
xAxis: {
type: 'category',
boundaryGap: false,
data: statData.xData
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
series: statData.yData
};
myChart.setOption(option);
例1:统计每个用户的订单金额, 由高到低显示前10名, 显示为饼图或柱状图.
callSvr("Ordr.query", {gres: "userId 用户编号", res: "userName 用户, SUM(amount) 金额", orderby: "sum DESC", pagesz: 10})
// 一般用userId而不是userName来分组, 因为不仅userName可能会重名, 而且userName一般是从外部表join过来的, 没有索引性能较差不适合做分组字段.
得到结果示例:
var rs = {
h: ["用户编号", "用户", "金额"],
d: [
[1001,"用户1",12000],
[1002,"用户2",10000]
]
}
即:
用户编号 用户 金额
-------------
1001 用户1 12000
1002 用户2 10000
...
通过rs2Stat转换:
var statData = rs2Stat(rs, {xcol:1}); // xcol指定横轴数据列, 缺省为第0列, 这里指定为第1列,用名字替代编号。ycol选项可指定统计值列, 这里没有指定,缺省为最后一列,
// 结果:
statData = {
xData: [
'用户1', '用户2'
],
yData: [
{name: '金额', data: [12000, 10000]}
]
}
例2:按年-月统计订单金额, 显示为柱状图或折线图.
callSvr("Ordr.query", {gres: "y,m", res: "SUM(amount) sum", orderby: "y,m"})
得到表:
y m sum
-----------
2019 11 30000
2019 12 34000
2020 2 25000
转换示例:
var rs = {
h: ["y", "m", "sum"],
d: [
[2019,11,30000],
[2019,12,34000],
[2020,2,25000],
]
}
var statData = rs2Stat(rs, {xcol:[0,1]});
// 结果:
statData = {
xData: [
'2019-11', '2019-12', '2020-2'
],
yData: [
{name: '累计', data: [30000, 34000, 25000]}
]
}
上面年月中缺少了2020-1, 如果要补上缺少的月份, 可以使用tmUnit参数指定日期类型, 注意这时原始数据中年月须已排好序:
var statData = rs2Stat(rs, {xcol:[0,1], ycol:2, tmUnit:"y,m"} );
// 指定tmUnit后, xcol缺省为前N列即tmUnit这几列, 如"y,m,d"(年月日)表示前3列即`xcol: [0,1,2]`. 上面参数可简写为:
var statData = rs2Stat(rs, {tmUnit:"y,m"} );
// 结果:
statData = {
xData: [
'2019-11', '2019-12', '2020-1', '2020-2'
],
yData: [
{name: '累计', data: [30000, 34000, 0, 25000]}
]
}
例3:按年-月统计各产品类别(cateId)的订单金额, 产品类别在列上显示(即显示为系列, 列为"年,月,类别1,类别2,..."):
callSvr("Ordr.query", {gres: "y,m,cateId", res: "cateName,SUM(amount) sum"}, orderby: "y,m")
y m cateId cateName sum
------------
2019 11 1 衣服 20000
2019 11 2 食品 12000
2019 12 2 食品 15000
2020 02 1 衣服 19000
结果需要将分类cateName转到列上, 即:
y m 衣服 食品
------------
2019 11 20000 12000
2019 12 0 15000
2020 02 19000 0
可以添加gcol参数指定转置列(pivot column):
var rs = {
h: ["y","m","cateId","cateName","sum"],
d: [
[2019, 11, 1, "衣服", 20000],
[2019, 11, 2, "食品", 12000],
[2019, 12, 2, "食品", 15000],
[2020, 02, 1, "衣服", 19000]
]
}
var statData = rs2Stat(rs, {
xcol: [0, 1], // 0,1列
gcol: 2,
gtext: 3 // gtext表示gcol如何显示, 数字3表示按第3列显示, 即"1","2"显示成"衣服", "食品"; gtext也可以用函数, 如 `function (val, row, i) { return row[3] }`
})
// 结果:
statData = {
xData: [
'2019-11', '2019-12', '2020-2'
],
yData: [
{name: '衣服', data: [20000, 0, 19000]},
{name: '食品', data: [12000, 15000, 0]}
]
}
如果还需要补上缺少的年月, 可以加tmUnit参数, 要求原始数据中年月须已排好序:
var statData = rs2Stat(rs, {
// xcol: [0, 1], // 有tmUnit参数时, 且刚好前N列表示时间, 则xcol可缺省
gcol: 2,
gtext: 3,
tmUnit: "y,m"
})
// 结果:
statData = {
xData: [
'2019-11', '2019-12', '2020-1', '2020-2' // '2020-1'是自动补上的
],
yData: [
{name: '衣服', data: [20000, 0, 0, 19000]},
{name: '食品', data: [12000, 15000, 0, 0]}
]
}
注意: 上面将分类cateName转到列上再转成统计数据, 也可以分步来做, 先用pivot函数:
var rs1 = pivot(rs, {
xcol: [0, 1],
gcol: 2,
gtext: 3
})
得到结果rs1:
y m 衣服 食品
------------
2019 11 20000 12000
2019 12 0 15000
2020 02 19000 0
再将rs1转成统计数据:
var statData = rs2Stat(rs1, {
xcol: [0, 1],
ycol: [2, 3], // 注意这时ycol是多列, 显式指定.
tmUnit: "y,m"
})
当有tmUnit时, 列按如下规则分布, 可以省去指定xcol, gcol等参数:
与tmUnit匹配的时间列 值统计列
与tmUnit匹配的时间列 分组列 值统计列
与tmUnit匹配的时间列 分组列 组名列 值统计列
例如以下列, 均可以只用参数 {tmUnit: "y,m,d"}
:
y,m,d,sum 完整参数为: { tmUnit: "y,m,d", xcol:[0,1,2], ycol: 3 }
y,m,d,cateName,sum 完整参数为: { tmUnit: "y,m,d", xcol:[0,1,2], gcol:3, ycol: 4 }
y,m,d,cateId,cateName,sum 完整参数为: { tmUnit: "y,m,d", xcol:[0,1,2], gcol:3, gtext:4, ycol: 5 }
指定tmUnit后会自动补齐缺失日期,并按预定格式显示。如果不想补日期,可以设置noTmInsert:1
选项。
示例一:
var rs = {
h: ["y", "m", "d", "sum"], // 时间维度为 y,m,d; sum为统计值
d: [
[2016, 6, 29, 13],
[2016, 7, 1, 2],
[2016, 7, 2, 9],
]
}
var statData = rs2Stat(rs, {tmUnit: "y,m,d"});
// 结果:
statData = {
xData: [
'2016-6-29', '2016-6-30', '2016-7-1', '2016-7-2' // 2016-6-30为自动补上的日期
],
yData: [
{name: 'sum', data: [13, 0, 2, 9]} // 分别对应xData中每个日期,其中'2016-6-30'没有数据自动补0
]
}
示例二: 有汇总字段
var rs = {
h: ["y", "m", "d", "sex", "sum"], // 时间维度为 y,m,d; sex为汇总字段, sum为累计值
d: [
[2016, 6, 29, '男', 10],
[2016, 6, 29, '女', 3],
[2016, 7, 1, '男', 2],
[2016, 7, 2, '男', 8],
[2016, 7, 2, '女', 1],
]
}
var statData = rs2Stat(rs, {tmUnit: "y,m,d"});
// 结果:
statData = {
xData: [
'2016-6-29', '2016-6-30', '2016-7-1', '2016-7-2' // 2016-6-30为自动补上的日期
],
yData: [
{name: '男', data: [10, 0, 2, 8]}, // 分别对应xData中每个日期,其中'2016-6-30'没有数据自动补0
{name: '女', data: [3, 0, 0, 1]} // '2016-6-30'与'2016-7-1'没有数据自动补0.
]
}
默认yData中的系列名(seriesName)直接使用汇总字段,但如果汇总字段后还有一列,则以该列作为显示名称。
示例三: 汇总字段"sex"后面还有一列"sexName", 因而使用sexName作为图表系列名用于显示. 而"sex"以"M","F"分别表示男女,仅做内部使用:
var rs = {
h: ["y", "m", "d", "sex", "sexName", "sum"], // 时间维度为 y,m,d; sex为汇总字段, sexName为汇总显示字段, sum为累计值
d: [
[2016, 6, 29, 'M', '男', 10],
[2016, 6, 29, 'F', '女', 3],
[2016, 7, 1, 'M', '男', 2],
[2016, 7, 2, 'M', '男', 8],
[2016, 7, 2, 'F', '女', 1],
]
}
var statData = rs2Stat(rs, {tmUnit: "y,m,d"});
// 结果:与示例二相同。它等价于调用:
var statData = rs2Stat(rs, {tmUnit: "y,m,d",
xcol: [0,1,2],
gcol: 3,
gtext: 4,
ycol: 5
});
示例四: 汇总字段支持格式化,假设性别字段以'M','F'分别表示'男', '女':
var rs = {
h: ["y", "m", "d", "sex", "sum"], // 时间维度为 y,m,d; sex为汇总字段, sum为累计值
d: [
[2016, 6, 29, 'M', 10],
[2016, 6, 29, 'F', 3],
[2016, 7, 1, 'M', 2],
[2016, 7, 2, 'M', 8],
[2016, 7, 2, 'F', 1],
]
}
var opt = {
tmUnit: "y,m,d",
// arr为当前行数组, i为统计字段在数组中的index, 即 arr[i] = value.
formatter: function (value, arr, i) {
return value=='M'?'男':'女';
}
};
var statData = rs2Stat(rs, opt);
// 结果:与示例二相同。
可以使用变量WUI.options.statFormatter指定全局formatter,如示例四也可以写:
WUI.options.statFormatter = {
sex: function (value, arr, i) {
return value=='M'?'男':'女';
}
}
var statData = rs2Stat(rs, {tmUnit: "y,m,d"});
在无汇总时,列"sum"会自动被改为"累计",这时默认在statFormatter中设置的:
WUI.options.statFormatter = {
sum: function (value) {
return '累计';
}
}
X轴数据也支持定制,通过设置formaterX回调函数,如tm="y,m,d"时,默认显示如"2020-10-1",现在想只显示"10-1",可以用:
var statData = WUI.rs2Stat(rs, {tmUnit:"y,m,d", formatterX:function (value) {
return value[1] + "-" + value[2]
}})
初始化echarts报表组件.
statData示例:
statData = {
xData: [
'2016-6-29', '2016-6-30', '2016-7-1', '2016-7-2'
],
yData: [
{name: 'sum', data: [13, 0, 2, 9]} // 分别对应xData中每个日期,其中'2016-6-30'没有数据自动补0
]
}
特别地,设置 chartOpt.swapXY = true,表示横向柱状图。
扩展项:暗黑模式请使用 chartOpt.theme = "dark"。
@see WUI.rs2Stat WUI.initPageStat
通用统计页模式
示例可参考超级管理端API日志统计(web/adm/pageApiLogStat)
html示例:
<div wui-script="pageUserRegStat.js" title="用户注册统计" my-initfn="initPageUserRegStat">
开始时间
<input type="text" name="createTm" data-op=">=" data-options="showSeconds:false" class="easyui-datetimebox txtTm1">
结束时间
<input type="text" name="createTm" data-op="<" data-options="showSeconds:false" class="easyui-datetimebox txtTm2">
快捷时间选择
<select class="txtTmRange">
<option value ="近8周">近8周</option>
<option value ="近6月">近6月</option>
</select>
各种过滤条件:
性别:
<select name="sex">
<option value ="">全部</option>
<option value ="男">男</option>
<option value ="女">女</option>
</select>
地域:
<select name="region" class="my-combobox" data-options="valueField:'id',textField:'name',url:WUI.makeUrl(...)"></select>
统计项
<select id="cboRes">
<option value ="COUNT(*) 总数">数量</option>
<option value="SUM(t) sum">调用时间(毫秒)</option>
<!-- 可以指定多个字段,逗号分隔,表示显示多个系列(这时下面的“分类汇总”是无效的),示例:
<option value ="totalMh 理论工时,totalMh1 实际工时,totalMh2 出勤工时">工时</option>
-->
</select>
汇总字段:
<select id="g">
<option value ="">无</option>
<option value ="sex">性别</option>
<option value="region">地域</option>
<!-- 可以指定两个字段,逗号分隔,格式"分组字段,分组显示字段"
<option value="userId,userName">用户</option>
<option value="itemId,itemName">物料</option>
-->
</select>
<input type="button" value="生成" class="btnStat"/>
时间维度类型:
<select id="tmUnit">
<option value="y,w">周报表</option>
<option value="y,m">月报表</option>
</select>
统计图:
<div class="divChart" data-ac="User.query"></div>
</div>
遍历jpage中带name属性的各组件,生成统计请求的参数,调用接口获取统计数据并显示统计图表.
初始化示例:
var statItf_ = WUI.initPageStat(jpage, setStatOpt);
function setStatOpt(chartIdx, opt)
{
// this是当前chart的jquery对象,chartIdx为序号,依此可针对每个chart分别设置参数
// 设置查询参数param.ac/param.res/param.cond等
var param = opt.queryParam;
param.cond += ...;
param.res = jpage.find("#cboRes").val();
// 设置时间维度,汇总字段
opt.tmUnit = jpage.find("#tmUnit").val();
opt.g = jpage.find("#g").val();
// 设置echarts参数
var chartOpt, seriesOpt;
if (chartIdx == 0) {
chartOpt = { ... };
seriesOpt = { ... };
}
else if (chartIdx == 1) {
chartOpt = { ... };
seriesOpt = { ... };
}
$.extend(true, opt, {
chartOpt: chartOpt,
seriesOpt: seriesOpt,
});
}
@param setStatOpt (chartIdx, opt) 回调设置每个chart. this为当前chart组件,chartIdx为当前chart的序号,从0开始。
@param opt= {tmUnit?, g?, queryParam, chartOpt, seriesOpt, onLoadData?, maxSeriesCnt?, formatter?, x?, useResOnly?}
统计模型
x表示横坐标字段,y表示纵坐标字段,g表示要转置到列上的字段(pivot字段)
参数运用:
g,gname
,g用于转置, gname用于显示。@param opt.tmUnit Enum. 时间维度
如果非空,则按时间维度分析,即按指定时间类型组织横轴数据,会补全时间。参考[JdcloudStat.tmUnit]()
如果设置,它会自动设置 opt.queryParam 中的gres/orderby参数。
@param opt.g 分组字段名
会影响opt.queryParam中的gres选项。
有时分组字段使用xxxId字段,但希望显示时用xxxName字段,这时可在g中包含两个字段。
示例,按场景分组显示日报表:
opt.tmUnit = "y,m,d"; // 日报表
opt.g = "sceneId,sceneName";
这样生成的opt.queryParam中:
gres="y,m,d,sceneId";
orderby="y,m,d";
res="sceneName,COUNT(*) sum";
@param opt.queryParam 接口查询参数
可以设置ac, res, gres, cond, orderby, pagesz等筋斗云框架通用查询参数,或依照接口文档设置。
设置opt.tmUnit/opt.g会自动设置其中部分参数。
此外 ac, res参数也可通过在.divChart组件上设置data-ac, data-res属性,如
<div class="divChart" data-ac="Ordr.query" data-res="SUM(amount) sum"></div>
关于接口返回数据到图表数据转换,参考rs2Stat函数:
@see WUI.rs2Stat
@param opt.chartOpt, opt.seriesOpt
参考百度echarts全局参数以及series参数: http://echarts.baidu.com/echarts2/doc/doc.html
@param opt.onLoadData (statData, opt) 处理统计数据后、显示图表前回调。
this为当前图表组件(jchart对象)。常用于根据统计数据调整图表显示,修改opt中的chartOpt, seriesOpt选项即可。
@param opt.maxSeriesCnt ?=10
最多显示多少图表系列(其它系列自动归并到“其它”中)。参考rs2Stat函数同名选项。
@param opt.formatter
对汇总数据进行格式化显示。参考 rs2Stat函数同名选项。
@see WUI.rs2Stat
@return statItf= {refreshStat(), setTmRange(desc)} 统计页接口
refreshStat()用于显示或刷新统计图。当调用initPageStat(jpage)且jpage中有.btnStat组件时,会自动点击该按钮以显示统计图。
setTmRange(desc)用于设置jpage中的.txtTm1, .txtTm2两个文本框,作为起止时间。
@see WUI.getTmRange
@see WUI.initChart 显示图表
也可以调用WUI.initChart及WUI.rs2Stat自行设置报表,示例如下:
var jchart = jpage.find(".divChart");
var tmUnit = "y,m"; // 按时间维度分析,按“年月”展开数据
var cond = "tm>='2016-1-1' and tm<'2017-1-1'";
// WUI.useBatchCall(); // 如果有多个报表,可以批量调用后端接口
// 会话访问量
callSvr("ApiLog.query", function (data) {
var opt = { tmUnit: tmUnit };
var statData = WUI.rs2Stat(data, opt);
var seriesOpt = {};
var chartOpt = {
title: {
text: "访问量(会话数)"
},
legend: null
};
WUI.initChart(jchart[0], statData, seriesOpt, chartOpt);
}, {res: "COUNT(distinct ses) sum", gres: tmUnit, orderby: tmUnit, cond: cond });
此外,也支持直接显示无汇总的数据。
示例:有以下接口:
RecM.query() -> tbl(who, tm, cpu)
返回 服务器(who)在每一分钟(tm)的最低cpu使用率。数据示例:
{ h: ["who", "tm", "cpu"],
d: [
["app", "2018-10-1 10:10:00", 89],
["app", "2018-10-1 10:11:00", 91],
["db", "2018-10-1 10:10:00", 68],
["db", "2018-10-1 10:11:00", 72]
]
}
由于tm已经汇总到分钟,现在希望直接显示tm对应的值,且按服务器不同("app"表示"应用服务器","db"表示"数据库服务器")分系列显示。查询:
RecM.query(res="tm,who,cpu", cond="...")
JS示例:
function initPageRecMStat()
{
...
var statItf_ = WUI.initPageStat(jpage, setStatOpt);
function setStatOpt(chartIdx, opt)
{
var param = opt.queryParam;
param.res = "cpu"; // y字段
opt.x = "tm"; // x字段
opt.g = "who", // g字段即图表系列(pivot字段)
opt.useResOnly = true; // 直接显示原始数据,无须用gres分组,所以设置useResOnly.
opt.formatter = function (value, arr, i) {
var map = {
"app": "应用服务器",
"db": "数据库服务器"
};
return map[value] || value;
};
}
}
用于快捷展示报表。
在订单列表上添加“月报表”按钮,点击显示订单月统计报表,并可以导出到Excel。
// function initPageCusOrder()
var btnStat1 = {text: "月报表", "wui-perm": "导出", iconCls:'icon-ok', handler: function () {
var queryParams = jtbl.datagrid("options").queryParams;
var url = WUI.makeUrl("Ordr.query", {
gres:"y 年,m 月, userId",
res:"userName 客户, COUNT(*) 订单数, SUM(amount) 总金额",
hiddenFields: "userId",
orderby: "总金额 DESC",
pagesz: -1
});
WUI.showPage("pageSimple", "订单月报表!", [url, queryParams]);
}};
jtbl.datagrid({
toolbar: WUI.dg_toolbar(jtbl, jdlg, ..., btnStat1),
...
});
注意:调用WUI.showPage时,标题以"!"结尾表示每次调用都刷新该页面。而默认行为是如果页面已打开,就直接显示而不会刷新。
注意:用pagesz:-1
表示不分页,加载所有数据(实际上受限于后端,默认1000条)。
如果要支持分页,必须指定页码参数:page:1
,如:
var url = WUI.makeUrl("Sn.query", {
res: 'id 工件编号, code 序列号, code2 设备号, actualTm 工单开工, actualTm1 工单完工, itemName 产品名称, cateName 产品型号, ec 相关工程变更',
page: 1
});
WUI.showPage("pageSimple", "工件列表!", [url]);
url中接口调用返回的数据支持query接口常用的hd/list/array格式。
常常与报表查询条件对话DlgReportCond一起使用, 先设置查询时间段,然后出报表,示例:
var btnStat1 = {text: "月统计", iconCls:'icon-ok', handler: function () {
DlgReportCond.show(function (data) {
var queryParams = WUI.getQueryParam({dt: [data.tm1, data.tm2]});
var url = WUI.makeUrl("Capacity.query", { gres: 'y 年,m 月, name 员工', res: 'SUM(mh) 总工时, SUM(mhA) 总加班', pagesz: -1 });
WUI.showPage("pageSimple", "出勤月统计!", [url, queryParams]);
});
}};
jtbl.datagrid({
toolbar: WUI.dg_toolbar(jtbl, jdlg, ..., btnStat1),
...
});
回调函数参数data是对话框中设置了name的输入字段,默认有tm1, tm2。
允许定制查询条件对话框,如添加查询字段,详见dlgReportCond对话框文档。
@see dlgReportCond
允许定制表格显示参数,如
WUI.showPage("pageSimple", "订单月报表!", [url, queryParams, onInitGrid]);
function onInitGrid(jpage, jtbl, dgOpt, columns, data)
{
// dgOpt: datagrid的选项,如设置 dgOpt.onClickCell等属性
// columns: 列数组,可设置列的formatter等属性
// data: ajax得到的原始数据
}
注意:关于分页:如果url中有pagesz=-1参数,则不分页。也可直接设置dgOpt.pagination指定。
示例:菜单上增加“工单工时统计”报表,在“数量”列上可以点击,点击后在新页面中显示该工单下的所有工件明细。
菜单上增加一项:
<a href="javascript:WUI.loadScript('page/mod_工单工时统计.js')">工单工时统计</a>
用WUI.loadScript可以动态加载JS文件,web和page目录下的JS文件默认都是禁止缓存的,因此修改文件后再点菜单可立即生效无须刷新。
在文件page/mod_工单工时统计.js
中写报表逻辑,并对“数量”列进行定制:
function show工单工时统计()
{
DlgReportCond.show(function (data) {
var queryParams = WUI.getQueryParam({createTm: [data.tm1, data.tm2]});
var url = WUI.makeUrl("Ordr.query", { res: 'id 工单号, createTm 生产日期, itemCode 产品编码, itemName 产品名称, cate2Name 产品系列, itemCate 产品型号, qty 数量, mh 理论工时, mh1 实际工时', pagesz: -1 });
WUI.showPage("pageSimple", "工单工时统计!", [url, queryParams, onInitGrid]);
});
function onInitGrid(jpage, jtbl, dgOpt, columns, data)
{
// dgOpt: datagrid的选项,如设置 dgOpt.onClickCell等属性
// columns: 列数组,可设置列的formatter等属性
// data: ajax得到的原始数据
$.each(columns, function (i, col) {
if (col.field == "数量")
col.formatter = formatter_数量;
});
// console.log(columns);
// var btn = { text: "XX", iconCts: "icon-search", handler: ... };
// 替换工具栏按钮
// dgOpt.toolbar = WUI.dg_toolbar(jtbl, null, btn1);
// 或是:追加工具栏按钮
// dgOpt.toolbar.push.apply(dgOpt.toolbar, WUI.dg_toolbar(jtbl, null, btn1));
// 不要工具栏
// dgOpt.toolbar = null;
}
function formatter_数量(value, row) {
if (!value)
return;
return WUI.makeLink(value, function () {
var orderId = row.工单号;
var pageFilter = {cond: {orderId: orderId }};
WUI.showPage("pageSn", {title: "工件-工单" + orderId, pageFilter: pageFilter});
});
}
}
以下划线结尾的列不显示,也不导出。
示例:显示工艺并可点击打开工艺对话框。
var url = WUI.makeUrl("Ordr.query", { res: 'id 工单号, flowId flowId_, flowName 工艺' });
WUI.showPage("pageSimple", "工单工时统计!", [url, null, onInitGrid]);
function onInitGrid(jpage, jtbl, dgOpt, columns, data)
{
$.each(columns, function (i, col) {
if (col.field == "工艺")
col.formatter = Formatter.linkTo("flowId_", "#dlgFlow");
});
}
上面flowId字段只用于链接,不显示,也不会导出。
在显示表格同时可以显示统计图。它与表格共用数据源。
// 也可以与pageSimple列表页结合,同时显示列表页和统计图:
var url = WUI.makeUrl("Ordr.query", {
gres: "y 年,m 月",
res: "count(*) 总数",
orderby: "y,m"
});
var showChartParam = [ {tmUnit: "y,m"} ];
WUI.showPage("pageSimple", "订单统计!", [url, null, null, showChartParam]);
其中showChartParam必须指定为一个数组,即WUI.showDlgChart函数的后三个参数:[rs2StatOpt, seriesOpt, chartOpt]
@see showDlgChart 显示统计图
如果列很多,会显示滚动条。
通过在queryParams中设置frozon,可指定前几列可以设置为冻结列,不跟随滚动条。
示例:
WUI.showPage("pageSimple", "订单月报表!", [WUI.makeUrl("Ordr.query"), {frozen: 1}]);
类似地,若要冻结前2列,则指定{frozen: 2}
。
在onInitGrid中设置dgOpt.treeField就会显示为树表;
可直接指定字段列表dgOpt.columns(默认是用的url接口返回的字段列表)。
示例,以下url返回树型结构的[{value, title, nodeClazz, @children}]
这几列,让其显示为树型:
var url = WUI.makeUrl("Muyuan.fields");
WUI.showPage("pageSimple", "场区!", [url, null, onInitGrid]);
function onInitGrid(jpage, jtbl, dgOpt, columns, data) {
// dgOpt: datagrid的选项,如设置 dgOpt.onClickCell等属性
// columns: 列数组,可设置列的formatter等属性
// data: ajax得到的原始数据
$.extend(dgOpt, {
treeField:"title",
url: WUI.makeUrl("Muyuan.fields"),
idField: "value",
textField: "title",
columns: [[
{field:'value',title:'编号',width:200},
{field:'title',title:'名称',width:500},
{field:'nodeClazz',title:'类别',width:200, formatter:Formatter.enum({
"region":"区域", "area":"子公司","field":"场区"
})}
]],
});
}
DlgReportCond.show(onOk, meta?, inst?);
用于显示查询对话框,默认包含tm1, tm2字段对应用户输入的开始、结束时间。
可以用WUI.getQueryCond生成查询条件,它支持任意一个字段为空的情况下也能生成正确的条件表达式。
DlgReportCond.show(function (data) {
var cond = WUI.getQueryCond({tm: [data.tm1, data.tm2]});
console.log(data, cond); // 示例: data={tm1: "2021-1-1 8:00", tm2: ""}, cond="tm>='2021-1-1 8:00'"
})
也可以增加自定义字段:
var meta = [
// title, dom, hint?
{title: "状态", dom: '<select name="status" class="my-combobox" data-options="jdEnumMap:OrderStatusMap"></select>'},
{title: "订单号", dom: "<textarea name='param' rows=5></textarea>", hint: '每行一个订单号'}
];
DlgReportCond.show(function (data) {
console.log(data); // data中增加了status和param字段(如果没有填写则没有)
var cond = WUI.getQueryCond({tm: [data.tm1, data.tm2], status: data.status, param: data.param});
console.log(cond);
}, meta)
如果有多个不同的使用场景,对应的自定义字段不同,可以指定实例名来实现,如订单和工单的查询条件有些不同,可分开写:
DlgReportCond.show(function (data) {
console.log(data);
}, meta1, "订单");
DlgReportCond.show(function (data) {
console.log(data);
}, meta2, "工单");
如果完全自定义对话框(连默认的日期选项也不要),可直接使用showDlgByMeta:
var meta = [
{title: "序列号", dom: '<input name="code" class="easyui-validatebox" required>'}
];
WUI.showDlgByMeta(meta, {modal:false, reset:false, title: "设备生命周期", onOk: async function (data) {
var pageFilter = {cond: data};
WUI.showPage("pageSn", {title: "设备生命周期-工件!", pageFilter: pageFilter});
}});
示例2: 对话框上面有计划日期、硫化区域、尺寸三个定制字段,其中计划日期是日期字段,通过{data: {dt: xxx}}
为它指定初始值;
“硫化区域”是个下拉框,使用my-combobox组件,通过jdEnumMap指定下拉列表项。在onOk回调中生成查询url并以pageSimple来显示。
在onInitGrid中定制了工具栏,dgOpt.toolbar置空表示不要工具栏了。
window.MachineMap = {
"~B*": "B区",
"~R*": "C区",
"~D*": "D区"
};
var meta = [
{title: "计划日期", dom: '<input name="dt" class="easyui-datebox" required>'},
{title: "硫化区域", dom: '<input name="machine" class="my-combobox" data-options="jdEnumMap: MachineMap">'},
{title: "尺寸", dom: '<input name="size">'}
];
var dt = new Date().addDay(-1); // 默认前1天
WUI.showDlgByMeta(meta, {modal:false, title: "生产计划查询", data: {dt: dt.format("yyyy-mm-dd")}, onOk: function (data) {
var url = WUI.makeUrl("Plan.query", data);
WUI.showPage("pageSimple", "生产计划!", [url, null, onInitGrid]);
}});
function onInitGrid(jpage, jtbl, dgOpt, columns, data) {
// 列表页不显示工具栏
dgOpt.toolbar = null;
}
@see showDlgByMeta
在一个页面中显示一个或多个Tabs组件(如果有多个Tabs,支持上下或左右排列),每个Tabs又可放置多个独立的页面,即:
<tabsA>
`- <tabA1-page1> <tabA2-page2>
<tabsB>
`- <tabB1-page3> <tabB2-page4>
用法示例:
// 显示1个tabs组件,id为"设备生命周期_1"(之后showPage要用); title加"!"后缀表示再次打开时刷新该页面
WUI.showPage("pageTab", "设备生命周期!");
// 两个tabs组件,上下各50%,每个tabs的id分别为"设备生命周期_1"和"设备生命周期_2"(之后showPage要用)
WUI.showPage("pageTab", {title: "设备生命周期!", tabs: "50%,50%"});
// 两个tabs,左右各50%
WUI.showPage("pageTab", {title: "设备生命周期!", tabs: "50%|50%"});
在指定的Tabs中显示页面可以用:
// 在第1个Tabs中显示页面, 用target参数指定tabs
WUI.showPage("pageSn", {target: "设备生命周期_1"} );
// 在第1个tabs中再显示1个页面
var url = WUI.makeUrl("Ordr.query", {
gres:"y 年,m 月, userId",
res:"userName 客户, COUNT(*) 订单数, SUM(amount) 总金额",
hiddenFields: "userId",
orderby: "总金额 DESC"
});
WUI.showPage("pageSimple", {target: "设备生命周期_1", title:"订单月报表!"}, [url]);
// 在第2个Tabs中显示页面
WUI.showPage("pageUi", {target: "设备生命周期_2", uimeta: "售后工单"});
注意:target以pageTab指定的title开头,再加上"_1"等后缀;
特别地,若title中含有空格、"-"等符号,则只取符号前的字符。
比如title="工单 - 1",则target可以用"工单_1"或"工单_2";而title="工单1"时,target可以用"工单1_1", "工单1_2"