Skip to content

FileList 文件列表

文件列表组件,支持多种视图模式和文件操作。复用 Button、Input、Select、Pagination、Tree、Empty 等组件,提供统一的交互体验。

基础用法

基础的文件列表展示。

名称
documents
2024/01/13 08:00
audio/mpeg
music.mp3
3.00 MB2024/01/09 08:00
application/pdf
project-design.pdf
1.95 MB2024/01/15 08:00
screenshot.png
screenshot.png
1000.00 KB2024/01/14 08:00
video/mp4
video-tutorial.mp4
10.00 MB2024/01/10 08:00
vue
<template>
  <ExFileList
    :files="files"
    @click="handleClick"
    @preview="handlePreview"
    @download="handleDownload"
    @delete="handleDelete"
  />
</template>

<script setup>
import { ref } from 'vue';

const files = ref([
  {
    id: '1',
    name: 'document.pdf',
    size: 2048576,
    type: 'application/pdf',
    modifiedTime: new Date(),
    isDirectory: false,
  },
  // ... 更多文件
]);

const handleClick = (file) => {
  console.log('点击文件:', file.name);
};
</script>

视图模式

支持列表、网格、表格、树形四种视图模式。使用 ExButton 组件实现视图切换。

名称
documents
audio/mpeg
music.mp3
3.00 MB
application/pdf
project-design.pdf
1.95 MB
screenshot.png
screenshot.png
1000.00 KB
video/mp4
video-tutorial.mp4
10.00 MB
vue
<template>
  <!-- 列表视图 -->
  <ExFileList :files="files" view-mode="list" />

  <!-- 网格视图 -->
  <ExFileList :files="files" view-mode="grid" />

  <!-- 表格视图 -->
  <ExFileList :files="files" view-mode="table" />

  <!-- 树形视图 -->
  <ExFileList :files="files" view-mode="tree" />
</template>

搜索和排序

使用 ExInput 和 ExSelect 组件实现搜索和排序功能。

名称
documents
2024/01/13 08:00
audio/mpeg
music.mp3
3.00 MB2024/01/09 08:00
application/pdf
project-design.pdf
1.95 MB2024/01/15 08:00
screenshot.png
screenshot.png
1000.00 KB2024/01/14 08:00
video/mp4
video-tutorial.mp4
10.00 MB2024/01/10 08:00
vue
<template>
  <ExFileList
    :files="files"
    :show-search="true"
    :show-sort="true"
    sort-field="name"
    sort-order="asc"
    @search="handleSearch"
  />
</template>

<script setup>
const handleSearch = (keyword) => {
  console.log('搜索:', keyword);
};
</script>

组件复用

  • 搜索框: 使用 ExInput 组件,支持清空按钮和前缀图标
  • 排序选择: 使用 ExSelect 组件,提供下拉选项
  • 排序按钮: 使用 ExButton 组件,显示升序/降序图标

文件选择

支持单选和多选模式。

名称
documents
2024/01/13 08:00
audio/mpeg
music.mp3
3.00 MB2024/01/09 08:00
application/pdf
project-design.pdf
1.95 MB2024/01/15 08:00
screenshot.png
screenshot.png
1000.00 KB2024/01/14 08:00
video/mp4
video-tutorial.mp4
10.00 MB2024/01/10 08:00
已选择: 0 个文件
vue
<template>
  <ExFileList
    :files="files"
    v-model:selected-ids="selectedIds"
    :selectable="true"
    :multiple="true"
  />
  <div>已选择: {{ selectedIds.length }} 个文件</div>
</template>

<script setup>
import { ref } from 'vue';

const selectedIds = ref([]);
</script>

分页

使用 ExPagination 组件实现分页功能。

名称
documents
2024/01/13 08:00
audio/mpeg
music.mp3
3.00 MB2024/01/09 08:00
application/pdf
project-design.pdf
1.95 MB2024/01/15 08:00
vue
<template>
  <ExFileList :files="files" :show-pagination="true" :page-size="20" />
</template>

分页组件

使用 ExPagination 组件替代原生分页按钮,提供:

  • 总数显示
  • 页码跳转
  • 每页条数切换
  • 统一的赛博朋克主题样式

空状态

使用 ExEmpty 组件显示空状态。

名称
空
暂无文件,请上传文件
vue
<template>
  <ExFileList :files="[]" empty-text="暂无文件,请上传文件" />
</template>

树形视图

使用 ExTree 组件实现树形文件浏览。

名称
vue
<template>
  <ExFileList :files="files" view-mode="tree" />
</template>

树形组件

