ui框架-文件列表展示
ui框架-文件列表展示
介绍
UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。
功能特性
- 支持列表模式和网格模式的切换展示
- 支持文件和文件夹的层级展示
- 支持文件选择和文件夹选择
- 支持拖拽上传文件
- 支持多选/单选模式
- 支持文件类型过滤
- 支持文件大小限制
- 支持图片文件预览
- 支持返回上级目录
- 显示文件大小和最后修改时间
安装
npm install bbyh-ui-file-list-diaplay
使用示例
<template><FileListDisplaydefaultViewMode="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"><inputtype="file"ref="fileInput"@change="handleFileSelect"multiplestyle="display: none"><inputtype="file"ref="folderInput"@change="handleFolderSelect"webkitdirectorystyle="display: none"><button @click="selectFiles">选择文件</button><button @click="selectFolder">选择文件夹</button></div></div><!-- 文件列表区域 --><divclass="file-container":class="viewMode"@dragover.prevent@drop.prevent="handleDrop"><template v-if="currentDirectory.children.length"><!-- 显示返回上级目录按钮 --><divv-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><!-- 显示文件和文件夹 --><divv-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"><!-- 如果是图片文件则显示预览 --><imgv-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);
}
效果展示
示例页面
图标模式
列表模式