diff --git a/public/static/admin/css/public.css b/public/static/admin/css/public.css index 33a23ae..c4d1725 100644 --- a/public/static/admin/css/public.css +++ b/public/static/admin/css/public.css @@ -175,8 +175,7 @@ body { .layuimini-upload-show a img { height: 80px; - -o-object-fit: cover; - object-fit: cover; + object-fit: cover; } .layuimini-upload-show .uploads-delete-tip { @@ -414,6 +413,8 @@ table样式 } .layui-layer-easy .layui-layer-moves { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; box-sizing: content-box; } @@ -727,6 +728,138 @@ table样式 border-radius: 0; } +.ua-layout-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 12px; +} + +.ua-layout-empty { + padding: 24px 0; + text-align: center; + color: #999; +} + +.ua-layout-loading { + min-height: 60px; + opacity: 0.55; + pointer-events: none; +} + +.ua-data-card { + display: flex; + flex-direction: column; + padding: 10px 12px; + border: 1px solid #f2f2f2; + border-radius: 5px; + background-color: #fff; + cursor: pointer; + transition: border-color 0.2s, box-shadow 0.2s; +} +.ua-data-card:hover { + border-color: #d2d2d2; +} + +.ua-data-card-checked { + border-color: rgb(22, 183, 119); + box-shadow: 0 0 0 1px rgb(22, 183, 119) inset; +} + +.ua-data-card-disabled { + background-color: #fafafa; + cursor: not-allowed; +} +.ua-data-card-disabled .ua-data-card-title, +.ua-data-card-disabled .ua-data-card-item-value, +.ua-data-card-disabled .ua-data-card-time { + color: #c2c2c2; +} + +.ua-data-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + margin-bottom: 8px; + border-bottom: 1px solid #f2f2f2; +} + +.ua-data-card-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: bold; +} + +.ua-data-card-check { + flex: none; + margin-left: 8px; + color: #d2d2d2; + font-size: 18px; + line-height: 1; +} + +.ua-data-card-checked .ua-data-card-check { + color: rgb(22, 183, 119); +} + +.ua-data-card-body { + flex: 1; +} + +.ua-data-card-item { + display: flex; + margin-bottom: 4px; + line-height: 22px; +} +.ua-data-card-item:last-child { + margin-bottom: 0; +} + +.ua-data-card-item-title { + flex: none; + color: #999; +} +.ua-data-card-item-title::after { + content: ":"; +} + +.ua-data-card-item-value { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ua-data-card-footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid #f2f2f2; +} + +.ua-data-card-time { + color: #999; + font-size: 12px; +} + +.ua-data-card-operat { + display: flex; + flex-wrap: wrap; + gap: 4px; + justify-content: flex-end; +} +.ua-data-card-operat a { + margin: 0; +} + +@media (max-width: 768px) { + .ua-layout-cards { + grid-template-columns: 1fr; + } +} @media (max-width: 768px) { .layuimini-form .layui-form-item { padding-right: 0; @@ -761,4 +894,4 @@ table样式 .search-tab .layui-tabs-body { display: none; -} \ No newline at end of file +} diff --git a/public/static/admin/css/public.scss b/public/static/admin/css/public.scss index bfa8629..b1e6e08 100644 --- a/public/static/admin/css/public.scss +++ b/public/static/admin/css/public.scss @@ -740,6 +740,148 @@ table样式 } +// 多形态渲染:内置 card 形态(模板管线给容器挂 .ua-layout-cards,卡片本体为 +// .ua-data-card 族;admin-table.js 内置 card 模板产出,样式本地化不依赖 CDN) +.ua-layout-cards { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 12px; +} + +.ua-layout-empty { + padding: 24px 0; + text-align: center; + color: #999; +} + +// 模板管线 reload 期间的加载态标记(wrapper 在 done 后移除) +.ua-layout-loading { + min-height: 60px; + opacity: .55; + pointer-events: none; +} + +.ua-data-card { + display: flex; + flex-direction: column; + padding: 10px 12px; + border: 1px solid #f2f2f2; + border-radius: 5px; + background-color: #fff; + cursor: pointer; + transition: border-color .2s, box-shadow .2s; + + &:hover { + border-color: #d2d2d2; + } +} + +.ua-data-card-checked { + border-color: rgb(22, 183, 119); + box-shadow: 0 0 0 1px rgb(22, 183, 119) inset; +} + +// 禁用行(_disabled):整卡置灰不可勾选(卡内操作按钮仍可用,与引擎 LAY_DISABLED 只锁勾选的语义一致) +.ua-data-card-disabled { + background-color: #fafafa; + cursor: not-allowed; + + .ua-data-card-title, + .ua-data-card-item-value, + .ua-data-card-time { + color: #c2c2c2; + } +} + +.ua-data-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding-bottom: 8px; + margin-bottom: 8px; + border-bottom: 1px solid #f2f2f2; +} + +.ua-data-card-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: bold; +} + +.ua-data-card-check { + flex: none; + margin-left: 8px; + color: #d2d2d2; + font-size: 18px; + line-height: 1; +} + +.ua-data-card-checked .ua-data-card-check { + color: rgb(22, 183, 119); +} + +.ua-data-card-body { + flex: 1; +} + +.ua-data-card-item { + display: flex; + margin-bottom: 4px; + line-height: 22px; + + &:last-child { + margin-bottom: 0; + } +} + +.ua-data-card-item-title { + flex: none; + color: #999; + + &::after { + content: ':'; + } +} + +.ua-data-card-item-value { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.ua-data-card-footer { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid #f2f2f2; +} + +.ua-data-card-time { + color: #999; + font-size: 12px; +} + +.ua-data-card-operat { + display: flex; + flex-wrap: wrap; + gap: 4px; + justify-content: flex-end; + + a { + margin: 0; + } +} + +@media (max-width: 768px) { + .ua-layout-cards { + grid-template-columns: 1fr; + } +} + @media (max-width: 768px) { .layuimini-form .layui-form-item { padding-right: 0; diff --git a/public/static/plugs/ulthon-admin/admin-table.js b/public/static/plugs/ulthon-admin/admin-table.js index 2e22d4e..e21822a 100644 --- a/public/static/plugs/ulthon-admin/admin-table.js +++ b/public/static/plugs/ulthon-admin/admin-table.js @@ -79,6 +79,106 @@ return result; }; + // ========== 内置 card 形态(name='card' 且无自定义钩子时启用)========== + // 结构参照旧寄生实现的 header/body/footer 骨架(代码全新):header(标题 + + // 勾位图标)、body(键值对字段列表)、footer(create_time + 操作按钮)。 + // 数据全部来自渲染上下文(_cells/_operatHtml/_checked/_disabled),列元信息读 + // controlledLayouts 的 cols(本闭包内),无任何业务字段硬编码。 + // + // 【与 buildLayoutContext 同规则的单元格列筛选】操作列无 field,须先于 + // field 判定识别;checkbox/radio 等无字段列跳过(勾选语义由勾位图标承接) + 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; + } + 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; + }; + + // 内置 card 勾选交互:点卡片任意处切换选中(操作按钮/表单控件等交互元素除外), + // 禁用行(_disabled)整卡不可勾选。选择态落 store(与引擎勾选同一事实源, + // 批量删除经 ua.table.checkStatus 分发即取到),随后重渲模板管线回显勾位图标。 + // 自定义模板形态的统一行选中委托是任务 18 范围,本委托只认 .ua-data-card。 + $(document).on('click', '.ua-data-card', function (e) { + var $card = $(this); + if ($card.hasClass('ua-data-card-disabled')) { + return; + } + if ($(e.target).closest('a,button,input,select,textarea,label,.layui-form-switch,.layui-form-checkbox,.layui-form-radio').length) { + return; + } + var tableId = $card.attr('data-table-id'); + 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; + } + window.UA_TABLE_STORE.selection.set(tableId, row); + ua.table.renderLayoutForm(tableId); + }); + // (b) 模板引擎优先级解析:tplFunction > tplLay > tplVue > tplLayId > tplVueId, // 提供一种即生效;五种全无且非 table 条目 → console.warn 并回退 table。 // lay/vue/layId/vueId 的引擎挂载在任务 16(laytpl)/17(Vue),此处只做分发判定。 @@ -86,6 +186,21 @@ 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}; } @@ -310,6 +425,9 @@ }); } + // 内置 card 形态容器挂网格布局类(样式见 public.scss 的 .ua-layout-cards), + // 自定义模板形态移除该标记,不受卡片网格影响 + $container.toggleClass('ua-layout-cards', resolved.entry.builtinCard === true); $container.html(html); // 容器恒为 .layui-form(lay-filter 与容器 id 同名),scoped form.render // 美化模板产出的 switch/checkbox 控件