(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 += '
' + (titleCol ? (row._cells[titleCol.field] || '') : '') + '
'; 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 是页面