diff --git a/.agents/skills/ulthon-card-template/SKILL.md b/.agents/skills/ulthon-card-template/SKILL.md index 8be82ce..9e510fc 100644 --- a/.agents/skills/ulthon-card-template/SKILL.md +++ b/.agents/skills/ulthon-card-template/SKILL.md @@ -279,7 +279,7 @@ layoutDefault: 'goodscard', - 操作按钮配置对象自 commit 53ee2e3 起有拷贝保护(渲染函数对配置只读):自定义按钮的 class/method 不会被渲染过程污染,形态反复切换后按钮样式与 URL 拼接保持稳定。这是已修复的历史坑:修复前的版本存在"切换形态后按钮 class 丢失、URL 滚雪球"的缺陷,老版本升级后若仍见此症状先确认包含 53ee2e3。 - 形态切换零请求,但 switch 开关切换不是:点击开关会触发 modify 提交加一次列表重查(各 1 个 XHR),这是框架标准链路,验收时勿与"切换零请求"混淆。 -- 卡片字段清单实时跟随列显隐:列筛选面板隐藏/恢复列后,卡片形态自动重渲(colToggled 监听 + store 当页数据零请求重渲),隐藏列不产出 `_cells` 对应键,模板侧无需规避。 +- 卡片字段清单实时跟随列显隐:列筛选面板隐藏/恢复列后,卡片形态自动重渲(colToggled 监听 + store 当页数据零请求重渲),隐藏列不产出 `_cells` 对应键,模板侧无需规避。显隐状态按页 localStorage 记忆,刷新保持。 ## 八、验收 checklist diff --git a/public/static/admin/css/public.css b/public/static/admin/css/public.css index 38c4706..cebd4a6 100644 --- a/public/static/admin/css/public.css +++ b/public/static/admin/css/public.css @@ -903,8 +903,28 @@ table样式 color: rgb(22, 183, 119); } +/* 卡片设置面板(admin-table.js toggleCardSettingPanel 产出;面板骨架复用上方 + .ul-nav-block 基类,此处补面板本体定位与视觉(与 .table-tpl-container 视图 + 切换下拉同语言)、配置行与自绘开关)。入口按钮 .ua-card-setting-btn 由受控 + 接线追加至左侧工具栏容器 .layui-table-tool-temp 末尾,position:relative 供 + 面板相对按钮向下展开锚定;按钮不带 lay-event,点击直调开关函数 */ +.ua-card-setting-btn { + position: relative; + margin-left: 10px; +} + .ua-card-setting-panel { + position: absolute; + left: 0; + top: 100%; + z-index: 999; min-width: 200px; + border: 1px solid #d2d2d2; + background-color: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12); + white-space: nowrap; + overflow-y: auto; + text-align: left; } .ua-card-setting-title { diff --git a/public/static/admin/css/public.scss b/public/static/admin/css/public.scss index ce5139c..caf00da 100644 --- a/public/static/admin/css/public.scss +++ b/public/static/admin/css/public.scss @@ -926,10 +926,28 @@ table样式 } } -// 卡片设置面板(admin-table.js listenToolbar case 'TABLE_CARD_SETTING' 产出; -// 面板骨架复用上方 .ul-nav-block 基类,此处只补面板本体、配置行与自绘开关) +// 卡片设置面板(admin-table.js toggleCardSettingPanel 产出;面板骨架复用上方 +// .ul-nav-block 基类,此处补面板本体定位与视觉(与 .table-tpl-container 视图 +// 切换下拉同语言)、配置行与自绘开关)。入口按钮 .ua-card-setting-btn 由受控 +// 接线追加至左侧工具栏容器 .layui-table-tool-temp 末尾,position:relative 供 +// 面板相对按钮向下展开锚定;按钮不带 lay-event,点击直调开关函数 +.ua-card-setting-btn { + position: relative; + margin-left: 10px; +} + .ua-card-setting-panel { + position: absolute; + left: 0; + top: 100%; + z-index: 999; min-width: 200px; + border: 1px solid #d2d2d2; + background-color: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, .12); + white-space: nowrap; + overflow-y: auto; + text-align: left; } .ua-card-setting-title { diff --git a/public/static/plugs/ulthon-admin/admin-table.js b/public/static/plugs/ulthon-admin/admin-table.js index 26f0b11..8512426 100644 --- a/public/static/plugs/ulthon-admin/admin-table.js +++ b/public/static/plugs/ulthon-admin/admin-table.js @@ -362,7 +362,7 @@ // 形态可见性同步:引擎数据区(表头+表体+合计行)与模板容器互斥显隐, // 引擎视图的工具栏(刷新/新增/批量删除等)两形态共用保持可见(print 与 - // 卡片设置图标除外,随形态互斥显隐),搜索区本就挂在表格元素之前不受影响。 + // 卡片设置按钮除外,随形态互斥显隐),搜索区本就挂在表格元素之前不受影响。 // 非 table 形态下引擎根本不渲染行数据 // (wrapper 非 table 分支不走引擎渲染),并非旧寄生实现"引擎恒渲+CSS 隐藏"。 var syncLayoutVisibility = function (tableId) { @@ -386,7 +386,104 @@ // 工具栏形态联动: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); + $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 = '
'; + var operatAlwaysOn = ua.getPageSetting('card-operat-always', '') === '1'; + cardSettingHtml += '
配置
'; + 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; + $.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($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) { @@ -841,23 +938,6 @@ extend: 'data-table-id="' + options.id + '"' }); } - // 卡片设置入口(pro#5):存在任何非 table 形态即注入(不依赖 - // 条目数>1,单形态 card 页也需要打开设置面板)。不写 extend 属性 - // (layui 不渲染该属性,面板定位走 lay-table-id 模式) - var hasNonTableLayout = false; - $.each(layoutEntries, function (i, entry) { - if (entry && entry.name !== 'table') { - hasNonTableLayout = true; - return false; - } - }); - if (hasNonTableLayout) { - defaultToolbar.push({ - title: '卡片设置', - layEvent: 'TABLE_CARD_SETTING', - icon: 'layui-icon-set' - }); - } } if (options.defaultToolbar === undefined) { @@ -882,22 +962,6 @@ extend: 'data-table-id="' + options.id + '"' }); } - // 卡片设置入口与 TABLE_LAYOUT 同规则去重追加:autoInjected - // 即默认注入 card,条件恒成立;已有同名 layEvent 不重复加 - var hasCardSettingBtn = false; - $.each(options.defaultToolbar, function (i, btn) { - if (btn && typeof btn === 'object' && btn.layEvent === 'TABLE_CARD_SETTING') { - hasCardSettingBtn = true; - return false; - } - }); - if (!hasCardSettingBtn) { - options.defaultToolbar.push({ - title: '卡片设置', - layEvent: 'TABLE_CARD_SETTING', - icon: 'layui-icon-set' - }); - } } @@ -1043,6 +1107,30 @@ } + // 列显隐记忆恢复:必须在引擎 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); @@ -1098,6 +1186,33 @@ e.stopPropagation(); }); + // 卡片设置入口按钮(pro#5 迭代:由右侧图标区 defaultToolbar 移至 + // 左侧工具栏末尾)。layui 2.10.1 左容器为 .layui-table-tool-temp + //(layui.js 工具栏模板 '
',左容器经 + // 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 = $('卡片设置') + .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, @@ -1188,6 +1303,19 @@ // 不变),卡片形态 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; } @@ -2244,103 +2372,6 @@ } }); break; - case 'TABLE_CARD_SETTING': - // 卡片设置面板(pro#5,交互结构参照 TABLE_LAYOUT 实现 - // 重建):入口图标仅卡片形态可见(syncLayoutVisibility - // 联动),面板 append 在工具栏图标元素内,面板内点击 - // 均阻断冒泡(不触发 layui toolbar 重开、不触发 - // document 收起),开关可反复切换;再点图标收起 - var cardSettingId = 'ua-card-setting-' + tableId; - var $toolbarSettingElem = $('[lay-table-id="' + tableId + '"]').find('[lay-event="TABLE_CARD_SETTING"]'); - var $cardSettingElem = $('#' + cardSettingId); - if ($cardSettingElem.length > 0) { - $cardSettingElem.remove(); - break; - } - - // 配置段 + 排序段(面板每次点图标重建,排序段随建随取 - // store 当前态,表头排序/搜索后重开面板即最新) - var cardSettingHtml = '
'; - var operatAlwaysOn = ua.getPageSetting('card-operat-always', '') === '1'; - cardSettingHtml += '
配置
'; - 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; - $.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($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'); - } - }); - break; } }); },