270 lines
7.1 KiB
Vue
270 lines
7.1 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 文件网格列表:卡片展示文件夹图标 / 文件类型图标、名称、元数据(文件大小 + 修改时间,
|
||
* 文件夹只有时间、不显示用户),右上角三点「更多」。viewMode 支持 grid(三列卡片)与 list(单行列表)。
|
||
* 多选模式(selectMode)下点击卡片改为勾选/取消;数据、选中集均由页面持有,这里只渲染与上报。
|
||
*/
|
||
import type { PropType } from "vue";
|
||
import type { FileItem, FileKind } from "@/data/custom/fileBrowser";
|
||
|
||
const props = defineProps({
|
||
/** 已过滤排序好的条目,组件按原顺序渲染 */
|
||
files: {
|
||
type: Array as PropType<FileItem[]>,
|
||
default: () => [],
|
||
},
|
||
/** 排布模式:grid 九宫格 / list 列表 */
|
||
viewMode: {
|
||
type: String as PropType<"grid" | "list">,
|
||
default: "grid",
|
||
},
|
||
/** 是否处于多选模式 */
|
||
selectMode: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
/** 多选模式下已勾选的 id 集合 */
|
||
selectedIds: {
|
||
type: Array as PropType<number[]>,
|
||
default: () => [],
|
||
},
|
||
/** 空列表提示文案(页面翻译好传入) */
|
||
emptyText: {
|
||
type: String,
|
||
default: "",
|
||
},
|
||
});
|
||
|
||
const emit = defineEmits<{
|
||
/** 点开条目(非多选模式的单击) */
|
||
(e: "open", file: FileItem): void;
|
||
/** 卡片右上角「更多」 */
|
||
(e: "more", file: FileItem): void;
|
||
/** 多选模式下勾选状态变化 */
|
||
(e: "toggle-select", id: number): void;
|
||
}>();
|
||
|
||
// 文件类型 -> 图标与配色(文件夹走纯 CSS 造型,不用字体图标)
|
||
const kindVisual: Record<Exclude<FileKind, "folder">, { icon: string; color: string }> = {
|
||
image: { icon: "photo-fill", color: "#0ea5e9" },
|
||
video: { icon: "play-right-fill", color: "#8b5cf6" },
|
||
document: { icon: "file-text-fill", color: "#3b82f6" },
|
||
audio: { icon: "volume-up", color: "#f59e0b" },
|
||
};
|
||
|
||
function visualOf(kind: FileKind): { icon: string; color: string } | null {
|
||
return kind === "folder" ? null : kindVisual[kind];
|
||
}
|
||
|
||
function isSelected(file: FileItem): boolean {
|
||
return props.selectedIds.includes(file.id);
|
||
}
|
||
|
||
function onCardTap(file: FileItem): void {
|
||
if (props.selectMode) {
|
||
emit("toggle-select", file.id);
|
||
return;
|
||
}
|
||
emit("open", file);
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<view class="file-grid-list" :class="{ 'file-grid-list--list': viewMode === 'list' }">
|
||
<view v-if="!files.length" class="file-grid-list__empty">
|
||
<text class="file-grid-list__empty-text">
|
||
{{ emptyText }}
|
||
</text>
|
||
</view>
|
||
<view v-for="file in files" :key="file.id" class="fgl-card"
|
||
:class="{ 'fgl-card--selected': selectMode && isSelected(file) }">
|
||
<view class="fgl-card__more" @tap.stop="emit('more', file)">
|
||
<u-icon name="more-dot-fill" size="30" color="#b8bcc4" />
|
||
</view>
|
||
|
||
<view class="fgl-card__body" @tap="onCardTap(file)">
|
||
<!-- 文件夹:纯 CSS 造型(图标库没有 folder),主体 + 左上页签 -->
|
||
<view v-if="file.kind === 'folder'" class="fgl-card__folder">
|
||
<view class="fgl-card__folder-tab" />
|
||
</view>
|
||
<u-icon v-else :name="visualOf(file.kind)!.icon" size="88" :color="visualOf(file.kind)!.color" />
|
||
|
||
<view class="fgl-card__info">
|
||
<text class="fgl-card__name">
|
||
{{ file.name }}
|
||
</text>
|
||
<view class="fgl-card__meta">
|
||
<text v-if="file.sizeText" class="fgl-card__meta-text">
|
||
{{ file.sizeText }}
|
||
</text>
|
||
<text class="fgl-card__meta-text">
|
||
{{ file.modifiedAt }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="selectMode" class="fgl-card__check">
|
||
<u-icon :name="isSelected(file) ? 'checkmark-circle-fill' : 'checkmark-circle'" size="40"
|
||
:color="isSelected(file) ? '#2b8fff' : '#c0c4cc'" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.file-grid-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
padding: 16rpx 20rpx 40rpx;
|
||
}
|
||
|
||
.file-grid-list__empty {
|
||
width: 100%;
|
||
padding: 120rpx 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.file-grid-list__empty-text {
|
||
font-size: 26rpx;
|
||
color: #9aa0a6;
|
||
}
|
||
|
||
.fgl-card {
|
||
position: relative;
|
||
width: calc(100% / 3 - 16rpx);
|
||
margin: 8rpx;
|
||
padding-bottom: 20rpx;
|
||
border-radius: 20rpx;
|
||
background-color: #f7f8fa;
|
||
|
||
&--selected {
|
||
background-color: #eaf4ff;
|
||
}
|
||
}
|
||
|
||
.fgl-card__more {
|
||
position: absolute;
|
||
top: 12rpx;
|
||
right: 12rpx;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 52rpx;
|
||
height: 44rpx;
|
||
border-radius: 12rpx;
|
||
background-color: rgba(31, 35, 41, 0.05);
|
||
}
|
||
|
||
.fgl-card__body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 36rpx 12rpx 0;
|
||
}
|
||
|
||
/* ---- 纯 CSS 文件夹:蓝色渐变圆角主体 + 左上页签 ---- */
|
||
.fgl-card__folder {
|
||
position: relative;
|
||
width: 132rpx;
|
||
height: 104rpx;
|
||
margin: 14rpx 0;
|
||
border-radius: 16rpx;
|
||
background: linear-gradient(135deg, #6ec2ff 0%, #2b8fff 100%);
|
||
box-shadow: 0 10rpx 20rpx rgba(43, 143, 255, 0.25);
|
||
}
|
||
|
||
.fgl-card__folder-tab {
|
||
position: absolute;
|
||
top: -16rpx;
|
||
left: 14rpx;
|
||
width: 56rpx;
|
||
height: 24rpx;
|
||
border-radius: 10rpx 10rpx 0 0;
|
||
background: linear-gradient(135deg, #6ec2ff 0%, #4aa6ff 100%);
|
||
}
|
||
|
||
.fgl-card__info {
|
||
width: 100%;
|
||
padding: 0 12rpx;
|
||
}
|
||
|
||
.fgl-card__name {
|
||
display: block;
|
||
overflow: hidden;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
color: #1f2329;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.fgl-card__meta {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
margin-top: 6rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.fgl-card__meta-text {
|
||
flex-shrink: 0;
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
font-size: 22rpx;
|
||
color: #9aa0a6;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.fgl-card__check {
|
||
position: absolute;
|
||
right: 12rpx;
|
||
bottom: 16rpx;
|
||
}
|
||
|
||
/* ---- 列表模式:单行排布,覆盖网格默认样式 ---- */
|
||
.file-grid-list--list {
|
||
flex-direction: column;
|
||
flex-wrap: nowrap;
|
||
padding: 8rpx 24rpx 40rpx;
|
||
}
|
||
|
||
.file-grid-list--list .fgl-card {
|
||
width: 100%;
|
||
margin: 0 0 16rpx;
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.file-grid-list--list .fgl-card__more {
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background-color: transparent;
|
||
}
|
||
|
||
.file-grid-list--list .fgl-card__body {
|
||
flex-direction: row;
|
||
padding: 24rpx 64rpx 24rpx 24rpx;
|
||
}
|
||
|
||
.file-grid-list--list .fgl-card__folder {
|
||
width: 72rpx;
|
||
height: 56rpx;
|
||
margin: 0 24rpx 0 0;
|
||
border-radius: 10rpx;
|
||
}
|
||
|
||
.file-grid-list--list .fgl-card__folder-tab {
|
||
top: -10rpx;
|
||
left: 8rpx;
|
||
width: 30rpx;
|
||
height: 14rpx;
|
||
border-radius: 6rpx 6rpx 0 0;
|
||
}
|
||
|
||
.file-grid-list--list .fgl-card__info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
</style>
|