Vue3项目Easy云盘(二):文件列表+新建目录+文件重命名+文件上传

一、文件列表

1.封装全局组件Table.vue

因为Main.vue等都会用到文件列表table,所以直接封装成组件。

src/components/Table.vue

<template><!-- 表格 --><div><el-tableref="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-columnv-if="options.selectType && options.selectType == 'checkbox'"type="selection"width="50"align="center"></el-table-column><!-- 序号 --><el-table-columnv-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-paginationv-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>

2.封装全局组件Icon.vue

因为需要展示上传文件,文件夹,图片,视频等的缩略图,所以,在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>

3.main.js引入全局组件


import Table from '@/components/Table.vue'
import Icon from '@/components/Icon.vue'app.component("Table",Table)
app.component("Icon",Icon)

4.封装文件列表样式组件file.list.scss

包括头部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"


5.文件列表搭建

完整版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>&nbsp上传</el-button></el-upload></div><el-button type="success" @click="newFolder" v-if="category == 'all'"><span class="iconfont icon-folder-add"></span>&nbsp新建文件夹</el-button><el-button@click="delFileBatch"type="danger":disabled="selectFileIdList.length == 0"><span class="iconfont icon-del"></span>&nbsp批量删除</el-button><el-button@click="moveFolderBatch"type="warning":disabled="selectFileIdList.length == 0"><span class="iconfont icon-move"></span>&nbsp批量移动</el-button><div class="search-panel"><el-inputclearableplaceholder="请输入文件名搜索"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"><Tableref="dataTableRef":columns="columns":showPagination="true":dataSource="tableData":fetch="loadDataList":initFetch="false":options="tableOptions"@rowSelected="rowSelected"><!-- 文件名 --><template #fileName="{ index, row }"><!-- showOp(row) 当鼠标放在当前行时,分享下载等图标出现 --><!-- cancelShowOp(row) 当鼠标离开当前行时,分享下载等图标消失 --><divclass="file-item"@mouseenter="showOp(row)"@mouseleave="cancelShowOp(row)"><!-- 显示文件图标 --><templatev-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-inputv-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><!-- 叉号 取消 --><spanclass="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><!-- 只有当是文件夹时才可下载 --><spanclass="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><FolderSelectref="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,// 文件父idfilePid: 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 = "";}// 当前编辑行状态为trueediting.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 是指批量选择时选择的文件IDfiledIdsArray = 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>

二、功能实现

功能:新建目录,文件上传,分享,下载,删除,重命名,移动

1.将文件以字节为单位的转换为其他单位,封装组件


src/utils/Utils.js

// 将文件以字节为单位的转换为其他单位
export default {size2Str: (limit) => {var size = "";if (limit < 0.1 * 1024) { //小于0.1KB,则转化成Bsize = limit.toFixed(2) + "B"} else if (limit < 0.1 * 1024 * 1024) { //小于0.1MB,则转化成KBsize = (limit / 1024).toFixed(2) + "KB"} else if (limit < 0.1 * 1024 * 1024 * 1024) { //小于0.1GB,则转化成MBsize = (limit / (1024 * 1024)).toFixed(2) + "MB"} else { //其他转化成GBsize = (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.新建目录功能

<!-- 按钮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;
};

3.文件重命名


<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 {// 直接将文件名赋给fileNameRealcureentData.fileNameReal = cureentData.fileName;// 没有后缀cureentData.fileSuffix = "";}// 当前编辑行状态为trueediting.value = true;nextTick(() => {editNameRef.value.focus();});
};

三、文件上传功能

(1)在子组件Main.vue里面定义,但是实际上传功能在Framework.vue中

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

(2)Main.vue中

上传按钮方法::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 });

(3)Framework.vue中

气泡框: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>

(4)回调

// 控制是否展示上传区域
const showUploader = ref(false);
// 文件上传处数据绑定
const uploaderRef = ref();
// 上传文件
const addFile = (data) => {const { file, filePid } = data;showUploader.value = true;// 调用子组件 Uploader中暴露的 addFile函数,并将参数传递给子组件uploaderRef.value.addFile(file, filePid);
};

四、气泡框上传区域定义组件Uploader(重点)

(1)src/views/main/Uploader.vue

