20260820223837
This commit is contained in:
1 parent
68ac5db038
commit
45854b0570
48 files changed
+7545
-650
No files matched your search
@@ -0,0 +1,295 @@
|
||||
<script setup>
|
||||
import { computed, h } from 'vue'
|
||||
import { Trash2, Download, Eye, MoreHorizontal } from '@lucide/vue'
|
||||
import Button from '@/components/ui/button/button.vue'
|
||||
import Checkbox from '@/components/ui/checkbox/checkbox.vue'
|
||||
import Tag from '@/components/ui/tag/tag.vue'
|
||||
import Dropdown from '@/components/ui/dropdown/dropdown.vue'
|
||||
import { getPreviewUrl } from '@/services/fileService'
|
||||
import {
|
||||
extFromName,
|
||||
extColor,
|
||||
isImageType,
|
||||
formatSize,
|
||||
} from './fileVisual'
|
||||
|
||||
const props = defineProps({
|
||||
file: { type: Object, required: true },
|
||||
selected: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['select', 'preview', 'download', 'delete'])
|
||||
|
||||
const isPending = computed(() => !!props.file?.pending)
|
||||
const isImage = computed(() => isImageType(props.file?.mx_filename || ''))
|
||||
|
||||
const thumbnailUrl = computed(() => {
|
||||
if (!isImage.value) return ''
|
||||
return props.file?.blobUrl || getPreviewUrl(props.file) || ''
|
||||
})
|
||||
|
||||
const extname = computed(() => extFromName(props.file?.mx_filename || ''))
|
||||
const extnameUpper = computed(() => extname.value.toUpperCase() || '?')
|
||||
const typeColor = computed(() => extColor(props.file?.mx_filename || ''))
|
||||
|
||||
const moreMenuItems = computed(() => [
|
||||
{ key: 'preview', label: '预览', icon: h(Eye, { size: 13 }) },
|
||||
])
|
||||
|
||||
function onCheck(checked) {
|
||||
emit('select', props.file, checked)
|
||||
}
|
||||
|
||||
function onCardClick() {
|
||||
if (!isPending.value) emit('preview', props.file)
|
||||
}
|
||||
|
||||
function onDownload() {
|
||||
if (!isPending.value) emit('download', props.file)
|
||||
}
|
||||
|
||||
function onDelete() {
|
||||
emit('delete', props.file)
|
||||
}
|
||||
|
||||
function onMoreClick(info) {
|
||||
if (info?.key === 'preview' && !isPending.value) emit('preview', props.file)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="fcard"
|
||||
:class="{ 'fcard--selected': selected, 'fcard--pending': isPending }"
|
||||
:title="file.mx_filename"
|
||||
@click="onCardClick"
|
||||
>
|
||||
<!-- 缩略图区域 -->
|
||||
<div class="fcard__thumb">
|
||||
<img v-if="isImage && thumbnailUrl" :src="thumbnailUrl" alt="" />
|
||||
<div v-else class="fcard__type" :style="{ '--c': typeColor }">
|
||||
{{ extnameUpper }}
|
||||
</div>
|
||||
|
||||
<!-- 左上 checkbox(始终可见) -->
|
||||
<span class="fcard__check" @click.stop>
|
||||
<Checkbox :model-value="selected" @change="onCheck" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 文件信息 -->
|
||||
<div class="fcard__info">
|
||||
<div class="fcard__name">
|
||||
<span class="fcard__title" :title="file.mx_filename">{{
|
||||
file.mx_filename
|
||||
}}</span>
|
||||
<Tag v-if="isPending" variant="outline" class="fcard__tag">待上传</Tag>
|
||||
</div>
|
||||
<div class="fcard__meta">{{ formatSize(file.mx_filesize) }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作栏:图标按钮(无文字,语言无关,避免多语言下文字长度撑破布局) -->
|
||||
<div class="fcard__footer" @click.stop>
|
||||
<Button type="ghost" :disabled="isPending" @click="onDownload">
|
||||
<Download />
|
||||
</Button>
|
||||
<span class="fcard__divider" />
|
||||
<Button type="ghost" class="fcard__btn-danger" @click="onDelete">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
<span class="fcard__divider" />
|
||||
<Dropdown :items="moreMenuItems" :disabled="isPending" @click="onMoreClick">
|
||||
<Button type="ghost" :disabled="isPending">
|
||||
<MoreHorizontal />
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fcard {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 6px;
|
||||
background: var(--fms-card);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
&--selected {
|
||||
border-color: var(--fms-primary);
|
||||
}
|
||||
|
||||
&--pending {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
/* 缩略图 */
|
||||
.fcard__thumb {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 84px;
|
||||
background: var(--fms-secondary-hover);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
/* 彩色类型方块(签名元素) */
|
||||
.fcard__type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--c);
|
||||
background: color-mix(in srgb, var(--c) 14%, #fff);
|
||||
border: 1px solid color-mix(in srgb, var(--c) 32%, #fff);
|
||||
}
|
||||
|
||||
/* checkbox:直接浮在缩略图上,不加底块/阴影,保持轻量 */
|
||||
.fcard__check {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
z-index: 3;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 常驻操作栏:下载 / 删除 / 更多 */
|
||||
.fcard__footer {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid var(--fms-border);
|
||||
background: var(--fms-card);
|
||||
|
||||
:deep(.button) {
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
min-height: 0;
|
||||
height: 32px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
/* 底部三个操作按钮必须是等宽 flex 子项:在 flex-basis: 0% 下浏览器按
|
||||
内容盒均分剩余空间,任何一方额外的 padding / border 都会让其 border-box
|
||||
更宽。故这里与 .popover-trigger 统一 padding:0 + border:none,
|
||||
让三个按钮 border-box 完全相等 */
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
color: var(--fms-text-secondary);
|
||||
/* 仅过渡颜色,避免 hover 时因 all 过渡引发的闪烁/位移 */
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--fms-primary);
|
||||
background: var(--fms-secondary-hover);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&.fcard__btn-danger:hover:not(:disabled) {
|
||||
color: var(--fms-danger);
|
||||
background: color-mix(in srgb, var(--fms-danger) 10%, #fff);
|
||||
}
|
||||
|
||||
/* 明确的禁用态:覆盖按钮库默认透明度,
|
||||
让 pending 时的下载/更多按钮图标明显变淡灰、鼠标显示禁止手势,
|
||||
lucide 图标用 currentColor 自动跟随。 */
|
||||
&:disabled {
|
||||
color: var(--fms-disabled-text);
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
cursor: not-allowed;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 「更多」按钮被 Dropdown 包裹:Popover 的 .popover-trigger 才是 flex 子项,
|
||||
必须让它与两个直接按钮一样 flex:1 且 padding/border 保持一致,
|
||||
三个操作按钮宽度才会一致 */
|
||||
:deep(.popover-trigger) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-self: auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
border: none;
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 操作栏按钮间的竖向分隔线:独立元素,不依赖按钮类名 / 禁用态,
|
||||
避免「更多」按钮被 dropdown 包裹且禁用时 border 被抹掉导致分隔线消失。 */
|
||||
.fcard__divider {
|
||||
flex: none;
|
||||
width: 1px;
|
||||
align-self: stretch;
|
||||
background: var(--fms-border);
|
||||
}
|
||||
|
||||
/* 文件信息 */
|
||||
.fcard__info {
|
||||
padding: 7px 10px 9px;
|
||||
}
|
||||
|
||||
.fcard__name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fcard__title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--fms-text);
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fcard__tag {
|
||||
flex: none;
|
||||
margin: 0;
|
||||
padding: 0 5px;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
height: 18px;
|
||||
color: var(--fms-warning);
|
||||
background: color-mix(in srgb, var(--fms-warning) 12%, #fff);
|
||||
border: 1px solid color-mix(in srgb, var(--fms-warning) 35%, #fff);
|
||||
}
|
||||
|
||||
.fcard__meta {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--fms-text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user