feat(seller): 素材管理页重构布局并统一主色交互

- 双层卡片嵌套改为单面板,tabs 与操作按钮并入顶部工具栏
- 悬停/选中红色描边统一为 Element Plus 主色
- 复选框改为悬停/选中时显示,选中增加淡蓝遮罩
- 分组行编辑入口悬停显示,添加分组改为置顶虚线按钮
- 分页增加总数显示,空列表显示空状态
This commit is contained in:
pikachu1995@126.com
2026-10-20 13:42:46 +08:00
parent 0c814ec956
commit 7d9bf776db
2 changed files with 410 additions and 345 deletions

View File

@@ -1,20 +1,250 @@
// 弹窗选择器模式:固定内容区高度,避免空分组时布局塌陷 // 商家素材库(OSS 管理)样式:全部规则挂在 .oss-manage 根类下,避免全局污染
.oss-manage-picker { .oss-manage {
.category-tabs-card { // 单面板容器
margin-bottom: 0; .oss-panel {
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
overflow: hidden;
}
:deep(.el-card__body) { // 顶部工具行:左侧分类 tab + 右侧操作按钮
padding-bottom: 0; .oss-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
padding: 14px 16px;
border-bottom: 1px solid #ebeef5;
}
.oss-tabs {
display: flex;
gap: 8px;
.oss-tab {
padding: 7px 18px;
border-radius: 16px;
font-size: 14px;
color: #606266;
cursor: pointer;
transition:
color 0.2s,
background 0.2s;
&:hover {
color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
}
&.active {
color: #fff;
background: var(--el-color-primary);
font-weight: 500;
}
} }
} }
> .el-row > .el-card { .oss-actions {
border: none; display: flex;
align-items: center;
gap: 10px;
}
// 内容区:左分组 + 右文件网格
.oss-manage-box {
.oss-side {
height: 100%;
box-sizing: border-box;
border-right: 1px solid #ebeef5;
padding: 14px 12px;
display: flex;
flex-direction: column;
}
.oss-add-group {
margin-bottom: 10px;
.oss-add-group-btn {
width: 100%;
border-style: dashed;
color: #606266;
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
}
}
}
.oss-main {
height: 100%;
box-sizing: border-box;
padding: 2px 16px 16px;
display: flex;
flex-direction: column;
}
.oss-grid {
flex: 1;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
margin-top: 14px;
.oss-grid-item {
width: 120px;
margin: 0 14px 14px 0;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
.oss-card {
width: 120px;
height: 120px;
border-radius: 6px;
border: 2px solid transparent;
overflow: hidden;
box-sizing: border-box;
position: relative;
transition: border-color 0.2s;
img,
.card-media {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.card-file {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
color: #909399;
}
// 选中遮罩(盖在媒体上方、复选框与操作条下方)
&.is-checked::before {
content: "";
position: absolute;
inset: 0;
background: var(--el-color-primary);
opacity: 0.08;
z-index: 2;
pointer-events: none;
}
.oss-card-check {
position: absolute;
top: 8px;
right: 8px;
z-index: 3;
height: auto;
margin: 0;
opacity: 0;
transition: opacity 0.2s;
:deep(.el-checkbox__label) {
display: none;
}
}
.oss-card-ops {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 30px;
background: rgba(255, 255, 255, 0.96);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s;
z-index: 3;
> div {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #606266;
cursor: pointer;
&:hover {
color: var(--el-color-primary);
}
}
}
&:hover {
border-color: var(--el-color-primary);
.oss-card-check {
opacity: 1;
}
.oss-card-ops {
opacity: 1;
}
}
&.is-checked {
border-color: var(--el-color-primary);
.oss-card-check {
opacity: 1;
}
}
&.oss-card-selectable {
cursor: pointer;
.oss-card-check {
opacity: 1;
}
}
}
.oss-name {
width: 120px;
margin-top: 6px;
font-size: 12px;
line-height: 1.4;
color: #606266;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
}
}
.oss-pager {
flex-shrink: 0;
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
}
// 弹窗选择器模式:去掉面板阴影,固定内容区最小高度,避免空分组时布局塌陷
&.oss-manage-picker {
.oss-panel {
border-radius: 0;
box-shadow: none; box-shadow: none;
:deep(.el-card__body) {
padding: 0 0 8px;
} }
.oss-toolbar {
padding: 10px 12px;
} }
.oss-manage-box { .oss-manage-box {
@@ -26,168 +256,19 @@
flex-direction: column; flex-direction: column;
} }
.file-list, .oss-side,
.pic-list { .oss-main {
flex: 1; flex: 1;
min-height: 520px; min-height: 520px;
} }
.pic-list { .oss-grid {
display: flex;
flex-direction: column;
}
.img-box {
flex: 1;
min-height: 400px; min-height: 400px;
} }
.page-box { .oss-pager {
flex-shrink: 0;
margin-top: auto; margin-top: auto;
} }
} }
}
.search {
.oss-operation {
margin-bottom: 2vh;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
button {
margin-right: 5px;
}
} }
} }
.none {
display: none;
}
.oss-manage-box {
.file-list {
height: 100%;
box-sizing: border-box;
border-right: 1px solid #e5e6eb;
padding: 20px 12px 28px;
}
.pic-list {
height: 100%;
box-sizing: border-box;
padding: 0 0 24px 0;
}
.img-box {
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
align-content: flex-start;
margin-top: 20px;
.img-item {
width: 120px;
height: 158px;
box-sizing: border-box;
margin: 0 13px 8px;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
.card {
width: 120px;
height: 120px;
border-radius: 8px;
border: 2px solid transparent;
overflow: hidden;
box-sizing: border-box;
position: relative;
img,
.card-media {
width: 100%;
height: 100%;
object-fit: cover;
}
.card-file {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
color: #909399;
}
.card-checkbox {
position: absolute;
top: 8px;
right: 8px;
z-index: 1000;
height: auto;
margin: 0;
:deep(.el-checkbox__label) {
display: none;
}
}
.preview {
width: 100%;
height: 26px;
background-color: #ffffff;
color: #666666;
position: absolute;
left: 0;
bottom: 0;
display: flex;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.12);
> div {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
}
}
.card:hover,
.custom-checkbox-card-checked {
border: 2px solid #ff5c58;
}
.oss-card-selectable {
cursor: pointer;
}
.text {
width: 120px;
height: 36px;
cursor: pointer;
color: #252931;
font-size: 14px;
line-height: 36px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
}
}
.page-box {
display: flex;
justify-content: flex-end;
margin-top: 20px;
}
.pagination-box {
display: flex;
flex-direction: row-reverse;
}
.demo-tree-render .el-tree-node__label {
width: 94%;
}
}

View File

@@ -1,24 +1,6 @@
<style lang="scss"> <style lang="scss">
@import "./ossManage.scss"; @import "./ossManage.scss";
.group-row {
padding-top: 16px;
margin-top: 8px;
border-top: 1px solid #ededed;
display: flex;
align-items: center;
justify-content: center;
> * {
margin-right: 10px;
}
}
.article-category {
flex: 2;
min-width: 200px;
}
.table { .table {
flex: 11; flex: 11;
} }
@@ -44,33 +26,24 @@
gap: 10px; gap: 10px;
} }
.tab-empty {
padding: 48px 0;
color: #999;
text-align: center;
}
.tabs-card {
margin-bottom: 16px;
}
.oss-group-tree { .oss-group-tree {
padding: 4px 8px 0; flex: 1;
padding: 4px 4px 0;
:deep(.el-tree-node__expand-icon) { :deep(.el-tree-node__expand-icon) {
display: none; display: none;
} }
:deep(.el-tree-node) { :deep(.el-tree-node) {
margin-bottom: 6px; margin-bottom: 4px;
} }
:deep(.el-tree-node__content) { :deep(.el-tree-node__content) {
padding: 0 10px !important; padding: 0 10px !important;
height: 42px; height: 38px;
border-radius: 6px; border-radius: 6px;
font-size: 15px; font-size: 14px;
line-height: 42px; line-height: 38px;
} }
:deep(.el-tree-node__content:hover) { :deep(.el-tree-node__content:hover) {
@@ -78,8 +51,8 @@
} }
:deep(.el-tree-node.is-current > .el-tree-node__content) { :deep(.el-tree-node.is-current > .el-tree-node__content) {
background: #ecf5ff; background: var(--el-color-primary-light-9);
color: #409eff; color: var(--el-color-primary);
font-weight: 500; font-weight: 500;
} }
} }
@@ -90,7 +63,11 @@
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
padding-right: 4px; padding-right: 4px;
font-size: 15px; font-size: 14px;
&:hover .oss-tree-more {
opacity: 1;
}
} }
.oss-tree-more { .oss-tree-more {
@@ -98,6 +75,12 @@
color: #909399; color: #909399;
padding: 0 4px; padding: 0 4px;
font-size: 16px; font-size: 16px;
opacity: 0;
transition: opacity 0.2s;
&:hover {
color: var(--el-color-primary);
}
} }
.file-preview-fallback { .file-preview-fallback {
@@ -124,21 +107,52 @@
</style> </style>
<template> <template>
<div class="search" :class="{ 'oss-manage-picker': isComponent }"> <div class="search oss-manage" :class="{ 'oss-manage-picker': isComponent }">
<el-card class="tabs-card category-tabs-card"> <div class="oss-panel">
<el-tabs v-model="activeCategoryTab" @tab-change="handleCategoryTabChange"> <div class="oss-toolbar">
<el-tab-pane label="图片" name="IMAGE" /> <div class="oss-tabs">
<el-tab-pane label="视频" name="VIDEO" /> <div
<el-tab-pane label="文件" name="FILE" /> class="oss-tab"
</el-tabs> :class="{ active: activeCategoryTab === 'IMAGE' }"
</el-card> @click="handleCategoryTabChange('IMAGE')"
<el-row> >图片</div>
<el-card> <div
<div> class="oss-tab"
:class="{ active: activeCategoryTab === 'VIDEO' }"
@click="handleCategoryTabChange('VIDEO')"
>视频</div>
<div
class="oss-tab"
:class="{ active: activeCategoryTab === 'FILE' }"
@click="handleCategoryTabChange('FILE')"
>文件</div>
</div>
<div class="oss-actions">
<el-upload
v-if="canUploadImages"
ref="upRef"
:action="sellerUrl + '/store/common/upload/file'"
:data="uploadData"
:headers="accessToken"
:before-upload="beforeUpload"
:on-error="handleError"
:on-success="handleSuccess"
:show-file-list="false"
multiple
style="display: inline-block"
>
<el-button type="primary">{{ uploadButtonText }}</el-button>
</el-upload>
<el-button v-if="!isComponent && showType == 'list'" @click="removeAll">批量删除</el-button>
</div>
</div>
<el-row class="oss-manage-box"> <el-row class="oss-manage-box">
<el-col v-if="canShowGroups" :span="isComponent ? 5 : 4"> <el-col v-if="canShowGroups" :span="isComponent ? 5 : 4">
<div class="file-list"> <div class="oss-side">
<div class="article-category mr_10"> <div v-if="!isComponent" class="oss-add-group">
<el-button class="oss-add-group-btn" @click="handleClickAddGroup">+ 添加分组</el-button>
</div>
<el-tree <el-tree
:key="treeRenderKey" :key="treeRenderKey"
:data="treeData" :data="treeData"
@@ -168,44 +182,17 @@
</span> </span>
</template> </template>
</el-tree> </el-tree>
<div v-if="!isComponent" class="group-row flex">
<el-button @click="handleClickAddGroup">添加分组</el-button>
</div>
</div>
</div> </div>
</el-col> </el-col>
<el-col :span="canShowGroups ? (isComponent ? 19 : 20) : 24"> <el-col :span="canShowGroups ? (isComponent ? 19 : 20) : 24">
<div class="pic-list"> <div class="oss-main">
<div>
<div class="oss-operation padding-row" style="display: flex; flex-direction: row-reverse">
<div>
<el-upload
v-if="canUploadImages"
ref="upRef"
:action="sellerUrl + '/store/common/upload/file'"
:data="uploadData"
:headers="accessToken"
:before-upload="beforeUpload"
:on-error="handleError"
:on-success="handleSuccess"
:show-file-list="false"
multiple
style="display: inline-block"
>
<el-button>{{ uploadButtonText }}</el-button>
</el-upload>
<el-button v-if="!isComponent && showType == 'list'" @click="removeAll">批量删除</el-button>
</div>
</div>
</div>
<div>
<el-checkbox-group v-model="selectedOss" @change="selectOssChange"> <el-checkbox-group v-model="selectedOss" @change="selectOssChange">
<div class="img-box"> <div class="oss-grid">
<div v-for="(item, index) in data" :key="index" class="img-item"> <div v-for="(item, index) in data" :key="index" class="oss-grid-item">
<div <div
class="card" class="oss-card"
:class="{ :class="{
'custom-checkbox-card-checked': selectedOss.includes(item.id + ',' + item.url), 'is-checked': selectedOss.includes(item.id + ',' + item.url),
'oss-card-selectable': isComponent && selectImage, 'oss-card-selectable': isComponent && selectImage,
}" }"
@mouseenter="onMouseOver(item, index)" @mouseenter="onMouseOver(item, index)"
@@ -214,7 +201,7 @@
> >
<el-checkbox <el-checkbox
:value="item.id + ',' + item.url" :value="item.id + ',' + item.url"
class="card-checkbox" class="oss-card-check"
@click.stop @click.stop
/> />
<img v-if="isImageFile(item)" :src="item.url" alt="" /> <img v-if="isImageFile(item)" :src="item.url" alt="" />
@@ -227,7 +214,7 @@
<div v-else class="card-file"> <div v-else class="card-file">
<el-icon :size="28"><Document /></el-icon> <el-icon :size="28"><Document /></el-icon>
</div> </div>
<div v-if="item.isShowPreview" class="preview"> <div v-if="item.isShowPreview" class="oss-card-ops">
<div @click.prevent="download(item)"> <div @click.prevent="download(item)">
<el-tooltip content="下载" placement="top"> <el-tooltip content="下载" placement="top">
<el-icon :size="18"><Download /></el-icon> <el-icon :size="18"><Download /></el-icon>
@@ -248,18 +235,18 @@
</div> </div>
</div> </div>
<el-tooltip :content="item.name" placement="bottom"> <el-tooltip :content="item.name" placement="bottom">
<div class="text">{{ item.name }}</div> <div class="oss-name">{{ item.name }}</div>
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
</el-checkbox-group> </el-checkbox-group>
</div> <el-empty v-if="!loading && !data.length" description="暂无文件" :image-size="100" />
<div class="page-box"> <div class="oss-pager">
<el-pagination <el-pagination
v-model:current-page="searchForm.pageNumber" v-model:current-page="searchForm.pageNumber"
v-model:page-size="searchForm.pageSize" v-model:page-size="searchForm.pageSize"
:total="total" :total="total"
layout="prev, pager, next, jumper" layout="total, prev, pager, next, jumper"
size="small" size="small"
@current-change="pageChange" @current-change="pageChange"
/> />
@@ -271,10 +258,7 @@
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</el-card>
</el-row>
<el-dialog <el-dialog
v-model="modalVisible" v-model="modalVisible"
:close-on-click-modal="false" :close-on-click-modal="false"