框架搭建:

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><!-- 状态描述:上传中/上传完成/秒传/失败 --><spanclass="status":style="{ color: STATUS[item.status].color }">{{item.status == "fail" ? item.errorMsg : STATUS[item.status].desc}}</span><!-- 上传中的大小显示,传了多少,速度 --><!-- v-if表示只会在文件上传过程中显示,123kb/200mb --><spanclass="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-progresstype="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,// 文件IDuid: 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,// 父级IDfilePid: 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),那么结束位置就是文件的总大小;否则,结束位置就是起始位置加上chunkSizelet 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中获取已加载的字节数loadedlet loaded = event.loaded;if (loaded > fileSize) {// 检查已加载的字节数是否超过了文件总大小(fileSize),如果是,则将其设置为fileSize。loaded = fileSize;}// 更新currentFile.uploadSize为当前分块的起始位置加上已加载的字节数currentFile.uploadSize = i * chunkSize + loaded;// 计算上传进度百分比,并更新currentFile.uploadProgresscurrentFile.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) {// 上传进度条为100currentFile.uploadProgress = 100;// 上传结束后,uploaderCallback,将Framework中的列表刷新emit("uploadCallback");break;}}
};// 计算文件的 MD5 值
// 会对大文件进行分片处理
const computeMD5 = (fileItem) => {let file = fileItem.file;// slice 分割文件// mozSlice 兼容firefox// webkitSlice 兼容webkitlet blobSlice =File.prototype.slice ||File.prototype.mozSlice ||File.prototype.webkitSlice;// chunkSize 每片的大小// chunks 切片数量(向上取整)let chunks = Math.ceil(file.size / chunkSize);// 当前切片的下标为0let currentChunk = 0;// 创建SparkMD5的实例,计算MD5let 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// 切片下标+1currentChunk++;// 如果 currentChunk 小于 chunks(总分片数),则继续读取下一个分片// 自动分片解析if (currentChunk < chunks) {/*  console.log(`第${file.name},${currentChunk}分片解析完成, 开始第${currentChunk + 1} / ${chunks}分片解析`); */// 计算当前进度百分比,并更新 resultFile.md5Progresslet 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,并返回文件的 UIDresolve(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>

(2)使用组件,Framework.vue中

<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();});
};

效果:

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

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

相关文章

用迭代加深解决加成序列问题

可以看到这个最坏的结果是100层搜索&#xff0c;但是其实1 2 4 8 16 32 64 128&#xff0c;到128的话也只要8&#xff0c;所以大概只需要10几层搜索就可以解决了&#xff0c;这个时候就可以用迭代加深的方法&#xff0c;深度一点点的加&#xff0c;如果大于概深度就舍去。有人说…

用户登录后端:登录密码解密后用PasswordEncoder验证密码是否正确

前置知识: 前端登录加密看用户登录 PasswordEncoder加密看PasswordEncoder详解 项目中因为要判断用户登录密码是否正确&#xff0c;通过输入错误次数锁住用户 1.后端配置rsa私钥 #密码加密传输&#xff0c;前端公钥加密&#xff0c;后端私钥解密 rsa:private_key: xxxx2. 读…

基于C++基础知识的指针

一、变量与指针 在C中&#xff0c;变量是用来存储数据的一个标识符&#xff0c;而指针是一个变量&#xff0c;该变量存储的是另一个变量的地址。 变量可以是不同的数据类型&#xff0c;包括整数&#xff08;int&#xff09;、浮点数&#xff08;float&#xff09;、字符&#…

智慧粮库/粮仓视频监管系统:AI视频智能监测保障储粮安全

智慧粮库视频监管系统是一种基于物联网、AI技术和视频监控技术的先进管理系统&#xff0c;主要用于对粮食储存环境进行实时监测、数据分析和预警。TSINGSEE青犀智慧粮库/粮仓视频智能管理系统方案通过部署多区域温、湿度、空气成分等多类传感器以及视频监控等设施&#xff0c;对…

IDEA及Maven配置代理及Maven中央仓库配置详解

一、配置代理 首先&#xff0c;需要本地开启代理入口&#xff0c;如图。 这个跟你使用代理软件有关。像我使用的是qv2ray。 其次&#xff0c;idea配置代理&#xff0c;如图。 1.1 idea配置代理 打开Settings&#xff0c;如图 1.2 maven配置代理 maven配置代理&#xff0c;修…

虚拟机CentOS密码重置

1&#xff0c;reboot重启 在出现下面的界面1按e 如果有选项就选择“CentOS Linux &#xff08;3.10.0-327.e17.x86_64&#xff09;7 &#xff08;Core&#xff09;”【我的电脑没有直接显示界面2】 界面1 界面2 2&#xff0c;在上述界面2中继续按e进入编辑模式 找到“ro cr…

partially initialized module ‘replicate‘ has no attribute ‘run‘

partially initialized module replicate has no attribute run(most likely due to a circular import) 在包名上停留查看impot 包的地址。 报错原因&#xff1a; 文件重名了&#xff0c;导入了 当前文件 。 修改文件名 即可。

Vue3路由及登录注销功能、设置导航守护功能模块

路由 在vue中&#xff0c;页面和组件都是.vue文件&#xff0c;可以说是一样的&#xff0c;结构、内容和生产方法都是一样&#xff0c;但是组件可以被反复使用&#xff0c;但页面一般只被使用一次。 路由的作用就是网页地址发生变化时&#xff0c;在App.vue页面的指定位置可以加…

