(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 cardSettingOutsideClose = null;
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 = '
';
html += '';
html += '
';
$.each(cellCols, function (i, col) {
if (titleCol && col.field === titleCol.field) {
return;
}
if (col.field === 'create_time') {
return;
}
html += '
'
+ '' + (col.title || col.field) + ''
+ '' + (row._cells[col.field] || '') + ''
+ '
';
});
html += '
';
html += '';
html += '
';
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 = $('');
$buttons.slice(3).appendTo($moreWrap);
$moreWrap.appendTo($operat);
$operat.append($(''));
});
};
// ========== 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('[lay-event="TABLE_CARD_SETTING"]').toggleClass('layui-hide', isTable);
};
// 卡片设置面板统一收起(外点收起/图标再点收起等路径共用):移除面板并摘除
// 外点捕获监听,保证不留孤儿监听
var closeCardSettingPanel = function (tableId) {
$('#ua-card-setting-' + tableId).remove();
if (cardSettingOutsideClose) {
document.removeEventListener('click', cardSettingOutsideClose, true);
cardSettingOutsideClose = null;
}
};
// 卡片设置面板开关(pro#5 迭代:入口为右侧 defaultToolbar 图标组最左侧的
// TABLE_CARD_SETTING 图标,走 layui toolbar 事件)。含面板创建/移除/开关/
// 排序/外点收起全套逻辑。面板 append 在本表的 .layui-table-tool 容器
//(layui 已 position:relative)而非图标元素内:layui.css 的
// .layui-btn:hover{opacity:.8} 会沿祖先链压暗整棵面板子树,断开父子
// 关系即根治。打开时用 getBoundingClientRect 内联定位(两 rect 同帧
// 相减与滚动无关),面板右缘对齐图标右缘、顶边贴图标下沿,向下向左
// 展开。面板内交互均阻断冒泡
var toggleCardSettingPanel = function (tableId) {
var cardSettingId = 'ua-card-setting-' + tableId;
var $view = $(controlledLayouts[tableId].elem).siblings('.layui-table-view').first();
var $toolbarSettingElem = $view.find('[lay-event="TABLE_CARD_SETTING"]');
var $toolElem = $view.find('.layui-table-tool').first();
var $cardSettingElem = $('#' + cardSettingId);
if ($cardSettingElem.length > 0) {
closeCardSettingPanel(tableId);
return;
}
// 配置段 + 排序段(面板每次点按钮重建,排序段随建随取
// store 当前态,表头排序/搜索后重开面板即最新)
var cardSettingHtml = '';
var operatAlwaysOn = ua.getPageSetting('card-operat-always', '') === '1';
cardSettingHtml += '
配置
';
cardSettingHtml += '
操作按钮始终显示'
+ '
';
// 当前排序读 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;
// 「排序」标题 + 重置链接:以 cardSortCurrent 键数判定是否渲染
// (排序字段可能是已隐藏列,不在下方排序行清单里,不能用行命中判定)
cardSettingHtml += '
排序'
+ (cardSortCurrent && Object.keys(cardSortCurrent).length > 0
? '
重置' : '')
+ '
';
$.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 += '
'
+ '' + (col.title || col.field) + ''
+ '
';
});
cardSettingHtml += '
';
$cardSettingElem = $(cardSettingHtml).appendTo($toolElem);
// 内联定位:面板右缘对齐图标右缘、顶边贴图标下沿(200px 宽面板
// 自图标向左下展开,完全落在视口内)。iconRect/toolRect 同帧相减,
// 与页面滚动无关
var iconRect = $toolbarSettingElem[0].getBoundingClientRect();
var toolRect = $toolElem[0].getBoundingClientRect();
$cardSettingElem.css({
top: (iconRect.bottom - toolRect.top) + 'px',
right: (toolRect.right - iconRect.right) + 'px'
});
// 面板内条目点击不冒泡(排序段等后续 .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();
});
// 排序状态就地刷新共用闭包:重读 store where.sort,遍历排序行更新
// current 高亮与方向图标(未排序行 i 保持 layui-hide,仅剩左侧标题),
// 并同步「重置」链接显隐——有排序(where.sort 有键,排序字段可能是
// 已隐藏列不在行清单里,不能用行命中判定)且无链接则补建,无排序则
// 移除。reloadData 调用链内 store setWhere 同步完成,调用即读即新
var refreshSortState = function () {
var sortState = window.UA_TABLE_STORE.dataProvider.getState(tableId);
var current = sortState && sortState.where ? sortState.where.sort : null;
$cardSettingElem.find('.ua-card-setting-sort-item').each(function () {
var sortType = current ? (current[$(this).data('field')] || null) : null;
$(this).toggleClass('current', !!sortType)
.find('i').attr('class', (sortType
? 'layui-icon layui-icon-' + (sortType === 'desc' ? 'down' : 'up')
: 'layui-icon layui-hide') + ' ua-card-setting-sort-arrow');
});
var $sortTitle = $cardSettingElem.find('.ua-card-setting-sort-title');
var $reset = $sortTitle.find('.ua-card-setting-sort-reset');
if (current && Object.keys(current).length > 0) {
if ($reset.length === 0) {
$sortTitle.append('重置');
}
} else {
$reset.remove();
}
};
// 排序:循环 升 → 降 → 取消。与 listenTableSort 同构:
// 以 lastTableWhere 为基底就地改 sort,显式传 where
// (store.reload 仅在 opts.where 存在时经 setWhere 更新
// 请求参数,漏传排序到不了服务端);initSort 供表头
// 箭头(取消时省略);当前态以 store getState 为准
// (搜索路径会整体覆盖 lastTableWhere,admin-table.js
// 侧变量不可作判定依据)。点击后面板保持打开,refreshSortState
// 就地刷新高亮与重置链接,可连续点击
$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);
refreshSortState();
});
// 重置排序:清除 where.sort 后重载(不带 initSort,取消排序语义
// 与排序 handler 的取消分支一致);面板保持打开、高亮清空,重置
// 链接自身随 refreshSortState 消失。stopPropagation 防外点收起,
// preventDefault 抑制 a 元素默认行为
$cardSettingElem.on('click', '.ua-card-setting-sort-reset', function (event) {
event.stopPropagation();
event.preventDefault();
var lastWhere = lastTableWhere[tableId] ? lastTableWhere[tableId] : {};
delete lastWhere.sort;
lastTableWhere[tableId] = lastWhere;
ua.table.reloadData(tableId, {where: lastWhere});
refreshSortState();
});
// 点击面板外区域收起(原生捕获阶段监听,先于一切 stopPropagation——
// layui 内置 LAYTABLE_COLS/PRINT/EXPORT 等图标的点击处理带
// stopPropagation 挡冒泡,冒泡阶段监听点不到,捕获阶段不受影响);
// 面板已不在图标元素内,判定为「既不在图标内、也不在面板内」才收起,
// 否则点面板自身会被误判为外部点立刻收起。绑定前先摘旧监听防重复绑定
if (cardSettingOutsideClose) {
document.removeEventListener('click', cardSettingOutsideClose, true);
}
cardSettingOutsideClose = function (event) {
// loading 进行期间忽略一切点击(模态遮罩上的点击不构成关闭语义),
// 加载结束后恢复正常收起。判定取并集宁保守勿漏:全局 layer.load 层
// (loading.show 产生,store.fetch / ua.request 请求期存在、hide 计数
// 归零即关)与模板容器加载态类(store reload wrapper 加、done/fail 摘)
// 任一命中即视为加载中
if ($('.layui-layer-loading').length || $('#ua-layout-' + tableId).hasClass('ua-layout-loading')) {
return;
}
var $t = $(event.target);
if (!$t.closest($toolbarSettingElem).length && !$t.closest('#' + cardSettingId).length) {
closeCardSettingPanel(tableId);
}
};
document.addEventListener('click', cardSettingOutsideClose, true);
};
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 是页面