mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-10-29 08:58:22 +08:00
- 列筛选面板勾选状态按页 localStorage 记忆,刷新后表格/卡片两侧一致恢复 - 卡片设置按钮由右侧图标区移至左侧操作按钮区,面板向下展开不再超出视口 - 入口改按钮直绑点击并抽出 toggleCardSettingPanel 复用,不再走 defaultToolbar 注入
2532 lines
124 KiB
PHP
2532 lines
124 KiB
PHP
(function () {
|
||
var ua = window.UA_ADMIN;
|
||
var form = window.UA_CORE.form,
|
||
layer = window.UA_CORE.layer,
|
||
table = window.UA_CORE.table,
|
||
laydate = window.UA_CORE.laydate,
|
||
laytpl = window.UA_CORE.laytpl,
|
||
util = window.UA_CORE.util;
|
||
|
||
var lastTableWhere = {};
|
||
var colToggledTimers = {};
|
||
var selectMode, selectConfirmCallback;
|
||
|
||
// reloadData 引擎接线(加载期执行一次):受控表(UA_TABLE_STORE 已注册)的数据
|
||
// 重载分流到 store,非受控表透传原引擎调用。原引用只备份一次,防止重复加载时
|
||
// 把包装层当原引用再包一层;透传用 arguments 保真,reloadData(id, opts, type)
|
||
// 三参形态的 type 语义不变。
|
||
if (table.__uaOriginalReloadData === undefined) {
|
||
table.__uaOriginalReloadData = table.reloadData;
|
||
}
|
||
var originalTableReloadData = table.__uaOriginalReloadData;
|
||
table.reloadData = function (tableId, opts) {
|
||
if (window.UA_TABLE_STORE.has(tableId)) {
|
||
return window.UA_TABLE_STORE.reload(tableId, opts);
|
||
}
|
||
return originalTableReloadData.apply(this, arguments);
|
||
};
|
||
|
||
// 受控表渲染接线(加载期执行一次):store 的 fetch/reload 只负责取数与状态、
|
||
// 不触发渲染,这里在唯一的数据到达点(store.reload 的成功回调)注入引擎
|
||
// 纯渲染——首屏/搜索/排序/switch 回滚/删除刷新全部经 shim 汇入 reload。
|
||
// 渲染必须走备份的原引擎引用:走 table.reloadData 会被上方 shim 路由回
|
||
// store 造成请求循环。initSort 透传保持表头排序箭头(与引擎 url 模式的
|
||
// listenTableSort 行为一致)。受控表分页条由 render() 内的
|
||
// syncControlledPager 自管:store 的 pager.jump 在内部直呼 fetch,外部
|
||
// 注入不了渲染回调,故 register 时不传 pagerElem 使 store 分页静默跳过,
|
||
// laypage 的 jump 经 shim(table.reloadData)回 store.reload 形成闭环。
|
||
var syncControlledPager = function (tableId) {
|
||
var state = window.UA_TABLE_STORE.dataProvider.getState(tableId);
|
||
if (!state) {
|
||
return;
|
||
}
|
||
layui.laypage.render({
|
||
elem: 'ua-store-pager-' + tableId,
|
||
count: state.count,
|
||
curr: state.page.curr,
|
||
limit: state.page.limit,
|
||
limits: state.limits,
|
||
layout: ['count', 'prev', 'page', 'next', 'limit', 'skip'],
|
||
jump: function (obj, first) {
|
||
if (!first) {
|
||
table.reloadData(tableId, {
|
||
page: {curr: obj.curr, limit: obj.limit}
|
||
});
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
// ========== 多形态渲染(五级模板协议)==========
|
||
// 形态注册表:tableId → {elem, cols, init, entries, byName, current}。
|
||
// render() 受控分支写入;受控渲染 wrapper 与 ua.table.renderLayoutForm 消费。
|
||
var controlledLayouts = {};
|
||
|
||
// 已回退告警去重表(同一形态条目只 warn 一次)
|
||
var warnedLayouts = {};
|
||
|
||
// (a) layoutList 规范化:字符串 'table'/'card' 转对象 {name, title};自定义条目
|
||
// {name, title, icon?, tplFunction?, tplLay?, tplVue?, tplLayId?, tplVueId?} 原样保留
|
||
var normalizeLayoutList = function (layoutList) {
|
||
var stringTitles = {table: '表格', card: '卡片'};
|
||
var result = [];
|
||
$.each(layoutList || [], function (i, item) {
|
||
if (typeof item === 'string') {
|
||
result.push({name: item, title: stringTitles[item] || item});
|
||
} else if (item && typeof item === 'object' && item.name) {
|
||
result.push(item);
|
||
}
|
||
});
|
||
return result;
|
||
};
|
||
|
||
// ========== 内置 card 形态(name='card' 且无自定义钩子时启用)==========
|
||
// 结构参照旧寄生实现的 header/body/footer 骨架(代码全新):header(标题 +
|
||
// 勾位图标)、body(键值对字段列表)、footer(create_time + 操作按钮)。
|
||
// 数据全部来自渲染上下文(_cells/_operatHtml/_checked/_disabled),列元信息读
|
||
// controlledLayouts 的 cols(本闭包内),无任何业务字段硬编码。
|
||
//
|
||
// 【与 buildLayoutContext 同规则的单元格列筛选】操作列无 field,须先于
|
||
// field 判定识别;checkbox/radio 等无字段列跳过(勾选语义由勾位图标承接);
|
||
// 隐藏列(col.hide)跳过——不进卡片行、不产 _cells,与表格"隐藏列不渲染
|
||
// 单元格"语义一致
|
||
var builtinCardCellCols = function (tableId) {
|
||
var cols = (controlledLayouts[tableId] && controlledLayouts[tableId].cols[0]) || [];
|
||
var cellCols = [];
|
||
$.each(cols, function (i, col) {
|
||
if (!col) {
|
||
return;
|
||
}
|
||
if (col.templet === ua.table.tool) {
|
||
return;
|
||
}
|
||
if (!col.field) {
|
||
return;
|
||
}
|
||
if (col.hide) {
|
||
return;
|
||
}
|
||
cellCols.push(col);
|
||
});
|
||
return cellCols;
|
||
};
|
||
|
||
// 内置 card 单行模板(tplFunction 调用约定:this/第一参 = 行上下文,第二参 = 列表上下文)
|
||
// - 标题列 = 首个字段列(含 checkbox 列的表格即"第二列",无 checkbox 表格即"首字段")
|
||
// - 标题列与 create_time 列不重复进 body(分别在 header/footer 展示)
|
||
// - create_time 无对应列时 footer 时间留空,不回退原始字段值(避免裸时间戳)
|
||
var builtinCardTemplate = function (row, list) {
|
||
var cellCols = builtinCardCellCols(list.tableId);
|
||
var titleCol = cellCols.length > 0 ? cellCols[0] : null;
|
||
|
||
var html = '<div class="ua-data-card'
|
||
+ (row._checked ? ' ua-data-card-checked' : '')
|
||
+ (row._disabled ? ' ua-data-card-disabled' : '')
|
||
+ '" data-table-id="' + list.tableId + '" data-id="' + row.id + '">';
|
||
|
||
html += '<div class="ua-data-card-header">';
|
||
html += '<div class="ua-data-card-title">' + (titleCol ? (row._cells[titleCol.field] || '') : '') + '</div>';
|
||
html += '<div class="ua-data-card-check"><i class="layui-icon layui-icon-ok-circle"></i></div>';
|
||
html += '</div>';
|
||
|
||
html += '<div class="ua-data-card-body">';
|
||
$.each(cellCols, function (i, col) {
|
||
if (titleCol && col.field === titleCol.field) {
|
||
return;
|
||
}
|
||
if (col.field === 'create_time') {
|
||
return;
|
||
}
|
||
html += '<div class="ua-data-card-item">'
|
||
+ '<span class="ua-data-card-item-title">' + (col.title || col.field) + '</span>'
|
||
+ '<span class="ua-data-card-item-value">' + (row._cells[col.field] || '') + '</span>'
|
||
+ '</div>';
|
||
});
|
||
html += '</div>';
|
||
|
||
html += '<div class="ua-data-card-footer">';
|
||
html += '<div class="ua-data-card-time">' + (row._cells['create_time'] || '') + '</div>';
|
||
html += '<div class="ua-data-card-operat">' + (row._operatHtml || '') + '</div>';
|
||
html += '</div>';
|
||
|
||
html += '</div>';
|
||
return html;
|
||
};
|
||
|
||
// 统一行选中委托(任务 18):模板渲染容器内凡带 data-table-id + data-id 属性
|
||
// 的行元素——内置 card 的 .ua-data-card 与自定义 laytpl/Vue 模板的卡片/勾位
|
||
// 元素——仅当点击落在行内 .ua-data-card-check 勾位元素内才切换选中,全部走
|
||
// 本委托,无任何形态专用事件。模板作者契约:给行根元素(或勾位元素)加这两
|
||
// 属性、勾位区域挂 .ua-data-card-check 类即获得点选交互,无需页面级监听。
|
||
// 交互元素(按钮/表单控件/layui 美化层)豁免不触发勾选;禁用行
|
||
// (ua-data-card-disabled 标记或行 LAY_DISABLED)整元素不可勾选。选择态落
|
||
// store(与引擎勾选同一事实源,批量删除经 ua.table.checkStatus 分发即取到),
|
||
// 随后走快速路径按模式只改勾位卡片 class 回显(radio 替换 / checkbox 切换),
|
||
// 目标卡元素找不到时兜底整容器重渲模板管线回显勾位标记。
|
||
$(document).on('click', '[data-table-id][data-id]', function (e) {
|
||
var $card = $(this);
|
||
var tableId = $card.attr('data-table-id');
|
||
// 仅模板渲染容器内的行元素生效(页面其他区域的 data-id 元素不受影响)
|
||
if ($card.closest('#ua-layout-' + tableId).length === 0) {
|
||
return;
|
||
}
|
||
if ($card.hasClass('ua-data-card-disabled')) {
|
||
return;
|
||
}
|
||
// 仅勾位点击生效:点卡片正文不触发勾选(点正文零开销)
|
||
if ($(e.target).closest('.ua-data-card-check').length === 0) {
|
||
return;
|
||
}
|
||
if ($(e.target).closest('a,button,input,select,textarea,label,.layui-form-switch,.layui-form-checkbox,.layui-form-radio').length) {
|
||
return;
|
||
}
|
||
var state = window.UA_TABLE_STORE && window.UA_TABLE_STORE.dataProvider.getState(tableId);
|
||
if (!state) {
|
||
return;
|
||
}
|
||
var row = null;
|
||
var rowId = $card.attr('data-id');
|
||
$.each(state.currentPageData || [], function (i, item) {
|
||
if (String(item.id) === String(rowId)) {
|
||
row = item;
|
||
return false;
|
||
}
|
||
});
|
||
if (!row || row.LAY_DISABLED) {
|
||
return;
|
||
}
|
||
// 快速路径(pro#4):不做整容器重渲,仅按模式调整勾位卡片 class——
|
||
// radio 替换(清旧卡、点新卡,重复点同一卡净效果保持选中)、checkbox
|
||
// 切换(含再点取消);DOM 不重建,含图卡片不闪。自定义模板未按契约
|
||
// 输出勾位元素时兜底整容器重渲,不丢功能。
|
||
var prevRadioId = (state.selectMode === 'radio' && state.selectedIds && state.selectedIds.length)
|
||
? state.selectedIds[0]
|
||
: null;
|
||
window.UA_TABLE_STORE.selection.set(tableId, row);
|
||
var $target = $('#ua-layout-' + tableId).find('[data-id="' + rowId + '"]');
|
||
if ($target.length === 0) {
|
||
ua.table.renderLayoutForm(tableId);
|
||
return;
|
||
}
|
||
if (state.selectMode === 'radio') {
|
||
if (prevRadioId !== null && String(prevRadioId) !== String(rowId)) {
|
||
$('#ua-layout-' + tableId).find('[data-id="' + prevRadioId + '"]').removeClass('ua-data-card-checked');
|
||
}
|
||
$target.addClass('ua-data-card-checked');
|
||
} else {
|
||
$target.toggleClass('ua-data-card-checked');
|
||
}
|
||
});
|
||
|
||
// (b) 模板引擎优先级解析:tplFunction > tplLay > tplVue > tplLayId > tplVueId,
|
||
// 提供一种即生效;五种全无且非 table 条目 → console.warn 并回退 table。
|
||
// lay/layId(任务 16,layui.laytpl)与 vue/vueId(任务 17,全局 Vue 2.6.10
|
||
// 运行时编译)均挂载于 renderLayoutForm。
|
||
var resolveLayoutEngine = function (entry) {
|
||
if (entry.name === 'table') {
|
||
return {kind: 'table', entry: entry};
|
||
}
|
||
// 内置 card 形态:name='card' 且未提供任何模板钩子时挂内置模板;提供了
|
||
// 钩子(tplFunction/tplLay/tplVue/tplLayId/tplVueId 任一)则照常走五级
|
||
// 优先级,自定义实现完全覆盖内置形态。builtinCard 标记供模板管线给容器
|
||
// 挂网格布局类(.ua-layout-cards)
|
||
if (entry.name === 'card') {
|
||
var hasHook = typeof entry.tplFunction === 'function'
|
||
|| (typeof entry.tplLay === 'string' && entry.tplLay !== '')
|
||
|| (typeof entry.tplVue === 'string' && entry.tplVue !== '')
|
||
|| (typeof entry.tplLayId === 'string' && entry.tplLayId !== '')
|
||
|| (typeof entry.tplVueId === 'string' && entry.tplVueId !== '');
|
||
if (!hasHook) {
|
||
var builtinEntry = $.extend({}, entry, {tplFunction: builtinCardTemplate, builtinCard: true});
|
||
return {kind: 'function', entry: builtinEntry};
|
||
}
|
||
}
|
||
if (typeof entry.tplFunction === 'function') {
|
||
return {kind: 'function', entry: entry};
|
||
}
|
||
if (typeof entry.tplLay === 'string' && entry.tplLay !== '') {
|
||
return {kind: 'lay', entry: entry};
|
||
}
|
||
if (typeof entry.tplVue === 'string' && entry.tplVue !== '') {
|
||
return {kind: 'vue', entry: entry};
|
||
}
|
||
if (typeof entry.tplLayId === 'string' && entry.tplLayId !== '') {
|
||
return {kind: 'layId', entry: entry};
|
||
}
|
||
if (typeof entry.tplVueId === 'string' && entry.tplVueId !== '') {
|
||
return {kind: 'vueId', entry: entry};
|
||
}
|
||
if (!warnedLayouts[entry.name]) {
|
||
warnedLayouts[entry.name] = true;
|
||
console.warn('[ua.table] layoutList 条目 "' + entry.name + '" 未提供任何模板钩子(tplFunction/tplLay/tplVue/tplLayId/tplVueId),已回退 table 形态');
|
||
}
|
||
return {kind: 'table', entry: entry};
|
||
};
|
||
|
||
// 当前形态解析:先取 options.layoutDefault(缺省 'table');运行期切形态
|
||
// 经 ua.table.setLayoutForm 改写 current。条目缺失/未登记时恒 table。
|
||
var resolveCurrentLayout = function (tableId) {
|
||
var layout = controlledLayouts[tableId];
|
||
var entry = (layout && layout.byName[layout.current]) || {name: 'table', title: '表格'};
|
||
return resolveLayoutEngine(entry);
|
||
};
|
||
|
||
// 模板管线失败回退(tplLayId/tplVueId 节点缺失 / laytpl 渲染错误 / Vue 未引入
|
||
// 或挂载渲染失败):当前形态归位 'table'(与"无钩子条目回退"同语义,幂等、
|
||
// 不写 localStorage 记忆,用户重新切换可再次触发),容器销毁 Vue 实例后清空
|
||
// 隐藏。调用方据 false 返回值做引擎纯渲染,随后的 syncLayoutVisibility 按
|
||
// table kind 复显引擎数据区,避免"容器残留上一形态旧内容 + 引擎数据被隐藏"
|
||
// 的视觉错位。
|
||
var fallbackLayoutToTable = function (tableId, $container) {
|
||
var layout = controlledLayouts[tableId];
|
||
if (layout) {
|
||
layout.current = 'table';
|
||
}
|
||
destroyLayoutVueInstances(tableId);
|
||
if ($container && $container.length) {
|
||
$container.addClass('layui-hide').empty();
|
||
}
|
||
return false;
|
||
};
|
||
|
||
// 卡片操作按钮折叠收纳:模板管线渲染完成后扫描各卡片操作区,直接子级
|
||
// a/button 超过阈值 3 时从第 4 个起移入隐藏收纳容器,并在操作区末尾追加
|
||
// "更多"开关锚点(展开委托在 render() 受控接线创建模板容器处绑定)。
|
||
// 阈值写死不配置;调用点在 form.render 之前——按钮为锚点无逐节点事件,
|
||
// 先移 DOM 再美化安全。Vue 分支不经过本函数(模板自理)。
|
||
var applyCardOperatFold = function ($container) {
|
||
$container.find('.ua-data-card-operat').each(function () {
|
||
var $operat = $(this);
|
||
var $buttons = $operat.children('a, button');
|
||
if ($buttons.length <= 3) {
|
||
return;
|
||
}
|
||
var $moreWrap = $('<div class="ua-card-operat-more-wrap layui-hide"></div>');
|
||
$buttons.slice(3).appendTo($moreWrap);
|
||
$moreWrap.appendTo($operat);
|
||
$operat.append($('<a class="ua-card-operat-more"><i class="layui-icon layui-icon-more"></i></a>'));
|
||
});
|
||
};
|
||
|
||
// ========== vue/vueId 引擎(任务 17,全局 Vue 2.6.10 运行时编译)==========
|
||
// 渲染模式为列表级单实例:模板内 v-for="d in rows" 循环行,一次渲染只挂载
|
||
// 一个实例(逐行 new Vue 的成本随行数线性膨胀,销毁纪律也难以收口)。
|
||
// 实例存活表:tableId → 当前实例数组。
|
||
var layoutVueInstances = {};
|
||
|
||
// 销毁本表全部存活实例(渲染入口 / fallbackLayoutToTable /
|
||
// syncLayoutVisibility 三处统一调用,幂等)。$destroy 只拆 watcher 与事件,
|
||
// 不动 DOM,容器清空由调用方完成。任何重渲路径(store done 管线/搜索/
|
||
// 切换/回退)都必须先销毁旧实例再挂新实例——销毁重建式是"实例存活期禁止
|
||
// 响应式数据变更"约束(约束 2)的落地方式:form.render 美化插入的 DOM
|
||
// (div.layui-form-switch 等)不受 Vue 虚拟 DOM 管理,存活期 re-patch 会
|
||
// 清除它们。
|
||
var destroyLayoutVueInstances = function (tableId) {
|
||
var list = layoutVueInstances[tableId] || [];
|
||
$.each(list, function (i, vm) {
|
||
try {
|
||
vm.$destroy();
|
||
} catch (e) {}
|
||
});
|
||
layoutVueInstances[tableId] = [];
|
||
};
|
||
|
||
// Vue 生产构建中模板编译错误不抛异常也不打日志(dev 告警被裁剪),坏模板
|
||
// 只会在挂载渲染时经全局 errorHandler 暴露——链式接管 Vue.config.errorHandler:
|
||
// 本框架实例(vm.__uaLayout 标记)按形态名 console.error 并记录
|
||
// lastVueLayoutError,由 renderLayoutForm 探测后回退;其余实例
|
||
// (propertyInput/tagInput 等页面自有 Vue)透传既有处理器或复刻默认行为。
|
||
var lastVueLayoutError = null;
|
||
var vueErrorHandlerInstalled = false;
|
||
var installVueErrorHandler = function () {
|
||
if (vueErrorHandlerInstalled) {
|
||
return;
|
||
}
|
||
vueErrorHandlerInstalled = true;
|
||
var previous = window.Vue.config.errorHandler;
|
||
window.Vue.config.errorHandler = function (err, vm, info) {
|
||
if (vm && vm.__uaLayout) {
|
||
var meta = vm.__uaLayout;
|
||
lastVueLayoutError = {tableId: meta.tableId, name: meta.name};
|
||
console.error('[ua.table] Vue 模板渲染失败(形态 "' + meta.name + '"):'
|
||
+ ((err && err.message) || err) + ',本次回退 table 形态');
|
||
return;
|
||
}
|
||
if (typeof previous === 'function') {
|
||
previous(err, vm, info);
|
||
return;
|
||
}
|
||
console.error(err);
|
||
};
|
||
};
|
||
|
||
// 形态可见性同步:引擎数据区(表头+表体+合计行)与模板容器互斥显隐,
|
||
// 引擎视图的工具栏(刷新/新增/批量删除等)两形态共用保持可见(print 与
|
||
// 卡片设置按钮除外,随形态互斥显隐),搜索区本就挂在表格元素之前不受影响。
|
||
// 非 table 形态下引擎根本不渲染行数据
|
||
// (wrapper 非 table 分支不走引擎渲染),并非旧寄生实现"引擎恒渲+CSS 隐藏"。
|
||
var syncLayoutVisibility = function (tableId) {
|
||
var layout = controlledLayouts[tableId];
|
||
if (!layout) {
|
||
return;
|
||
}
|
||
var $view = $(layout.elem).siblings('.layui-table-view').first();
|
||
var $box = $view.find('.layui-table-box').first();
|
||
var $total = $view.find('.layui-table-total').first();
|
||
var $container = $('#ua-layout-' + tableId);
|
||
var isTable = resolveCurrentLayout(tableId).kind === 'table';
|
||
if (isTable) {
|
||
destroyLayoutVueInstances(tableId);
|
||
$box.add($total).removeClass('layui-hide');
|
||
$container.addClass('layui-hide').empty();
|
||
} else {
|
||
$box.add($total).addClass('layui-hide');
|
||
$container.removeClass('layui-hide');
|
||
}
|
||
// 工具栏形态联动:print 仅表格形态有意义;卡片设置仅非表格形态有意义。
|
||
// filter(LAYTABLE_COLS)与视图切换(TABLE_LAYOUT)两形态常驻不动
|
||
$view.find('[lay-event="LAYTABLE_PRINT"]').toggleClass('layui-hide', !isTable);
|
||
$view.find('.ua-card-setting-btn').toggleClass('layui-hide', isTable);
|
||
};
|
||
|
||
// 卡片设置面板开关(pro#5 迭代:入口由右侧图标区移至左侧工具栏末尾的
|
||
// .ua-card-setting-btn 按钮,点击直调本函数;不再走 layui toolbar 事件)。
|
||
// 含面板创建/移除/开关/排序/外点收起全套逻辑,面板 append 在按钮元素内
|
||
//(按钮 position:relative,面板向右向下展开),面板内交互均阻断冒泡
|
||
var toggleCardSettingPanel = function (tableId) {
|
||
var cardSettingId = 'ua-card-setting-' + tableId;
|
||
var $toolbarSettingElem = $(controlledLayouts[tableId].elem).siblings('.layui-table-view').first().find('.ua-card-setting-btn');
|
||
var $cardSettingElem = $('#' + cardSettingId);
|
||
if ($cardSettingElem.length > 0) {
|
||
$cardSettingElem.remove();
|
||
return;
|
||
}
|
||
|
||
// 配置段 + 排序段(面板每次点按钮重建,排序段随建随取
|
||
// store 当前态,表头排序/搜索后重开面板即最新)
|
||
var cardSettingHtml = '<div id="' + cardSettingId + '" class="ul-nav-block ua-card-setting-panel">';
|
||
var operatAlwaysOn = ua.getPageSetting('card-operat-always', '') === '1';
|
||
cardSettingHtml += '<div class="ua-card-setting-title">配置</div>';
|
||
cardSettingHtml += '<div class="ua-card-setting-item"><span>操作按钮始终显示</span>'
|
||
+ '<span class="ua-card-setting-switch' + (operatAlwaysOn ? ' on' : '') + '"></span></div>';
|
||
cardSettingHtml += '<div class="ua-card-setting-title">排序</div>';
|
||
// 当前排序读 store 单一事实源(where.sort 形状为 {字段名: 'asc'|'desc'}
|
||
// 单键映射,与 store setSort/buildRequestData 同构)
|
||
var cardSortState = window.UA_TABLE_STORE.dataProvider.getState(tableId);
|
||
var cardSortCurrent = cardSortState && cardSortState.where ? cardSortState.where.sort : null;
|
||
$.each(((controlledLayouts[tableId] && controlledLayouts[tableId].cols[0]) || []), function (i, col) {
|
||
if (!col || !col.field || col.sort === false || col.hide) {
|
||
return;
|
||
}
|
||
var sortClass = '';
|
||
var sortIconClass = 'layui-icon layui-hide';
|
||
if (cardSortCurrent && cardSortCurrent[col.field]) {
|
||
sortClass = ' current';
|
||
sortIconClass = 'layui-icon layui-icon-' + (cardSortCurrent[col.field] === 'desc' ? 'down' : 'up');
|
||
}
|
||
cardSettingHtml += '<div class="ua-card-setting-sort-item item' + sortClass + '" data-field="' + col.field + '">'
|
||
+ (col.title || col.field)
|
||
+ ' <i class="' + sortIconClass + '"></i></div>';
|
||
});
|
||
cardSettingHtml += '</div>';
|
||
|
||
$cardSettingElem = $(cardSettingHtml).appendTo($toolbarSettingElem);
|
||
|
||
// 面板内条目点击不冒泡(排序段等后续 .item 条目沿用)
|
||
$cardSettingElem.on('click', '.item', function (event) {
|
||
event.stopPropagation();
|
||
});
|
||
|
||
// 开关:页级记忆 + 模板容器即时生效,不收起面板不重渲
|
||
$cardSettingElem.on('click', '.ua-card-setting-switch', function (event) {
|
||
var on = !$(this).hasClass('on');
|
||
ua.setPageSetting('card-operat-always', on ? '1' : '');
|
||
$(this).toggleClass('on', on);
|
||
$('#ua-layout-' + tableId).toggleClass('ua-card-operat-always', on);
|
||
event.stopPropagation();
|
||
});
|
||
|
||
// 排序:循环 升 → 降 → 取消。与 listenTableSort 同构:
|
||
// 以 lastTableWhere 为基底就地改 sort,显式传 where
|
||
// (store.reload 仅在 opts.where 存在时经 setWhere 更新
|
||
// 请求参数,漏传排序到不了服务端);initSort 供表头
|
||
// 箭头(取消时省略);当前态以 store getState 为准
|
||
// (搜索路径会整体覆盖 lastTableWhere,admin-table.js
|
||
// 侧变量不可作判定依据)。点击后收起面板
|
||
$cardSettingElem.on('click', '.ua-card-setting-sort-item', function (event) {
|
||
event.stopPropagation();
|
||
var field = $(this).data('field');
|
||
var sortState = window.UA_TABLE_STORE.dataProvider.getState(tableId);
|
||
var currentType = sortState && sortState.where && sortState.where.sort ? (sortState.where.sort[field] || null) : null;
|
||
var lastWhere = lastTableWhere[tableId] ? lastTableWhere[tableId] : {};
|
||
var reloadOpts = {where: lastWhere};
|
||
if (currentType === 'asc') {
|
||
lastWhere.sort = {};
|
||
lastWhere.sort[field] = 'desc';
|
||
reloadOpts.initSort = {field: field, type: 'desc'};
|
||
} else if (currentType === 'desc') {
|
||
delete lastWhere.sort;
|
||
} else {
|
||
lastWhere.sort = {};
|
||
lastWhere.sort[field] = 'asc';
|
||
reloadOpts.initSort = {field: field, type: 'asc'};
|
||
}
|
||
lastTableWhere[tableId] = lastWhere;
|
||
ua.table.reloadData(tableId, reloadOpts);
|
||
$cardSettingElem.remove();
|
||
$(document).off('click.ua-card-setting');
|
||
});
|
||
|
||
// 点击面板外区域收起(命名空间事件,收起时解除);判定以入口按钮为界
|
||
$(document).off('click.ua-card-setting').on('click.ua-card-setting', function (event) {
|
||
if (!$(event.target).closest($toolbarSettingElem).length) {
|
||
$('#' + cardSettingId).remove();
|
||
$(document).off('click.ua-card-setting');
|
||
}
|
||
});
|
||
};
|
||
|
||
if (window.UA_TABLE_STORE) {
|
||
var storeReload = window.UA_TABLE_STORE.reload;
|
||
window.UA_TABLE_STORE.reload = function (tableId, opts) {
|
||
opts = opts || {};
|
||
var userDone = opts.done;
|
||
var userFail = opts.fail;
|
||
// 模板形态的加载态容器标记(容器内加载样式由任务 14 的内置形态接入)
|
||
var $layoutContainer = $('#ua-layout-' + tableId);
|
||
$layoutContainer.addClass('ua-layout-loading');
|
||
// 失败通道(res.code != 0 / HTTP error)不进 done,需经 fail 摘除加载态,
|
||
// 避免请求失败后容器残留半透明禁点状态
|
||
opts.fail = function () {
|
||
$layoutContainer.removeClass('ua-layout-loading');
|
||
if (typeof userFail === 'function') {
|
||
userFail.apply(null, arguments);
|
||
}
|
||
};
|
||
opts.done = function (res) {
|
||
// 受控渲染分发:table 形态走引擎纯渲染(现状),非 table 形态走
|
||
// 模板管线(渲染上下文 + 行级循环);模板管线不可用(Vue 未引入/
|
||
// 挂载渲染失败;tplLayId/tplVueId 节点缺失/laytpl 渲染错误)时
|
||
// 回退引擎纯渲染兜底
|
||
var engineRender = function () {
|
||
originalTableReloadData.call(table, tableId, {
|
||
data: res.data,
|
||
totalRow: res.totalRow,
|
||
initSort: opts.initSort
|
||
});
|
||
};
|
||
if (resolveCurrentLayout(tableId).kind === 'table' || !ua.table.renderLayoutForm(tableId)) {
|
||
engineRender();
|
||
}
|
||
$layoutContainer.removeClass('ua-layout-loading');
|
||
syncControlledPager(tableId);
|
||
syncLayoutVisibility(tableId);
|
||
if (typeof userDone === 'function') {
|
||
userDone(res);
|
||
}
|
||
};
|
||
return storeReload(tableId, opts);
|
||
};
|
||
}
|
||
|
||
ua.table = {
|
||
// 受控/非受控分发入口(后续任务直呼改造的定义点):非受控路径走备份的
|
||
// 原引擎引用,不经过上方已包装的 table.reloadData,避免递归
|
||
reloadData: function (tableId, opts) {
|
||
if (window.UA_TABLE_STORE.has(tableId)) {
|
||
return window.UA_TABLE_STORE.reload(tableId, opts);
|
||
}
|
||
return originalTableReloadData.apply(this, arguments);
|
||
},
|
||
checkStatus: function (tableId) {
|
||
if (window.UA_TABLE_STORE.has(tableId)) {
|
||
return window.UA_TABLE_STORE.checkStatus(tableId);
|
||
}
|
||
return table.checkStatus(tableId);
|
||
},
|
||
// ========== 多形态渲染管线(五级模板协议)==========
|
||
// (c) 渲染上下文构建器:对 store 当页每行产出
|
||
// {...row, _cells: {field: 列 templet 渲染后的 HTML}, _operatHtml: 操作按钮 HTML,
|
||
// _checked: 是否选中, _disabled: 是否禁选}
|
||
// _cells 复用 formatCols 之后的列渲染器族(image/switch/list/自定义 templet 与
|
||
// 引擎同行为),逐列注入 LAY_COL(与引擎 templet 调用同构:渲染器经 data.LAY_COL
|
||
// 读列配置);_operatHtml 走既有 ua.table.tool(按钮权限 data-* 属性原样输出,
|
||
// 事件靠页面级委托天然覆盖,无旧的"暂时隐藏按钮"逻辑);_checked/_disabled
|
||
// 数据源为 store 选中集合(selectedIds)与行 LAY_DISABLED 标记。
|
||
// 隐藏列(col.hide)跳过——不产 _cells、不进卡片行,与表格"隐藏列不渲染
|
||
// 单元格"语义一致。
|
||
buildLayoutContext: function (tableId) {
|
||
var layout = controlledLayouts[tableId];
|
||
var state = window.UA_TABLE_STORE.dataProvider.getState(tableId);
|
||
var rows = (state && state.currentPageData) || [];
|
||
var selectedIds = (state && state.selectedIds) || [];
|
||
var cols = (layout && layout.cols[0]) || [];
|
||
|
||
var operatCol = null;
|
||
var cellCols = [];
|
||
$.each(cols, function (i, col) {
|
||
if (!col) {
|
||
return;
|
||
}
|
||
// 操作列无 field,须先于 field 判定识别
|
||
if (col.templet === ua.table.tool) {
|
||
operatCol = col;
|
||
return;
|
||
}
|
||
if (!col.field) {
|
||
// checkbox/radio 等无字段列:勾选语义由 _checked/_disabled 与模板作者承接
|
||
return;
|
||
}
|
||
if (col.hide) {
|
||
return;
|
||
}
|
||
cellCols.push(col);
|
||
});
|
||
|
||
var context = {
|
||
tableId: tableId,
|
||
rows: [],
|
||
page: state ? state.page : {curr: 1, limit: 0},
|
||
count: state ? state.count : 0,
|
||
checkedAll: state ? window.UA_TABLE_STORE.selection.isAll(tableId) : false,
|
||
// 面板设置快照:模板经 d._list.settings / list.settings 只读
|
||
settings: {cardOperatAlways: ua.getPageSetting('card-operat-always', '') === '1'}
|
||
};
|
||
|
||
$.each(rows, function (i, row) {
|
||
var rowCtx = $.extend({}, row);
|
||
rowCtx._cells = {};
|
||
$.each(cellCols, function (j, col) {
|
||
var cellData = $.extend({}, row);
|
||
cellData.LAY_COL = col;
|
||
try {
|
||
if (typeof col.templet === 'function') {
|
||
rowCtx._cells[col.field] = col.templet(cellData) || '';
|
||
} else if (typeof col.templet === 'string') {
|
||
rowCtx._cells[col.field] = laytpl(col.templet).render(cellData) || '';
|
||
} else {
|
||
rowCtx._cells[col.field] = '';
|
||
}
|
||
} catch (e) {
|
||
rowCtx._cells[col.field] = '';
|
||
}
|
||
});
|
||
|
||
if (operatCol) {
|
||
var toolData = $.extend({}, row);
|
||
toolData.LAY_COL = operatCol;
|
||
rowCtx._operatHtml = ua.table.tool(toolData);
|
||
} else {
|
||
rowCtx._operatHtml = '';
|
||
}
|
||
|
||
rowCtx._checked = selectedIds.indexOf(String(row.id)) > -1;
|
||
rowCtx._disabled = !!row.LAY_DISABLED;
|
||
|
||
context.rows.push(rowCtx);
|
||
});
|
||
|
||
return context;
|
||
},
|
||
// (d) 行级循环渲染框架:模板只定义单行 HTML 生成函数,框架循环拼接并维护
|
||
// 空态容器;加载态由 store.fetch 的全局 loading 与容器的 ua-layout-loading
|
||
// 标记承接(内置形态的加载态样式在任务 14 接入)。
|
||
//
|
||
// 【tplFunction 调用约定】
|
||
// 单行 HTML = tplFunction.call(rowCtx, rowCtx, listCtx)
|
||
// - this/第一参 = 行上下文 {...row, _cells, _operatHtml, _checked, _disabled}
|
||
// - 第二参 = 列表上下文 {tableId, rows, page, count, checkedAll, settings}
|
||
// - 返回值 = 该行的 HTML 字符串(框架直接拼接,不做转义)
|
||
// 【laytpl 调用约定(tplLay / tplLayId)】
|
||
// 单行 HTML = laytpl(tplLay | $(tplLayId).html()).render(rowCtx)
|
||
// - d = 行上下文,与 tplFunction 第一参同构,另挂 d._list = 列表上下文
|
||
// (与 tplFunction 第二参对齐;laytpl 仅支持单数据对象)
|
||
// - 模板语法(layui 内置 laytpl v2.0.0 实测):{{ d.x }} 与 {{= d.x }}
|
||
// 均经 laytpl.escape 转义;{{- d.x }} 原样输出(_cells/_operatHtml 等
|
||
// HTML 内容必须用此形态);{{# }} 为 JS 语句(if/each 等)
|
||
// - tplLayId 是页面 <script type="text/html"> 节点选择器,节点必须位于
|
||
// 渲染容器之外(下方硬约束 2);script 内容为原始文本,laytpl 的 {{ }}
|
||
// 不会被浏览器解析,.html() 读取即可
|
||
// - laytpl 编译/运行错误不抛异常,render() 返回 "Laytpl Error: ..." 字符串
|
||
// (laytpl 内部已 console.error 细节),框架探测到后按条目名 console.error
|
||
// 并返回 false,调用方回退引擎纯渲染
|
||
// 【Vue 调用约定(tplVue / tplVueId,任务 17)】
|
||
// 列表级单实例:框架把模板包进单一根节点 <div class="ua-layout-vue">
|
||
// (Vue 2 实例模板根元素禁用 v-for 且生产构建该错误静默,包裹层规避
|
||
// 全部根约束问题,多根模板也因此可用),行循环由模板作者用
|
||
// v-for="d in rows" 承接:
|
||
// - rows = 行上下文数组,d 与 tplFunction/tplLay 的行上下文同构
|
||
// (d._cells.xxx / d._operatHtml / d._checked / d._disabled);
|
||
// - list = 列表上下文 {tableId, rows, page, count, checkedAll, settings}
|
||
// (laytpl 的 d._list 同物;Vue 实例对 _/$ 前缀 data 键不做代理,
|
||
// 裸标识符 _list 会 ReferenceError,故 Vue 形态改名为 list);
|
||
// - _cells/_operatHtml 含 HTML,必须用 v-html 输出,纯文本用 {{ }};
|
||
// - tplVueId 是页面 <script type="text/x-template"> 节点选择器,节点
|
||
// 必须位于渲染容器之外(下方硬约束 2),.html() 读取原始文本;
|
||
// - 挂载成功后一次性 form.render(约束 1);实例存活期禁止响应式数据
|
||
// 变更(约束 2):渲染上下文经 Object.freeze 冻结,数据不可变即不会
|
||
// re-patch,form.render 美化 DOM 不被清除,更新一律销毁重建;
|
||
// - 模板编译错误在 Vue 生产构建不抛异常,经链式全局 errorHandler 记录
|
||
// (installVueErrorHandler),框架探测后按条目名 console.error 并
|
||
// 返回 false,调用方回退引擎纯渲染。
|
||
// 【两条硬约束】
|
||
// 1. 模板容器恒带 class="layui-form"(form 控件渲染与事件受 .layui-form
|
||
// 祖先约束,缺失则模板里的 switch/checkbox 全部失效);
|
||
// 2. laytpl/Vue 模板节点(<script type="text/html"> / text/x-template)必须
|
||
// 位于渲染容器之外,防止被模板引擎编译时吞掉 {{ }} 插值(节点由页面
|
||
// 模板区提供)。
|
||
// 返回 false 表示管线不可渲染(table 形态或模板引擎未挂载/渲染失败),调用
|
||
// 方回退引擎纯渲染。
|
||
renderLayoutForm: function (tableId) {
|
||
if (controlledLayouts[tableId] === undefined) {
|
||
return false;
|
||
}
|
||
// 销毁重建式(约束 2):任何重渲路径进入本函数先销毁上一轮 Vue 实例,
|
||
// 容器被清空的其余路径由 fallbackLayoutToTable / syncLayoutVisibility
|
||
// 兜底同规则销毁
|
||
destroyLayoutVueInstances(tableId);
|
||
var resolved = resolveCurrentLayout(tableId);
|
||
if (resolved.kind === 'table') {
|
||
return false;
|
||
}
|
||
|
||
var $container = $('#ua-layout-' + tableId);
|
||
if ($container.length === 0) {
|
||
return false;
|
||
}
|
||
|
||
// laytpl 引擎(任务 16):tplLay = 模板字符串;tplLayId = 模板节点选择器
|
||
var laytplCompiled = null;
|
||
if (resolved.kind === 'lay' || resolved.kind === 'layId') {
|
||
var template;
|
||
if (resolved.kind === 'layId') {
|
||
var $tplNode = $(resolved.entry.tplLayId);
|
||
if ($tplNode.length === 0) {
|
||
console.error('[ua.table] tplLayId 节点不存在:' + resolved.entry.tplLayId
|
||
+ '(形态 "' + resolved.entry.name + '"),本次回退 table 形态');
|
||
return fallbackLayoutToTable(tableId, $container);
|
||
}
|
||
template = $tplNode.html();
|
||
} else {
|
||
template = resolved.entry.tplLay;
|
||
}
|
||
laytplCompiled = laytpl(template);
|
||
}
|
||
|
||
// vue/vueId 引擎(任务 17):tplVue = 模板字符串;tplVueId =
|
||
// <script type="text/x-template"> 节点选择器(节点须在渲染容器之外)
|
||
var vueTemplate = null;
|
||
if (resolved.kind === 'vue' || resolved.kind === 'vueId') {
|
||
if (typeof window.Vue === 'undefined') {
|
||
console.error('[ua.table] 全局 Vue 未引入(形态 "' + resolved.entry.name + '"),本次回退 table 形态');
|
||
return fallbackLayoutToTable(tableId, $container);
|
||
}
|
||
installVueErrorHandler();
|
||
if (resolved.kind === 'vueId') {
|
||
var $vueTplNode = $(resolved.entry.tplVueId);
|
||
if ($vueTplNode.length === 0) {
|
||
console.error('[ua.table] tplVueId 节点不存在:' + resolved.entry.tplVueId
|
||
+ '(形态 "' + resolved.entry.name + '"),本次回退 table 形态');
|
||
return fallbackLayoutToTable(tableId, $container);
|
||
}
|
||
vueTemplate = $vueTplNode.html();
|
||
} else {
|
||
vueTemplate = resolved.entry.tplVue;
|
||
}
|
||
}
|
||
|
||
var context = ua.table.buildLayoutContext(tableId);
|
||
|
||
if (vueTemplate !== null) {
|
||
// 自定义模板形态移除内置 card 网格类(与 laytpl 同规则)
|
||
$container.toggleClass('ua-layout-cards', resolved.entry.builtinCard === true);
|
||
// 常显开关类随渲染重挂(设置面板开关已写入页级 pageSetting,
|
||
// 切形态/重渲后保持;CSS 三态效果见 public.scss)
|
||
$container.toggleClass('ua-card-operat-always', ua.getPageSetting('card-operat-always', '') === '1');
|
||
if (context.rows.length === 0) {
|
||
$container.html('<div class="ua-layout-empty">无数据</div>');
|
||
form.render(null, 'ua-layout-' + tableId);
|
||
return true;
|
||
}
|
||
// 冻结渲染上下文(约束 2 的强制保证):顶层数据不可扩展即不会被
|
||
// observe,模板表达式改写失效、不触发 re-patch,form.render 插入
|
||
// 的美化 DOM 安全;数据更新只经销毁重建(本函数开头已销毁)
|
||
Object.freeze(context.rows);
|
||
$.each(context.rows, function (i, rowCtx) {
|
||
Object.freeze(rowCtx);
|
||
});
|
||
Object.freeze(context);
|
||
var vm = null;
|
||
try {
|
||
vm = new window.Vue({
|
||
template: '<div class="ua-layout-vue">' + vueTemplate + '</div>',
|
||
data: {rows: context.rows, list: context}
|
||
});
|
||
vm.__uaLayout = {tableId: tableId, name: resolved.entry.name};
|
||
vm.$mount();
|
||
} catch (e) {
|
||
// 挂载期同步异常(CSP Function 构造失败等);渲染期错误已由
|
||
// 全局 errorHandler 记录并告警,此处不再重复;此时实例尚未
|
||
// 注册进存活表,需就地销毁
|
||
if (lastVueLayoutError === null) {
|
||
console.error('[ua.table] Vue 模板挂载失败(形态 "' + resolved.entry.name + '"):'
|
||
+ ((e && e.message) || e) + ',本次回退 table 形态');
|
||
}
|
||
if (vm) {
|
||
try {
|
||
vm.$destroy();
|
||
} catch (ignored) {}
|
||
}
|
||
return fallbackLayoutToTable(tableId, $container);
|
||
}
|
||
// 编译/渲染错误探测(errorHandler 已按形态名 console.error);
|
||
// 同上,实例未注册,先就地销毁再归位
|
||
if (lastVueLayoutError && lastVueLayoutError.tableId === tableId) {
|
||
lastVueLayoutError = null;
|
||
try {
|
||
vm.$destroy();
|
||
} catch (ignored) {}
|
||
return fallbackLayoutToTable(tableId, $container);
|
||
}
|
||
lastVueLayoutError = null;
|
||
if (!vm || !vm.$el) {
|
||
console.error('[ua.table] Vue 模板渲染为空(形态 "' + resolved.entry.name + '"),本次回退 table 形态');
|
||
return fallbackLayoutToTable(tableId, $container);
|
||
}
|
||
layoutVueInstances[tableId] = [vm];
|
||
// $destroy 只拆实例不动 DOM(见 destroyLayoutVueInstances 注释),
|
||
// 上一轮实例的 DOM 残留必须在此显式清空,否则每次重渲都会累积
|
||
$container.empty().append(vm.$el);
|
||
// 约束 1:容器恒为 .layui-form(lay-filter 与容器 id 同名),
|
||
// 挂载后一次性 scoped form.render 美化模板产出的 switch/checkbox
|
||
form.render(null, 'ua-layout-' + tableId);
|
||
return true;
|
||
}
|
||
|
||
var html = '';
|
||
if (context.rows.length === 0) {
|
||
html = '<div class="ua-layout-empty">无数据</div>';
|
||
} else {
|
||
try {
|
||
$.each(context.rows, function (i, rowCtx) {
|
||
if (laytplCompiled) {
|
||
rowCtx._list = context;
|
||
var rowHtml = laytplCompiled.render(rowCtx) || '';
|
||
if (rowHtml.indexOf('Laytpl Error:') === 0) {
|
||
// laytpl 错误不抛异常(实测),探测返回值后主动抛出,
|
||
// 由外层 catch 统一告警并整体回退
|
||
throw new Error(rowHtml);
|
||
}
|
||
html += rowHtml;
|
||
} else {
|
||
html += resolved.entry.tplFunction.call(rowCtx, rowCtx, context);
|
||
}
|
||
});
|
||
} catch (e) {
|
||
console.error('[ua.table] 模板渲染失败(形态 "' + resolved.entry.name + '"):'
|
||
+ e.message + ',本次回退 table 形态');
|
||
return fallbackLayoutToTable(tableId, $container);
|
||
}
|
||
}
|
||
|
||
// 内置 card 形态容器挂网格布局类(样式见 public.scss 的 .ua-layout-cards),
|
||
// 自定义模板形态移除该标记,不受卡片网格影响
|
||
$container.toggleClass('ua-layout-cards', resolved.entry.builtinCard === true);
|
||
// 常显开关类随渲染重挂(设置面板开关已写入页级 pageSetting,
|
||
// 切形态/重渲后保持;CSS 三态效果见 public.scss)
|
||
$container.toggleClass('ua-card-operat-always', ua.getPageSetting('card-operat-always', '') === '1');
|
||
$container.html(html);
|
||
// 卡片操作按钮折叠收纳(function/laytpl 共用分支,见 applyCardOperatFold)
|
||
applyCardOperatFold($container);
|
||
// 容器恒为 .layui-form(lay-filter 与容器 id 同名),scoped form.render
|
||
// 美化模板产出的 switch/checkbox 控件
|
||
form.render(null, 'ua-layout-' + tableId);
|
||
return true;
|
||
},
|
||
// 当前形态名(任务 15 的切换 UI 消费)
|
||
getLayoutForm: function (tableId) {
|
||
return controlledLayouts[tableId] ? controlledLayouts[tableId].current : 'table';
|
||
},
|
||
// 切换当前形态:仅改写运行期状态并重渲染,不发起网络请求(数据取自
|
||
// store 当页状态)。目标条目不存在返回 false;模板管线不可用(无钩子
|
||
// 条目 / tplLayId/tplVueId 节点缺失 / laytpl 渲染错误 / Vue 未引入或
|
||
// 挂载渲染失败,均已在 renderLayoutForm 内归位 current='table')时走
|
||
// 引擎纯渲染兜底。
|
||
setLayoutForm: function (tableId, name) {
|
||
var layout = controlledLayouts[tableId];
|
||
if (!layout || !layout.byName[name]) {
|
||
return false;
|
||
}
|
||
layout.current = name;
|
||
|
||
if (!ua.table.renderLayoutForm(tableId)) {
|
||
// table 形态(含无钩子条目回退):引擎纯渲染当页数据,直呼备份
|
||
// 原引用不走 shim(防路由回 store 造成 fetch)
|
||
var state = window.UA_TABLE_STORE.dataProvider.getState(tableId);
|
||
var reloadOpts = {
|
||
data: state ? state.currentPageData : [],
|
||
totalRow: state && state.lastResponse ? state.lastResponse.totalRow : undefined
|
||
};
|
||
// 当前排序(store where.sort 单键映射)转引擎 initSort
|
||
// {field, type},切回表格形态时表头箭头与数据一致
|
||
var currentSort = state && state.where ? state.where.sort : null;
|
||
if (currentSort && Object.keys(currentSort).length > 0) {
|
||
var sortField = Object.keys(currentSort)[0];
|
||
var sortType = currentSort[sortField];
|
||
if (sortType === 'asc' || sortType === 'desc') {
|
||
reloadOpts.initSort = {field: sortField, type: sortType};
|
||
}
|
||
}
|
||
originalTableReloadData.call(table, tableId, reloadOpts);
|
||
}
|
||
syncLayoutVisibility(tableId);
|
||
syncControlledPager(tableId);
|
||
return true;
|
||
},
|
||
render: function (options) {
|
||
options.init = options.init || ua.init;
|
||
options.modifyReload = ua.parame(options.modifyReload, true);
|
||
options.elem = options.elem || options.init.tableElem;
|
||
options.id = options.id || options.init.tableRenderId;
|
||
options.scrollPos = options.scrollPos || 'fixed';
|
||
options.layFilter = options.id + '_LayFilter';
|
||
options.url = options.url || ua.url(options.init.indexUrl);
|
||
options.headers = ua.headers();
|
||
options.page = ua.parame(options.page, true);
|
||
options.search = ua.parame(options.search, true);
|
||
options.skin = options.skin || 'line';
|
||
options.autoSort = options.autoSort || false;
|
||
options.limit = options.limit || 15;
|
||
options.limits = options.limits || [10, 15, 20, 25, 50, 100];
|
||
options.cols = options.cols || [];
|
||
options.rowDisableCallback = options.rowDisableCallback || null;
|
||
|
||
var defaultToolbar = ['filter', 'print'];
|
||
|
||
if (options.search) {
|
||
defaultToolbar.push({
|
||
title: '搜索',
|
||
layEvent: 'TABLE_SEARCH',
|
||
icon: 'layui-icon-search',
|
||
extend: 'data-table-id="' + options.id + '"'
|
||
});
|
||
}
|
||
|
||
// 受控表形态清单规范化须先于引擎渲染:视图切换按钮的注入条件
|
||
// (条目数>1)依赖它,受控分支直接复用。非受控(逃生口)表不
|
||
// normalize。
|
||
var layoutEntries = [];
|
||
var autoInjected = false;
|
||
// 受控是默认数据流,仅显式 controlled:false 的逃生口页跳过
|
||
if (options.controlled !== false) {
|
||
// 未配置 layoutList 的受控页双端自动启用内置 card:内置
|
||
// card 无需页面模板(数据全来自渲染上下文),自动注入可行。
|
||
// 注入后条目数>1,双端工具栏自动出现"视图"按钮。显式配置
|
||
// layoutList 的页面仍优先不注入(含 usercard 示例页等存量配置)。
|
||
if (!options.layoutList || !options.layoutList.length) {
|
||
options.layoutList = ['table', 'card'];
|
||
autoInjected = true;
|
||
}
|
||
layoutEntries = normalizeLayoutList(options.layoutList);
|
||
if (layoutEntries.length > 1) {
|
||
// 视图切换按钮(任务 15 (b),交互结构参照旧 TABLE_LAYOUT
|
||
// 实现重建):下拉条目在点击时按 layoutEntries 动态构建
|
||
defaultToolbar.push({
|
||
title: '视图',
|
||
layEvent: 'TABLE_LAYOUT',
|
||
icon: 'layui-icon-template-1',
|
||
extend: 'data-table-id="' + options.id + '"'
|
||
});
|
||
}
|
||
}
|
||
|
||
if (options.defaultToolbar === undefined) {
|
||
options.defaultToolbar = defaultToolbar;
|
||
} else if (autoInjected) {
|
||
// 自动注入 card 且页面显式配置了 defaultToolbar 时,上方
|
||
// push 视图按钮的 defaultToolbar 数组被整体丢弃,"默认 card
|
||
// 且可切回 table"会落空,这里对最终配置去重追加(仅注入场景
|
||
// 触发;显式 layoutList 页均不受影响)
|
||
var hasLayoutBtn = false;
|
||
$.each(options.defaultToolbar, function (i, btn) {
|
||
if (btn && typeof btn === 'object' && btn.layEvent === 'TABLE_LAYOUT') {
|
||
hasLayoutBtn = true;
|
||
return false;
|
||
}
|
||
});
|
||
if (!hasLayoutBtn) {
|
||
options.defaultToolbar.push({
|
||
title: '视图',
|
||
layEvent: 'TABLE_LAYOUT',
|
||
icon: 'layui-icon-template-1',
|
||
extend: 'data-table-id="' + options.id + '"'
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
var optionBefore = function () { };
|
||
if (options.before != undefined) {
|
||
optionBefore = options.before;
|
||
}
|
||
options.before = function (tableIns) {
|
||
optionBefore(tableIns);
|
||
};
|
||
|
||
var optionDone = function () { };
|
||
if (options.done != undefined) {
|
||
optionDone = options.done;
|
||
}
|
||
options.done = function (res, curr, count) {
|
||
optionDone(res, curr, count);
|
||
|
||
// 监听表格内的复制组件
|
||
ua.api.copyText('[lay-id=' + options.id + ']');
|
||
};
|
||
|
||
/**
|
||
* 为空时认为自己是普通状态,否则是单选、多选、子页面,此时添加等默认行为走弹框而不是新标签页
|
||
*/
|
||
selectMode = ua.getQueryVariable("select_mode");
|
||
|
||
selectConfirmCallback = ua.getQueryVariable('select_confirm_callback', 'onTableDataConfirm');
|
||
|
||
if (selectMode == 'checkbox') {
|
||
if (options.cols[0][0].type == 'radio') {
|
||
options.cols[0][0].type = 'checkbox';
|
||
} else if (options.cols[0][0].type != 'checkbox') {
|
||
options.cols[0].unshift({
|
||
type: 'checkbox'
|
||
});
|
||
}
|
||
} else if (selectMode == 'radio') {
|
||
if (options.cols[0][0].type == 'checkbox') {
|
||
options.cols[0][0].type = 'radio';
|
||
|
||
} else if (options.cols[0][0].type != 'radio') {
|
||
options.cols[0].unshift({
|
||
type: 'radio'
|
||
});
|
||
|
||
}
|
||
} else {
|
||
if(!ua.isParentIndex()){
|
||
selectMode = 'childPage'
|
||
}
|
||
}
|
||
|
||
|
||
// 判断元素对象是否有嵌套的
|
||
options.cols = ua.table.formatCols(options.cols, options.init);
|
||
|
||
// 初始化表格lay-filter
|
||
$(options.elem).attr('lay-filter', options.layFilter);
|
||
|
||
// 初始化表格搜索
|
||
if (options.search === true) {
|
||
options = ua.table.renderSearch(options.cols, options.elem, options.id, options);
|
||
}
|
||
|
||
// 初始化表格左上方工具栏
|
||
options.toolbar = options.toolbar || ['refresh', 'add', 'delete', 'export'];
|
||
|
||
if (selectMode == 'checkbox' || selectMode == 'radio') {
|
||
options.toolbar.unshift('selectConfirm');
|
||
|
||
options.height = 'full-85';
|
||
}
|
||
|
||
if (options.init.formFullScreen == true) {
|
||
options.init.formFullScreen = 'true';
|
||
} else {
|
||
options.init.formFullScreen = 'false';
|
||
}
|
||
|
||
options.toolbar = ua.table.renderToolbar(options.toolbar, options.elem, options.id, options.init);
|
||
|
||
// 判断是否有操作列表权限
|
||
options.cols = ua.table.renderOperat(options.cols, options.elem);
|
||
|
||
// 判断是否有操作列表权限
|
||
options.cols = ua.table.renderTrueHide(options.cols, options);
|
||
|
||
|
||
var parseData = function (res) { return res; };
|
||
|
||
if (typeof options.parseData === 'function') {
|
||
parseData = options.parseData;
|
||
}
|
||
|
||
// 受控是默认数据流(store 取数 + 引擎纯渲染),controlled:false
|
||
// 是逃生口:依赖引擎 url 模式行为(如自定义 parseData)的下游
|
||
// 页面可显式传 controlled:false 退回。
|
||
if (options.controlled !== false) {
|
||
// 受控数据流:数据请求与分页职责移交 UA_TABLE_STORE,引擎只做
|
||
// 纯渲染(空数据起步、关闭引擎分页,由 store 的 laypage 自绘)。
|
||
// 选择态注入(rowDisableCallback / URL selectedIds / LAY_CHECKED
|
||
// 跨页回显)由 store.fetch 的 injectSelectionMarks 承接,故不装
|
||
// 引擎 parseData。
|
||
delete options.url;
|
||
delete options.parseData;
|
||
options.data = [];
|
||
options.page = false;
|
||
} else {
|
||
options.parseData = function (res) {
|
||
|
||
// 初始化已经选择的值
|
||
if (selectMode == 'checkbox' || selectMode == 'radio') {
|
||
if (typeof options.rowDisableCallback == 'function') {
|
||
for (let index = 0; index < res.data.length; index++) {
|
||
const dataItem = res.data[index];
|
||
|
||
var rowDisableResult = options.rowDisableCallback(dataItem);
|
||
if (rowDisableResult === false || rowDisableResult === true) {
|
||
res.data[index].LAY_DISABLED = rowDisableResult;
|
||
}
|
||
}
|
||
}
|
||
|
||
var selectedIds = ua.getQueryVariable('selectedIds', '');
|
||
|
||
if (selectedIds.length > 0) {
|
||
var selectedIdArr = selectedIds.split(',');
|
||
|
||
for (let index = 0; index < res.data.length; index++) {
|
||
const dataItem = res.data[index];
|
||
|
||
if (selectedIdArr.indexOf(dataItem.id.toString()) > -1) {
|
||
res.data[index].LAY_DISABLED = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
res = parseData(res);
|
||
return res;
|
||
};
|
||
}
|
||
|
||
|
||
// 列显隐记忆恢复:必须在引擎 table.render 首次渲染之前改写
|
||
// col.hide——cols 为同一引用(layui 浅拷贝 config.cols ===
|
||
// options.cols),引擎表格、列筛选面板勾选态、卡片收集三处自动
|
||
// 一致;controlledLayouts 注册晚于本行,同样消费改写后的引用。
|
||
// 记忆损坏时静默忽略,不中断渲染
|
||
var savedHiddenFields = ua.getPageSetting('table-cols-hide-' + options.id, '');
|
||
if (savedHiddenFields) {
|
||
try {
|
||
var hiddenFieldMap = {};
|
||
$.each(JSON.parse(savedHiddenFields), function (i, fieldName) {
|
||
hiddenFieldMap[fieldName] = true;
|
||
});
|
||
$.each(options.cols, function (i, row) {
|
||
$.each(row || [], function (j, col) {
|
||
if (col.field && hiddenFieldMap[col.field]) {
|
||
col.hide = true;
|
||
}
|
||
});
|
||
});
|
||
} catch (e) {
|
||
// 视为无记忆
|
||
}
|
||
}
|
||
|
||
// 初始化表格
|
||
var newTable = table.render(options);
|
||
|
||
// 监听表格搜索开关显示
|
||
ua.table.listenToolbar(options.layFilter, options.id);
|
||
|
||
// 监听表格瓶排序
|
||
ua.table.listenTableSort(options);
|
||
|
||
// 监听表格开关切换
|
||
ua.table.renderSwitch(options.cols, options.init, options.id, options.modifyReload);
|
||
|
||
// 监听表格开关切换
|
||
ua.table.listenEdit(options.init, options.layFilter, options.id, options.modifyReload);
|
||
|
||
// 监听导出事件
|
||
ua.table.listenExport(options);
|
||
|
||
// 监听表格选择器
|
||
ua.table.listenTableSelectConfirm(options);
|
||
|
||
// 受控接线(默认数据流,在共享监听挂载之后):注册 store、自建
|
||
// 分页容器、同步勾选事件、首屏取数。搜索/排序/switch/编辑等既有
|
||
// 监听的 table.reloadData 调用经 shim 自动分流到 store.reload(渲染
|
||
// 由顶部接线层注入的 done 承接),此处无需逐点改造。
|
||
// controlled:false 的逃生口页不走此分支,保持引擎 url 模式。
|
||
if (options.controlled !== false) {
|
||
// 分页容器挂在引擎视图容器之后(表格容器外),不与引擎分页共存。
|
||
// 注意 layui render 后原 table 元素被移出视图(成为 view 的兄弟
|
||
// 节点),不能用 closest 向上找 view
|
||
var pagerId = 'ua-store-pager-' + options.id;
|
||
$('#' + pagerId).remove();
|
||
$(options.elem).siblings('.layui-table-view').first().after(
|
||
'<div id="' + pagerId + '" class="ua-store-pager"></div>'
|
||
);
|
||
|
||
// 模板形态容器:与 ua-store-pager 并列(分页条两形态共用)。
|
||
// 两条硬约束:容器恒带 class="layui-form"(form 控件渲染与事件受
|
||
// .layui-form 祖先约束);laytpl/Vue 模板节点必须位于本容器之外
|
||
// (由页面模板区提供,本层不生成任何模板节点)
|
||
var layoutId = 'ua-layout-' + options.id;
|
||
$('#' + layoutId).remove();
|
||
$('#' + pagerId).before(
|
||
'<div id="' + layoutId + '" class="ua-layout-container layui-form" lay-filter="ua-layout-' + options.id + '"></div>'
|
||
);
|
||
|
||
// 卡片操作按钮"更多"展开委托:容器每次 render 经上方 remove 后
|
||
// 重建,容器级委托绑定一次即随容器生命周期,无重复绑定;展开
|
||
// 状态不记忆(重渲后收回)属预期
|
||
$('#' + layoutId).on('click', '.ua-card-operat-more', function (e) {
|
||
$(this).toggleClass('on').siblings('.ua-card-operat-more-wrap').toggleClass('layui-hide');
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
});
|
||
|
||
// 卡片设置入口按钮(pro#5 迭代:由右侧图标区 defaultToolbar 移至
|
||
// 左侧工具栏末尾)。layui 2.10.1 左容器为 .layui-table-tool-temp
|
||
//(layui.js 工具栏模板 '<div class="layui-table-tool"><div
|
||
// class="layui-table-tool-temp"></div><div
|
||
// class="layui-table-tool-self"></div>',左容器经
|
||
// o.layTool.find(".layui-table-tool-temp").html(t) 渲染现有操作
|
||
// 按钮)。不带 lay-event(避免 layui toolbar 委托与直绑双触发),
|
||
// 点击直调 toggleCardSettingPanel。初始 layui-hide,追加在本分支
|
||
// 内、首次 syncLayoutVisibility 调用之前,由其按当前形态定初始
|
||
// 显隐(表格形态直开页按钮不会闪现)
|
||
var $cardSettingView = $(options.elem).siblings('.layui-table-view').first();
|
||
var hasNonTableLayout = false;
|
||
$.each(layoutEntries, function (i, entry) {
|
||
if (entry && entry.name !== 'table') {
|
||
hasNonTableLayout = true;
|
||
return false;
|
||
}
|
||
});
|
||
if (hasNonTableLayout && $cardSettingView.find('.ua-card-setting-btn').length === 0) {
|
||
var $cardSettingBtn = $('<a class="layui-btn layui-btn-sm ua-card-setting-btn layui-hide" title="卡片设置"><i class="layui-icon layui-icon-set"></i>卡片设置</a>')
|
||
.appendTo($cardSettingView.find('.layui-table-tool-temp'));
|
||
$cardSettingBtn.on('click', function (event) {
|
||
toggleCardSettingPanel(options.id);
|
||
event.stopPropagation();
|
||
});
|
||
}
|
||
|
||
// 形态注册:layoutList 已在 defaultToolbar 段规范化(视图按钮
|
||
// 注入共用)。运行期切形态经 ua.table.setLayoutForm。
|
||
// options.cols 此时已过 formatCols/renderOperat/renderTrueHide,
|
||
// 渲染器族与 operat 缺省值已就绪,构建上下文直接消费
|
||
var layoutByName = {};
|
||
$.each(layoutEntries, function (i, entry) {
|
||
layoutByName[entry.name] = entry;
|
||
});
|
||
|
||
// 默认形态解析(按端三层链):a.localStorage 按页记忆(最
|
||
// 高优先)> b.按端显式 layoutDefault > c.端缺省。layoutDefault
|
||
// 兼容三种写法:对象 {pc, mobile} 按当前端取值(端键缺省视为
|
||
// 未配置)、字符串旧写法双端同值、true 旧协议即"自动判断"视
|
||
// 为未配置。b/c 层取到的形态名不在 layoutByName 内一律忽略
|
||
// (防脏数据,与记忆同规)。端缺省:pc 取 table、无则首条目;
|
||
// mobile 取 card、无则 table、再无则首条目。外层空清单守卫
|
||
// 保持:非受控逃生口页或垃圾 layoutList 全无效时 layoutEntries
|
||
// 为空,恒 'table',任何分支不在空数组上取 [0]。
|
||
var defaultLayoutName = 'table';
|
||
if (layoutEntries.length > 0) {
|
||
var device = ua.checkMobile() ? 'mobile' : 'pc';
|
||
// layoutDefault 规格化为按端对象:字符串双端同值;true
|
||
// 旧协议与未配置均视为 null(走端缺省)
|
||
var layoutDefaultByDevice = options.layoutDefault;
|
||
if (typeof layoutDefaultByDevice === 'string') {
|
||
layoutDefaultByDevice = {pc: layoutDefaultByDevice, mobile: layoutDefaultByDevice};
|
||
} else if (layoutDefaultByDevice === null || typeof layoutDefaultByDevice !== 'object') {
|
||
layoutDefaultByDevice = null;
|
||
}
|
||
var configuredLayout = layoutDefaultByDevice ? (layoutDefaultByDevice[device] || null) : null;
|
||
if (layoutByName[configuredLayout]) {
|
||
// b 层:按端显式配置生效(脏值已被清单内校验拦截)
|
||
defaultLayoutName = configuredLayout;
|
||
} else if (device === 'pc') {
|
||
// c 层 pc 端缺省:table 优先,无则首条目
|
||
defaultLayoutName = layoutByName['table'] ? 'table' : layoutEntries[0].name;
|
||
} else if (layoutByName['card']) {
|
||
// c 层 mobile 端缺省:card 优先
|
||
defaultLayoutName = 'card';
|
||
} else if (layoutByName['table']) {
|
||
defaultLayoutName = 'table';
|
||
} else {
|
||
defaultLayoutName = layoutEntries[0].name;
|
||
}
|
||
// a 层:记忆最高优先,值不在清单内忽略(防脏数据)
|
||
var savedLayout = ua.getPageSetting('table-layout-default', '');
|
||
if (layoutByName[savedLayout]) {
|
||
defaultLayoutName = savedLayout;
|
||
}
|
||
}
|
||
|
||
controlledLayouts[options.id] = {
|
||
elem: options.elem,
|
||
cols: options.cols,
|
||
init: options.init,
|
||
entries: layoutEntries,
|
||
byName: layoutByName,
|
||
current: defaultLayoutName
|
||
};
|
||
|
||
window.UA_TABLE_STORE.register(options.id, {
|
||
indexUrl: options.init.indexUrl,
|
||
where: options.where,
|
||
page: {curr: 1, limit: options.limit},
|
||
limits: options.limits,
|
||
selectMode: selectMode,
|
||
rowDisableCallback: options.rowDisableCallback
|
||
// 不传 pagerElem:store 的 pager.jump 内部直呼 fetch 无法
|
||
// 触发引擎渲染,分页条由接线层 syncControlledPager 自管
|
||
});
|
||
|
||
// 引擎勾选事件 → store 选中集合:跨页勾选回显(LAY_CHECKED 注入)
|
||
// 的数据源。layui 2.10 事件回调 obj.data 是当次操作的单行对象
|
||
// (type=all 时不含可用行数据),全选场景以 store 的当页数据为准
|
||
//(selection.checkAll 缺省即当页数据,禁用行由其内部跳过)
|
||
table.on('checkbox(' + options.layFilter + ')', function (obj) {
|
||
if (obj.type === 'all') {
|
||
window.UA_TABLE_STORE.selection.checkAll(options.id, undefined, obj.checked);
|
||
} else {
|
||
window.UA_TABLE_STORE.selection.set(options.id, obj.data);
|
||
}
|
||
});
|
||
table.on('radio(' + options.layFilter + ')', function (obj) {
|
||
window.UA_TABLE_STORE.selection.set(options.id, obj.data);
|
||
});
|
||
|
||
// 列筛选勾选 → 卡片实时重渲:表格形态短路(列筛选行为完全
|
||
// 不变),卡片形态 debounce 100ms 后从 store 当页数据重渲
|
||
//(renderLayoutForm 零网络请求,连点不抖动)
|
||
table.on('colToggled(' + options.layFilter + ')', function (obj) {
|
||
// 列显隐按页记忆:表格/卡片两种形态都能从列筛选面板触发
|
||
// 本事件,保存必须放在形态短路之前。收集当前隐藏列的字段
|
||
// 名,全显时存空串即清除记忆
|
||
var hiddenFields = [];
|
||
$.each(controlledLayouts[options.id].cols, function (i, row) {
|
||
$.each(row || [], function (j, col) {
|
||
if (col.field && col.hide === true && hiddenFields.indexOf(col.field) === -1) {
|
||
hiddenFields.push(col.field);
|
||
}
|
||
});
|
||
});
|
||
ua.setPageSetting('table-cols-hide-' + options.id, hiddenFields.length ? JSON.stringify(hiddenFields) : '');
|
||
|
||
if (resolveCurrentLayout(options.id).kind === 'table') {
|
||
return;
|
||
}
|
||
clearTimeout(colToggledTimers[options.id]);
|
||
colToggledTimers[options.id] = setTimeout(function () {
|
||
ua.table.renderLayoutForm(options.id);
|
||
}, 100);
|
||
});
|
||
|
||
// 初始形态可见性:layoutDefault 非 table 时先隐引擎空视图(避免
|
||
// 空表头闪烁),首个 reload 完成后 wrapper 再次同步
|
||
syncLayoutVisibility(options.id);
|
||
|
||
// 首屏取数:经接线后的 store.reload 触发 fetch + 引擎纯渲染
|
||
window.UA_TABLE_STORE.reload(options.id);
|
||
}
|
||
|
||
return newTable;
|
||
},
|
||
renderToolbar: function (data, elem, tableId, init) {
|
||
data = data || [];
|
||
var toolbarHtml = '';
|
||
$.each(data, function (i, v) {
|
||
if (v === 'refresh') {
|
||
toolbarHtml += ' <button class="layui-btn layui-btn-sm layuimini-btn-primary" data-table-refresh="' + tableId + '"><i class="fa fa-refresh"></i> </button>\n';
|
||
} else if (v === 'add') {
|
||
if (ua.checkAuth('add', elem)) {
|
||
if(selectMode){
|
||
toolbarHtml += '<button class="layui-btn layui-btn-normal layui-btn-sm" data-open="' + init.addUrl + '" data-title="添加" data-full="' + init.formFullScreen + '"><i class="fa fa-plus"></i> 添加</button>\n';
|
||
}else{
|
||
toolbarHtml += '<button class="layui-btn layui-btn-normal layui-btn-sm" layuimini-content-href="' + init.addUrl + '" data-title="添加" data-full="' + init.formFullScreen + '"><i class="fa fa-plus"></i> 添加</button>\n';
|
||
}
|
||
}
|
||
} else if (v === 'delete') {
|
||
if (ua.checkAuth('delete', elem)) {
|
||
toolbarHtml += '<button class="layui-btn layui-btn-sm layui-btn-danger" data-url="' + init.deleteUrl + '" data-table-delete="' + tableId + '"><i class="fa fa-trash-o"></i> 删除</button>\n';
|
||
}
|
||
} else if (v === 'export') {
|
||
if (ua.checkAuth('export', elem)) {
|
||
toolbarHtml += '<button class="layui-btn layui-btn-sm layui-btn-success easyadmin-export-btn" data-url="' + init.exportUrl + '" data-table-export="' + tableId + '"><i class="fa fa-file-excel-o"></i> 导出</button>\n';
|
||
}
|
||
} else if (v === 'selectConfirm') {
|
||
toolbarHtml += '<button class="layui-btn layui-btn-sm layui-btn-success select-confirm" data-table-target="' + tableId + '"> 确定选择</button>\n';
|
||
} else if (typeof v === "object") {
|
||
$.each(v, function (ii, vv) {
|
||
vv.class = vv.class || '';
|
||
vv.icon = vv.icon || '';
|
||
vv.auth = vv.auth || 'add';
|
||
vv.url = vv.url || '';
|
||
vv.method = vv.method || 'open';
|
||
vv.title = vv.title || vv.text;
|
||
vv.text = vv.text || vv.title;
|
||
vv.extend = vv.extend || '';
|
||
vv.checkbox = vv.checkbox || false;
|
||
if (ua.checkAuth(vv.auth, elem)) {
|
||
toolbarHtml += ua.table.buildToolbarHtml(vv, tableId);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
return '<div>' + toolbarHtml + '</div>';
|
||
},
|
||
renderSearch: function (cols, elem, tableId, options) {
|
||
// TODO 只初始化第一个table搜索字段,如果存在多个(绝少数需求),得自己去扩展
|
||
cols = cols[0] || {};
|
||
var newCols = [];
|
||
var formHtml = '';
|
||
var tabSetting = {};
|
||
var formatFilter = {},
|
||
formatOp = {};
|
||
$.each(cols, function (i, d) {
|
||
d.field = d.field || false;
|
||
d.fieldAlias = ua.parame(d.fieldAlias, d.field);
|
||
d.title = d.title || d.field || '';
|
||
d.selectList = d.selectList || {};
|
||
d.search = ua.parame(d.search, true);
|
||
d.searchTip = d.searchTip || '请输入' + d.title || '';
|
||
d.searchValue = d.searchValue || undefined;
|
||
d.searchHide = d.searchHide || '';
|
||
d.defaultSearchValue = d.defaultSearchValue;
|
||
d.searchOp = d.searchOp || '%*%';
|
||
d.timeType = d.timeType || 'datetime';
|
||
|
||
d.elemIdName = d.fieldAlias;
|
||
|
||
var a = '';
|
||
var b = '';
|
||
|
||
if (typeof d.fieldAlias == 'string') {
|
||
|
||
if (d.fieldAlias.indexOf('[') == 0) {
|
||
|
||
var fieldPlusArr = d.fieldAlias.replace('[').split(']');
|
||
|
||
d.elemIdName = fieldPlusArr.join('-');
|
||
}
|
||
|
||
d.elemIdName = d.elemIdName.replace('.', '-');
|
||
}
|
||
|
||
|
||
if (d.defaultSearchValue !== undefined) {
|
||
if (!d.searchValue || d.searchValue.length == 0) {
|
||
d.searchValue = d.defaultSearchValue;
|
||
}
|
||
} else {
|
||
if (d.field) {
|
||
d.defaultSearchValue = ua.getQueryVariable(d.field);
|
||
if (d.defaultSearchValue != undefined) {
|
||
d.searchValue = d.defaultSearchValue;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (d.searchValue !== undefined) {
|
||
|
||
if (d.search == 'number_limit') {
|
||
var paramsArr = d.searchValue.split(',');
|
||
|
||
a = paramsArr[0];
|
||
b = paramsArr[1];
|
||
|
||
if (a) {
|
||
formatFilter['[' + d.field + ']min'] = a;
|
||
formatOp['[' + d.field + ']min'] = 'min';
|
||
}
|
||
|
||
if (b) {
|
||
formatFilter['[' + d.field + ']max'] = b;
|
||
formatOp['[' + d.field + ']max'] = 'max';
|
||
}
|
||
} else if (d.search == 'time_limit') {
|
||
var paramsArr = d.searchValue.split(',');
|
||
|
||
a = paramsArr[0];
|
||
b = paramsArr[1];
|
||
|
||
if (a) {
|
||
formatFilter['[' + d.field + ']min_date'] = a;
|
||
formatOp['[' + d.field + ']min_date'] = 'min_date';
|
||
}
|
||
|
||
if (b) {
|
||
formatFilter['[' + d.field + ']max_date'] = b;
|
||
formatOp['[' + d.field + ']max_date'] = 'max_date';
|
||
}
|
||
} else {
|
||
formatFilter[d.field] = d.searchValue;
|
||
formatOp[d.field] = d.searchOp;
|
||
}
|
||
}
|
||
|
||
var formSearchHideClass = '';
|
||
|
||
if (d.searchHide) {
|
||
formSearchHideClass = ' search-hide-item';
|
||
}
|
||
|
||
if (d.searchValue === undefined) {
|
||
d.searchValue = '';
|
||
}
|
||
|
||
if (d.field !== false) {
|
||
if (d.search !== false) {
|
||
switch (d.search) {
|
||
case true:
|
||
formHtml += '\t<div class="layui-form-item layui-inline ' + formSearchHideClass + ' ">\n' +
|
||
'<label class="layui-form-label">' + d.title + '</label>\n' +
|
||
'<div class="layui-input-inline">\n' +
|
||
'<input id="c-' + d.elemIdName + '" name="' + d.fieldAlias + '" data-search-op="' + d.searchOp + '" value="' + d.searchValue + '" placeholder="' + d.searchTip + '" class="layui-input">\n' +
|
||
'</div>\n' +
|
||
'</div>';
|
||
break;
|
||
case 'select':
|
||
d.searchOp = '=';
|
||
var selectHtml = '';
|
||
|
||
if (d.tab) {
|
||
tabSetting.field = d.field;
|
||
tabSetting.elemIdName = d.elemIdName;
|
||
tabSetting.list = d.selectList;
|
||
tabSetting.index = -1;
|
||
}
|
||
|
||
var selectListIndex = 0;
|
||
$.each(d.selectList, function (sI, sV) {
|
||
var selected = '';
|
||
if (sI === d.searchValue) {
|
||
selected = 'selected=""';
|
||
if (d.tab) {
|
||
tabSetting.index = selectListIndex;
|
||
}
|
||
}
|
||
selectHtml += '<option value="' + sI + '" ' + selected + '>' + sV + '</option>/n';
|
||
selectListIndex++;
|
||
});
|
||
if (d.tab) {
|
||
formHtml += '<input type="hidden" id="c-' + d.elemIdName + '" name="' + d.fieldAlias + '" value="' + d.searchValue + '">';
|
||
break;
|
||
}
|
||
formHtml += '\t<div class="layui-form-item layui-inline ' + formSearchHideClass + ' ">\n' +
|
||
'<label class="layui-form-label">' + d.title + '</label>\n' +
|
||
'<div class="layui-input-inline">\n' +
|
||
'<select class="layui-select" id="c-' + d.elemIdName + '" name="' + d.fieldAlias + '" data-search-op="' + d.searchOp + '" >\n' +
|
||
'<option value="">- 全部 -</option> \n' +
|
||
selectHtml +
|
||
'</select>\n' +
|
||
'</div>\n' +
|
||
'</div>';
|
||
break;
|
||
case 'range':
|
||
d.searchOp = 'range';
|
||
formHtml += '\t<div class="layui-form-item layui-inline ' + formSearchHideClass + ' ">\n' +
|
||
'<label class="layui-form-label">' + d.title + '</label>\n' +
|
||
'<div class="layui-input-inline">\n' +
|
||
'<input id="c-' + d.elemIdName + '" name="' + d.fieldAlias + '" data-search-op="' + d.searchOp + '" value="' + d.searchValue + '" placeholder="' + d.searchTip + '" class="layui-input">\n' +
|
||
'</div>\n' +
|
||
'</div>';
|
||
break;
|
||
case 'time':
|
||
d.searchOp = '=';
|
||
formHtml += '\t<div class="layui-form-item layui-inline ' + formSearchHideClass + ' ">\n' +
|
||
'<label class="layui-form-label">' + d.title + '</label>\n' +
|
||
'<div class="layui-input-inline">\n' +
|
||
'<input id="c-' + d.elemIdName + '" name="' + d.fieldAlias + '" data-search-op="' + d.searchOp + '" value="' + d.searchValue + '" placeholder="' + d.searchTip + '" class="layui-input">\n' +
|
||
'</div>\n' +
|
||
'</div>';
|
||
break;
|
||
case 'time_limit':
|
||
d.searchOp = '=';
|
||
formHtml += '\t<div class="layui-form-item form-item-time-limit layui-inline ' + formSearchHideClass + ' ">\n' +
|
||
'<label class="layui-form-label">' + d.title + '</label>\n' +
|
||
'<div class="layui-input-inline">\n' +
|
||
'<input id="c-' + d.elemIdName + '-min_date" name="[' + d.fieldAlias + ']min_date" data-search-op="min_date" value="' + a + '" placeholder="最小值" class="layui-input">\n' +
|
||
'<input id="c-' + d.elemIdName + '-max_date" name="[' + d.fieldAlias + ']max_date" data-search-op="max_date" value="' + b + '" placeholder="最大值" class="layui-input">\n' +
|
||
'</div>\n' +
|
||
'</div>';
|
||
break;
|
||
case 'number_limit':
|
||
d.searchOp = '=';
|
||
formHtml += '\t<div class="layui-form-item form-item-number-limit layui-inline ' + formSearchHideClass + ' ">\n' +
|
||
'<label class="layui-form-label">' + d.title + '</label>\n' +
|
||
'<div class="layui-input-inline">\n' +
|
||
'<input id="c-' + d.elemIdName + '-min" name="[' + d.fieldAlias + ']min" data-search-op="min" type="text" value="' + a + '" placeholder="最小值" class="layui-input">\n' +
|
||
'<input id="c-' + d.elemIdName + '-max" name="[' + d.fieldAlias + ']max" data-search-op="max" type="text" value="' + b + '" placeholder="最大值" class="layui-input">\n' +
|
||
'</div>\n' +
|
||
'</div>';
|
||
break;
|
||
}
|
||
newCols.push(d);
|
||
} else {
|
||
// TODO:支持更多种类型的初始化,比如number_limit、time_limit
|
||
formHtml += '<input type="hidden" id="c-' + d.elemIdName + '" name="' + d.fieldAlias + '" value="' + d.searchValue + '">';
|
||
}
|
||
}
|
||
});
|
||
|
||
if (tabSetting.field) {
|
||
var tabId = 'tabFieldset_' + tableId;
|
||
$(elem).before('<div id="' + tabId + '" class="search-tab" data-field="' + tabSetting.field + '"></div>');
|
||
var header = [
|
||
{
|
||
title: '全部',
|
||
key: '',
|
||
}
|
||
];
|
||
for (const tabSettingListKey in tabSetting.list) {
|
||
if (Object.prototype.hasOwnProperty.call(tabSetting.list, tabSettingListKey)) {
|
||
const tabItem = tabSetting.list[tabSettingListKey];
|
||
header.push({
|
||
title: tabItem,
|
||
key: tabSettingListKey,
|
||
});
|
||
}
|
||
}
|
||
layui.tabs.render({
|
||
elem: '#' + tabId,
|
||
header: header,
|
||
index: tabSetting.index + 1,
|
||
});
|
||
layui.tabs.on('afterChange(' + tabId + ')', function (data) {
|
||
var value = $(data.thisHeaderItem).attr('lay-key');
|
||
$('#c-' + tabSetting.elemIdName).val(value);
|
||
$('[lay-filter="' + tableId + '_filter"]').trigger('click');
|
||
|
||
});
|
||
}
|
||
|
||
if (formHtml !== '') {
|
||
|
||
$(elem).before('<fieldset id="searchFieldset_' + tableId + '" class="table-search-fieldset layui-hide">\n' +
|
||
'<legend>条件搜索</legend>\n' +
|
||
'<form class="layui-form layui-form-pane form-search" lay-filter="' + tableId + '_filter_form">\n' +
|
||
formHtml +
|
||
'<div class="layui-form-item layui-inline" style="margin-left: 115px">\n' +
|
||
'<button type="submit" class="layui-btn layui-btn-normal" data-type="tableSearch" data-table="' + tableId + '" lay-submit lay-filter="' + tableId + '_filter"> 搜 索</button>\n' +
|
||
'<button type="reset" class="layui-btn layui-btn-primary" data-table-reset="' + tableId + '"> 重 置 </button>\n' +
|
||
' </div>' +
|
||
'</form>' +
|
||
'</fieldset>');
|
||
|
||
ua.table.listenTableSearch(tableId);
|
||
|
||
// 初始化form表单
|
||
form.render();
|
||
$.each(newCols, function (ncI, ncV) {
|
||
if (ncV.search === 'range') {
|
||
laydate.render({ range: true, type: ncV.timeType, elem: '[name="' + ncV.fieldAlias + '"]' });
|
||
}
|
||
if (ncV.search === 'time') {
|
||
laydate.render({ type: ncV.timeType, elem: '[name="' + ncV.fieldAlias + '"]' });
|
||
}
|
||
if (ncV.search === 'time_limit') {
|
||
laydate.render({ type: ncV.timeType, elem: '[name="[' + ncV.fieldAlias + ']min_date"]' });
|
||
laydate.render({ type: ncV.timeType, elem: '[name="[' + ncV.fieldAlias + ']max_date"]' });
|
||
}
|
||
});
|
||
}
|
||
|
||
options.where = {
|
||
filter: JSON.stringify(formatFilter),
|
||
op: JSON.stringify(formatOp)
|
||
};
|
||
|
||
lastTableWhere[tableId] = options.where;
|
||
|
||
return options;
|
||
},
|
||
renderSwitch: function (cols, tableInit, tableId, modifyReload) {
|
||
tableInit.modifyUrl = tableInit.modifyUrl || false;
|
||
cols = cols[0] || {};
|
||
tableId = tableId || ua.init.tableRenderId;
|
||
if (cols.length > 0) {
|
||
$.each(cols, function (i, v) {
|
||
v.filter = v.filter || false;
|
||
if (v.filter !== false && tableInit.modifyUrl !== false) {
|
||
ua.table.listenSwitch({ filter: v.filter, url: tableInit.modifyUrl, tableId: tableId, modifyReload: modifyReload });
|
||
}
|
||
});
|
||
}
|
||
},
|
||
renderOperat(data, elem) {
|
||
for (dk in data) {
|
||
var col = data[dk];
|
||
var operat = col[col.length - 1].operat;
|
||
if (operat !== undefined) {
|
||
var check = false;
|
||
for (key in operat) {
|
||
var item = operat[key];
|
||
if (typeof item === 'string') {
|
||
if (ua.checkAuth(item, elem)) {
|
||
check = true;
|
||
break;
|
||
}
|
||
} else {
|
||
for (k in item) {
|
||
var v = item[k];
|
||
if (v.auth == undefined) {
|
||
v.auth = 'add';
|
||
}
|
||
if (ua.checkAuth(v.auth, elem)) {
|
||
check = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!check) {
|
||
data[dk].pop();
|
||
}
|
||
}
|
||
|
||
}
|
||
return data;
|
||
},
|
||
renderTrueHide(data, options) {
|
||
var newData = [];
|
||
for (dk in data) {
|
||
var newCol = [];
|
||
var col = data[dk];
|
||
|
||
col.forEach(colItem => {
|
||
var trueHide = false;
|
||
if (typeof colItem.trueHide == 'function') {
|
||
trueHide = colItem.trueHide(colItem, col, options);
|
||
} else if (typeof colItem.trueHide == 'string') {
|
||
trueHide = !ua.checkAuth(colItem.trueHide, options.elem);
|
||
} else {
|
||
trueHide = colItem.trueHide;
|
||
}
|
||
|
||
if (!trueHide) {
|
||
newCol.push(colItem);
|
||
}
|
||
});
|
||
|
||
newData.push(newCol);
|
||
}
|
||
|
||
return newData;
|
||
},
|
||
buildToolbarHtml: function (toolbar, tableId) {
|
||
var html = '';
|
||
toolbar.class = toolbar.class || '';
|
||
toolbar.icon = toolbar.icon || '';
|
||
toolbar.auth = toolbar.auth || 'add';
|
||
toolbar.url = toolbar.url || '';
|
||
toolbar.extend = toolbar.extend || '';
|
||
toolbar.method = toolbar.method || 'open';
|
||
toolbar.field = toolbar.field || 'id';
|
||
toolbar.title = toolbar.title || toolbar.text;
|
||
toolbar.text = toolbar.text || toolbar.title;
|
||
toolbar.checkbox = toolbar.checkbox || false;
|
||
|
||
var formatToolbar = toolbar;
|
||
formatToolbar.icon = formatToolbar.icon !== '' ? '<i class="' + formatToolbar.icon + '"></i> ' : '';
|
||
formatToolbar.class = formatToolbar.class !== '' ? 'class="' + formatToolbar.class + '" ' : '';
|
||
if (toolbar.method === 'open') {
|
||
formatToolbar.method = formatToolbar.method !== '' ? 'data-open="' + formatToolbar.url + '" data-title="' + formatToolbar.title + '" ' : '';
|
||
} else if (toolbar.method === 'none') { // 常用于与extend配合,自定义监听按钮
|
||
formatToolbar.method = '';
|
||
} else if (toolbar.method === 'blank') {
|
||
formatToolbar.method = ' href="' + formatToolbar.url + '" target="_blank" ';
|
||
} else if (operat.method === 'tab') {
|
||
formatToolbar.method = ' layuimini-content-href="' + formatToolbar.url + '" data-title="' + formatToolbar.title + '"';
|
||
} else {
|
||
formatToolbar.method = formatToolbar.method !== '' ? 'data-request="' + formatToolbar.url + '" data-title="' + formatToolbar.title + '" ' : '';
|
||
}
|
||
formatToolbar.checkbox = toolbar.checkbox ? ' data-checkbox="true" ' : '';
|
||
formatToolbar.tableId = tableId !== undefined ? ' data-table="' + tableId + '" ' : '';
|
||
html = '<button ' + formatToolbar.class + formatToolbar.method + formatToolbar.extend + formatToolbar.checkbox + formatToolbar.tableId + '>' + formatToolbar.icon + formatToolbar.text + '</button>';
|
||
|
||
return html;
|
||
},
|
||
buildOperatHtml: function (operat, data) {
|
||
var html = '';
|
||
// 拷贝替代引用:后续默认值写入与格式化不得写回共享配置对象
|
||
operat = $.extend({}, operat);
|
||
operat.class = operat.class || '';
|
||
operat.icon = operat.icon || '';
|
||
operat.auth = operat.auth || 'add';
|
||
operat.url = operat.url || '';
|
||
operat.extend = operat.extend || '';
|
||
operat.method = operat.method || 'open';
|
||
operat.field = operat.field || 'id';
|
||
operat.data = operat.data || ['id'];
|
||
operat.titleField = operat.titleField || 'title';
|
||
operat.title = operat.title || operat.text;
|
||
operat.text = operat.text || operat.title;
|
||
|
||
var titleEndfix = '';
|
||
|
||
if (typeof operat.titleField == 'function') {
|
||
titleEndfix = operat.titleField(data, operat);
|
||
|
||
} else if (data[operat.titleField]) {
|
||
titleEndfix = '-' + data[operat.titleField];
|
||
}
|
||
|
||
if (typeof operat.text == 'function') {
|
||
operat.text = operat.text(data, operat);
|
||
}
|
||
|
||
if (typeof operat.title == 'function') {
|
||
operat.title = operat.title(data, operat);
|
||
}
|
||
|
||
var formatOperat = operat;
|
||
formatOperat.icon = formatOperat.icon !== '' ? '<i class="' + formatOperat.icon + '"></i> ' : '';
|
||
formatOperat.class = formatOperat.class !== '' ? 'class="' + formatOperat.class + '" ' : '';
|
||
if (operat.method === 'open') {
|
||
formatOperat.method = formatOperat.method !== '' ? 'data-open="' + formatOperat.url + '" data-title="' + formatOperat.title + titleEndfix + '" ' : '';
|
||
} else if (operat.method === 'none') { // 常用于与extend配合,自定义监听按钮
|
||
formatOperat.method = '';
|
||
} else if (operat.method === 'blank') {
|
||
formatOperat.method = ' href="' + formatOperat.url + '" target="_blank" ';
|
||
} else if (operat.method === 'tab') {
|
||
formatOperat.method = ' layuimini-content-href="' + formatOperat.url + '" data-title="' + formatOperat.title + titleEndfix + '"';
|
||
} else {
|
||
formatOperat.method = formatOperat.method !== '' ? 'data-request="' + formatOperat.url + '" data-title="' + formatOperat.title + titleEndfix + '" ' : '';
|
||
}
|
||
|
||
formatOperat.dataBind = ' ';
|
||
operat.data.forEach((item, index) => {
|
||
formatOperat.dataBind += 'data-' + item + '="' + data[item] + '" ';
|
||
});
|
||
|
||
html = '<a ' + formatOperat.class + formatOperat.method + formatOperat.extend + formatOperat.dataBind + '>' + formatOperat.icon + formatOperat.text + '</a>';
|
||
|
||
return html;
|
||
},
|
||
toolSpliceUrl(url, field, data) {
|
||
url = url.indexOf("?") !== -1 ? url + '&' + field + '=' + data[field] : url + '?' + field + '=' + data[field];
|
||
return url;
|
||
},
|
||
formatCols: function (cols, init) {
|
||
for (i in cols) {
|
||
var col = cols[i];
|
||
for (index in col) {
|
||
var val = col[index];
|
||
|
||
if (typeof val.hide == 'function') {
|
||
cols[i][index]['hide'] = val.hide(val, cols, init);
|
||
}
|
||
if (val['width'] == undefined && val['minWidth'] == undefined) {
|
||
var width = null;
|
||
if (val.title) {
|
||
width = val.title.length * 15 + 55;
|
||
}
|
||
|
||
if (width != null) {
|
||
cols[i][index]['minWidth'] = width;
|
||
}
|
||
}
|
||
|
||
// if str end with _time
|
||
if (val.field && val.field.indexOf('_time') !== -1) {
|
||
cols[i][index]['minWidth'] = 160;
|
||
}
|
||
|
||
|
||
if (val.sort === undefined) {
|
||
cols[i][index]['sort'] = true;
|
||
}
|
||
if (val.defaultValue === undefined) {
|
||
cols[i][index]['defaultValue'] = '';
|
||
}
|
||
|
||
// 判断是否包含初始化数据
|
||
if (val.init === undefined) {
|
||
cols[i][index]['init'] = init;
|
||
}
|
||
|
||
// 格式化列操作栏
|
||
if (val.templet === ua.table.tool && val.operat === undefined) {
|
||
cols[i][index]['operat'] = ['edit', 'delete'];
|
||
}
|
||
// 格式化列操作栏
|
||
if (val.templet === ua.table.tool) {
|
||
cols[i][index]['sort'] = false;
|
||
}
|
||
|
||
// 判断是否包含开关组件
|
||
if (val.templet === ua.table.switch && val.filter === undefined) {
|
||
cols[i][index]['filter'] = val.field;
|
||
}
|
||
|
||
// 判断是否含有搜索下拉列表
|
||
if (val.selectList !== undefined && val.search === undefined) {
|
||
cols[i][index]['search'] = 'select';
|
||
}
|
||
|
||
// 判断是否初始化对齐方式
|
||
if (val.align === undefined) {
|
||
cols[i][index]['align'] = 'left';
|
||
}
|
||
|
||
// 部分字段开启排序
|
||
var sortDefaultFields = ['id', 'sort'];
|
||
if (val.sort === undefined && sortDefaultFields.indexOf(val.field) >= 0) {
|
||
cols[i][index]['sort'] = true;
|
||
}
|
||
|
||
// 初始化图片高度
|
||
if (val.templet === ua.table.image && val.imageHeight === undefined) {
|
||
cols[i][index]['imageHeight'] = 26;
|
||
cols[i][index]['sort'] = false;
|
||
}
|
||
// 图片默认不搜索
|
||
if (val.templet === ua.table.image && val.search === undefined) {
|
||
cols[i][index]['search'] = false;
|
||
}
|
||
|
||
// 判断是否列表数据转换
|
||
if (val.selectList !== undefined && val.templet === undefined) {
|
||
cols[i][index]['templet'] = ua.table.list;
|
||
}
|
||
|
||
// 判断是否多层对象
|
||
if (val.field !== undefined && val.field.split(".").length > 1) {
|
||
if (val.templet === undefined) {
|
||
cols[i][index]['templet'] = ua.table.value;
|
||
}
|
||
}
|
||
|
||
// 如果未定义则默认使用value
|
||
if (cols[i][index]['templet'] === undefined) {
|
||
cols[i][index]['templet'] = ua.table.value;
|
||
} else {
|
||
// 针对特定的模板做数据调整
|
||
|
||
if (cols[i][index]['templet'] == ua.table.list) {
|
||
if (val.selectValue) {
|
||
|
||
var newSelectList = {};
|
||
val.selectList.map(item => {
|
||
newSelectList[item[val.selectValue]] = item[val.selectLabel];
|
||
});
|
||
|
||
cols[i][index]['selectList'] = newSelectList;
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
if (val.fieldFormat == undefined) {
|
||
|
||
switch (val.templet) {
|
||
case ua.table.image:
|
||
val.fieldFormat = 'image';
|
||
break;
|
||
case ua.table.date:
|
||
val.fieldFormat = 'date';
|
||
break;
|
||
default:
|
||
val.fieldFormat = 'text';
|
||
|
||
if (val.selectList !== undefined) {
|
||
val.fieldFormat = 'select';
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return cols;
|
||
},
|
||
tool: function (data) {
|
||
var option = data.LAY_COL;
|
||
option.operat = option.operat || ['edit', 'delete'];
|
||
var elem = option.init.tableElem || ua.init.tableElem;
|
||
var html = '';
|
||
$.each(option.operat, function (i, item) {
|
||
if (typeof item === 'string') {
|
||
switch (item) {
|
||
case 'edit':
|
||
var editMethod = 'tab';
|
||
if(selectMode){
|
||
editMethod = 'open';
|
||
}
|
||
var operat = {
|
||
class: 'layui-btn layui-btn-success layui-btn-xs',
|
||
method: editMethod,
|
||
field: 'id',
|
||
icon: '',
|
||
text: '编辑',
|
||
title: '编辑信息',
|
||
auth: 'edit',
|
||
url: option.init.editUrl,
|
||
extend: option.init.formFullScreen == 'true' ? ' data-full="true"' : ''
|
||
};
|
||
operat.url = ua.table.toolSpliceUrl(operat.url, operat.field, data);
|
||
if (ua.checkAuth(operat.auth, elem)) {
|
||
html += ua.table.buildOperatHtml(operat, data);
|
||
}
|
||
break;
|
||
case 'delete':
|
||
var operat = {
|
||
class: 'layui-btn layui-btn-danger layui-btn-xs',
|
||
method: 'get',
|
||
field: 'id',
|
||
icon: '',
|
||
text: '删除',
|
||
title: '确定删除?',
|
||
auth: 'delete',
|
||
url: option.init.deleteUrl,
|
||
extend: ""
|
||
};
|
||
operat.url = ua.table.toolSpliceUrl(operat.url, operat.field, data);
|
||
if (ua.checkAuth(operat.auth, elem)) {
|
||
html += ua.table.buildOperatHtml(operat, data);
|
||
}
|
||
break;
|
||
}
|
||
|
||
} else if (typeof item === 'object') {
|
||
$.each(item, function (i, operat) {
|
||
// 拷贝替代引用:默认值写入与 url 拼接不得写回共享配置对象
|
||
operat = $.extend({}, operat);
|
||
operat.class = operat.class || '';
|
||
operat.icon = operat.icon || '';
|
||
operat.auth = operat.auth || 'add';
|
||
operat.url = operat.url || '';
|
||
operat.method = operat.method || 'open';
|
||
operat.field = operat.field || 'id';
|
||
operat.title = operat.title || operat.text;
|
||
operat.text = operat.text || operat.title;
|
||
operat.extend = operat.extend || '';
|
||
|
||
operat._if = operat._if || function () { return true; };
|
||
|
||
if (typeof operat._if == 'function') {
|
||
if (operat._if(data, operat) !== true) {
|
||
return '';
|
||
}
|
||
} else if (typeof operat._if == 'string') {
|
||
var ifValue = ua.table.returnColumnValue(data, operat._if, false);
|
||
if (!ifValue) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
// 自定义表格opreat按钮的弹窗标题风格,extra是表格里的欲加入标题中的字段
|
||
operat.extra = operat.extra || '';
|
||
if (data[operat.extra] !== undefined) {
|
||
operat.title = data[operat.extra] + ' - ' + operat.title;
|
||
}
|
||
|
||
if (typeof operat.url == 'function') {
|
||
operat.url = operat.url(data, operat);
|
||
}
|
||
|
||
|
||
if (typeof operat.field != 'function') {
|
||
if (!ua.empty(operat.field)) {
|
||
operat.url = ua.table.toolSpliceUrl(operat.url, operat.field, data);
|
||
}
|
||
} else {
|
||
|
||
var fieldParam = operat.field(data, operat);
|
||
|
||
if (typeof fieldParam == 'string') {
|
||
operat.url = fieldParam;
|
||
} else {
|
||
var querys = '';
|
||
if (operat.url.indexOf("?") !== -1) {
|
||
if (operat.url.indexOf("?") !== operat.url.length - 1) {
|
||
querys = '&';
|
||
}
|
||
} else {
|
||
querys = '?';
|
||
}
|
||
operat.url += querys + $.param(fieldParam);
|
||
}
|
||
|
||
}
|
||
|
||
if (ua.checkAuth(operat.auth, elem)) {
|
||
html += ua.table.buildOperatHtml(operat, data);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
return html;
|
||
},
|
||
list: function (data) {
|
||
var option = data.LAY_COL;
|
||
option.selectList = option.selectList || {};
|
||
|
||
var value = ua.table.returnColumnValue(data);
|
||
if (option.selectList[value] === undefined || option.selectList[value] === '' || option.selectList[value] === null) {
|
||
return value;
|
||
} else {
|
||
return option.selectList[value];
|
||
}
|
||
},
|
||
filePreview: function (data) {
|
||
var mimeName = data.mime_type.split('/')[0];
|
||
|
||
if (mimeName == 'image') {
|
||
return ua.table.image(data);
|
||
} else {
|
||
|
||
var value = ua.table.returnColumnValue(data);
|
||
|
||
var groupName = ua.getExtGroupName(data.file_ext);
|
||
|
||
return '<a href="' + value + '"><img style="height:40px;width:40px" src="/static/admin/images/upload-icons/' + groupName + '.png" /></a>';
|
||
}
|
||
|
||
},
|
||
image: function (data) {
|
||
var option = data.LAY_COL;
|
||
option.imageWidth = option.imageWidth || 200;
|
||
option.imageHeight = option.imageHeight || 26;
|
||
option.imageSplit = option.imageSplit || '|';
|
||
option.imageJoin = option.imageJoin || '<br>';
|
||
option.title = option.title || option.field;
|
||
var title = data[option.title];
|
||
var value = ua.table.returnColumnValue(data);
|
||
if (value === undefined || value === null) {
|
||
return '<img style="max-width: ' + option.imageWidth + 'px; max-height: ' + option.imageHeight + 'px;" src="' + value + '" data-image="' + title + '">';
|
||
} else {
|
||
var values = value.split(option.imageSplit),
|
||
valuesHtml = [];
|
||
values.forEach((value, index) => {
|
||
valuesHtml.push('<img style="max-width: ' + option.imageWidth + 'px; max-height: ' + option.imageHeight + 'px;" src="' + value + '" data-image="' + title + '">');
|
||
});
|
||
return valuesHtml.join(option.imageJoin);
|
||
}
|
||
},
|
||
url: function (data) {
|
||
var option = data.LAY_COL;
|
||
|
||
var urlNameField = option.urlNameField || '';
|
||
|
||
var value = ua.table.returnColumnValue(data);
|
||
|
||
if (ua.empty(value)) {
|
||
return '';
|
||
}
|
||
|
||
var urlName = value;
|
||
|
||
if (urlNameField != '') {
|
||
try {
|
||
var urlNameFieldType = typeof urlNameField;
|
||
|
||
if (urlNameFieldType == 'string') {
|
||
urlName = data[urlNameField];
|
||
} else if (urlNameFieldType == 'function') {
|
||
urlName = urlNameField(data);
|
||
}
|
||
|
||
} catch (e) {
|
||
|
||
}
|
||
}
|
||
|
||
return '<a class="layuimini-table-url" href="' + value + '" target="_blank" class="label bg-green">' + urlName + '</a>';
|
||
},
|
||
switch: function (data) {
|
||
var option = data.LAY_COL;
|
||
|
||
if (!ua.checkAuth('modify', option.init.tableElem)) {
|
||
return ua.table.list(data);
|
||
}
|
||
|
||
option.filter = option.filter || option.field || null;
|
||
option.checked = option.checked || 1;
|
||
|
||
|
||
var value = ua.table.returnColumnValue(data);
|
||
var checked = value === option.checked ? 'checked' : '';
|
||
return laytpl('<input type="checkbox" name="' + option.field + '" value="' + data.id + '" lay-skin="switch" lay-filter="' + option.filter + '" ' + checked + ' >').render(data);
|
||
},
|
||
price: function (data) {
|
||
var value = ua.table.returnColumnValue(data);
|
||
return '<span>¥' + value + '</span>';
|
||
},
|
||
percent: function (data) {
|
||
var value = ua.table.returnColumnValue(data);
|
||
return '<span>' + value + '%</span>';
|
||
},
|
||
icon: function (data) {
|
||
var value = ua.table.returnColumnValue(data);
|
||
return '<i class="' + value + '"></i>';
|
||
},
|
||
text: function (data) {
|
||
var value = ua.table.returnColumnValue(data);
|
||
return '<span class="line-limit-length">' + value + '</span>';
|
||
},
|
||
value: function (data) {
|
||
var value = ua.table.returnColumnValue(data);
|
||
return '<span>' + value + '</span>';
|
||
},
|
||
//时间戳转日期
|
||
date: function (data) {
|
||
var option = data.LAY_COL;
|
||
|
||
value = ua.table.returnColumnValue(data);
|
||
if (!ua.empty(value)) {
|
||
value = util.toDateString(value * 1000, option.format || 'yyyy-MM-dd HH:mm:ss');
|
||
}
|
||
return '<span>' + value + '</span>';
|
||
},
|
||
bytes: (data) => {
|
||
var size = ua.table.returnColumnValue(data);
|
||
sizeReadable = ua.bytes(size);
|
||
return sizeReadable;
|
||
},
|
||
copyText(data) {
|
||
var option = data.LAY_COL;
|
||
var value = ua.table.returnColumnValue(data);
|
||
var copyValue = value;
|
||
var copyText = option.copyText;
|
||
|
||
if (typeof copyText === 'function') {
|
||
copyValue = copyText(value, data);
|
||
}
|
||
|
||
return '<span data-toggle="copy-text" data-clipboard-text="' + copyValue + '"><i class="fa fa-copy"></i> ' + value + '</span>';
|
||
},
|
||
// 统一列返回数据处理
|
||
returnColumnValue(data, field, defaultValue) {
|
||
if (!data.LAY_COL) {
|
||
return '';
|
||
}
|
||
var option = data.LAY_COL;
|
||
|
||
field = field || option.field;
|
||
defaultValue = defaultValue || option.defaultValue;
|
||
|
||
var valueParser = option.valueParser;
|
||
var value = undefined;
|
||
try {
|
||
value = eval("data." + field);
|
||
} catch (e) {
|
||
|
||
}
|
||
if (typeof valueParser == 'function') {
|
||
value = valueParser(value, data);
|
||
}
|
||
|
||
if (value === null || value === undefined) {
|
||
value = defaultValue;
|
||
}
|
||
|
||
return value;
|
||
},
|
||
listenTableSearch: function (tableId) {
|
||
form.on('submit(' + tableId + '_filter)', function (data) {
|
||
var dataField = data.field;
|
||
var formatFilter = {},
|
||
formatOp = {};
|
||
$.each(dataField, function (key, val) {
|
||
if (val !== '') {
|
||
formatFilter[key] = val;
|
||
|
||
var elemId = ua.table.renderSearchFormItemElementId(key);
|
||
|
||
var op = $('#c-' + elemId).attr('data-search-op');
|
||
op = op || '%*%';
|
||
formatOp[key] = op;
|
||
}
|
||
});
|
||
|
||
var where = {
|
||
filter: JSON.stringify(formatFilter),
|
||
op: JSON.stringify(formatOp)
|
||
};
|
||
lastTableWhere[tableId] = where;
|
||
table.reloadData(tableId, {
|
||
page: {
|
||
curr: 1
|
||
}
|
||
, where: where
|
||
}, 'data');
|
||
return false;
|
||
});
|
||
},
|
||
listenSwitch: function (option, ok) {
|
||
option.filter = option.filter || '';
|
||
option.url = option.url || '';
|
||
option.field = option.field || option.filter || '';
|
||
option.tableId = option.tableId || ua.init.tableRenderId;
|
||
option.modifyReload = option.modifyReload || false;
|
||
form.on('switch(' + option.filter + ')', function (obj) {
|
||
var checked = obj.elem.checked ? 1 : 0;
|
||
if (typeof ok === 'function') {
|
||
return ok({
|
||
id: obj.value,
|
||
checked: checked,
|
||
});
|
||
} else {
|
||
var data = {
|
||
id: obj.value,
|
||
field: option.field,
|
||
value: checked,
|
||
};
|
||
ua.request.post({
|
||
url: option.url,
|
||
prefix: true,
|
||
data: data,
|
||
}, function (res) {
|
||
if (option.modifyReload) {
|
||
table.reloadData(option.tableId);
|
||
}
|
||
}, function (res) {
|
||
ua.msg.error(res.msg, function () {
|
||
table.reloadData(option.tableId);
|
||
});
|
||
}, function () {
|
||
table.reloadData(option.tableId);
|
||
});
|
||
}
|
||
});
|
||
},
|
||
listenTableSort(option) {
|
||
//触发排序事件
|
||
table.on('sort(' + option.layFilter + ')', function (obj) { //注:sort 是工具条事件名,test 是 table 原始容器的属性 lay-filter="对应的值"
|
||
|
||
var lastWhere = lastTableWhere[option.id] ? lastTableWhere[option.id] : {};
|
||
|
||
lastWhere.sort = {};
|
||
lastWhere.sort[obj.field] = obj.type;
|
||
|
||
table.reloadData(option.id, {
|
||
initSort: obj //记录初始排序,如果不设的话,将无法标记表头的排序状态。
|
||
, where: lastWhere
|
||
});
|
||
|
||
|
||
});
|
||
},
|
||
listenToolbar: function (layFilter, tableId) {
|
||
table.on('toolbar(' + layFilter + ')', function (obj) {
|
||
var options = obj.config;
|
||
var tableId = obj.config.id;
|
||
// 搜索表单的显示
|
||
switch (obj.event) {
|
||
case 'TABLE_SEARCH':
|
||
var searchFieldsetId = 'searchFieldset_' + tableId;
|
||
var _that = $("#" + searchFieldsetId);
|
||
if (_that.hasClass("layui-hide")) {
|
||
_that.removeClass('layui-hide');
|
||
} else {
|
||
_that.addClass('layui-hide');
|
||
}
|
||
break;
|
||
case 'TABLE_LAYOUT':
|
||
// 视图切换下拉(任务 15 (b),交互结构参照旧 TABLE_LAYOUT
|
||
// 实现重建):按钮仅在受控表 layoutList 条目>1 时注入,
|
||
// 条目清单来自 controlledLayouts 注册表。点击条目 =
|
||
// localStorage 按页记忆 + setLayoutForm 切换(数据取自
|
||
// store 当页状态,零网络请求)
|
||
var layoutEntryId = 'ua-layout-list-' + tableId;
|
||
var $toolbarLayoutElem = $('[lay-table-id="' + tableId + '"]').find('[lay-event="TABLE_LAYOUT"]');
|
||
var $layoutListElem = $('#' + layoutEntryId);
|
||
if ($layoutListElem.length > 0) {
|
||
$layoutListElem.remove();
|
||
break;
|
||
}
|
||
|
||
var layoutCurrent = ua.table.getLayoutForm(tableId);
|
||
var layoutIcons = {table: 'layui-icon-table', card: 'layui-icon-app'};
|
||
var layoutHtml = '<div id="' + layoutEntryId + '" class="ul-nav-block table-tpl-container">';
|
||
$.each(controlledLayouts[tableId].entries, function (i, item) {
|
||
var itemIcon = item.icon || layoutIcons[item.name] || 'layui-icon-template-1';
|
||
var itemClassName = item.name === layoutCurrent ? ' current' : '';
|
||
layoutHtml += '<div class="item' + itemClassName + '" data-name="' + item.name + '">'
|
||
+ '<div class="item-icon"><i class="layui-icon ' + itemIcon + '"></i></div>'
|
||
+ '<div class="item-title">' + item.title + '</div>'
|
||
+ '</div>';
|
||
});
|
||
layoutHtml += '</div>';
|
||
|
||
$layoutListElem = $(layoutHtml).appendTo($toolbarLayoutElem);
|
||
|
||
$layoutListElem.on('click', '.item', function (event) {
|
||
var layoutName = $(this).data('name');
|
||
ua.setPageSetting('table-layout-default', layoutName);
|
||
ua.table.setLayoutForm(tableId, layoutName);
|
||
$layoutListElem.remove();
|
||
$(document).off('click.ua-table-layout');
|
||
event.stopPropagation();
|
||
});
|
||
|
||
// 点击下拉外区域收起(命名空间事件,收起时解除)
|
||
$(document).off('click.ua-table-layout').on('click.ua-table-layout', function (event) {
|
||
if (!$(event.target).closest($toolbarLayoutElem).length) {
|
||
$('#' + layoutEntryId).remove();
|
||
$(document).off('click.ua-table-layout');
|
||
}
|
||
});
|
||
break;
|
||
}
|
||
});
|
||
},
|
||
listenEdit: function (tableInit, layFilter, tableId, modifyReload) {
|
||
tableInit.modifyUrl = tableInit.modifyUrl || false;
|
||
tableId = tableId || ua.init.tableRenderId;
|
||
if (tableInit.modifyUrl !== false) {
|
||
table.on('edit(' + layFilter + ')', function (obj) {
|
||
var value = obj.value,
|
||
data = obj.data,
|
||
id = data.id,
|
||
field = obj.field;
|
||
var _data = {
|
||
id: id,
|
||
field: field,
|
||
value: value,
|
||
};
|
||
ua.request.post({
|
||
url: tableInit.modifyUrl,
|
||
prefix: true,
|
||
data: _data,
|
||
}, function (res) {
|
||
if (modifyReload) {
|
||
table.reloadData(tableId);
|
||
}
|
||
}, function (res) {
|
||
ua.msg.error(res.msg, function () {
|
||
table.reloadData(tableId);
|
||
});
|
||
}, function () {
|
||
table.reloadData(tableId);
|
||
});
|
||
});
|
||
}
|
||
},
|
||
listenTableSelectConfirm(options) {
|
||
$('.select-confirm').click(function () {
|
||
var checkStatus = ua.table.checkStatus(options.id);
|
||
|
||
if (checkStatus.data.length == 0) {
|
||
layer.msg('请选择数据');
|
||
return false;
|
||
}
|
||
|
||
parent.window[selectConfirmCallback](checkStatus.data);
|
||
});
|
||
},
|
||
listenExport: function (options) {
|
||
|
||
var exportFields = {};
|
||
|
||
var imageFields = [];
|
||
|
||
var selectFields = {};
|
||
|
||
var dateFields = [];
|
||
|
||
options.cols[0].forEach(col => {
|
||
if (col.field) {
|
||
|
||
exportFields[col.field] = col.title;
|
||
|
||
options.cols[0].forEach(col => {
|
||
if (col.field) {
|
||
|
||
exportFields[col.field] = col.title;
|
||
|
||
switch (col.fieldFormat) {
|
||
case 'image':
|
||
imageFields.push(col.field);
|
||
break;
|
||
|
||
case 'select':
|
||
if (Object.keys(col.selectList).length > 0) {
|
||
selectFields[col.field] = col.selectList;
|
||
}
|
||
break;
|
||
case 'date':
|
||
|
||
dateFields.push(col.field);
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
}
|
||
});
|
||
|
||
}
|
||
});
|
||
|
||
// excel导出
|
||
$('body').on('click', '[data-table-export]', function () {
|
||
var tableId = $(this).attr('data-table-export'),
|
||
url = $(this).attr('data-url');
|
||
|
||
var searchVals = form.val(tableId + '_filter_form');
|
||
|
||
var dataField = searchVals;
|
||
|
||
var formatFilter = {},
|
||
formatOp = {};
|
||
$.each(dataField, function (key, val) {
|
||
if (val !== '') {
|
||
formatFilter[key] = val;
|
||
|
||
var elemId = ua.table.renderSearchFormItemElementId(key);
|
||
|
||
var op = $('#c-' + elemId).attr('data-search-op');
|
||
op = op || '%*%';
|
||
formatOp[key] = op;
|
||
}
|
||
});
|
||
|
||
var searchQuery = {
|
||
filter: JSON.stringify(formatFilter),
|
||
op: JSON.stringify(formatOp),
|
||
fields: JSON.stringify(exportFields),
|
||
image_fields: JSON.stringify(imageFields),
|
||
select_fields: JSON.stringify(selectFields),
|
||
date_fields: JSON.stringify(dateFields),
|
||
};
|
||
|
||
var query = $.param(searchQuery);
|
||
|
||
var index = ua.msg.confirm('根据查询进行导出,确定导出?', function () {
|
||
|
||
toUrl = ua.url(url);
|
||
if (toUrl.indexOf('?') < 0) {
|
||
toUrl += '?';
|
||
} else {
|
||
toUrl += '&';
|
||
}
|
||
toUrl += query;
|
||
|
||
window.open(toUrl);
|
||
|
||
layer.close(index);
|
||
});
|
||
});
|
||
},
|
||
renderSearchFormItemElementId(key) {
|
||
|
||
var elemId = key;
|
||
if (key.indexOf('[') == 0) {
|
||
var keyArr = key.replace('[', '').split(']');
|
||
|
||
elemId = keyArr[0] + '-' + keyArr[1];
|
||
}
|
||
|
||
elemId = elemId.replace('.', '-');
|
||
|
||
return elemId;
|
||
}
|
||
};
|
||
})();
|