赞
踩
因为Main.vue等都会用到文件列表table,所以直接封装成组件。
src/components/Table.vue
- <template>
- <!-- 表格 -->
- <div>
- <el-table
- ref="dataTable"
- :data="dataSource.list || []"
- :height="tableHeight"
- :stripe="options.stripe"
- :border="options.border"
- header-row-class-name="table-header-row"
- highlight-current-row
- @row-click="handleRowClick"
- @selection-change="handleSelectionChange"
- >
- <!-- selection 选择框 -->
- <el-table-column
- v-if="options.selectType && options.selectType == 'checkbox'"
- type="selection"
- width="50"
- align="center"
- ></el-table-column>
- <!-- 序号 -->
- <el-table-column
- v-if="options.showIndex"
- label="序号"
- type="index"
- width="60"
- align="center"
- ></el-table-column>
- <!-- 数据列 -->
- <template v-for="(column, index) in columns">
- <!-- 如果数据列中有插槽, 将其改造成插槽 -->
- <template v-if="column.scopedSlots">
- <el-table-column
- :key="index"
- :prop="column.prop"
- :label="column.label"
- :align="column.align || 'left'"
- :width="column.width"
- >
- <template #default="scope">
- <slot
- :name="column.scopedSlots"
- :index="scope.$index"
- :row="scope.row"
- >
- </slot>
- </template>
- </el-table-column>
- </template>
- <!-- 如果不是插槽,就正常操作 -->
- <template v-else>
- <el-table-column
- :key="index"
- :prop="column.prop"
- :label="column.label"
- :align="column.align || 'left'"
- :width="column.width"
- :fixed="column.fixed"
- >
- </el-table-column>
- </template>
- </template>
- </el-table>
-
- <!-- 分页 -->
- <!-- page-sizes 每页显示个数选择器的选项设置 -->
- <!-- page-size 每页显示条目个数 -->
- <!-- current-page 当前页数 -->
- <!-- layout 组件布局,子组件名用逗号分隔 -->
- <!-- size-change page-size 改变时触发 -->
- <!-- current-change current-page 改变时触发 -->
- <div class="pagination" v-if="showPagination">
- <el-pagination
- v-if="dataSource.totalCount"
- background
- :total="dataSource.totalCount"
- :page-sizes="[15, 30, 50, 100]"
- :page-size="dataSource.pageSize"
- :current-page.sync="dataSource.pageNo"
- :layout="layout"
- @size-change="handlePageSizeChange"
- @current-change="handlePageNoChange"
- style="text-align: right"
- ></el-pagination>
- </div>
- </div>
- </template>
-
- <script setup>
- import { ref, computed } from "vue";
-
- // 将选中的行传递给父组件Main
- const emit = defineEmits(["rowSelected", "rowClick"]);
- // 子组件接受父组件的值
- const props = defineProps({
- dataSource: Object,
- showPagination: {
- type: Boolean,
- default: true,
- },
- showPageSize: {
- type: Boolean,
- default: true,
- },
- options: {
- type: Object,
- default: {
- extHeight: 0,
- showIndex: false,
- },
- },
- columns: Array,
- fetch: Function, // 获取数据的函数
- initFetch: {
- type: Boolean,
- default: true,
- },
- });
-
- // 分页处布局
- const layout = computed(() => {
- return `total, ${
- props.showPageSize ? "sizes" : ""
- }, prev, pager, next, jumper`;
- });
-
- // 计算顶部高度
- //顶部 60 , 内容区域距离顶部 20, 内容上下内间距 15*2 分页区域高度 46
- const topHeight = 60 + 20 + 30 + 46;
-
- // 计算当前表格高度,实现页面内部滚动
- const tableHeight = ref(
- props.options.tableHeight
- ? props.options.tableHeight
- : window.innerHeight - topHeight - props.options.extHeight
- );
-
- const init = () => {
- if (props.initFetch && props.fetch) {
- // 获取数据
- props.fetch();
- }
- };
- init();
-
- const dataTable = ref();
- // 清除选中
- const clearSelection = () => {
- dataTable.value.clearSelection();
- };
- // 设置行选中
- const setCurrentRow = (rowKey, rowValue) => {
- let row = props.dataSource.list.find((item) => {
- return item[rowKey] === rowValue;
- });
- dataTable.value.setCurrentRow(row);
- };
- // 将父组件最新的行信息更新到子组件中
- // 将子组件暴露出去,否则无法调用
- defineExpose({ setCurrentRow, clearSelection });
-
- // 行点击
- const handleRowClick = (row) => {
- emit("rowClick", row);
- };
- // 行选中(多行)
- const handleSelectionChange = (row) => {
- emit("rowSelected", row);
- };
-
- // 切换每页大小
- const handlePageSizeChange = (size) => {
- props.dataSource.pageSize = size;
- props.dataSource.pageNo = 1;
- // 获取数据
- props.fetch();
- };
-
- // 切换页码
- const handlePageNoChange = (pageNo) => {
- props.dataSource.pageNo = pageNo;
- // 获取数据
- props.fetch();
- };
- </script>
-
- <style lang="scss" scoped>
- .pagination {
- padding-top: 10px;
- padding-right: 10px;
- }
- .el-pagination {
- justify-content: right;
- }
-
- :deep .el-table__cell {
- padding: 4px 0px;
- }
- </style>
因为需要展示上传文件,文件夹,图片,视频等的缩略图,所以,在Icon组件里面直接定义好各种类型显示的缩略图。
src/components/Icon.vue
- <template>
- <!-- 图标 -->
- <span :style="{ width: width + 'px', height: width + 'px' }" class="icon">
- <img :src="getImage()" :style="{ 'object-fit': fit }" />
- </span>
- </template>
-
- <script setup>
- import { ref, reactive, getCurrentInstance } from "vue";
- const { proxy } = getCurrentInstance();
- const props = defineProps({
- fileType: {
- type: Number,
- },
- iconName: {
- type: String,
- },
- cover: {
- type: String,
- },
- width: {
- type: Number,
- default: 32,
- },
- fit: {
- type: String,
- default: "cover",
- },
- });
-
- const fileTypeMap = {
- 0: { desc: "目录", icon: "folder" },
- 1: { desc: "视频", icon: "video" },
- 2: { desc: "音频", icon: "music" },
- 3: { desc: "图片", icon: "image" },
- 4: { desc: "exe", icon: "pdf" },
- 5: { desc: "doc", icon: "word" },
- 6: { desc: "excel", icon: "excel" },
- 7: { desc: "纯文本", icon: "txt" },
- 8: { desc: "程序", icon: "code" },
- 9: { desc: "压缩包", icon: "zip" },
- 10: { desc: "其他文件", icon: "others" },
- };
-
- const getImage = () => {
- // 当上传的不是本地文件,而是服务器上转码之后的图片或者视频
- if (props.cover) {
- return proxy.globalInfo.imageUrl + props.cover;
- }
- let icon = "unknow_icon";
- // 根据文件名判断图标
- if (props.iconName) {
- icon = props.iconName;
- } else {
- // 根据文件类型判断图标
- const iconMap = fileTypeMap[props.fileType];
- if (iconMap != undefined) {
- icon = iconMap["icon"];
- }
- }
- return new URL(`/src/assets/icon-image/${icon}.png`, import.meta.url).href;
- };
- </script>
-
- <style lang="scss" scoped>
- .icon {
- text-align: center;
- display: inline-block;
- border-radius: 3px;
- overflow: hidden;
- img {
- width: 100%;
- height: 100%;
- }
- }
- </style>
-
- import Table from '@/components/Table.vue'
- import Icon from '@/components/Icon.vue'
-
-
- app.component("Table",Table)
- app.component("Icon",Icon)
包括头部top,文件列表样式file-list,没有数据样式no-data
src/assets/file.list.scss
- .top {
- margin-top: 20px;
- .top-op {
- display: flex;
- align-items: center;
- .btn {
- margin-right: 10px;
- }
- .search-panel {
- margin-left: 10px;
- width: 300px;
- }
- .icon-refresh {
- cursor: pointer;
- margin-left: 10px;
- }
- .not-allow {
- background: #d2d2d2 !important;
- cursor: not-allowed;
- }
- }
- }
-
- .file-list {
- .file-item {
- display: flex;
- align-items: center;
- padding: 6px 0px;
- .file-name {
- margin-left: 8px;
- flex: 1;
- width: 0;
- overflow: hidden;
- // 当对象内文本溢出时显示省略标记(...)
- text-overflow: ellipsis;
- // 不换行 强行文本在同一行显示
- white-space: nowrap;
- span {
- cursor: pointer;
- &:hover {
- color: #06a7ff;
- }
- }
- .transfer-status {
- font-size: 13px;
- margin-left: 10px;
- color: #e6a23c;
- }
- .transfer-fail {
- color: #f75000;
- }
- }
- .edit-panel {
- flex: 1;
- width: 0;
- display: flex;
- align-items: center;
- margin: 0px 5px;
- .iconfont {
- margin-left: 10px;
- background: #0c95f7;
- color: #fff;
- padding: 3px 5px;
- border-radius: 5px;
- cursor: pointer;
- }
- .not-allow {
- cursor: not-allowed;
- background: #7cb1d7;
- color: #ddd;
- text-decoration: none;
- }
- }
- .op {
- width: 280px;
- margin-left: 15px;
- .iconfont {
- font-size: 13px;
- margin-left: 5px;
- color: #06a7ff;
- cursor: pointer;
- }
- .iconfont::before {
- margin-right: 1px;
- }
- }
- }
- }
-
- // justify-content 设置主轴上的子元素排列方式
- // align-content 设置侧轴上的子元素的排列方式(多行)
- .no-data {
- // vh就是当前屏幕可见高度的1%
- // height:100vh == height:100%;
- // calc(100vh - 150px)表示整个浏览器窗口高度减去150px的大小
- height: calc(100vh - 150px);
- display: flex;
- // align-items 设置侧轴上的子元素的排列方式(单行)
- align-items: center;
- // 设置主轴上的子元素排列方式
- justify-content: center;
- .no-data-inner {
- text-align: center;
- .tips {
- margin-top: 10px;
- }
- .op-list {
- margin-top: 20px;
- display: flex;
- justify-content: center;
- align-items: center;
- .op-item {
- cursor: pointer;
- width: 100px;
- height: 100px;
- margin: 0px 10px;
- padding: 5px 0px;
- background: rgb(241, 241, 241);
- }
- }
- }
- }
src/views/main/Main.vue引入
@import "@/assets/file.list.scss"
完整版Main.vue
src/views/main/Main.vue
- <template>
- <div>
- <div class="top">
- <!-- 头部按钮处 -->
- <div class="top-op">
- <div class="btn">
- <!-- show-file-list 是否显示已上传文件列表 -->
- <!-- with-credentials 支持发送 cookie 凭证信息 -->
- <!-- multiple 是否支持多选文件 -->
- <!-- http-request 覆盖默认的 Xhr 行为,允许自行实现上传文件的请求 -->
- <!-- accept 接受上传的文件类型 -->
- <el-upload
- :show-file-list="false"
- :with-credentials="true"
- :multiple="true"
- :http-request="addFile"
- :accept="fileAccept"
- >
- <el-button type="primary">
- <span class="iconfont icon-upload"></span>
-  上传
- </el-button>
- </el-upload>
- </div>
- <el-button type="success" @click="newFolder" v-if="category == 'all'">
- <span class="iconfont icon-folder-add"></span>
-  新建文件夹
- </el-button>
- <el-button
- @click="delFileBatch"
- type="danger"
- :disabled="selectFileIdList.length == 0"
- >
- <span class="iconfont icon-del"></span>
-  批量删除
- </el-button>
- <el-button
- @click="moveFolderBatch"
- type="warning"
- :disabled="selectFileIdList.length == 0"
- >
- <span class="iconfont icon-move"></span>
-  批量移动
- </el-button>
- <div class="search-panel">
- <el-input
- clearable
- placeholder="请输入文件名搜索"
- v-model="fileNameFuzzy"
- @keyup.enter="search"
- >
- <template #suffix>
- <i class="iconfont icon-search" @click="search"></i>
- </template>
- </el-input>
- </div>
- <div class="iconfont icon-refresh" @click="loadDataList"></div>
- </div>
- <!-- 导航 -->
- <Navigation ref="navigationRef" @navChange="navChange"></Navigation>
- </div>
-
- <!-- 文件列表 -->
- <div class="file-list" v-if="tableData.list && tableData.list.length > 0">
- <Table
- ref="dataTableRef"
- :columns="columns"
- :showPagination="true"
- :dataSource="tableData"
- :fetch="loadDataList"
- :initFetch="false"
- :options="tableOptions"
- @rowSelected="rowSelected"
- >
- <!-- 文件名 -->
- <template #fileName="{ index, row }">
- <!-- showOp(row) 当鼠标放在当前行时,分享下载等图标出现 -->
- <!-- cancelShowOp(row) 当鼠标离开当前行时,分享下载等图标消失 -->
- <div
- class="file-item"
- @mouseenter="showOp(row)"
- @mouseleave="cancelShowOp(row)"
- >
- <!-- 显示文件图标 -->
- <template
- v-if="(row.fileType == 3 || row.fileType == 1) && row.status == 2"
- >
- <!-- 如果文件类型是图片或者视频,且已经成功转码,则执行 Icon中的cover -->
- <Icon :cover="row.fileCover" :width="32"></Icon>
- </template>
- <template v-else>
- <!-- 如果文件夹类型是文件,则文件类型是该文件类型 -->
- <Icon v-if="row.folderType == 0" :fileType="row.fileType"></Icon>
- <!-- 如果文件夹类型是目录,则文件类型就是目录0 -->
- <Icon v-if="row.folderType == 1" :fileType="0"></Icon>
- </template>
-
- <!-- 显示文件名称 -->
- <!-- v-if="!row.showEdit" 如果该行文件没有编辑 -->
- <span class="file-name" v-if="!row.showEdit" :title="row.fileName">
- <span @click="preview(row)">{{ row.fileName }}</span>
- <span v-if="row.status == 0" class="transfer-status">转码中</span>
- <span v-if="row.status == 1" class="transfer-status transfer-fail"
- >转码失败</span
- >
- </span>
-
- <!-- 点击新建文件夹时显示行 -->
- <div class="edit-panel" v-if="row.showEdit">
- <el-input
- v-model.trim="row.fileNameReal"
- ref="editNameRef"
- :maxLength="190"
- @keyup.enter="saveNameEdit(index)"
- >
- <template #suffix>{{ row.fileSuffix }}</template>
- </el-input>
-
- <!-- 对号 确定 -->
- <span
- :class="[
- 'iconfont icon-right1',
- row.fileNameReal ? '' : 'not-allow',
- ]"
- @click="saveNameEdit(index)"
- ></span>
-
- <!-- 叉号 取消 -->
- <span
- class="iconfont icon-error"
- @click="cancelNameEdit(index)"
- ></span>
- </div>
-
- <!-- 当鼠标放在当前行时显示 -->
- <span class="op">
- <template v-if="row.showOp && row.fileId && row.status == 2">
- <span class="iconfont icon-share1" @click="share(row)">
- 分享
- </span>
- <!-- 只有当是文件夹时才可下载 -->
- <span
- class="iconfont icon-download"
- v-if="row.folderType == 0"
- @click="download(row)"
- >
- 下载
- </span>
- <span class="iconfont icon-del" @click="delFile(row)">
- 删除
- </span>
- <span class="iconfont icon-edit" @click="editFileName(index)">
- 重命名
- </span>
- <span class="iconfont icon-move" @click="moveFolder(row)">
- 移动
- </span>
- </template>
- </span>
- </div>
- </template>
-
- <!-- 文件大小 -->
- <template #fileSize="{ index, row }">
- <span v-if="row.fileSize">
- {{ proxy.Utils.size2Str(row.fileSize) }}</span
- >
- </template>
- </Table>
- </div>
-
- <div class="no-data" v-else>
- <div class="no-data-inner">
- <Icon iconName="no_data" :width="120" fit="fill"></Icon>
- <div class="tips">当前目录为空,上传你的第一个文件吧</div>
- <div class="op-list">
- <el-upload
- :show-file-list="false"
- :with-credentials="true"
- :multiple="true"
- :http-request="addFile"
- :accept="fileAccept"
- >
- <div class="op-item">
- <Icon iconName="file" :width="60"></Icon>
- <div>上传文件</div>
- </div>
- </el-upload>
- <div class="op-item" v-if="category == 'all'" @click="newFolder">
- <Icon iconName="folder" :width="60"></Icon>
- <div>新建目录</div>
- </div>
- </div>
- </div>
- </div>
- <FolderSelect
- ref="folderSelectRef"
- @folderSelect="moveFolderDone"
- ></FolderSelect>
-
- <!-- 预览 -->
- <Preview ref="previewRef"></Preview>
-
- <!-- 分享 -->
- <ShareFile ref="shareRef"></ShareFile>
- </div>
- </template>
-
- <script setup>
- import CategoryInfo from "@/js/CategoryInfo.js";
- import ShareFile from "./ShareFile.vue";
-
- import { ref, reactive, getCurrentInstance, nextTick, computed } from "vue";
- import { useRouter, useRoute } from "vue-router";
- const { proxy } = getCurrentInstance();
- const router = useRouter();
- const route = useRoute();
-
- // 实现上传文件的请求
- // 将Main子组件页面的数据传递给Framwork父组件
- const emit = defineEmits(["addFile"]);
- const addFile = async (fileData) => {
- emit("addFile", { file: fileData.file, filePid: currentFolder.value.fileId });
- };
-
- // 添加文件回调
- const reload = () => {
- showLoading.value = false;
- loadDataList();
- };
- defineExpose({ reload });
-
- const api = {
- loadDataList: "/file/loadDataList",
- rename: "/file/rename",
- newFoloder: "/file/newFoloder",
- getFolderInfo: "/file/getFolderInfo",
- delFile: "/file/delFile",
- changeFileFolder: "/file/changeFileFolder",
- createDownloadUrl: "/file/createDownloadUrl",
- download: "/api/file/download",
- };
-
- // 实现文件选择
- const fileAccept = computed(() => {
- const categoryItem = CategoryInfo[category.value];
- return categoryItem ? categoryItem.accept : "*";
- });
-
- // 列表头信息
- const columns = [
- {
- label: "文件名",
- prop: "fileName",
- scopedSlots: "fileName",
- },
- {
- label: "修改时间",
- prop: "lastUpdateTime",
- width: 200,
- },
- {
- label: "文件大小",
- prop: "fileSize",
- scopedSlots: "fileSize",
- width: 200,
- },
- ];
-
- // 搜索功能
- const search = () => {
- showLoading.value = true;
- loadDataList();
- };
-
- // 数据源
- const tableData = ref({});
- // 表格选项
- const tableOptions = {
- extHeight: 50,
- selectType: "checkbox",
- };
- // 文件名
- const fileNameFuzzy = ref();
-
- const showLoading = ref(true);
- // 分类
- const category = ref();
- // 当前文件夹
- const currentFolder = ref({ fileId: 0 });
-
- // 获得数据;
- const loadDataList = async () => {
- let params = {
- // 页码
- pageNo: tableData.value.pageNo,
- // 分页大小
- pageSize: tableData.value.pageSize,
- // 文件名(模糊)
- fileNameFuzzy: fileNameFuzzy.value,
- // 分类
- category: category.value,
- // 文件父id
- filePid: currentFolder.value.fileId,
- };
- if (params.category !== "all") {
- delete params.filePid;
- }
- let result = await proxy.Request({
- url: api.loadDataList,
- showLoading: showLoading,
- params,
- });
- if (!result) {
- return;
- }
- tableData.value = result.data;
- editing.value = false;
- };
-
- // 当鼠标放在当前行时,分享下载等图标出现
- const showOp = (row) => {
- // 关闭所有的显示
- tableData.value.list.forEach((element) => {
- element.showOp = false;
- });
- // 只开启当前显示
- row.showOp = true;
- };
-
- const cancelShowOp = (row) => {
- row.showOp = false;
- };
-
- // 编辑行(新建文件夹时编辑行)
- // 当前编辑行状态
- const editing = ref(false);
- // 新建文件夹行内填充的内容绑定
- const editNameRef = ref();
-
- // 新建文件夹
- const newFolder = () => {
- // 如果当前编辑行存在,则再次点击新建文件夹按钮时不起作用
- if (editing.value) {
- return;
- }
- // 让其他行都不允许编辑
- tableData.value.list.forEach((element) => {
- element.showEdit = false;
- });
- editing.value = true;
- tableData.value.list.unshift({
- showEdit: true,
- fileType: 0,
- fileId: "",
- filePid: currentFolder.value.fileId,
- });
- nextTick(() => {
- editNameRef.value.focus();
- });
- };
-
- // 取消新建文件夹操作
- const cancelNameEdit = (index) => {
- const fileData = tableData.value.list[index];
- // 如果存在这个文件的话,说明此处是重命名操作,那么可以直接将编辑行关闭
- if (fileData.fileId) {
- fileData.showEdit = false;
- } else {
- // 如果不存在的话,那么直接将此行删除
- tableData.value.list.splice(index, 1);
- }
- // 当前编辑行状态为:未编辑
- editing.value = false;
- };
-
- // 确定新建文件夹操作
- const saveNameEdit = async (index) => {
- const { fileId, filePid, fileNameReal } = tableData.value.list[index];
- if (fileNameReal == "" || fileNameReal.indexOf("/") != -1) {
- proxy.Message.warning("文件名不能为空且不能含有斜杠");
- return;
- }
- // 重命名
- let url = api.rename;
- if (fileId == "") {
- // 当文件ID不存在时,新建目录
- url = api.newFoloder;
- }
- let result = await proxy.Request({
- url: url,
- params: {
- fileId,
- filePid: filePid,
- fileName: fileNameReal,
- },
- });
- if (!result) {
- return;
- }
- tableData.value.list[index] = result.data;
- editing.value = false;
- };
-
- // 重命名 编辑文件名
- const editFileName = (index) => {
- // 如果现在有新建文件夹的编辑行,那么先将其删除,并且将序号减一
- if (tableData.value.list[0].fileId == "") {
- tableData.value.list.splice(0, 1);
- index = index - 1;
- }
- tableData.value.list.forEach((element) => {
- element.showEdit = false;
- });
- let cureentData = tableData.value.list[index];
- cureentData.showEdit = true;
-
- //编辑文件
- if (cureentData.folderType == 0) {
- cureentData.fileNameReal = cureentData.fileName.substring(
- 0,
- cureentData.fileName.indexOf(".")
- );
- cureentData.fileSuffix = cureentData.fileName.substring(
- cureentData.fileName.indexOf(".")
- );
- } else {
- cureentData.fileNameReal = cureentData.fileName;
- cureentData.fileSuffix = "";
- }
-
- // 当前编辑行状态为true
- editing.value = true;
- nextTick(() => {
- editNameRef.value.focus();
- });
- };
-
- // 行选中
- // 多选 批量选中
- const selectFileIdList = ref([]);
- const rowSelected = (rows) => {
- selectFileIdList.value = [];
- rows.forEach((item) => {
- selectFileIdList.value.push(item.fileId);
- });
- };
-
- // 删除单个文件
- const delFile = (row) => {
- proxy.Confirm(
- `你确定要删除【$row.fileName】吗?删除的文件可在 10 天内通过回收站还原`,
- async () => {
- let result = await proxy.Request({
- url: api.delFile,
- params: {
- fileIds: row.fileId,
- },
- });
- if (!result) {
- return;
- }
- // 重新获取数据
- loadDataList();
- }
- );
- };
-
- // 批量删除文件
- const delFileBatch = () => {
- if (selectFileIdList.value.length == 0) {
- return;
- }
- proxy.Confirm(
- `你确定要删除这些文件吗?删除的文件可在 10 天内通过回收站还原`,
- async () => {
- let result = await proxy.Request({
- url: api.delFile,
- params: {
- fileIds: selectFileIdList.value.join(","),
- },
- });
- if (!result) {
- return;
- }
- // 重新获取数据
- loadDataList();
- }
- );
- };
-
- // 移动目录
- const folderSelectRef = ref();
- // 当前要移动的文件(单个文件)
- const currentMoveFile = ref({});
-
- // 移动单个文件
- const moveFolder = (data) => {
- currentMoveFile.value = data;
- folderSelectRef.value.showFolderDialog(currentFolder.value.fileId);
- };
-
- // 移动批量文件
- const moveFolderBatch = () => {
- currentMoveFile.value = {};
- folderSelectRef.value.showFolderDialog(currentFolder.value.fileId);
- };
-
- // 移动文件操作
- const moveFolderDone = async (folderId) => {
- // 如果要移动到当前目录,提醒无需移动
- if (
- currentMoveFile.value.filePid == folderId ||
- currentFolder.value.fileId == folderId
- ) {
- proxy.Message.warning("文件正在当前目录,无需移动");
- return;
- }
- let filedIdsArray = [];
- // 如果是单个文件移动
- if (currentMoveFile.value.fileId) {
- filedIdsArray.push(currentMoveFile.value.fileId);
- } else {
- // 如果是多个文件移动
- // concat 连接多个数组
- // selectFileIdList 是指批量选择时选择的文件ID
- filedIdsArray = filedIdsArray.concat(selectFileIdList.value);
- }
- let result = await proxy.Request({
- url: api.changeFileFolder,
- params: {
- fileIds: filedIdsArray.join(","),
- filePid: folderId,
- },
- });
- if (!result) {
- return;
- }
- // 调用子组件暴露的close方法,实现当前弹出框页面的关闭
- folderSelectRef.value.close();
- // 更新当前文件列表
- loadDataList();
- };
-
- // 绑定导航栏
- const navigationRef = ref();
-
- // 预览
- const previewRef = ref();
- const preview = (data) => {
- // 如果是目录(文件夹)
- if (data.folderType == 1) {
- navigationRef.value.openFolder(data);
- return;
- }
- if (data.status != 2) {
- proxy.Message.warning("文件未完成转码,无法预览");
- return;
- }
- previewRef.value.showPreview(data, 0);
- };
-
- // 目录
- const navChange = (data) => {
- const { curFolder, categoryId } = data;
- currentFolder.value = curFolder;
- showLoading.value = true;
- category.value = categoryId;
- loadDataList();
- };
-
- // 下载文件
- const download = async (row) => {
- let result = await proxy.Request({
- url: api.createDownloadUrl + "/" + row.fileId,
- });
-
- if (!result) {
- return;
- }
-
- window.location.href = api.download + "/" + result.data;
- };
-
- // 分享文件
- // 利用ShareFile组件暴露出的show函数,实现将Main组件中的函数传递给ShareFile组件
- const shareRef = ref();
- const share = (row) => {
- shareRef.value.show(row);
- };
- </script>
-
- <style lang="scss" scoped>
- @import "@/assets/file.list.scss";
- </style>
功能:新建目录,文件上传,分享,下载,删除,重命名,移动
src/utils/Utils.js
- // 将文件以字节为单位的转换为其他单位
- export default {
- size2Str: (limit) => {
- var size = "";
- if (limit < 0.1 * 1024) { //小于0.1KB,则转化成B
- size = limit.toFixed(2) + "B"
- } else if (limit < 0.1 * 1024 * 1024) { //小于0.1MB,则转化成KB
- size = (limit / 1024).toFixed(2) + "KB"
- } else if (limit < 0.1 * 1024 * 1024 * 1024) { //小于0.1GB,则转化成MB
- size = (limit / (1024 * 1024)).toFixed(2) + "MB"
- } else { //其他转化成GB
- size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB"
- }
- var sizeStr = size + ""; //转成字符串
- var index = sizeStr.indexOf("."); //获取小数点处的索引
- var dou = sizeStr.substr(index + 1, 2) //获取小数点后两位的值
- if (dou == "00") { //判断后两位是否为00,如果是则删除00
- return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
- }
- return size;
- },
- }
main.js引入
-
- import Utils from './utils/Utils'
-
- app.config.globalProperties.Utils=Utils
- <!-- 按钮2 -->
- <el-button type="success" @click="newFolder">
- <span class="iconfont icon-folder-add"></span>
- 新建文件夹
- </el-button>
回调:
- // 编辑行(新建文件夹时编辑行)
- // 当前编辑行状态
- const editing = ref(false);
- // 新建文件夹行内填充的内容绑定
- const editNameRef = ref();
- // 新建文件夹
- const newFolder = () => {
- // 如果当前编辑行存在,则再次点击新建文件夹按钮时不起作用
- // 确保在编辑现有项目时,不能同时开始编辑新的项目。
- if (editing.value) {
- return;
- }
- // 让其他行都不允许编辑
- tableData.value.list.forEach((element) => {
- element.showEdit = false;
- });
- // 表示现在有一个项目正在被编辑
- editing.value = true;
- // 在列表顶部添加新文件夹:
- tableData.value.list.unshift({
- showEdit: true,
- fileType: 0,
- fileId: "",
- filePid: currentFolder.value.fileId,// 父文件夹的ID
- });
- // 在下一个“tick”中将焦点设置到某个输入框:
- nextTick(() => {
- editNameRef.value.focus();
- });
- };
- // 取消新建文件夹操作
- const cancelNameEdit = (index) => {
- const fileData = tableData.value.list[index];
- // 如果存在这个文件的话,说明此处是重命名操作,那么可以直接将编辑行关闭
- if (fileData.fileId) {
- fileData.showEdit = false;
- } else {
- // 如果不存在的话,那么直接将此行删除
- // 删除位于 index 位置的一个项目。删除后,数组的长度将减少1,并且所有高于 index 的元素都会向下移动一个位置。
- tableData.value.list.splice(index, 1);
- }
- // 当前编辑行状态为:未编辑
- editing.value = false;
- };
- // 确定新建文件夹操作
- const saveNameEdit = async (index) => {
- // 使用解构赋值从tableData.value.list数组中的指定索引位置获取fileId、filePid和fileNameReal。
- const { fileId, filePid, fileNameReal } = tableData.value.list[index];
- // 如果文件名fileNameReal为空或包含斜杠(/),则显示警告并退出函数。
- if (fileNameReal == "" || fileNameReal.indexOf("/") != -1) {
- proxy.Message.warning("文件名不能为空且不能含有斜杠");
- return;
- }
- // 如果fileId为空,表示这是新建目录而不是重命名,所以将请求的URL设置为api.newFoloder;否则,使用默认的api.rename来重命名现有文件或文件夹。
- // 重命名
- let url = api.rename;
- if (fileId == "") {
- // 当文件ID不存在时,新建目录
- url = api.newFoloder;
- }
- // 使用proxy.Request发送一个异步请求,该请求包含URL和要传递的参数(如fileId、filePid和fileName)。这里假设proxy.Request是一个返回Promise的函数,用于发送HTTP请求。
- let result = await proxy.Request({
- url: url,
- params: {
- fileId,
- filePid: filePid,
- fileName: fileNameReal,
- },
- });
- // 如果请求没有成功(例如,返回null或undefined),则直接退出函数。
- if (!result) {
- return;
- }
- // 如果请求成功,使用响应中的数据更新tableData.value.list数组中的相应项。
- tableData.value.list[index] = result.data;
- // 关闭编辑状态
- editing.value = false;
- };
-
- <span class="iconfont icon-edit" @click="editFileName(index)">
- 重命名
- </span>
回调:
- // 重命名 编辑文件名
- const editFileName = (index) => {
- // 如果现在有新建文件夹的编辑行
- if (tableData.value.list[0].fileId == "") {
- // 那么先将其删除
- tableData.value.list.splice(0, 1);
- // 并且将序号减一,否则重命名会出错顺序
- index = index - 1;
- }
- tableData.value.list.forEach((element) => {
- // 默认情况下所有行都不显示编辑状态。
- element.showEdit = false;
- });
- // 获取要编辑的行的数据(根据传入的index)
- let cureentData = tableData.value.list[index];
- // 表示该行现在处于编辑状态
- cureentData.showEdit = true;
-
- //编辑文件
- if (cureentData.folderType == 0) {
- // 如果folderType为0,表示这是一个文件(或不是文件夹)
- // 使用substring和indexOf方法从文件名中提取文件名(不带后缀)和文件后缀
- cureentData.fileNameReal = cureentData.fileName.substring(
- 0,
- cureentData.fileName.indexOf(".")
- );
- cureentData.fileSuffix = cureentData.fileName.substring(
- cureentData.fileName.indexOf(".")
- );
- // 如果不是文件
- } else {
- // 直接将文件名赋给fileNameReal
- cureentData.fileNameReal = cureentData.fileName;
- // 没有后缀
- cureentData.fileSuffix = "";
- }
-
- // 当前编辑行状态为true
- editing.value = true;
- nextTick(() => {
- editNameRef.value.focus();
- });
- };
- <el-upload :show-file-list="false" :with-credentials="true" :multiple="true" :http-request="addFile"
- :accept="fileAccept">
- <el-button type="primary">
- <span class="iconfont icon-upload"></span>
- 上传
- </el-button>
- </el-upload>
上传按钮方法::http-request="addFile"
回调:
- // 实现上传文件的请求
- // 定义了一个名为 addFile 的事件,该事件可以被外部(如父组件)监听。
- const emit = defineEmits(["addFile"]);
- // 它接收一个 fileData 参数,并使用之前定义的 emit 函数来触发一个 addFile 事件。事件传递的数据是一个对象,包含 file(从 fileData.file 获取)和 filePid(从 currentFolder.value.fileId 获取)。
- const addFile = async (fileData) => {
- emit("addFile", { file: fileData.file, filePid: currentFolder.value.fileId });
- };
- // 当前文件夹
- // currentFolder 引用用于存储当前文件夹的 ID,这个 ID 可能会随着用户操作而改变
- const currentFolder = ref({ fileId: 0 });
气泡框:v-model:visible="showUploader"
- <!-- v-slot="{ Component } 解构插槽 -->
- <!-- 让router-view的插槽能够访问子组件中的数据 -->
- <!-- 访问的数据就是Component -->
- <router-view v-slot="{ Component }">
- <component @addFile="addFile" ref="routerViewRef" :is="Component"></component>
- </router-view>
- // 控制是否展示上传区域
- const showUploader = ref(false);
- // 文件上传处数据绑定
- const uploaderRef = ref();
- // 上传文件
- const addFile = (data) => {
- const { file, filePid } = data;
- showUploader.value = true;
- // 调用子组件 Uploader中暴露的 addFile函数,并将参数传递给子组件
- uploaderRef.value.addFile(file, filePid);
- };
框架搭建:
1.上传标题
2.上传文件列表:文件名,文件上传进度条,文件上传状态(图标+描述+大小展示),操作按钮(不同情境),判断是否有上传文件(记得引入NoData图标组件)。
- <template>
- <div class="uploader-panel">
- <!-- 上传标题 -->
- <div class="uploader-title">
- <span>上传任务</span>
- <span class="tips">(仅展示本次上传任务)</span>
- </div>
- <!-- 上传列表 -->
- <div class="file-list">
- <!-- 遍历上传的每一项 -->
- <div v-for="(item, index) in fileList" class="file-item">
- <!-- 上传的每一项 -->
- <div class="upload-panel">
- <!-- 文件名 -->
- <div class="file-name">{{ item.fileName }}</div>
- <!-- 上传进度条 -->
- <div class="progress">
- <!-- 当状态为 上传中/上传完成/秒传时显示 -->
- <!-- Element UI 的进度条组件,percentage 属性(进度条的百分比)绑定到 item.uploadProgress 这个数据上。 -->
- <el-progress :percentage="item.uploadProgress" v-if="item.status == STATUS.uploading.value ||
- item.status == STATUS.upload_seconds.value ||
- item.status == STATUS.upload_finish.value
- "></el-progress>
- </div>
- <!-- 下方上传状态:图标+描述 -->
- <div class="upload-status">
- <!-- 图标:✔/✖ -->
- <!-- 一个静态的 'iconfont' 和一个根据 item.status 从 STATUS 对象中获取的图标类名。 -->
- <span :class="['iconfont', 'icon-' + STATUS[item.status].icon]"
- :style="{ color: STATUS[item.status].color }">
- </span>
- <!-- 状态描述:上传中/上传完成/秒传/失败 -->
- <span
- class="status"
- :style="{ color: STATUS[item.status].color }"
- >{{
- item.status == "fail" ? item.errorMsg : STATUS[item.status].desc
- }}
- </span>
- <!-- 上传中的大小显示,传了多少,速度 -->
- <!-- v-if表示只会在文件上传过程中显示,123kb/200mb -->
- <span
- class="upload-info"
- v-if="item.status == STATUS.uploading.value">
- {{ proxy.Utils.size2Str(item.uploadSize) }}/{{
- proxy.Utils.size2Str(item.totalSize)
- }}
- </span>
- </div>
- </div>
- <!-- 后面的操作按钮 -->
- <div class="op">
- <!-- 显示 MD5解析 信息 -->
- <!-- 解析中,圆形进度条,只在文件或数据的初始化阶段显示,而不是在整个上传过程中 -->
- <el-progress
- type="circle"
- :width="50"
- :percentage="item.md5Progress"
- v-if="item.status == STATUS.init.value"
- ></el-progress>
- <!-- 按钮 -->
- <div class="op-btn">
- <!-- 如果是上传中,提供暂停和上传两个按钮 -->
- <span v-if="item.status == STATUS.uploading.value">
- <!-- 上传按钮 -->
- <Icon
- :width="28"
- class="btn-item"
- iconName="upload"
- v-if="item.pause"
- title="上传"
- @click="startUpload(item.uid)"
- ></Icon>
- <!-- 暂停按钮 -->
- <Icon
- :width="28"
- class="btn-item"
- iconName="pause"
- title="暂停"
- @click="pauseUpload(item.uid)"
- v-else
- ></Icon>
- </span>
- <!-- 在上传过程中,不是解析&上传完成&秒传的情况下,提供删除按钮(不想传了) -->
- <Icon
- :width="28"
- class="del btn-item"
- iconName="del"
- title="删除"
- v-if="item.status != STATUS.init.value &&
- item.status != STATUS.upload_finish.value &&
- item.status != STATUS.upload_seconds.value
- "
- @click="delUpload(item.uid, index)"
- ></Icon>
- <!-- 在是上传完成/秒传的情况下,提供清除按钮 -->
- <Icon
- :width="28"
- class="clean btn-item"
- iconName="clean"
- title="清除"
- v-if="item.status == STATUS.upload_finish.value ||
- item.status == STATUS.upload_seconds.value
- "
- @click="delUpload(item.uid, index)"
- ></Icon>
- </div>
- </div>
- </div>
- <!-- 当没有文件上传时的显示 -->
- <div v-if="fileList.length == 0">
- <NoData msg="暂无上传任务"></NoData>
- </div>
- </div>
- </div>
- </template>
- ......
- <style lang="scss" scoped>
- .uploader-panel {
- .uploader-title {
- border-bottom: 1px solid #ddd;
- line-height: 40px;
- padding: 0px 10px;
- font-size: 15px;
-
- .tips {
- font-size: 13px;
- color: rgb(169, 169, 169);
- }
- }
-
- .file-list {
- // 如果内容溢出,则浏览器提供滚动条。
- overflow: auto;
- padding: 10px 0px;
- min-height: calc(100vh / 2);
- max-height: calc(100vh - 120px);
-
- .file-item {
- position: relative;
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 3px 10px;
- background-color: #fff;
- border-bottom: 1px solid #ddd;
- }
-
- .file-item:nth-child(even) {
- background-color: #fcf8f4;
- }
-
- .upload-panel {
- flex: 1;
-
- .file-name {
- color: rgb(64, 62, 62);
- }
-
- .upload-status {
- display: flex;
- align-items: center;
- margin-top: 5px;
-
- .iconfont {
- margin-right: 3px;
- }
-
- .status {
- color: red;
- font-size: 13px;
- }
-
- .upload-info {
- margin-left: 5px;
- font-size: 12px;
- color: rgb(112, 111, 111);
- }
- }
-
- .progress {
- height: 10px;
- }
- }
-
- .op {
- width: 100px;
- display: flex;
- align-items: center;
- justify-content: flex-end;
-
- .op-btn {
- .btn-item {
- cursor: pointer;
- }
-
- .del,
- .clean {
- margin-left: 5px;
- }
- }
- }
- }
- }</style>
功能实现:
定义上传状态STATUS,
定义addFile暴露给父组件 FrameWork,方便其调用该方法defineExpose({ addFile });,
接收一个参数uid
并返回一个与给定uid
匹配的文件对象(如果存在的话)
计算MD5值computeMD5,
上传文件uploadFile ,根据文件Id获取到文件getFileUid.
(下面的代码会有详细注释)
- <script setup>
- import {
- getCurrentInstance,
- onMounted,
- reactive,
- ref,
- watch,
- nextTick,
- } from "vue";
- import SparkMD5 from "spark-md5";
- const { proxy } = getCurrentInstance();
-
- const api = {
- upload: "/file/uploadFile",
- };
-
- // 定义不同的上传状态
- const STATUS = {
- emptyfile: {
- value: "emptyfile",
- desc: "文件为空",
- color: "#F75000",
- icon: "close",
- },
- fail: {
- value: "fail",
- desc: "上传失败",
- color: "#F75000",
- icon: "close",
- },
- init: {
- value: "init",
- desc: "解析中",
- color: "#e6a23c",
- icon: "clock",
- },
- uploading: {
- value: "uploading",
- desc: "上传中",
- color: "#409eff",
- icon: "upload",
- },
- upload_finish: {
- value: "upload_finish",
- desc: "上传完成",
- color: "#67c23a",
- icon: "ok",
- },
- upload_seconds: {
- value: "upload_seconds",
- desc: "秒传",
- color: "#67c23a",
- icon: "ok",
- },
- };
- // 分片时,每片的大小
- const chunkSize = 1024 * 1024 * 5;
- // 文件列表
- const fileList = ref([]);
- // 删除的文件的ID
- const delList = ref([]);
-
- const addFile = async (file, filePid) => {
- const fileItem = {
- // 文件
- file: file,
- // 文件ID
- uid: file.uid,
- // md5进度(转圈进度)
- md5Progress: 0,
- // md5值
- md5: null,
- // 文件名,文件展示的名字
- fileName: file.name,
- // 上传状态
- status: STATUS.init.value,
- // 已上传大小
- uploadSize: 0,
- // 文件总大小
- totalSize: file.size,
- // 上传进度
- uploadProgress: 0,
- //暂停
- pause: false,
- // 当前分片
- chunkIndex: 0,
- // 父级ID
- filePid: filePid,
- // 错误信息
- errorMsg: null,
- };
- // 把上传文件加到上传列表前面
- fileList.value.unshift(fileItem);
- // 如果文件大小为0,
- if (fileItem.totalSize == 0) {
- // 表示为空文件状态
- fileItem.status = STATUS.emptyfile.value;
- // 退出
- return;
- }
- // 文件大小不为0,代码将尝试计算该文件的MD5值
- let md5FileUid = await computeMD5(fileItem);
- // 检测md5值是否有效
- if (md5FileUid == null) {
- return;
- }
- // 上传文件
- uploadFile(md5FileUid);
- };
- // 暴露给父组件 FrameWork,方便其调用该方法
- defineExpose({ addFile });
-
- // 上传文件
- const emit = defineEmits(["uploadCallback"]);
- // 异步函数,接收两个参数:uid(文件的唯一标识符)和 chunkIndex(要上传的切片的索引,默认为0)
- const uploadFile = async (uid, chunkIndex) => {
- chunkIndex = chunkIndex ? chunkIndex : 0;
- // 获取当前文件
- let currentFile = getFileByUid(uid);
- // 计算切片数量
- const file = currentFile.file;
- const fileSize = currentFile.totalSize;
- const chunks = Math.ceil(fileSize / chunkSize);
- // 给定的 chunkIndex 开始,遍历所有切片
- for (let i = chunkIndex; i < chunks; i++) {
- // 判断如果在文件上传的过程中删除了文件,那么直接跳出循环
- // 调用 indexOf 方法来查找 uid 在 delList.value 列表中的索引
- let delIndex = delList.value.indexOf(uid);
- if (delIndex != -1) {
- // 使用 splice 方法来移除它。splice 方法接受两个参数:要开始移除的元素的索引(这里是 delIndex),以及要移除的元素数量(这里是 1,因为我们只移除一个元素)。
- delList.value.splice(delIndex, 1);
- break;
- }
- // 如果当前文件被暂停,那么直接跳出循环
- if (currentFile.pause) break;
-
- // 获取分片
- // start 变量表示当前数据块在原始文件中的起始字节位置
- let start = i * chunkSize;
- // 如果起始位置加上chunkSize超过了文件的总大小(fileSize),那么结束位置就是文件的总大小;否则,结束位置就是起始位置加上chunkSize
- let end = start + chunkSize >= fileSize ? fileSize : start + chunkSize;
- // 提取从start到end(不包括end)的字节范围,并返回一个新的Blob对象,该对象包含该范围内的数据。这个新的Blob对象(chunkFile)就是我们要上传的数据块。
- let chunkFile = file.slice(start, end);
-
- // 发起HTTP请求
- // uploadResult存储上传请求的响应结果
- let uploadResult = await proxy.Request({
- url: api.upload,//API的上传端点(URL)
- showLoading: false,
- dataType: "file",
- params: {
- file: chunkFile,//要上传的文件分块,它是一个Blob对象
- fileName: file.name,
- fileMd5: currentFile.md5,
- chunkIndex: i,
- chunks: chunks,//被分割的总片数
- fileId: currentFile.fileId,
- filePid: currentFile.filePid,
- },
- showError: false,
- // 报错
- // 它接收一个errorMsg参数,表示错误信息
- errorCallback: (errorMsg) => {
- // 然后,它将currentFile.status设置为失败状态
- currentFile.status = STATUS.fail.value;
- // 并将errorMsg保存到currentFile.errorMsg中
- currentFile.errorMsg = errorMsg;
- },
- // 进度更新
- // 接收一个event对象,该对象包含了关于上传进度的信息
- uploadProgressCallback: (event) => {
- // 从event中获取已加载的字节数loaded
- let loaded = event.loaded;
- if (loaded > fileSize) {
- // 检查已加载的字节数是否超过了文件总大小(fileSize),如果是,则将其设置为fileSize。
- loaded = fileSize;
- }
- // 更新currentFile.uploadSize为当前分块的起始位置加上已加载的字节数
- currentFile.uploadSize = i * chunkSize + loaded;
- // 计算上传进度百分比,并更新currentFile.uploadProgress
- currentFile.uploadProgress = Math.floor(
- (currentFile.uploadSize / fileSize) * 100
- );
- },
- });
-
- // 上传请求可能没有成功执行或返回了无效的结果
- if (uploadResult == null) {
- break;
- }
- // 更新文件信息
- currentFile.fileId = uploadResult.data.fileId;
- currentFile.status = STATUS[uploadResult.data.status].value;
- currentFile.chunkIndex = i;
- // 如果状态是秒传和上传完成,则执行以下操作
- if (
- uploadResult.data.status == STATUS.upload_seconds.value ||
- uploadResult.data.status == STATUS.upload_finish.value
- ) {
- // 上传进度条为100
- currentFile.uploadProgress = 100;
- // 上传结束后,uploaderCallback,将Framework中的列表刷新
- emit("uploadCallback");
- break;
- }
- }
- };
-
- // 计算文件的 MD5 值
- // 会对大文件进行分片处理
- const computeMD5 = (fileItem) => {
- let file = fileItem.file;
- // slice 分割文件
- // mozSlice 兼容firefox
- // webkitSlice 兼容webkit
- let blobSlice =
- File.prototype.slice ||
- File.prototype.mozSlice ||
- File.prototype.webkitSlice;
- // chunkSize 每片的大小
- // chunks 切片数量(向上取整)
- let chunks = Math.ceil(file.size / chunkSize);
- // 当前切片的下标为0
- let currentChunk = 0;
- // 创建SparkMD5的实例,计算MD5
- let spark = new SparkMD5.ArrayBuffer();
- // 使用 FileReader 读取文件的数据
- let fileReader = new FileReader();
- // 已删除文件的索引
- const delList = ref([]);
-
- // 加载数据
- // loadNext读取文件的下一个块
- let loadNext = () => {
- // 当前片段在文件中的起始字节位置
- let start = currentChunk * chunkSize;
- // 起始位置加上片段大小,超出文件的总大小(file.size),大小为file.size,不超出则将结束位置设置为文件的总大小。
- let end = start + chunkSize >= file.size ? file.size : start + chunkSize;
- // 来异步读取文件的指定片段
- fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
- };
- // 当 computeMD5 函数被调用时,它会立即开始读取文件的第一个片段。
- loadNext();
-
- // 使用 Promise 封装文件分片读取和 MD5 哈希值计算
- // 这个 Promise 将在文件的所有分片都被读取并计算完 MD5 哈希值后解决(resolve),并返回文件的唯一标识符(UID)
- return new Promise((resolve, reject) => {
- // 根据文件ID获取到文件
- let resultFile = getFileByUid(file.uid);
- // 当读取操作成功完成时调用
- // 当 FileReader 读取完一个文件分片后,会触发 onload 事件
- fileReader.onload = (e) => {
- // 向SparkMD5实例中添加数据
- spark.append(e.target.result); // Append array buffer
- // 切片下标+1
- currentChunk++;
- // 如果 currentChunk 小于 chunks(总分片数),则继续读取下一个分片
- // 自动分片解析
- if (currentChunk < chunks) {
- /* console.log(
- `第${file.name},${currentChunk}分片解析完成, 开始第${
- currentChunk + 1
- } / ${chunks}分片解析`
- ); */
- // 计算当前进度百分比,并更新 resultFile.md5Progress
- let percent = Math.floor((currentChunk / chunks) * 100);
- resultFile.md5Progress = percent;
- // 再次读取数据,读取下一个文件分片
- loadNext();
- } else {
- // 如果当前切片下标不比切片数量小,说明解析到最后了
- // 调用 SparkMD5 的 end() 方法来计算最终的 MD5 哈希值
- let md5 = spark.end();
- /* console.log(
- `MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
- file.size
- } 用时:${new Date().getTime() - time} ms`
- ); */
- // 释放 SparkMD5 实例占用的资源
- spark.destroy(); //释放缓存
- // 设置 resultFile.md5Progress 为 100,表示进度完成。
- resultFile.md5Progress = 100;
- // 设置 resultFile.status 为上传状态
- resultFile.status = STATUS.uploading.value;
- // 设置 resultFile.md5 为计算得到的 MD5 哈希值
- resultFile.md5 = md5;
- // 调用 resolve(fileItem.uid); 来解决 Promise,并返回文件的 UID
- resolve(fileItem.uid);
- }
- };
- // 当读取操作发生错误时调用
- fileReader.onerror = () => {
- // 将 resultFile 对象的 md5Progress 属性设置为 -1,表示 MD5 计算过程遇到了错误。
- resultFile.md5Progress = -1;
- // 设置文件状态为失败
- resultFile.status = STATUS.fail.value;
-
- resolve(fileItem.uid);
- };
- // Promise 链的一个捕获处理器(catch handler),用于处理 Promise 链中任何地方的错误
- }).catch((error) => {
- return null;
- });
- };
-
- // 根据文件ID获取到文件
- const getFileByUid = (uid) => {
- let file = fileList.value.find((item) => {
- return item.file.uid === uid;
- });
- return file;
- };
- </script>
- <template #default>
- 这里是上传区域
- <Uploader ref="uploaderRef" @uploadCallback="uploadCallbackHandler"></Uploader>
- </template>
引入
import Uploader from "@/views/main/Uploader.vue";
回调:
- // 上传文件回调
- const uploadCallbackHandler = () => {
- nextTick(() => {
- // 它首先等待DOM更新完成(通过nextTick)
- // 然后重新加载一个组件(可能是router-view)
- routerViewRef.value.reload();
- // 并最后调用一个函数来获取空间使用情况。
- getUseSpace();
- });
- };
效果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。