ui框架-文件列表展示

news2025/6/10 17:22:27

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 配置

属性名类型默认值说明
defaultViewModeString‘list’默认视图模式,可选值:‘list’/‘grid’
defaultFilesArray[]默认显示的文件列表
multipleBooleantrue是否允许多选
acceptedTypesArray[]允许的文件类型,空数组表示允许所有类型
maxFileSizeNumber100最大文件大小(MB)
showFileSizeBooleantrue是否显示文件大小
enableDropBooleantrue是否允许拖拽上传
showToolbarBooleantrue是否显示工具栏
showBackButtonBooleantrue是否显示返回上级目录按钮

事件

事件名参数说明
selectselectedFiles选择文件时触发
enter-directorydirectory进入文件夹时触发
file-error{file, error}文件验证失败时触发
dropfiles拖拽文件时触发
view-mode-changemode视图模式改变时触发

支持的文件类型图标

  • 常见文档: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

开发说明

  1. 安装依赖
npm install
  1. 启动开发服务器
npm run serve
  1. 构建生产版本
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);
}

效果展示

示例页面
在这里插入图片描述

图标模式
在这里插入图片描述

列表模式
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2406868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…

算术操作符与类型转换:从基础到精通

目录 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符&#xff1a;、-、*、/、% 赋值操作符&#xff1a;和复合赋值 单⽬操作符&#xff1a;、--、、- 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 在先前的文…

jdbc查询mysql数据库时,出现id顺序错误的情况

我在repository中的查询语句如下所示&#xff0c;即传入一个List<intager>的数据&#xff0c;返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致&#xff0c;会导致返回的id是从小到大排列的&#xff0c;但我不希望这样。 Query("SELECT NEW com…

sshd代码修改banner

sshd服务连接之后会收到字符串&#xff1a; SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢&#xff1f; 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头&#xff0c…

前端开发者常用网站

Can I use网站&#xff1a;一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use&#xff1a;Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站&#xff1a;MDN JavaScript权威网站&#xff1a;JavaScript | MDN

如何在Windows本机安装Python并确保与Python.NET兼容

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁

赛门铁克威胁猎手团队最新报告披露&#xff0c;数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据&#xff0c;严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能&#xff0c;但SEMR…

Matlab实现任意伪彩色图像可视化显示

Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中&#xff0c;如何展示好看的实验结果图像非常重要&#xff01;&#xff01;&#xff01; 1、灰度原始图像 灰度图像每个像素点只有一个数值&#xff0c;代表该点的​​亮度&#xff08;或…

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…

《信号与系统》第 6 章 信号与系统的时域和频域特性

目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …

一些实用的chrome扩展0x01

简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序&#xff0c;无论是测试应用程序、搜寻漏洞还是收集情报&#xff0c;它们都能提升工作流程。 FoxyProxy 代理管理工具&#xff0c;此扩展简化了使用代理&#xff08;如 Burp…

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)

Name&#xff1a;3ddown Serial&#xff1a;FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名&#xff1a;Axure 序列号&#xff1a;8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP

02.运算符

目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&&#xff1a;逻辑与 ||&#xff1a;逻辑或 &#xff01;&#xff1a;逻辑非 短路求值 位运算符 按位与&&#xff1a; 按位或 | 按位取反~ …

uni-app学习笔记三十五--扩展组件的安装和使用

由于内置组件不能满足日常开发需要&#xff0c;uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件&#xff0c;需要安装才能使用。 一、安装扩展插件 安装方法&#xff1a; 1.访问uniapp官方文档组件部分&#xff1a;组件使用的入门教程 | uni-app官网 点击左侧…

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 原创笔记&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 上一篇&#xff1a;《数据结构第4章 数组和广义表》…