17 M-LAG 配置思路

16 华三数据中心最流行的技术 M-LAG-CSDN博客 M-LAG 配置思路 什么是M-LAG&#xff1f;为什么需要M-LAG&#xff1f; - 华为 (huawei.com) 1 配置 M-LAG 的固定的MAC地址 [SW-MLAG]m-lag system-mac 2-2-2 2 配置M-LAG 的系统标识符系统范围1到2 [SW-MLAG]m-lag system-nu…

MongoDB安装及接入springboot

环境&#xff1a;windows、jdk8、springboot2 1.MongoDB概述 MongoDB是一个开源、高性能、无模式&#xff08;模式自由&#xff09;的文档&#xff08;Bson&#xff09;型数据库&#xff1b;其特点如下&#xff1a; 模式自由 ---- 不需要提前创建表 直接放数据就可以 支持高并…

STM32窗口看门狗的操作

STM32的窗口看门狗的主要功能是&#xff0c;程序过早的喂狗还有太晚喂狗&#xff0c;都会触发单片机重启&#xff0c;就是有一个时间段&#xff0c;在这个时间段内喂狗才不会触发单片机重启。 下面我就总结一下窗口看门狗的设置过程&#xff1a; 第一步&#xff1a;开启窗口看…

vscode怎么设置背景图片?

vscode背景图片是可以自己设置的&#xff0c;软件安装后默认背景的颜色是黑色的&#xff0c;这是默认的设计&#xff0c;如果要修改背景为指定的图片&#xff0c;那么我们需要安装插件&#xff0c;然后再通过代码来设置背景图片的样式&#xff0c;下面我们就来看看详细的教程。…

代数结构:5、格与布尔代数

16.1 偏序与格 偏序集&#xff1a;设P是集合&#xff0c;P上的二元关系“≤”满足以下三个条件&#xff0c;则称“≤”是P上的偏序关系&#xff08;或部分序关系&#xff09; &#xff08;1&#xff09;自反性&#xff1a;a≤a&#xff0c;∀a∈P&#xff1b; &#xff08;2…

旅游推荐管理系统(小组项目)

文章目录 前言 一、项目介绍 1. 项目目的 2. 项目意义 2.1 提升旅游体验 2.2 促进旅游业发展 2.3 数据积累与分析 2.4 提升服务品质 2.5 优化资源配置 二、项目结构 1. 主要使用的技术 1.1 若依&#xff08;Ruoyi&#xff09;框架 1.2 Vue.js框架 1.3 Ajax 1.4 …

【进程通信】了解信号以及信号的产生

文章目录 0.前言1.信号的基本概念1.1中断1.1.1 软中断1.1.2硬中断 1.2异步1.2.1异步和同步的比较 2.信号的主要用途3.信号的特点4.查看信号4.1Core和Term的区别4.2生成Core文件 5.初识捕捉信号5.1signal函数 6.产生信号的方式6.1.通过终端按键产生信号6.2.调用系统函数向进程发…

国内智能搜索工具实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

分享10类正规的网上赚钱平台,让你摆脱单一收入

在这个互联网飞速发展的时代&#xff0c;你是否还在为单一的收入来源而焦虑&#xff1f;别担心&#xff0c;今天带你解锁10种网上赚钱的新姿势&#xff0c;让你的收入不再单一&#xff0c;甚至可能翻倍&#xff01; 1. 文库类&#xff1a;知识的变现 你知道吗&#xff1f;你的…

k8s 数据流向 与 核心概念详细介绍

目录 一 k8s 数据流向 1&#xff0c;超级详细版 2&#xff0c;核心主键及含义 3&#xff0c;K8S 创建Pod 流程 4&#xff0c;用户访问流程 二 Kubernetes 核心概念 1&#xff0c;Pod 1.1 Pod 是什么 1.2 pod 与容器的关系 1.3 pod中容器 的通信 2&#xff0c; …

imx91的uboot编译

一、准备操作 下载半导体厂家的uboot源码 如这里我要下载的是imx91的恩智浦linux芯片bootloader 进入半导体厂家官网 下载源码&#xff0c;略 更新linux源&#xff0c;这里我是替换成清华源 vi /etc/apt/sources.list deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ fo…

【江科大STM32学习笔记】新建工程

1.建立工程文件夹&#xff0c;Keil中新建工程&#xff0c;选择型号 2.工程文件夹里建立Start、Library、User等文件夹&#xff0c;复制固件库里面的文件到工程文件夹 为添加工程文件准备&#xff0c;建文件夹是因为文件比较多需要分类管理&#xff0c;需要用到的文件一定要复…