This commit is contained in:
owenchen committed 2026-10-14 14:48:29 +08:00
1 parent 8a2adb0a92
commit bd678b0112
22 files changed
+1968 -81

No files matched your search

@@ -0,0 +1,269 @@
<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>