improve(ui):优化管理后台的表格显示效果
1. 表头横向滚动条变小 2. 表格占位图片垂直居中 3. 移除表格底部分割线
This commit is contained in:
1 parent
2de068633c
commit
c9cdfd7138
18 files changed
+300
-34
No files matched your search
@@ -242,6 +242,17 @@ body {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* 表格横向滚动条细化:Arco 默认轨道 15px、滑块 9px,列多时悬停表头会出现粗滚动条遮住表头文字。
|
||||
压成 3px 细线贴在底部,仅作「可横向滚动」的提示;拖拽/点击滚动同步逻辑不变,表头表体通用 */
|
||||
.arco-table .arco-scrollbar-track-direction-horizontal {
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
.arco-table .arco-scrollbar-thumb-direction-horizontal .arco-scrollbar-thumb-bar {
|
||||
height: 3px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.arco-table-td {
|
||||
border-bottom: 1px solid var(--ta-border-light);
|
||||
}
|
||||
@@ -259,6 +270,40 @@ body {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* page-table 布局下表体被拉伸为滚动容器,固定 240px 的空态行会顶在表体上方。
|
||||
空数据时把表体区域(含固定列触发的表头/表体分离结构:Scrollbar 根 → arco-table-body →
|
||||
table)逐层拉满表头以下的剩余高度,再让空态行撑满表格,「暂无数据」即在整个表体
|
||||
可视区内垂直居中,表头行高度不变 */
|
||||
.page-table .arco-table-content:has(.arco-table-tr-empty) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 表体区域的 Scrollbar 根(表头是第一个子节点,取 last-child 只拉伸表体) */
|
||||
.page-table .arco-table-content:has(.arco-table-tr-empty) > .arco-scrollbar:last-child {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 表体滚动容器(Arco 把 body 类挂在 scrollbar-container 上) */
|
||||
.page-table .arco-table-content:has(.arco-table-tr-empty) .arco-table-body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-table .arco-table-content:has(.arco-table-tr-empty) .arco-table-body .arco-table-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 空态行撑满拉高后的表格(此表只剩该行,无表头行分高) */
|
||||
.page-table .arco-table-content:has(.arco-table-tr-empty) .arco-table-tr-empty {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.arco-card {
|
||||
border-radius: var(--ta-radius);
|
||||
@@ -464,16 +509,16 @@ body {
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
/* 右下角分页栏 */
|
||||
/* 右下角分页栏:表格底线(末行 td 的 border-bottom)即分隔线,
|
||||
footer 不再画 border-top / 留 margin-top,避免表格底部出现两条临近分割线,
|
||||
仅用 padding-top 与表格保持间距 */
|
||||
.page-table .table-footer {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
|
||||
/* 统计总数从表格左上角移到底部后:footer 内存在统计栏时两端对齐(统计居左、分页居右),
|
||||
|
||||
@@ -692,9 +692,7 @@ const { exporting, exportFile } = useExport({
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
|
||||
.table-footer :deep(.table-stats) {
|
||||
|
||||
@@ -326,12 +326,13 @@ onMounted(() => {
|
||||
:on-before-ok="handleSaveModels"
|
||||
:ok-loading="modelsLoading"
|
||||
>
|
||||
<ASpin :loading="modelsLoading">
|
||||
<ASpin :loading="modelsLoading" class="models-transfer-wrap">
|
||||
<ATransfer
|
||||
v-model="selectedModelIds"
|
||||
:data="transferOptions"
|
||||
:title="['可选模型', '已选模型']"
|
||||
searchable
|
||||
class="models-transfer"
|
||||
/>
|
||||
</ASpin>
|
||||
</AModal>
|
||||
@@ -347,7 +348,6 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
|
||||
.table-footer :deep(.table-stats) {
|
||||
@@ -356,4 +356,12 @@ onMounted(() => {
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
/* 模型穿梭框:弹窗内水平居中,两侧列表加高 */
|
||||
.models-transfer-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.models-transfer :deep(.arco-transfer-view) {
|
||||
height: 420px;
|
||||
}
|
||||
</style>
|
||||
@@ -1010,9 +1010,7 @@ function resetImport() {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
|
||||
.table-footer :deep(.table-stats) {
|
||||
|
||||
@@ -418,9 +418,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
|
||||
.table-footer :deep(.table-stats) {
|
||||
|
||||
@@ -138,8 +138,6 @@ onMounted(fetchData)
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
</style>
|
||||
@@ -312,9 +312,7 @@ onMounted(() => {
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border-light, #e5e6eb);
|
||||
}
|
||||
.email-body {
|
||||
background: var(--color-fill-2, #f7f8fa);
|
||||
|
||||
@@ -367,8 +367,6 @@ const { exporting, exportFile } = useExport({
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
</style>
|
||||
@@ -549,9 +549,7 @@ const columns: TableColumnData[] = [
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
|
||||
.perm-toolbar {
|
||||
|
||||
@@ -462,9 +462,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border-light, #e5e6eb);
|
||||
}
|
||||
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
|
||||
.table-footer :deep(.table-stats) {
|
||||
|
||||
@@ -615,7 +615,6 @@ onMounted(() => {
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.retention-pop {
|
||||
width: 260px;
|
||||
|
||||
@@ -398,8 +398,6 @@ onMounted(() => {
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
</style>
|
||||
@@ -532,9 +532,7 @@ const { exporting, exportFile } = useExport({
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
|
||||
.role-empty-hint {
|
||||
|
||||
@@ -477,8 +477,6 @@ const { exporting, exportFile } = useExport({
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
</style>
|
||||
@@ -492,8 +492,6 @@ const { exporting, exportFile } = useExport({
|
||||
.table-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
</style>
|
||||
@@ -1551,9 +1551,7 @@ const { exporting, exportFile } = useExport({
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--color-border-light, #e5e6eb);
|
||||
}
|
||||
|
||||
.table-footer-left {
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--ta-border-light);
|
||||
}
|
||||
.table-footer .table-stats {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Icon from '@/components/common/Icon.vue'
|
||||
|
||||
// 卡片式异步任务加载占位:按成品形状(图片 1:1 / 视频 16:9)占位的动效卡片,
|
||||
// 中央进度环展示百分比(无进度数值时转为旋转弧的不确定态),
|
||||
// 底层为浅色渐变底 + 双色光斑漂移 + 斜向扫光,视觉对齐 OpenAI / Gemini 生成中的占位效果。
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
kind?: 'image' | 'video'
|
||||
progress?: string
|
||||
label?: string
|
||||
sublabel?: string
|
||||
}>(),
|
||||
{
|
||||
kind: 'image',
|
||||
progress: '',
|
||||
label: '生成中...',
|
||||
sublabel: '',
|
||||
},
|
||||
)
|
||||
|
||||
// 从进度文本解析 0-100 数值(上游协议为 "45%" 形态);解析不到时走不确定态
|
||||
const pct = computed<number | null>(() => {
|
||||
const m = /(\d+(?:\.\d+)?)/.exec(props.progress)
|
||||
if (!m) return null
|
||||
const n = Number(m[1])
|
||||
if (!Number.isFinite(n)) return null
|
||||
return Math.min(100, Math.max(0, Math.round(n)))
|
||||
})
|
||||
|
||||
// 进度环几何参数:viewBox 100x100,r=42
|
||||
const RADIUS = 42
|
||||
const CIRCUMFERENCE = 2 * Math.PI * RADIUS
|
||||
const dashOffset = computed(() => CIRCUMFERENCE * (1 - (pct.value ?? 0) / 100))
|
||||
|
||||
// 渐变 id 按实例自增,避免同页多个卡片时 url(#id) 引用错乱
|
||||
let seq = 0
|
||||
const gradId = `gen-grad-${++seq}`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gen-card animate-scale-in" :class="kind === 'video' ? 'gen-card--video' : 'gen-card--image'">
|
||||
<!-- 底层:浅色渐变底 + 光斑漂移 + 斜向扫光 -->
|
||||
<div class="gen-card__bg"></div>
|
||||
<div class="gen-blob gen-blob--a"></div>
|
||||
<div class="gen-blob gen-blob--b"></div>
|
||||
<div class="gen-sheen"></div>
|
||||
|
||||
<!-- 中央:进度环 + 状态文案 -->
|
||||
<div class="gen-center">
|
||||
<div class="gen-ring">
|
||||
<svg viewBox="0 0 100 100" class="gen-ring__svg">
|
||||
<defs>
|
||||
<linearGradient :id="gradId" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#2dd4bf" />
|
||||
<stop offset="100%" stop-color="#0d9488" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- 轨道 -->
|
||||
<circle cx="50" cy="50" :r="RADIUS" fill="none" stroke="rgba(148, 163, 184, 0.25)" stroke-width="6" />
|
||||
<!-- 不确定态:旋转短弧 -->
|
||||
<circle
|
||||
v-if="pct === null"
|
||||
class="gen-ring__indeterminate"
|
||||
cx="50" cy="50" :r="RADIUS"
|
||||
fill="none" :stroke="`url(#${gradId})`"
|
||||
stroke-width="6" stroke-linecap="round"
|
||||
:stroke-dasharray="`${CIRCUMFERENCE * 0.24} ${CIRCUMFERENCE}`"
|
||||
/>
|
||||
<!-- 确定态:按进度绘制弧长 -->
|
||||
<circle
|
||||
v-else
|
||||
class="gen-ring__progress"
|
||||
cx="50" cy="50" :r="RADIUS"
|
||||
fill="none" :stroke="`url(#${gradId})`"
|
||||
stroke-width="6" stroke-linecap="round"
|
||||
:stroke-dasharray="CIRCUMFERENCE"
|
||||
:stroke-dashoffset="dashOffset"
|
||||
/>
|
||||
</svg>
|
||||
<div class="gen-ring__text">
|
||||
<span v-if="pct !== null" class="gen-pct">{{ pct }}<small>%</small></span>
|
||||
<Icon v-else name="hourglass" size="lg" class="animate-pulse text-primary-500" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="gen-label">{{ label }}</p>
|
||||
<p v-if="sublabel" class="gen-sublabel">{{ sublabel }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 卡片容器:按成品形态占位 */
|
||||
.gen-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #99f6e4;
|
||||
box-shadow: 0 10px 34px rgba(91, 104, 151, 0.08);
|
||||
}
|
||||
.gen-card--image {
|
||||
aspect-ratio: 1 / 1;
|
||||
max-width: 360px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.gen-card--video {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
/* 底层浅色渐变 */
|
||||
.gen-card__bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, #f0fdfa 0%, #f8fafc 45%, #eff6ff 100%);
|
||||
}
|
||||
|
||||
/* 双色光斑缓慢漂移(青 + 靛),叠加出极光质感 */
|
||||
.gen-blob {
|
||||
position: absolute;
|
||||
width: 60%;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 50%;
|
||||
filter: blur(42px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.gen-blob--a {
|
||||
left: -12%;
|
||||
top: -16%;
|
||||
background: radial-gradient(circle, rgba(45, 212, 191, 0.4), transparent 70%);
|
||||
animation: gen-drift-a 7s ease-in-out infinite alternate;
|
||||
}
|
||||
.gen-blob--b {
|
||||
right: -14%;
|
||||
bottom: -20%;
|
||||
background: radial-gradient(circle, rgba(129, 140, 248, 0.32), transparent 70%);
|
||||
animation: gen-drift-b 9s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes gen-drift-a {
|
||||
from { transform: translate(0, 0) scale(1); }
|
||||
to { transform: translate(22%, 16%) scale(1.18); }
|
||||
}
|
||||
@keyframes gen-drift-b {
|
||||
from { transform: translate(0, 0) scale(1.12); }
|
||||
to { transform: translate(-20%, -14%) scale(0.94); }
|
||||
}
|
||||
|
||||
/* 斜向扫光带,周期性掠过卡片 */
|
||||
.gen-sheen {
|
||||
position: absolute;
|
||||
top: -12%;
|
||||
bottom: -12%;
|
||||
width: 42%;
|
||||
background: linear-gradient(105deg, transparent, rgba(255, 255, 255, 0.55), transparent);
|
||||
transform: skewX(-16deg) translateX(-130%);
|
||||
animation: gen-sweep 2.8s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
@keyframes gen-sweep {
|
||||
0% { transform: skewX(-16deg) translateX(-130%); }
|
||||
55%, 100% { transform: skewX(-16deg) translateX(330%); }
|
||||
}
|
||||
|
||||
/* 中央内容 */
|
||||
.gen-center {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
/* 进度环:白色圆盘承托,保证文字与光斑叠加时的可读性 */
|
||||
.gen-ring {
|
||||
position: relative;
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 8px 24px rgba(76, 91, 142, 0.12);
|
||||
}
|
||||
.gen-ring__svg {
|
||||
position: absolute;
|
||||
inset: 6px;
|
||||
}
|
||||
.gen-ring__indeterminate {
|
||||
transform-box: view-box;
|
||||
transform-origin: center;
|
||||
animation: gen-spin 1.1s linear infinite;
|
||||
}
|
||||
.gen-ring__progress {
|
||||
transition: stroke-dashoffset 0.6s ease;
|
||||
}
|
||||
@keyframes gen-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.gen-ring__text {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.gen-pct {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #0f766e;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
.gen-pct small {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-left: 1px;
|
||||
}
|
||||
.gen-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #334155;
|
||||
}
|
||||
.gen-sublabel {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
max-width: 80%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 弱动效偏好:静止装饰动画,仅保留进度过渡 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.gen-blob--a,
|
||||
.gen-blob--b,
|
||||
.gen-sheen,
|
||||
.gen-ring__indeterminate {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user