Files
ulthon_admin/extend/base/admin/view/system/admin/index.html
augushong 45bc4a7f43 refactor(curd): 移除生成模板与框架页面 data-auth 声明(含 uploadfile 权限映射回归本页节点的行为变化说明)
- CURD 生成模板 index.code 删除 6 个 data-auth 属性行,生成页面零权限声明
- extend/base 13 个列表页 index.html 清理全部 data-auth-* 声明(含注释文本同步改写)
- system/host/index.js 的 set-master 按钮 auth 改写为全节点 'system.host/setMaster'
  (驼峰动作段逐字保留,parseNodeStr 仅 snake 化控制器段);system/auth 的
  assignUsers 解析后与注册节点逐字相等,无需改写
- 行为变化:system/uploadfile 页原 data-auth-add/edit/delete 误映射 system.menu/*
  (上游模板复制残留),删除后按钮按本页 system.uploadfile/* 节点判定——该页
  控制器仅注册 delete 动作,非超管用户的添加按钮从此正确隐藏(现状是
  有 system.menu/add 权限即显示但点击被后端拒绝),data-auth-edit 为死属性
  (本页无编辑按钮)
- VersionBase::VERSION v2.7.0 → v2.8.0,使 admin-core.js 静态引用缓存键
  (?t=get_site_version_key())失效,防下游升级后旧缓存 js 叠加新布局导致
  非超管按钮全隐
2026-10-04 01:31:36 +00:00

236 lines
8.6 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" 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 为操作列渲染产物:编辑/设置密码/删除,已按
当前登录者权限裁剪),点击行为由框架页面级委托承接 -->
<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>