Files
ulthon_admin/extend/base/admin/view/system/admin/index.html
augushong a9f9045d10 fix(table): 卡片勾选收敛至勾位图标并消除勾选重渲闪烁(pro#4)
- 委托收敛勾位判定+快速路径 class 回显
- 勾位光标样式
- usercard/goodscard 勾位适配
- 两个技能文档契约同步
2026-10-03 11:55:56 +00:00

236 lines
8.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="layuimini-container">
<div class="layuimini-main">
<table id="currentTable" class="layui-table layui-hide" data-auth-add="{:auth('system.admin/add')}" data-auth-edit="{:auth('system.admin/edit')}" data-auth-delete="{:auth('system.admin/delete')}" data-auth-password="{:auth('system.admin/password')}" data-auth-modify="{:auth('system.admin/modify')}" lay-filter="currentTable">
</table>
</div>
</div>
<!-- 用户管理名片形态(layoutList 自定义 Vue 形态官方示例)
硬约束:本节点必须位于页面根容器之外、Vue 渲染容器(#ua-layout-{tableId},
由框架在表格容器后动态创建)之外,否则会被 Vue 编译吞掉。
上下文契约(admin-table.js buildLayoutContext):
- rows:行上下文数组,d 与表格行数据同构(含原始字段 + _cells/_operatHtml/_checked/_disabled)
- list:列表上下文 {tableId, page, count, checkedAll}(Vue 形态不叫 _list)
- 行根元素携带 data-table-id + data-id 两属性即获得统一行选中委托(点击勾位
图标切换勾选,按钮/表单控件/layui 美化层自动豁免),选中态经 :class 高亮回显
- _cells/_operatHtml 含渲染产物 HTML,必须用 v-html 输出;纯文本字段用
{{ }} 原生插值(自动转义,与 laytpl 的 {{- }} 不同)
- 事件无需自行绑定:操作按钮与 switch 经页面级委托和 form 事件天然生效
布局 v2(视觉升级):头部左头像+姓名/账户两行、右状态开关(不再独占
一行);主体紧凑小字段(手机/登录次数)+ 备注单行省略;底部上边框分隔,
左创建时间右操作按钮组 -->
<script type="text/x-template" id="usercardTpl">
<div class="usercard-list">
<div class="ua-data-card usercard"
v-for="d in rows"
:key="d.id"
:data-id="d.id"
:data-table-id="list.tableId"
:class="{'ua-data-card-checked': d._checked, 'ua-data-card-disabled': d._disabled}">
<!-- 头部:左 48px 圆形头像(head_img 原始字段直绑,绕开渲染器内联
缩略样式;默认头像占位垫底,图片加载失败 @error 隐藏 img 纯
DOM 降级,不触碰冻结上下文)+ 姓名/登录账户上下两行;
右状态开关悬浮同行:_cells.status 为 switch 渲染器输出的原生
checkbox,容器 layui-form 祖先 + 挂载后 form.render 美化,
切换经页面级 switch 委托提交 modify 接口,失败自动回滚重渲 -->
<div class="usercard-header">
<span class="usercard-avatar-box">
<span class="usercard-avatar usercard-avatar-fallback" title="未设置头像"><i class="layui-icon layui-icon-username"></i></span>
<img v-if="d.head_img" :src="d.head_img" :alt="d.nickname || d.username"
class="usercard-avatar usercard-avatar-img"
@error="$event.target.style.display = 'none'">
</span>
<div class="usercard-id">
<span class="usercard-name" :title="d.nickname || d.username">{{ d.nickname || d.username }}</span>
<span class="usercard-account" :title="d.username">{{ d.username }}</span>
</div>
<div class="usercard-status" v-html="d._cells.status"></div>
<div class="ua-data-card-check"><i class="layui-icon layui-icon-ok-circle"></i></div>
</div>
<!-- 主体:紧凑小字段两列排布(手机无值显示 —,登录次数为 pill 徽标),
备注有值才渲染、单行省略;均为纯文本原始字段插值 -->
<div class="usercard-body">
<div class="usercard-fields">
<div class="usercard-field">
<span class="usercard-field-label">手机</span>
<span class="usercard-field-value" :title="d.phone">{{ d.phone || '—' }}</span>
</div>
<div class="usercard-field">
<span class="usercard-field-label">登录次数</span>
<span class="usercard-badge">{{ d.login_num }}</span>
</div>
</div>
<div v-if="d.remark" class="usercard-remark" :title="d.remark">{{ d.remark }}</div>
</div>
<!-- 底部:上边框分隔,左创建时间(模型获取器已格式化)右操作按钮组
(_operatHtml 为操作列渲染产物:编辑/设置密码/删除,已按
data-auth-* 裁剪权限),点击行为由框架页面级委托承接 -->
<div class="usercard-footer">
<span class="usercard-time">{{ d.create_time }}</span>
<div class="ua-data-card-operat usercard-operat" v-html="d._operatHtml"></div>
</div>
</div>
</div>
</script>
<!-- 名片形态页面级样式 v2:统一设计语言覆写仅作用于本页 .usercard
(圆角 8px/1px #eee 边框/hover 阴影/选中主题蓝描边+浅蓝底/徽标 pill/
12px 内边距与 8-10px 区块节奏),公共 .ua-data-card 族不动;
网格布局与头像圆形为本页自建 -->
<style>
.usercard-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 12px;
}
.usercard {
padding: 12px;
border: 1px solid #eee;
border-radius: 8px;
}
.usercard:hover {
border-color: #e0e0e0;
box-shadow: 0 2px 12px rgba(0, 0, 0, .08);
}
.usercard.ua-data-card-checked {
border-color: #1e9fff;
box-shadow: 0 0 0 1px #1e9fff inset;
background-color: #f2f9ff;
}
.usercard.ua-data-card-checked:hover {
box-shadow: 0 0 0 1px #1e9fff inset, 0 2px 12px rgba(0, 0, 0, .08);
}
.usercard-header {
display: flex;
align-items: center;
gap: 10px;
}
.usercard-avatar-box {
position: relative;
flex: none;
width: 48px;
height: 48px;
}
.usercard-avatar {
position: absolute;
top: 0;
left: 0;
width: 48px;
height: 48px;
border-radius: 50%;
border: 1px solid #eee;
background-color: #f6f7f9;
}
.usercard-avatar-fallback {
display: flex;
align-items: center;
justify-content: center;
color: #c2c2c2;
font-size: 22px;
}
.usercard-avatar-img {
object-fit: cover;
}
.usercard-id {
flex: 1;
min-width: 0;
}
.usercard-name {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 20px;
font-size: 14px;
font-weight: 600;
color: #333;
}
.usercard-account {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 18px;
color: #999;
font-size: 12px;
}
.usercard-status {
flex: none;
margin-left: auto;
}
.usercard-status .layui-form-switch {
margin: 0;
}
.usercard-body {
margin-top: 10px;
}
.usercard-fields {
display: flex;
gap: 10px;
}
.usercard-field {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 6px;
line-height: 20px;
}
.usercard-field-label {
flex: none;
color: #999;
font-size: 12px;
}
.usercard-field-value {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #333;
font-size: 13px;
}
.usercard-badge {
display: inline-block;
padding: 0 8px;
border-radius: 10px;
background-color: #eef6ff;
color: #1e9fff;
font-size: 12px;
line-height: 18px;
}
.usercard-remark {
margin-top: 6px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #999;
font-size: 12px;
line-height: 18px;
}
.usercard-footer {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.usercard-time {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #999;
font-size: 12px;
}
.usercard-operat {
flex: none;
}
@media (max-width: 768px) {
.usercard-list {
grid-template-columns: 1fr;
}
}
</style>