Files
ulthon_admin/extend/base/admin/view/mall/goods/index.html

242 lines
9.2 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('mall.goods/add')}"
data-auth-edit="{:auth('mall.goods/edit')}"
data-auth-delete="{:auth('mall.goods/delete')}"
data-auth-stock="{:auth('mall.goods/stock')}"
data-auth-modify="{:auth('mall.goods/modify')}"
lay-filter="currentTable">
</table>
</div>
</div>
<!-- 商品卡片形态(layoutList 自定义 Vue 形态,参照用户管理名片官方示例)
硬约束:本节点必须位于页面根容器之外、Vue 渲染容器(#ua-layout-currentTableRenderId,
由框架在表格容器后动态创建)之外,否则会被 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 输出;纯文本字段用
{{ }} 原生插值(自动转义);处理函数内不得修改渲染上下文数据(冻结契约)
- 事件无需自行绑定:操作按钮与 switch 经页面级委托和 form 事件天然生效
取值通道(对齐官方范例契约):
- status 开关走 _cells(switch 渲染器产物)+ v-html,悬浮于图面右上角
(absolute 半透明白底小容器),不占正文行;切换由页面级委托承接
(.layui-form-switch 美化层在豁免名单内),容器只调外间距不改 DOM 结构
- logo 大图绕开渲染器直绑原始字段(渲染器内联缩略图样式不适合大图);
占位图标层常驻其下,img 加载失败时 @error 仅隐藏 img 露出占位(只操作
DOM,不改模板数据),无 logo 时同样显示占位
- title/cate.title/价格/库存/销量/create_time 为无渲染器纯文本,直接原始字段插值 -->
<script type="text/x-template" id="goodscardTpl">
<div class="goodscard-list">
<div class="ua-data-card goodscard"
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}">
<!-- 图区:4:3 商品大图(撑满裁切不变形、圆角随卡片),无图或 404 时
降级为占位图标;右上角悬浮状态开关(半透明白底小容器),
左上角勾位图标按行选中态显示 -->
<div class="goodscard-cover">
<span class="goodscard-img-empty" title="未设置图片"><i class="layui-icon layui-icon-picture"></i></span>
<img v-if="d.logo" :src="d.logo" :alt="d.title" class="goodscard-img"
@error="$event.target.style.display = 'none'">
<div class="goodscard-status" v-html="d._cells.status"></div>
<i v-if="d._checked" class="layui-icon layui-icon-ok-circle goodscard-check"></i>
</div>
<!-- 主体:名称(600 字重、最多两行截断)+ 分类徽标与销量同排 + 价格
层级(折扣价红大字、市场价灰删除线)+ 库存徽标(stock<20 红
"缺货",否则绿"库存 N") -->
<div class="goodscard-body">
<div class="goodscard-title" :title="d.title">{{ d.title }}</div>
<div class="goodscard-meta">
<span class="goodscard-cate">{{ d.cate && d.cate.title ? d.cate.title : '未分类' }}</span>
<span class="goodscard-sales">销量 {{ d.sales }}</span>
</div>
<div class="goodscard-price-row">
<span class="goodscard-price"><em>¥</em>{{ d.discount_price }}</span>
<span v-if="parseFloat(d.market_price) > 0" class="goodscard-market-price">¥{{ d.market_price }}</span>
</div>
<div class="goodscard-stock-row">
<span v-if="d.stock < 20" class="goodscard-stock goodscard-stock-out">缺货</span>
<span v-else class="goodscard-stock goodscard-stock-in">库存 {{ d.stock }}</span>
</div>
</div>
<!-- 底部:创建时间 + 操作按钮组(_operatHtml 为操作列渲染产物:
编辑/入库/详情/删除,已按 data-auth-* 裁剪权限),点击行为由
框架页面级委托承接 -->
<div class="ua-data-card-footer goodscard-footer">
<span class="ua-data-card-time goodscard-time">{{ d.create_time }}</span>
<div class="ua-data-card-operat" v-html="d._operatHtml"></div>
</div>
</div>
</div>
</script>
<!-- 商品卡片形态页面级样式(示例边界:网格布局、大图封面、悬浮开关、价格/
徽标为本页自建,卡片基础观感按统一设计语言在本页覆盖——圆角 8px、边框
#eee、hover 投影、选中主题蓝描边浅蓝底;操作按钮排布仍复用 public.css
的 .ua-data-card 族,不修改公共样式) -->
<style>
.goodscard-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 12px;
}
.goodscard {
padding: 0;
overflow: hidden;
border: 1px solid #eee;
border-radius: 8px;
}
.goodscard:hover {
border-color: #eee;
box-shadow: 0 2px 12px rgba(0, 0, 0, .08);
}
.goodscard.ua-data-card-checked {
border-color: #1e9fff;
box-shadow: inset 0 0 0 1px #1e9fff;
background-color: rgba(30, 159, 255, 0.05);
}
.goodscard-cover {
position: relative;
aspect-ratio: 4 / 3;
background-color: #f6f6f6;
border-bottom: 1px solid #f2f2f2;
}
.goodscard-img-empty {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
color: #d5d5d5;
font-size: 36px;
}
.goodscard-img {
position: relative;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.goodscard-status {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
padding: 2px 6px;
background-color: rgba(255, 255, 255, 0.85);
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
}
.goodscard-status .layui-form-switch {
margin: 0;
}
.goodscard-check {
position: absolute;
top: 8px;
left: 8px;
color: #1e9fff;
font-size: 22px;
background-color: #fff;
border-radius: 50%;
}
.goodscard-body {
flex: 1;
padding: 10px 12px 12px;
}
.goodscard-title {
display: -webkit-box;
overflow: hidden;
min-height: 40px;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
font-weight: 600;
font-size: 14px;
line-height: 20px;
}
.goodscard-meta {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 6px;
overflow: hidden;
}
.goodscard-cate {
flex: none;
max-width: 70%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 2px 8px;
background-color: #f5f5f5;
border-radius: 10px;
color: #666;
font-size: 12px;
line-height: 16px;
}
.goodscard-sales {
flex: none;
color: #999;
font-size: 12px;
}
.goodscard-price-row {
display: flex;
align-items: baseline;
margin-top: 8px;
}
.goodscard-price {
color: #ff5722;
font-size: 18px;
font-weight: 600;
}
.goodscard-price em {
font-style: normal;
font-size: 14px;
}
.goodscard-market-price {
margin-left: 8px;
color: #999;
font-size: 12px;
text-decoration: line-through;
}
.goodscard-stock-row {
display: flex;
align-items: center;
margin-top: 8px;
}
.goodscard-stock {
padding: 2px 8px;
border-radius: 10px;
font-size: 12px;
line-height: 16px;
}
.goodscard-stock-out {
color: #ff5722;
background-color: rgba(255, 87, 34, 0.08);
}
.goodscard-stock-in {
color: rgb(22, 183, 119);
background-color: rgba(22, 183, 119, 0.08);
}
.goodscard-footer {
margin-top: 0;
padding: 8px 12px;
}
.goodscard-time .layui-icon {
font-size: 12px;
}
@media (max-width: 768px) {
.goodscard-list {
grid-template-columns: 1fr;
}
}
</style>