Files
Docker/uniapp/youjiala/src/components/file-grid-list/file-grid-list.vue
T
2026-10-14 14:48:29 +08:00

270 lines
7.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>