(function () { // ulthon-admin.js 会把 UA_ADMIN 挂到 window.ua 并 delete window.UA_ADMIN, // 本文件位于 admin-core.js 之后(UA_ADMIN 尚存),此处回退 window.ua 兼容独立加载 var ua = window.UA_ADMIN || window.ua; var laypage = layui.laypage; /** * 表格数据层(数据源 / 分页 / 选择 / 重载编排) * * 与 admin-table.js 的 url 模式解耦:由本层负责取数并维护表格运行状态, * 渲染层(table / 模板 / 卡片形态)只消费本层产出的响应数据。 * * 语义平移来源(行为保持一致): * - LAY_DISABLED 注入:admin-table.js render 内 parseData 注入段 * (select_mode 页面 URL selectedIds 回显禁选 + rowDisableCallback) * - LAY_CHECKED 注入:store 选中集合命中行(引擎 data 模式每次 reloadData 重建 * cache,不注入则翻页回来勾选全丢) * - where 单一事实源:admin-table.js lastTableWhere(renderSearch 产物 + listenTableSort 合入) * - 失败双通道提示:admin-core.js ua.request.ajax(res.code != 0 与 HTTP error) * - radio 选择语义:tableData 组件 selectType:'radio'(新选中替换旧选中) */ // 分页条数默认档位(与 admin-table.js render 默认 limits 一致) var DEFAULT_LIMITS = [10, 15, 20, 25, 50, 100]; // 注册表:tableId → 表格运行状态 var states = {}; // where 单一事实源(语义平移自 admin-table.js 的 lastTableWhere) var lastTableWhere = {}; // ========== 内部工具 ========== var getState = function (tableId) { return states[tableId] || null; }; // filter/op 统一为 JSON 字符串(renderSearch/listenTableSearch 产出的 wire 格式, // 后端 buildTableParames 按此解析),sort 保持对象(layui where.sort 同构) var normalizeWhere = function (where) { where = where || {}; var normalized = {}; if (where.filter !== undefined) { normalized.filter = typeof where.filter === 'string' ? where.filter : JSON.stringify(where.filter); } if (where.op !== undefined) { normalized.op = typeof where.op === 'string' ? where.op : JSON.stringify(where.op); } if (where.sort !== undefined) { normalized.sort = where.sort; } return normalized; }; // 剥离 LAY_ 开头的临时字段(与 table.checkStatus.data 的"不含临时字段"语义同构) var stripTempFields = function (rows) { var cleanRows = []; for (var i = 0; i < rows.length; i++) { var clean = {}; for (var key in rows[i]) { if (Object.prototype.hasOwnProperty.call(rows[i], key) && String(key).indexOf('LAY_') !== 0) { clean[key] = rows[i][key]; } } cleanRows.push(clean); } return cleanRows; }; // ========== (a) dataProvider ========== // 请求参数:page/limit + where(filter/op 为 JSON 字符串、sort 为对象) var buildRequestData = function (state) { var data = { page: state.page.curr, limit: state.page.limit }; var where = state.where || {}; var keys = ['filter', 'op', 'sort']; for (var i = 0; i < keys.length; i++) { if (where[keys[i]] !== undefined) { data[keys[i]] = where[keys[i]]; } } return data; }; // 选择态标记注入 // LAY_DISABLED 段:语义平移自 admin-table.js parseData 注入段(仅选择器页面生效) // LAY_CHECKED 段:store 选中集合命中行,跨页勾选回显的关键 var injectSelectionMarks = function (state, res) { var rows = res.data || []; if (state.selectMode == 'checkbox' || state.selectMode == 'radio') { if (typeof state.rowDisableCallback == 'function') { for (var i = 0; i < rows.length; i++) { var dataItem = rows[i]; var rowDisableResult = state.rowDisableCallback(dataItem); if (rowDisableResult === false || rowDisableResult === true) { rows[i].LAY_DISABLED = rowDisableResult; } } } var urlSelectedIds = ua.getQueryVariable('selectedIds', ''); if (urlSelectedIds.length > 0) { var selectedIdArr = urlSelectedIds.split(','); for (var j = 0; j < rows.length; j++) { if (selectedIdArr.indexOf(rows[j].id.toString()) > -1) { rows[j].LAY_DISABLED = true; } } } } // LAY_CHECKED:选中集合命中行(含 URL selectedIds 预置的选择器回显行), // 同时刷新 selectedRows 行引用,保持 dataCache 与最新一次数据一致 for (var k = 0; k < rows.length; k++) { var selectedIdx = state.selectedIds.indexOf(String(rows[k].id)); if (selectedIdx > -1) { rows[k].LAY_CHECKED = true; state.selectedRows[selectedIdx] = rows[k]; } } }; /** * 拉取列表数据 * * @param {string} tableId 已注册的表格 ID * @param {function} [done] 成功回调,入参为后端响应 {code:0, msg, count, data} * @param {boolean} [isCorrection] 页码纠偏自动重取标记(防递归兜底,业务不传) */ var fetch = function (tableId, done, isCorrection) { var state = getState(tableId); if (!state) { ua.msg.error('表格未注册:' + tableId); return; } // 语义同 ua.request.ajax 的空地址拦截 if (state.indexUrl === '' || state.indexUrl === undefined) { ua.msg.error('请求地址不能为空'); return; } // 弥补 data 模式没有引擎 loading 的差异:fetch 前后显隐全局 loading loading.show(); $.ajax({ url: ua.url(state.indexUrl), type: 'get', dataType: 'json', headers: ua.headers(), data: buildRequestData(state), timeout: 60000, success: function (res) { loading.hide(); if (res.code === 0) { // 页码纠偏:curr 超出末页时回落到末页并自动重取 var count = parseInt(res.count) || 0; var lastPage = Math.max(1, Math.ceil(count / state.page.limit)); if (state.page.curr > lastPage) { state.page.curr = lastPage; if (!isCorrection) { fetch(tableId, done, true); return; } } injectSelectionMarks(state, res); state.count = count; state.currentPageData = res.data || []; state.lastResponse = res; lastTableWhere[tableId] = state.where; // 刷新分页条(count 变化后重渲染;jump 首次回调只同步状态不请求) pager.render(tableId); if (typeof done === 'function') { done(res); } } else { // res.code != 0:错误提示并保持当前视图(不改动已渲染数据) var msg = res.msg == undefined ? '返回数据格式有误' : res.msg; ua.msg.error(msg); } }, error: function (xhr, textstatus, thrown) { loading.hide(); // HTTP error 通道:语义平移 ua.request.ajax,守卫 responseJSON // (404 等非 JSON 响应时 responseJSON 为 undefined,直接取 message 会抛异常) var errorMsg = ''; if (xhr.responseJSON && xhr.responseJSON.message) { errorMsg = xhr.responseJSON.message; } ua.msg.error('Status:' + xhr.status + ',' + xhr.statusText + ',请稍后再试!
' + errorMsg); } }); }; // ========== (b) pager ========== /** * layui.laypage.render 封装:分页条驱动 fetch * DOM 容器由调用方传入(register 的 pagerElem 或 render 的第二参) */ var pager = { render: function (tableId, elem) { var state = getState(tableId); if (!state) { return; } if (elem !== undefined) { state.pagerElem = elem; } if (!state.pagerElem) { return; } laypage.render({ elem: state.pagerElem, 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) { state.page.curr = obj.curr; state.page.limit = obj.limit; if (!first) { fetch(tableId); } } }); } }; // ========== (c) selection ========== // radio:checkStatus 返回单行数组、isAll 恒 false(tableData selectType:'radio' 语义) var selection = { /** * 单行选中 * radio:新选中替换旧选中 * checkbox:再次点击为取消(layui checkbox 交互语义) */ set: function (tableId, row) { var state = getState(tableId); if (!state || row === undefined || row === null) { return; } var id = String(row.id); if (state.selectMode === 'radio') { state.selectedIds = [id]; state.selectedRows = [row]; return; } var index = state.selectedIds.indexOf(id); if (index > -1) { state.selectedIds.splice(index, 1); state.selectedRows.splice(index, 1); } else { state.selectedIds.push(id); state.selectedRows.push(row); } }, /** * 全选/取消全选当页:禁用行不参与 * rows 缺省取最近一次成功返回的当页数据;checked 为 false 时取消全选 * radio 模式无全选语义(layui radio 列不渲染表头全选),保持 no-op */ checkAll: function (tableId, rows, checked) { var state = getState(tableId); if (!state || state.selectMode === 'radio') { return; } rows = rows === undefined ? state.currentPageData : (rows || []); checked = checked !== false; for (var i = 0; i < rows.length; i++) { if (rows[i].LAY_DISABLED) { continue; } var id = String(rows[i].id); var index = state.selectedIds.indexOf(id); if (checked) { if (index === -1) { state.selectedIds.push(id); state.selectedRows.push(rows[i]); } else { // 刷新行数据引用 state.selectedRows[index] = rows[i]; } } else if (index > -1) { state.selectedIds.splice(index, 1); state.selectedRows.splice(index, 1); } } }, // 清空选中集合 clear: function (tableId) { var state = getState(tableId); if (!state) { return; } state.selectedIds = []; state.selectedRows = []; }, /** * 当页是否全选:isAll = 选中数 === 当页数 - 禁用数(当页口径,与 layui isAll 语义一致) * radio 恒 false */ isAll: function (tableId) { var state = getState(tableId); if (!state || state.selectMode === 'radio') { return false; } var rows = state.currentPageData || []; var selectable = 0; var selected = 0; for (var i = 0; i < rows.length; i++) { if (rows[i].LAY_DISABLED) { continue; } selectable++; if (state.selectedIds.indexOf(String(rows[i].id)) > -1) { selected++; } } return selectable > 0 && selected === selectable; }, /** * 导出与 table.checkStatus 同构的结果 {data, dataCache, isAll} * data:选中行数据(不含 LAY_ 临时字段);dataCache:原始缓存(含临时字段) * radio 语义:selectedRows 至多一行(单行数组),isAll 恒 false */ checkStatus: function (tableId) { var state = getState(tableId); if (!state) { return { data: [], dataCache: [], isAll: false }; } return { data: stripTempFields(state.selectedRows), dataCache: state.selectedRows, isAll: selection.isAll(tableId) }; } }; // ========== (d) reload 编排 ========== /** * 更新 where:整体替换 * 语义平移 renderSearch 末尾对 lastTableWhere[tableId] 的无条件覆盖 * (页面级 options.where 为死参数的现状如实平移:不单独合并) */ var setWhere = function (tableId, where) { var state = getState(tableId); if (!state) { return; } state.where = normalizeWhere(where); lastTableWhere[tableId] = state.where; }; /** * 排序合入 where * 语义平移 listenTableSort:lastWhere.sort = {field: type} */ var setSort = function (tableId, field, type) { var state = getState(tableId); if (!state) { return; } state.where.sort = {}; state.where.sort[field] = type; lastTableWhere[tableId] = state.where; }; /** * 重载编排:opts.where / opts.page 合入状态后 fetch * * - opts.where:传入时经 setWhere 整体替换 lastTableWhere(单一事实源), * 不传则沿用当前 where(刷新语义保持筛选与排序) * - opts.page:{curr, limit} 部分合入;搜索场景由调用方传 {curr: 1} * (语义平移 listenTableSearch 的 reloadData page.curr=1) * - opts.done:透传给 fetch 的成功回调 */ var reload = function (tableId, opts) { var state = getState(tableId); if (!state) { ua.msg.error('表格未注册:' + tableId); return; } opts = opts || {}; if (opts.where !== undefined) { setWhere(tableId, opts.where); } if (opts.page) { if (opts.page.curr !== undefined) { state.page.curr = opts.page.curr; } if (opts.page.limit !== undefined) { state.page.limit = opts.page.limit; } } fetch(tableId, opts.done); }; // ========== register / has 注册表 ========== /** * 注册表格运行状态 * * config 支持项: * - indexUrl:列表数据地址(必填,可为 'system.quick/index' 或以 / 开头的完整路径) * - init.indexUrl:等价入口(与页面 _common.js 的 init 对象形态一致) * - where:初始查询条件(缺省空条件,请求不携带 filter/op 参数) * - page:{curr, limit} 初始页码与条数(limit 亦可直接传 config.limit) * - limits:分页档位(缺省 [10,15,20,25,50,100]) * - selectMode:'checkbox' | 'radio'(缺省读 URL select_mode,与选择器页面约定一致) * - rowDisableCallback:选择器页面行禁用回调(语义平移 admin-table.js) * - pagerElem:分页条容器(也可后续经 pager.render 传入) */ var register = function (tableId, config) { config = config || {}; var selectMode = config.selectMode; if (selectMode !== 'checkbox' && selectMode !== 'radio') { // 语义平移:选择器页面 URL 携带 select_mode=checkbox|radio(admin-table.js 同源读取) selectMode = ua.getQueryVariable('select_mode', ''); if (selectMode !== 'checkbox' && selectMode !== 'radio') { selectMode = null; } } var state = { id: tableId, indexUrl: config.indexUrl !== undefined ? config.indexUrl : (config.init ? config.init.indexUrl : ''), where: normalizeWhere(config.where), page: { curr: (config.page && config.page.curr) || 1, limit: config.limit || (config.page && config.page.limit) || 15 }, limits: config.limits || DEFAULT_LIMITS, selectMode: selectMode, rowDisableCallback: config.rowDisableCallback || null, selectedIds: [], selectedRows: [], count: 0, currentPageData: [], pagerElem: config.pagerElem || null, lastResponse: null }; // 选择器页面回显:URL selectedIds 预置进选中集合(与 admin-table.js 同源读取), // 使回显行获得 LAY_CHECKED(选中且禁改) if (selectMode) { var urlSelectedIds = ua.getQueryVariable('selectedIds', ''); if (urlSelectedIds.length > 0) { var selectedIdArr = urlSelectedIds.split(','); for (var i = 0; i < selectedIdArr.length; i++) { if (selectedIdArr[i] !== '' && state.selectedIds.indexOf(selectedIdArr[i]) === -1) { state.selectedIds.push(selectedIdArr[i]); } } } } states[tableId] = state; lastTableWhere[tableId] = state.where; return state; }; // 该 tableId 是否已注册(任务 5 接线 shim 依赖此判定分流受控/非受控表) var has = function (tableId) { return states[tableId] !== undefined; }; // ========== 挂载 ========== // 五个主入口:register / has / reload / fetch / checkStatus // 子模块暴露给任务 5 接线(search/sort 监听 → dataProvider.setWhere/setSort) // 与后续渲染形态(模板/卡片)复用(selection/pager) window.UA_TABLE_STORE = { register: register, has: has, reload: reload, fetch: fetch, checkStatus: function (tableId) { return selection.checkStatus(tableId); }, dataProvider: { fetch: fetch, setWhere: setWhere, setSort: setSort, getState: getState, getLastResponse: function (tableId) { var state = getState(tableId); return state ? state.lastResponse : null; } }, pager: { render: pager.render }, selection: selection }; })();