使用 ExTree 组件实现树形视图,支持:

  • 文件夹展开/收起
  • 节点选择
  • 复选框多选
  • 自定义节点内容

自定义操作

自定义文件操作按钮。

名称
documents
2024/01/13 08:00
audio/mpeg
music.mp3
3.00 MB2024/01/09 08:00
application/pdf
project-design.pdf
1.95 MB2024/01/15 08:00
screenshot.png
screenshot.png
1000.00 KB2024/01/14 08:00
video/mp4
video-tutorial.mp4
10.00 MB2024/01/10 08:00
vue
<template>
  <ExFileList
    :files="files"
    :show-actions="true"
    :previewable="true"
    :downloadable="true"
    :deletable="true"
    @preview="handlePreview"
    @download="handleDownload"
    @delete="handleDelete"
  />
</template>

<script setup>
const handlePreview = (file) => {
  // 预览文件
};

const handleDownload = (file) => {
  // 下载文件
};

const handleDelete = (file) => {
  // 删除文件
};
</script>

加载状态

使用 ExLoading 组件显示加载状态。

vue
<template>
  <ExFileList :files="files" :loading="loading" />
</template>

<script setup>
import { ref } from 'vue';

const files = ref([]);
const loading = ref(true);

// 模拟加载
setTimeout(() => {
  loading.value = false;
  files.value = [
    // ... 文件数据
  ];
}, 2000);
</script>

加载组件

使用 ExLoading 组件显示加载状态,配置为:

  • 非全屏模式
  • 无遮罩层
  • 大尺寸旋转动画

API

FileList Props

属性说明类型默认值
files文件列表数据FileItem[][]
view-mode视图模式'list' | 'grid' | 'table' | 'tree''list'
selectable是否可选择booleanfalse
multiple是否多选booleanfalse
selected-ids / v-model:selected-ids选中的文件IDstring[][]
show-toolbar是否显示工具栏booleantrue
show-search是否显示搜索booleantrue
show-sort是否显示排序booleantrue
show-view-switch是否显示视图切换booleantrue
sort-field排序字段'name' | 'size' | 'type' | 'modifiedTime''name'
sort-order排序顺序'asc' | 'desc''asc'
show-size是否显示文件大小booleantrue
show-type是否显示文件类型booleantrue
show-modified-time是否显示修改时间booleantrue
show-actions是否显示操作按钮booleantrue
previewable是否可预览booleantrue
downloadable是否可下载booleantrue
deletable是否可删除booleantrue
loading是否加载中booleanfalse
empty-text空状态文本string'暂无文件'
page-size每页显示数量number20
show-pagination是否显示分页booleanfalse

FileItem 类型

typescript
interface FileItem {
  id: string;
  name: string;
  size: number;
  type: string;
  modifiedTime?: Date;
  thumbUrl?: string;
  isDirectory: boolean;
  children?: FileItem[];
}

FileList Events

事件名说明回调参数
update:selected-ids选中状态变化(ids: string[])
update:view-mode视图模式变化(mode: ViewMode)
click点击文件(file: FileItem)
dblclick双击文件(file: FileItem)
select选择文件(file: FileItem, selected: boolean)
preview预览文件(file: FileItem)
download下载文件(file: FileItem)
delete删除文件(file: FileItem)
search搜索(keyword: string)
sort排序(field: SortField, order: SortOrder)
page-change页码变化(page: number)

FileList Methods

方法名说明参数
selectAll全选()
unselectAll取消全选()
selectFile选择文件(id: string)
unselectFile取消选择文件(id: string)
getSelectedFiles获取选中的文件() => FileItem[]
refresh刷新列表()
getElement获取DOM元素() => HTMLDivElement | null

组件复用说明

FileList 组件充分复用了现有组件,提供统一的交互体验:

使用的组件

组件用途优势
ExButton视图切换、全选按钮、排序按钮统一的按钮样式和交互
ExInput搜索输入框支持清空、前缀图标
ExSelect排序字段选择下拉选项、统一样式
ExPagination分页控制完整的分页功能
ExTree树形视图文件夹展开、节点操作
ExEmpty空状态显示统一的空状态样式
ExLoading加载状态统一的加载动画

设计优势

  1. 统一的视觉风格 - 所有交互元素使用相同的赛博朋克主题
  2. 一致的交互体验 - 按钮、输入框等行为保持一致
  3. 易于维护 - 组件更新自动应用到文件列表
  4. 代码复用 - 减少重复代码,提高开发效率

无障碍支持

  • 完整的键盘导航支持
  • ARIA 属性支持
  • 支持屏幕阅读器

完整示例:资源管理器

