ui框架-文件列表展示
介绍
UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。
功能特性
- 支持列表模式和网格模式的切换展示
- 支持文件和文件夹的层级展示
- 支持文件选择和文件夹选择
- 支持拖拽上传文件
- 支持多选/单选模式
- 支持文件类型过滤
- 支持文件大小限制
- 支持图片文件预览
- 支持返回上级目录
- 显示文件大小和最后修改时间
安装
npm install bbyh-ui-file-list-diaplay
使用示例
<template>
<FileListDisplay
defaultViewMode="grid"
:defaultFiles="files"
:acceptedTypes="['jpg', 'png', 'pdf']"
:maxFileSize="50"
:multiple="true"
:showFileSize="true"
:enableDrop="true"
@select="handleSelect"
@file-error="handleError"
/>
</template>
<script setup>
import FileListDisplay from "bbyh-ui-file-list-diaplay";
import { ref } from 'vue';
const files = ref([]);
const handleSelect = (selectedFiles) => {
console.log('Selected files:', selectedFiles);
};
const handleError = (error) => {
console.error('File error:', error);
};
</script>
Props 配置
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
defaultViewMode | String | ‘list’ | 默认视图模式,可选值:‘list’/‘grid’ |
defaultFiles | Array | [] | 默认显示的文件列表 |
multiple | Boolean | true | 是否允许多选 |
acceptedTypes | Array | [] | 允许的文件类型,空数组表示允许所有类型 |
maxFileSize | Number | 100 | 最大文件大小(MB) |
showFileSize | Boolean | true | 是否显示文件大小 |
enableDrop | Boolean | true | 是否允许拖拽上传 |
showToolbar | Boolean | true | 是否显示工具栏 |
showBackButton | Boolean | true | 是否显示返回上级目录按钮 |
事件
事件名 | 参数 | 说明 |
---|---|---|
select | selectedFiles | 选择文件时触发 |
enter-directory | directory | 进入文件夹时触发 |
file-error | {file, error} | 文件验证失败时触发 |
drop | files | 拖拽文件时触发 |
view-mode-change | mode | 视图模式改变时触发 |
支持的文件类型图标
- 常见文档:txt, doc, docx, xls, xlsx, ppt, pptx, pdf
- 图片:jpg, jpeg, png, gif, bmp, webp
- 开发相关:js, java, c, cpp, py, css, html, json
- 其他:exe, zip, rar 等
浏览器兼容性
- Chrome >= 80
- Firefox >= 75
- Safari >= 13
- Edge >= 80
开发说明
- 安装依赖
npm install
- 启动开发服务器
npm run serve
- 构建生产版本
npm run build
源码下载
ui框架-文件列表展示
npm仓库
ui框架-文件列表展示
核心代码
code/src/components/FileListDisplay.vue
<template>
<div class="file-list-display">
<!-- 工具栏 -->
<div class="toolbar">
<div class="view-mode">
<span
:class="['mode-item', { active: viewMode === 'list' }]"
@click="viewMode = 'list'"
>
列表模式
</span>
<span
:class="['mode-item', { active: viewMode === 'grid' }]"
@click="viewMode = 'grid'"
>
图标模式
</span>
</div>
<div class="operations">
<input
type="file"
ref="fileInput"
@change="handleFileSelect"
multiple
style="display: none"
>
<input
type="file"
ref="folderInput"
@change="handleFolderSelect"
webkitdirectory
style="display: none"
>
<button @click="selectFiles">选择文件</button>
<button @click="selectFolder">选择文件夹</button>
</div>
</div>
<!-- 文件列表区域 -->
<div
class="file-container"
:class="viewMode"
@dragover.prevent
@drop.prevent="handleDrop"
>
<template v-if="currentDirectory.children.length">
<!-- 显示返回上级目录按钮 -->
<div
v-if="currentDirectory.parent"
class="file-item"
@click="navigateToParent"
>
<div class="file-icon">
<img :src="getFileIcon({type: 'back'})" alt="back">
</div>
<div class="file-info">
<div class="file-name">..</div>
</div>
</div>
<!-- 显示文件和文件夹 -->
<div
v-for="item in currentDirectory.children"
:key="item.path"
class="file-item"
:class="{ selected: selectedFiles.includes(item) }"
@click="handleItemClick(item)"
@dblclick="handleItemDoubleClick(item)"
>
<div class="file-icon">
<!-- 如果是图片文件则显示预览 -->
<img
v-if="isImageFile(item)"
:src="getImagePreview(item)"
:alt="item.name"
class="image-preview"
@error="handleImageError(item)"
>
<!-- 非图片文件显示默认图标 -->
<img v-else :src="getFileIcon(item)" :alt="item.name" class="not-image-preview">
</div>
<div class="file-info">
<div class="file-name">{{ item.name }}</div>
<div class="file-size">{{
formatFileSize(item.isDirectory ? calculateDirectorySize(item) : item.size)
}}
</div>
</div>
</div>
</template>
<div v-else class="empty-tip">
当前文件夹为空
</div>
</div>
</div>
</template>
<script setup>
import {onMounted, onUnmounted, ref, watch} from 'vue';
import {getFileIcon} from './fileIcons';
import {buildFileTree, calculateDirectorySize} from './fileUtils';
// Props 定义
const props = defineProps({
// 默认视图模式
defaultViewMode: {
type: String,
default: 'list',
validator: (value) => ['list', 'grid'].includes(value)
},
// 默认文件列表
defaultFiles: {
type: Array,
default: () => []
},
// 是否允许多选
multiple: {
type: Boolean,
default: true
},
// 允许的文件类型
acceptedTypes: {
type: Array,
default: () => []
},
// 最大文件大小(MB)
maxFileSize: {
type: Number,
default: 100
},
// 是否显示文件大小
showFileSize: {
type: Boolean,
default: true
},
// 是否允许拖拽上传
enableDrop: {
type: Boolean,
default: true
},
// 是否显示工具栏
showToolbar: {
type: Boolean,
default: true
},
// 是否显示返回上级目录按钮
showBackButton: {
type: Boolean,
default: true
}
});
// Emits 定义
const emit = defineEmits([
'select', // 选择文件时触发
'enter-directory', // 进入文件夹时触发
'file-error', // 文件验证失败时触发
'drop', // 拖拽文件时触发
'view-mode-change' // 视图模式改变时触发
]);
// 视图模式
const viewMode = ref(props.defaultViewMode);
// 文件输入引用
const fileInput = ref();
const folderInput = ref();
// 选择文件
const selectFiles = () => {
fileInput.value.click();
};
// 选择文件夹
const selectFolder = () => {
folderInput.value.click();
};
// 格式化文件大小
const formatFileSize = (size) => {
if (size < 1024) return size + ' B';
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB';
if (size < 1024 * 1024 * 1024) return (size / (1024 * 1024)).toFixed(2) + ' MB';
return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
};
// 文件树根节点
const root = ref(buildFileTree([]));
// 当前显示的目录
const currentDirectory = ref(root.value);
// 选中的文件
const selectedFiles = ref([]);
// 返回上级目录
const navigateToParent = () => {
if (currentDirectory.value.parent) {
currentDirectory.value = currentDirectory.value.parent;
selectedFiles.value = [];
}
};
// 监听视图模式变化
watch(viewMode, (newMode) => {
emit('view-mode-change', newMode);
});
// 初始化默认文件
onMounted(() => {
if (props.defaultFiles.length) {
root.value = buildFileTree(props.defaultFiles);
currentDirectory.value = root.value;
}
});
// 验证文件
const validateFile = (file) => {
// 验证文件类型
if (props.acceptedTypes.length) {
const fileType = file.name.split('.').pop()?.toLowerCase();
if (!props.acceptedTypes.includes(fileType)) {
emit('file-error', {
file,
error: 'file-type-not-allowed'
});
return false;
}
}
// 验证文件大小
if (props.maxFileSize && file.size > props.maxFileSize * 1024 * 1024) {
emit('file-error', {
file,
error: 'file-too-large'
});
return false;
}
return true;
};
// 修改文件选择处理函数
const handleFileSelect = (event) => {
const newFiles = Array.from(event.target.files)
.filter(validateFile);
if (newFiles.length) {
root.value = buildFileTree([...root.value.children.map(node => node.file), ...newFiles]);
currentDirectory.value = root.value;
emit('select', newFiles);
}
event.target.value = '';
};
// 修改文件夹选择处理函数
const handleFolderSelect = (event) => {
const newFiles = Array.from(event.target.files)
.filter(validateFile);
if (newFiles.length) {
root.value = buildFileTree([...root.value.children.map(node => node.file), ...newFiles]);
currentDirectory.value = root.value;
emit('select', newFiles);
}
event.target.value = '';
};
// 修改拖拽处理函数
const handleDrop = (event) => {
if (!props.enableDrop) return;
const newFiles = Array.from(event.dataTransfer.files)
.filter(validateFile);
if (newFiles.length) {
root.value = buildFileTree([...root.value.children.map(node => node.file), ...newFiles]);
currentDirectory.value = root.value;
emit('drop', newFiles);
}
};
// 修改文件点击处理函数
const handleItemClick = (item) => {
if (!props.multiple) {
selectedFiles.value = [item];
} else {
const index = selectedFiles.value.indexOf(item);
if (index === -1) {
selectedFiles.value.push(item);
} else {
selectedFiles.value.splice(index, 1);
}
}
emit('select', selectedFiles.value);
};
// 修改双击处理函数
const handleItemDoubleClick = (item) => {
if (item.isDirectory) {
currentDirectory.value = item;
selectedFiles.value = [];
emit('enter-directory', item);
}
};
// 判断是否为图片文件
const isImageFile = (file) => {
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
const extension = file.name.split('.').pop()?.toLowerCase();
return imageTypes.includes(extension);
};
// 获取图片预览URL
const getImagePreview = (file) => {
if (!file.file) return '';
return URL.createObjectURL(file.file);
};
// 处理图片加载错误
const handleImageError = (item) => {
console.warn(`Failed to load preview for ${item.name}`);
// 图片加载失败时使用默认图标
return getFileIcon(item);
};
// 在组件卸载时清理创建的URL
onUnmounted(() => {
currentDirectory.value.children.forEach(item => {
if (isImageFile(item)) {
URL.revokeObjectURL(getImagePreview(item));
}
});
});
</script>
<style scoped>
.file-list-display {
width: 100%;
height: 100%;
border: 1px solid #ddd;
border-radius: 4px;
}
.toolbar {
padding: 10px;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.view-mode .mode-item {
padding: 5px 10px;
cursor: pointer;
margin-right: 10px;
}
.view-mode .mode-item.active {
background: #e6f7ff;
color: #1890ff;
border-radius: 4px;
}
.operations button {
margin-left: 10px;
padding: 5px 15px;
background-color: #67c23a;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.operations button:hover {
background-color: #85ce61;
}
.file-container {
padding: 20px;
min-height: 200px;
}
.file-container.list .file-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.file-container.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 20px;
}
.file-container.grid .file-item {
text-align: center;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.file-item {
cursor: pointer;
transition: background-color 0.2s;
}
.file-item:hover {
background-color: #f5f5f5;
}
.file-item.selected {
background-color: #e6f7ff;
}
.file-icon {
width: 60px;
height: 60px;
padding: 10px 0;
display: flex;
justify-content: center;
align-items: center;
}
/* 列表模式下的图片预览 */
.file-container.list .file-icon {
width: 40px;
height: 40px;
padding: 2px;
margin-right: 10px;
}
.file-container.list .image-preview {
width: 36px;
height: 36px;
object-fit: cover;
border-radius: 4px;
}
.file-container.list .not-image-preview {
width: 36px;
height: 36px;
object-fit: cover;
border-radius: 4px;
}
/* 网格模式下的图片预览 */
.file-container.grid .file-icon {
width: 100px;
height: 100px;
padding: 5px;
}
.file-container.grid .image-preview {
width: 90px;
height: 90px;
object-fit: cover;
border-radius: 8px;
}
.file-container.grid .not-image-preview {
width: 90px;
height: 90px;
object-fit: cover;
border-radius: 8px;
}
.image-preview {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.image-preview:hover {
transform: scale(1.05);
}
.file-info {
flex: 1;
}
.file-name {
font-size: 14px;
margin-bottom: 4px;
word-break: break-all;
}
.file-size {
font-size: 12px;
color: #999;
}
.empty-tip {
text-align: center;
color: #999;
padding: 40px 0;
}
</style>
code/src/components/fileIcons.js
const fileIcons = {
// 文本文件
txt: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#4A90E2" d="M14 2H6C4.89 2 4 2.89 4 4V20C4 21.11 4.89 22 6 22H18C19.11 22 20 21.11 20 20V8L14 2ZM16 18H8V16H16V18ZM16 14H8V12H16V14ZM13 9V3.5L18.5 9H13Z"/>
</svg>`,
// 添加新的文件类型图标
exe: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#F44336" d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6M12,8A4,4 0 0,0 8,12A4,4 0 0,0 12,16A4,4 0 0,0 16,12A4,4 0 0,0 12,8Z"/>
</svg>`,
js: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#FFD600" d="M3,3H21V21H3V3M7.73,18.04C8.13,18.89 8.92,19.59 10.27,19.59C11.77,19.59 12.8,18.79 12.8,17.04V11.26H11.1V17C11.1,17.86 10.75,18.08 10.2,18.08C9.62,18.08 9.38,17.68 9.11,17.21L7.73,18.04M13.71,17.86C14.21,18.84 15.22,19.59 16.8,19.59C18.4,19.59 19.6,18.76 19.6,17.23C19.6,15.82 18.79,15.19 17.35,14.57L16.93,14.39C16.2,14.08 15.89,13.87 15.89,13.37C15.89,12.96 16.2,12.64 16.7,12.64C17.18,12.64 17.5,12.85 17.79,13.37L19.1,12.5C18.55,11.54 17.77,11.17 16.7,11.17C15.19,11.17 14.22,12.13 14.22,13.4C14.22,14.78 15.03,15.43 16.25,15.95L16.67,16.13C17.45,16.47 17.91,16.68 17.91,17.26C17.91,17.74 17.46,18.09 16.76,18.09C15.93,18.09 15.45,17.66 15.09,17.06L13.71,17.86Z"/>
</svg>`,
java: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#E65100" d="M9.37,17.51C3.4,18.15 3.4,15.82 5,15.82C5,15.82 4.79,15.91 4.79,15.91C4.79,15.91 3,16.12 6.16,17.1C6.16,17.1 9.42,17.58 11.5,16.85C11.5,16.85 11.9,16.65 12.25,16.36C10.45,16.54 9.37,17.51 9.37,17.51M16.21,18.69C16.21,18.69 17.5,19.08 17.5,19.08C17.5,19.08 13.89,19.03 9.29,18.25C9.29,18.25 8.11,17.91 7.25,17.71C7.25,17.71 9.29,18.86 16.21,18.69M11.56,14.3C11.56,14.3 12,14.94 12,14.94C12,14.94 8.4,14.75 5.36,15.61C5.36,15.61 4,16.06 4,16.06C4,16.06 7.09,14.21 11.56,14.3M12.89,11.93C12.89,11.93 13.24,12.32 13.24,12.32C13.24,12.32 9.79,12.23 7.2,13.04C7.2,13.04 5.92,13.45 5.92,13.45C5.92,13.45 8.86,11.85 12.89,11.93M13.5,9.33C13.5,9.33 13.96,9.74 13.96,9.74C13.96,9.74 10.33,9.7 7.85,10.43C7.85,10.43 6.5,10.86 6.5,10.86C6.5,10.86 9.68,9.29 13.5,9.33M14,6.45C14,6.45 14.32,6.85 14.32,6.85C14.32,6.85 10.57,7.04 8.18,7.73C8.18,7.73 6.84,8.15 6.84,8.15C6.84,8.15 10.24,6.62 14,6.45M5.95,10.2L6.31,10.03C6.31,10.03 4.92,10.84 4.92,10.84C4.92,10.84 7.05,9.37 14.14,9.55C14.14,9.55 14.39,9.92 14.39,9.92C14.39,9.92 8.07,9.85 5.95,10.2M5.95,12.66L6.31,12.5C6.31,12.5 4.92,13.3 4.92,13.3C4.92,13.3 7.05,11.83 14.14,12.01C14.14,12.01 14.39,12.38 14.39,12.38C14.39,12.38 8.07,12.31 5.95,12.66M5.95,15.29L6.31,15.12C6.31,15.12 4.92,15.93 4.92,15.93C4.92,15.93 7.05,14.46 14.14,14.64C14.14,14.64 14.39,15.01 14.39,15.01C14.39,15.01 8.07,14.95 5.95,15.29Z"/>
</svg>`,
c: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#0277BD" d="M16,2V4H14V2H10V4H8V2H4V22H8V20H10V22H14V20H16V22H20V2H16M18,20H16V18H14V20H10V18H8V20H6V4H8V6H10V4H14V6H16V4H18V20M16,10V8H8V10H16M16,16V14H8V16H16Z"/>
</svg>`,
py: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#FFC107" d="M19.14,7.5A2.86,2.86 0 0,1 22,10.36V14.14A2.86,2.86 0 0,1 19.14,17H12C12,17.39 12.32,17.96 12.71,17.96H17V19.64A2.86,2.86 0 0,1 14.14,22.5H9.86A2.86,2.86 0 0,1 7,19.64V15.89C7,14.31 8.28,13.04 9.86,13.04H15.11C16.69,13.04 17.96,11.76 17.96,10.18V7.5H19.14M14.86,19.29C14.46,19.29 14.14,19.59 14.14,20.18C14.14,20.77 14.46,20.89 14.86,20.89A0.71,0.71 0 0,0 15.57,20.18C15.57,19.59 15.25,19.29 14.86,19.29M4.86,17.5C3.28,17.5 2,16.22 2,14.64V10.86C2,9.28 3.28,8 4.86,8H12C12,7.61 11.68,7.04 11.29,7.04H7V5.36C7,3.78 8.28,2.5 9.86,2.5H14.14C15.72,2.5 17,3.78 17,5.36V9.11C17,10.69 15.72,11.96 14.14,11.96H8.89C7.31,11.96 6.04,13.24 6.04,14.82V17.5H4.86M9.14,5.71C9.54,5.71 9.86,5.41 9.86,4.82C9.86,4.23 9.54,4.11 9.14,4.11C8.75,4.11 8.43,4.23 8.43,4.82C8.43,5.41 8.75,5.71 9.14,5.71Z"/>
</svg>`,
cpp: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#00897B" d="M10.5,15.97L10.91,18.41C10.65,18.55 10.23,18.68 9.67,18.8C9.1,18.93 8.48,19 7.81,19C6.94,19 6.21,18.84 5.61,18.5C5,18.16 4.53,17.69 4.18,17.07C3.84,16.45 3.67,15.72 3.67,14.88L3.67,14.88V13.69C3.67,12.82 3.85,12.07 4.19,11.45C4.54,10.82 5,10.35 5.59,10.05C6.18,9.75 6.87,9.6 7.65,9.6C8.77,9.6 9.6,9.79 10.13,10.17C10.66,10.55 11,11.11 11.15,11.86L9.97,12.12C9.87,11.69 9.68,11.37 9.39,11.17C9.1,10.97 8.67,10.87 8.11,10.87C7.31,10.87 6.69,11.14 6.27,11.68C5.84,12.22 5.63,12.97 5.63,13.94L5.63,13.94V14.72C5.63,15.71 5.86,16.47 6.32,17.01C6.79,17.55 7.42,17.82 8.21,17.82C8.74,17.82 9.19,17.75 9.57,17.61L9.57,16.47H7.81V15.31H10.5V15.97M20,4C21.1,4 22,4.9 22,6V18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4H20M12.63,11.59H14.12V10.09H15.62V11.59H17.12V13.09H15.62V14.59H14.12V13.09H12.63V11.59M16.12,11.59H17.62V10.09H19.12V11.59H20.62V13.09H19.12V14.59H17.62V13.09H16.12V11.59Z"/>
</svg>`,
css: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#42A5F5" d="M5,3L4.35,6.34H17.94L17.5,8.5H3.92L3.26,11.83H16.85L16.09,15.64L10.61,17.45L5.86,15.64L6.19,14H2.85L2.06,18L9.91,21L18.96,18L20.16,11.97L20.4,10.76L21.94,3H5Z"/>
</svg>`,
html: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#E44D26" d="M12,17.56L16.07,16.43L16.62,10.33H9.38L9.2,8.3H16.8L17,6.31H7L7.56,12.32H14.45L14.22,14.9L12,15.5L9.78,14.9L9.64,13.24H7.64L7.93,16.43L12,17.56M4.07,3H19.93L18.5,19.2L12,21L5.5,19.2L4.07,3Z"/>
</svg>`,
json: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path fill="#FBC02D" d="M5,3H7V5H5V10A2,2 0 0,1 3,12A2,2 0 0,1 5,14V19H7V21H5C3.93,20.73 3,20.1 3,19V15A2,2 0 0,0 1,13H0V11H1A2,2 0 0,0 3,9V5A2,2 0 0,1 5,3M19,3A2,2 0 0,1 21,5V9A2,2 0 0,0 23,11H24V13H23A2,2 0 0,0 21,15V19A2,2 0 0,1 19,21H17V19H19V14A2,2 0 0,1 21,12A2,2 0 0,1 19,10V5H17V3H19M12,15A1,1 0 0,1 13,16A1,1 0 0,1 12,17A1,1 0 0,1 11,16A1,1 0 0,1 12,15M8,15A1,1 0 0,1 9,16A1,1 0 0,1 8,17A1,1 0 0,1 7,16A1,1 0 0,1 8,15M16,15A1,1 0 0,1 17,16A1,1 0 0,1 16,17A1,1 0 0,1 15,16A1,1 0 0,1 16,15Z"/>
</svg>`,
// Markdown文件
md: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#7C4DFF" d="M20.56 18H3.44C2.65 18 2 17.37 2 16.59V7.41C2 6.63 2.65 6 3.44 6H20.56C21.35 6 22 6.63 22 7.41V16.59C22 17.37 21.35 18 20.56 18ZM6 12H8V16H10V12H12L9 9L6 12ZM14 16H16V12H18L15 9L12 12H14V16Z"/>
</svg>`,
// PDF文件
pdf: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#FF5252" d="M14,2L20,8V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V4A2,2 0 0,1 6,2H14M18,20V9H13V4H6V20H18M10.92,12.31C10.68,11.54 10.15,9.08 11.55,9.04C12.95,9 12.03,12.16 12.03,12.16C12.42,13.65 14.05,14.72 14.05,14.72C14.55,14.57 17.4,14.24 17,15.72C16.57,17.2 13.5,15.81 13.5,15.81C11.55,15.95 10.09,16.47 10.09,16.47C8.96,18.58 7.64,19.5 7.1,18.61C6.43,17.5 9.23,16.07 9.23,16.07C10.68,13.72 10.92,12.31 10.92,12.31M11.57,13.15C11.17,14.45 10.37,15.84 10.37,15.84C11.22,15.5 13.08,15.11 13.08,15.11C11.94,14.11 11.57,13.15 11.57,13.15M13.81,15.1C14.05,15.23 15.03,15.63 15.35,15.4C15.67,15.17 14.3,14.96 13.81,15.1M11.33,9.93C10.67,9.89 10.65,10.86 10.76,11.45C10.87,12.04 11.16,11.76 11.16,11.76C11.16,11.76 11.64,9.97 11.33,9.93M9.94,16.61C9.94,16.61 8.7,17.45 8.7,17.71C8.7,17.97 9.45,17.15 9.94,16.61Z"/>
</svg>`,
// 图片文件
png: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#4CAF50" d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M13.96,12.29L11.21,15.83L9.25,13.47L6.5,17H17.5L13.96,12.29Z"/>
</svg>`,
jpeg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#FF9800" d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M13.96,12.29L11.21,15.83L9.25,13.47L6.5,17H17.5L13.96,12.29Z"/>
</svg>`,
// Excel文件
xlsx: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#2E7D32" d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M15.8,20H14L12,16.6L10,20H8.2L11.1,15.5L8.2,11H10L12,14.4L14,11H15.8L12.9,15.5L15.8,20M13,9V3.5L18.5,9H13Z"/>
</svg>`,
// Word文件
docx: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#1976D2" d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M15.2,20H13.8L12,13.2L10.2,20H8.8L6.6,11H8.1L9.5,17.8L11.3,11H12.6L14.4,17.8L15.8,11H17.3L15.2,20M13,9V3.5L18.5,9H13Z"/>
</svg>`,
// 文件夹
folder: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#FFA000" d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z"/>
</svg>`,
// 在 fileIcons 对象中添加
back: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#666666" d="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"/>
</svg>`,
// 默认文件图标
default: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path fill="#757575" d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
</svg>`
};
// 获取文件图标的函数
export function getFileIcon(file) {
// 如果是文件夹
if (file.type === 'directory') {
return `data:image/svg+xml;base64,${btoa(fileIcons.folder)}`;
}
if (file.type === 'back') {
return `data:image/svg+xml;base64,${btoa(fileIcons.back)}`;
}
// 获取文件扩展名
const extension = file.name.split('.').pop()?.toLowerCase();
// 根据文件扩展名返回对应的图标
const iconSvg = fileIcons[extension] || fileIcons.default;
return `data:image/svg+xml;base64,${btoa(iconSvg)}`;
}
code/src/components/fileUtils.js
// 文件节点类型
export class FileNode {
constructor(file, parent = null) {
this.name = file.name;
this.size = file.size;
this.type = file.type;
this.lastModified = file.lastModified;
this.isDirectory = file.type === 'directory';
this.parent = parent;
this.children = [];
this.path = file.webkitRelativePath || file.name;
this.file = file; // 保存原始文件对象
}
}
// 构建文件树
export function buildFileTree(files) {
const root = {
children: [],
isDirectory: true,
name: 'root'
};
for (const file of files) {
const paths = file.webkitRelativePath ? file.webkitRelativePath.split('/') : [file.name];
let currentNode = root;
// 如果是来自文件夹选择的文件
if (paths.length > 1) {
// 遍历路径创建目录结构
for (let i = 0; i < paths.length - 1; i++) {
const dirName = paths[i];
let dir = currentNode.children.find(child => child.name === dirName);
if (!dir) {
dir = new FileNode({
name: dirName,
type: 'directory',
size: 0,
lastModified: file.lastModified
}, currentNode);
currentNode.children.push(dir);
}
currentNode = dir;
}
}
// 添加文件节点
const fileNode = new FileNode(file, currentNode);
currentNode.children.push(fileNode);
}
return root;
}
// 计算文件夹大小
export function calculateDirectorySize(node) {
if (!node.isDirectory) {
return node.size;
}
return node.children.reduce((total, child) => {
return total + calculateDirectorySize(child);
}, 0);
}
效果展示
示例页面
图标模式
列表模式