一个类似 Windows 资源管理器的完整示例,包含面包屑导航、文件夹浏览、文件操作等功能。

文件夹文件夹
展开图标我的文档
名称
工作文件
2024/01/20 08:00
视频
2024/01/10 08:00
图片
2024/01/15 08:00
text/markdown
README.md
4.00 KB2024/01/25 08:00
4 个项目
vue
<template>
  <div class="explorer-demo">
    <!-- 工具栏 -->
    <div class="explorer-toolbar">
      <div class="explorer-toolbar-left">
        <ExButton size="small" :disabled="!canGoBack" @click="handleGoBack">
          <template #prefix>
            <img src="..." alt="返回" width="16" height="16" />
          </template>
          返回
        </ExButton>
        <ExButton size="small" @click="handleRefresh">
          <template #prefix>
            <img src="..." alt="刷新" width="16" height="16" />
          </template>
          刷新
        </ExButton>
      </div>

      <!-- 面包屑导航 -->
      <div class="explorer-breadcrumb">
        <span v-for="(crumb, index) in breadcrumbs" :key="crumb.id">
          <button @click="handleBreadcrumbClick(index)">
            {{ crumb.name }}
          </button>
          <span v-if="index < breadcrumbs.length - 1">/</span>
        </span>
      </div>
    </div>

    <!-- 主内容区域 -->
    <div class="explorer-main">
      <!-- 左侧目录树 -->
      <div class="explorer-sidebar">
        <div class="explorer-sidebar-header">
          <img src="..." alt="文件夹" width="20" height="20" />
          <span>文件夹</span>
        </div>
        <ExTree
          :data="treeData"
          :selected-keys="treeSelectedKeys"
          :expanded-keys="treeExpandedKeys"
          :show-icon="true"
          @node-click="handleTreeNodeClick"
          @update:expanded-keys="(keys) => (treeExpandedKeys = keys)"
        />
      </div>

      <!-- 右侧文件列表 -->
      <div class="explorer-content">
        <ExFileList
          :files="currentFiles"
          v-model:selected-ids="selectedFileIds"
          v-model:view-mode="viewMode"
          :selectable="true"
          :multiple="true"
          :show-toolbar="true"
          :show-pagination="true"
          :page-size="10"
          @click="handleFileClick"
          @dblclick="handleFileDblClick"
          @preview="handlePreview"
          @download="handleDownload"
          @delete="handleDelete"
        />
      </div>
    </div>

    <!-- 状态栏 -->
    <div class="explorer-statusbar">
      <span>{{ currentFiles.length }} 个项目</span>
      <span v-if="selectedFileIds.length > 0"> 已选择 {{ selectedFileIds.length }} 个项目 </span>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

// 文件系统数据结构
const fileSystem = ref({
  id: 'root',
  name: '我的文档',
  isDirectory: true,
  children: [
    {
      id: 'folder-1',
      name: '工作文件',
      isDirectory: true,
      children: [
        {
          id: 'file-1-1',
          name: '项目方案.docx',
          size: 524288,
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
          modifiedTime: new Date(),
          isDirectory: false,
        },
      ],
    },
    // ... 更多文件和文件夹
  ],
});

// 当前路径(ID 数组)
const currentPath = ref(['root']);
const selectedFileIds = ref([]);
const viewMode = ref('list');

// 计算当前显示的文件列表
const currentFiles = computed(() => {
  let current = fileSystem.value;

  for (let i = 1; i < currentPath.value.length; i++) {
    const folderId = currentPath.value[i];
    const folder = current.children?.find((f) => f.id === folderId);
    if (folder && folder.isDirectory) {
      current = folder;
    }
  }

  return current.children || [];
});

// 面包屑导航
const breadcrumbs = computed(() => {
  const crumbs = [{ id: 'root', name: '我的文档' }];
  let current = fileSystem.value;

  for (let i = 1; i < currentPath.value.length; i++) {
    const folderId = currentPath.value[i];
    const folder = current.children?.find((f) => f.id === folderId);
    if (folder) {
      crumbs.push({ id: folder.id, name: folder.name });
      current = folder;
    }
  }

  return crumbs;
});

const canGoBack = computed(() => currentPath.value.length > 1);

// 文件操作
const handleFileClick = (file) => {
  if (file.isDirectory) {
    currentPath.value.push(file.id);
    selectedFileIds.value = [];
  }
};

const handleBreadcrumbClick = (index) => {
  currentPath.value = currentPath.value.slice(0, index + 1);
  selectedFileIds.value = [];
};

const handleGoBack = () => {
  if (canGoBack.value) {
    currentPath.value.pop();
    selectedFileIds.value = [];
  }
};

// 树形数据转换
const treeData = computed(() => {
  const convertToTreeNode = (item, parentPath = []) => {
    const path = [...parentPath, item.id];
    return {
      key: item.id,
      label: item.name,
      icon: item.isDirectory
        ? 'https://api.iconify.design/ri/folder-line.svg'
        : 'https://api.iconify.design/ri/file-text-line.svg',
      children: item.children
        ?.filter((child) => child.isDirectory)
        .map((child) => convertToTreeNode(child, path)),
      isLeaf: !item.isDirectory || !item.children?.some((c) => c.isDirectory),
      data: { path },
    };
  };

  return [convertToTreeNode(fileSystem.value)];
});

const treeSelectedKeys = computed(() => {
  return [currentPath.value[currentPath.value.length - 1]];
});

const treeExpandedKeys = ref(['root']);

const handleTreeNodeClick = (node) => {
  if (node.data?.path) {
    currentPath.value = node.data.path;
    selectedFileIds.value = [];
  }
};
</script>

<style scoped>
.explorer-demo {
  display: flex;
  flex-direction: column;
  height: 600px;
  background: var(--ex-color-bg-secondary);
  border: 1px solid var(--ex-color-border-primary);
  border-radius: var(--ex-radius-lg);
  overflow: hidden;
}

.explorer-toolbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 16px;
  background: var(--ex-color-bg-elevated);
  border-bottom: 1px solid var(--ex-color-border-primary);
}

.explorer-toolbar-left {
  display: flex;
  gap: 8px;
}

.explorer-breadcrumb {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  overflow-x: auto;
}

.explorer-breadcrumb-item {
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}

.explorer-breadcrumb-btn {
  padding: 4px 8px;
  background: transparent;
  border: none;
  color: var(--ex-color-text-secondary);
  cursor: pointer;
  border-radius: var(--ex-radius-sm);
  transition: all 0.2s;
}

.explorer-breadcrumb-btn:hover {
  background: rgba(0, 240, 255, 0.1);
  color: var(--ex-color-neon-blue-500);
}

.explorer-breadcrumb-separator {
  color: var(--ex-color-text-tertiary);
}

.explorer-main {
  display: flex;
  flex: 1;
  overflow: hidden;
}

.explorer-sidebar {
  width: 250px;
  background: var(--ex-color-bg-elevated);
  border-right: 1px solid var(--ex-color-border-primary);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.explorer-sidebar-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  background: var(--ex-color-bg-secondary);
  border-bottom: 1px solid var(--ex-color-border-primary);
  font-weight: var(--ex-font-semibold);
  color: var(--ex-color-text-primary);
}

.explorer-content {
  flex: 1;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.explorer-statusbar {
  display: flex;
  justify-content: space-between;
  padding: 8px 16px;
  background: var(--ex-color-bg-elevated);
  border-top: 1px solid var(--ex-color-border-primary);
  font-size: var(--ex-text-sm);
  color: var(--ex-color-text-secondary);
}
</style>

功能特性

这个资源管理器示例包含以下功能:

  1. 左侧目录树 - 使用 ExTree 组件显示文件夹结构,支持展开/收起
  2. 面包屑导航 - 显示当前路径,点击可快速跳转
  3. 返回按钮 - 返回上一级目录
  4. 刷新按钮 - 刷新当前目录
  5. 文件夹浏览 - 单击文件夹进入,双击文件预览
  6. 多选操作 - 支持选择多个文件进行批量操作
  7. 视图切换 - 列表、网格、表格、树形四种视图
  8. 搜索排序 - 快速查找和排序文件
  9. 状态栏 - 显示文件数量和选中状态
  10. 文件操作 - 预览、下载、删除等操作
  11. 双向同步 - 左侧树和右侧列表状态同步

实现要点

  • 左侧目录树: 使用 ExTree 组件,只显示文件夹,支持展开/收起
  • 树形数据转换: 递归转换文件系统为树形节点,保存路径信息
  • 双向同步: 点击树节点更新右侧列表,右侧导航同步树的选中状态
  • 路径追踪: 使用路径数组 currentPath 追踪导航历史
  • 面包屑导航: 支持快速跳转到任意层级
  • 文件操作: 文件夹单击进入,文件双击预览
  • 响应式布局: 左右分栏,自适应高度

主题定制

可以通过 CSS 变量自定义文件列表样式:

css
:root {
  --ex-file-list-bg: var(--ex-color-bg-secondary);
  --ex-file-list-border-color: var(--ex-color-border-primary);
  --ex-file-list-hover-bg: rgba(0, 240, 255, 0.1);
}