Vue3+TS 实现批量拖拽文件夹上传图片组件封装
1、html 代码:
代码中的表格引入了 vxe-table 插件
<Tag /> 是自己封装的说明组件
表格列表这块我使用了插槽来增加扩展性,可根据自己需求,在组件外部做调整
<template><div class="dragUpload"><el-dialog v-model="data.visible"width="35%"center:draggable="draggable":destroy-on-close="true":close-on-click-modal="false":close-on-press-escape="false":before-close="closeDialogFn"><template #header><h3>{{ fileData.step === 2 ? '提示:文件超出大小限制' : '拖拽上传'}}</h3></template><!-- 文件上传区域 --><div class="drag-box"v-if="fileData.step === 1"@dragover="handleDragOver"@dragleave="handleDragOver"@drop="handleDrop"><div class="div-text" ><div class="drag-tip">拖拽文件至此区域<span class="click-txt" @click="toUploadFloder">点击上传</span></div><div class="btn-wrap"><el-button v-if="singleFile" @click="toUploadFile">上传文件</el-button><el-button @click="toUploadFloder">上传文件夹</el-button><inputv-if="singleFile":style="{ display: 'none' }"type="file"ref="fileUploadRef"@change="handleFileChange"multiple/><input:style="{ display: 'none' }"type="file"ref="fileUploadFloderRef"@change="handleFloderChange"webkitdirectorymultiple/></div></div></div><!-- 超出限制后,展示的列表 --><div v-if="fileData.step === 2"><!-- 组件内默认展示 --><div class="max-h311" v-if="!openSlot"><vxe-table:data="fileData.goBeyondTable"height="100%":checkbox-config="{showHeader: true,trigger: 'cell'}"><template #empty><div class="no-data-box"><i class="icon_noData"></i><div class="m-t6">暂无数据</div></div></template><vxe-column min-width="180" :title="`${data.fileName || 'SPU'}文件名`"><template #default="{ row }">{{ row[fileData.firstFileName] ? row[fileData.firstFileName] : '--' }}</template></vxe-column><vxe-column min-width="111" title="大小"><template #default="{ row }">{{ row.size ? row.size : '--' }} MB</template></vxe-column><vxe-column width="100" title="操作" :visible="fileData.goBeyondTable.length > 1"><template #default="{ $rowIndex, row }"><el-tooltipcontent="移除"placement="top":hide-after="0"><a class="icon_delete f-s18"href="javscript:"@click="handleDelete($rowIndex, row)"></a></el-tooltip></template></vxe-column></vxe-table></div><!-- 插槽,可使用外部传入 --><template v-else><slot name="errorTable"></slot></template></div><Tag class="m-t12"v-if="data.fileSize":content="fileData.step === 1 ? `文件大小不能超过 ${data.fileSize} MB` : `文件大小不能超过 ${data.fileSize}MB,目前文件大小 ${fileData.allSize} MB`"/><template #footer v-if="fileData.step === 2"><div class="dialog-footer"><el-button @click="closeDialogFn">取消</el-button><!-- 默认使用内部提交逻辑 --><template v-if="!openSlot"><el-button type="primary" @click="handleUploadToServer(true)">提交</el-button></template><!-- 开启插槽,则使用外部自定义 --><template v-else><slot name="footerBtn"></slot></template></div></template></el-dialog></div>
</template>
2、js 代码:
这块主要思路是:将文件夹判断后进行递归,获取出文件夹中的文件出来,最后类似单个文件上传,然后将文件流进行遍历 append 进创建的 FormData 对象。具体方法看:readFiles()
<script lang="ts" setup>
import { reactive, ref, getCurrentInstance } from 'vue';const { proxy }: any = getCurrentInstance();
const $tool = proxy.$tool;const props = defineProps({// 组件参数配置data: {type: Object,default: () => ({// fileSize: 100, // 文件大小限制// imgType: ['png', 'jpg', 'jpeg'], // 图片类型限制// fileName: '', // 超出后列表展示的文件名:不传默认为SPU}),},// 是否支持窗口拖拽,默认truedraggable: {type: Boolean,default: true},// 是否支持打开 file 单文件上传,不传默认falsesingleFile: {type: Boolean,default: false},//是否需要开启:列表上传失败 和 提交按钮插槽:默认不开启,展示组件内的失败列表 和 提交逻辑openSlot: {type: Boolean,default: false}
});/*** @param dragUploadAxiosFn 上传接口抛出,外部做处理,不与组件内部逻辑耦合* @param dragUploadErrorTable 超出限制后,展示的列表插槽抛出,外部做处理,不与组件内部逻辑耦合*/
const emit = defineEmits(['dragUploadAxiosFn', 'dragUploadErrorTable']);const fileUploadRef = ref();
const fileUploadFloderRef = ref();const fileData: any = reactive({step: 1, // 步骤: 1:文件拖拽上传;2:文件超出提示uploadList: [], //上传的文件列表waitUploadList: [], //存储待上传的文件列表fileSizeList: [], //存储遍历出来文件里面所有的图片路径及大小goBeyondTable: [], //超出限制后,将遍历项还原成文件夹项展示的列表allSize: 0, //文件总大小 MBfirstFileName: 'pathName0' //第一列字段:key
});/*文件上传input*/
const toUploadFile = () => {fileUploadRef.value.click();
};/*文件夹上传input*/
const toUploadFloder = () => {fileUploadFloderRef.value.click();
};/*选择文件改变*/
const handleFileChange = (e: any) => {if (e.target.files) {let filesList: any = Array.from(e.target.files);filesList.forEach((item: any) => {let size = item.size / 1024 / 1024;fileData.allSize += size;let obj: any = getPath(item.name);changeFileSizeList(item, obj);});fileData.allSize = fileData.allSize.toFixed(2); // 文件总大小 MBfileData.waitUploadList = filesList;if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法handleUploadToServer(); //上传文件到服务器}
};/*文件夹目录上传*/
const handleFloderChange = (e: any) => {if (e.target.files) {let filesList: any = Array.from(e.target.files);filesList.forEach((item: any) => { let size = item.size / 1024 / 1024;fileData.allSize += size;let obj: any = getPath(item.webkitRelativePath); // 通过路径获取名称方法changeFileSizeList(item, obj);});filesList.reverse(); // 反转数组,保证最先选择的文件排在最后面fileData.allSize = fileData.allSize.toFixed(2); // 文件总大小 MBfileData.waitUploadList = filesList;if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法handleUploadToServer(); //上传文件到服务器}
};// 拖放进入目标区域
const handleDragOver = (event) => {event.preventDefault();
};// 拖拽放置
const handleDrop = async (event) => {event.preventDefault();const files = [];const promises: any[] = [];for (const item of event.dataTransfer.items) {const entry: any = item.webkitGetAsEntry();if (!entry.isDirectory && !props.singleFile) {proxy.$message.error(`只支持文件夹上传,不支持单个文件上传!`);return;}promises.push(readFiles(entry));}const resultFilesArrays = await Promise.all(promises); // 等待所有文件读取完成fileData.waitUploadList = resultFilesArrays.flat();if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法handleUploadToServer(); //上传文件到服务器
};//文件各种限制判断方法封装
const fileLimitFn = (fileSizeList: any) => {console.log('fileData.fileSizeList', fileData.fileSizeList);//文件大小超出限制if (props.data.fileSize) {if (!fileSizeLimit(fileSizeList)) { fileData.goBeyondTable = getGoBeyondTable(fileSizeList);fileData.step = 2;return false;}}//图片类型限制判断if (props.data.imgType) {if (!fileImgType(fileSizeList)) return false;}return true;
};//文件超出限制
const fileSizeLimit = (fileSizeList: any) => {let allSize = fileSizeList.reduce((accumulator, currentValue) => {if (currentValue) {return accumulator + currentValue.size;}return accumulator;}, 0);let fileSize = props.data.fileSize * 1024 * 1024;fileData.allSize = (allSize / 1024 / 1024).toFixed(2); //存储文件总大小 MBif (allSize > fileSize) {proxy.$message.error(`文件大小不能超过 ${props.data.fileSize} MB`);emit('dragUploadErrorTable', fileData);return false;}return true;
};// 文件中图片,格式类型判断
const fileImgType = (fileSizeList: any) => {let findList: any = fileSizeList.filter((item: any) => !props.data.imgType.includes(item.type));if (findList.length > 0) {proxy.$message.error(`图片只能上传 ${props.data.imgType} 格式`);return false;};return true;
};// 操作数据:文件超出后,展示的列表
const getGoBeyondTable = (fileSizeList: any) => {// 遍历,相同第一列为一项,size累加let result: any = fileSizeList.reduce((accumulator, current) => {if (accumulator[current[fileData.firstFileName]]) { //如果已经存在,则累加sizeaccumulator[current[fileData.firstFileName]].size += current.size;} else {accumulator[current[fileData.firstFileName]] = { ...current };}return accumulator;}, {});// 将结果对象转换回数组result = Object.values(result);// 处理size为MB单位result.forEach((item: any) => {item.size = (item.size / 1024 / 1024).toFixed(2);});return result;
};//移除超出文件列表的项
const handleDelete = (rowIndex: number, row: any) => {fileData.goBeyondTable.splice(rowIndex, 1);fileData.allSize = (fileData.allSize - row.size).toFixed(2); //更新总大小MBfileData.fileSizeList = fileData.fileSizeList.filter((item: any) => item[fileData.firstFileName] !== row[fileData.firstFileName]);fileData.waitUploadList = fileData.waitUploadList.filter((item: any) => item[fileData.firstFileName] !== row[fileData.firstFileName]);
};/*请求上传到服务器*/
const handleUploadToServer = (clcik?: boolean) => {fileData.uploadList = fileData.waitUploadList;if (clcik && fileData.allSize > props.data.fileSize) {proxy.$message.error(`文件大小不能超过 ${props.data.fileSize} MB`);return;}let formData = new FormData();fileData.uploadList.forEach((item: any) => {formData.append(`${item.filePathName}`, item);});// // 遍历FormData对象并打印其内容:查看FormData对象数据是否正确// for (let [key, value] of formData.entries()) {// console.log(`${key}: ${value}`);// }emit('dragUploadAxiosFn', formData, fileData.uploadList); //上传参数抛出,外部做操作,不在组件做耦合
};//此方法:如果是文件夹,则会递归调用自己,所以最后都会走 else 的逻辑
const readFiles = async (item: any) => { if (item.isDirectory) {// 是一个文件夹const directoryReader = item.createReader();// readEntries是一个异步方法const entries: any[] = await new Promise((resolve, reject) => {directoryReader.readEntries(resolve, reject);});let files = [];for (const entry of entries) {const resultFiles: any = await readFiles(entry);files = files.concat(resultFiles);}return files;} else {// file也是一个异步方法const file = await new Promise((resolve, reject) => {item.file(resolve, reject);});let obj: any = getPath(item.fullPath); //通过路径获取名称方法changeFileSizeList(file, obj);return [file];}
};//更改 fileData.fileSizeList 的值:公共
const changeFileSizeList = (file: any, obj: any) => {file.filePathName = obj.filePathName; //添加路径名称file[fileData.firstFileName] = obj.pathObj[fileData.firstFileName]; //添加第一列文件名file.pathObj = obj.pathObj;let index = file.name.lastIndexOf('.');fileData.fileSizeList.push({ //添加图片路径、大小、名称filePathName: obj.filePathName,size: file.size,type: file.name.substring(index + 1),...obj.pathObj});
};//通过路径获取名称方法:公共
const getPath = (path: string) => {let filePathName: any = path; // 传给后端的全路径if (path.startsWith('/')) { // 如果路径以斜杠开头,则删除第一个斜杠filePathName = path.slice(1);}let parts = filePathName.split('/'); // 路径分割成数组let pathObj = {}; // 存储每个部分for (let i = 0; i < parts.length; i++) {if (parts[i] !== '') { // 跳过空字符串(如果路径以 / 开头或结尾)pathObj['pathName' + (i)] = parts[i];}}return {filePathName: filePathName,pathObj: pathObj}
};//关闭事件
const closeDialogFn = () => {if (fileData.step === 1) {props.data.visible = false; //关闭弹窗return;}proxy.$messageBox({title: '关闭',message: '关闭后不会保留您所做的更改,确定关闭吗?',callback: (value: string) => {//confirm=确认;cancel=取消if (value === 'confirm') {fileData.step = 1;props.data.visible = false; //关闭弹窗}}});
};
</script>
3、css 代码:
<style lang="scss" scoped>
.drag-box {position: relative;.progress-bar {position: absolute;z-index: 100;width: 100%;top: 0;left: 0px;right: 0px;bottom: -5px;display: flex;justify-content: center;align-items: center;background-color: rgba(255, 255, 255, 0.8);:deep(.el-progress.el-progress--line) {width: 100%;margin-left: 10px;}}.uploaded-list-wrap {max-height: 200px;overflow-y: auto;.uploaded-item {display: flex;justify-content: space-between;align-items: center;cursor: pointer;margin-bottom: 3px;.text-content {width: 80%;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}.icon {width: 25px;height: 25px;}.success-icon {display: block;}.delete-icon {display: none;}&:hover {.success-icon {display: none;}.delete-icon {display: block;}}}}
}.div-text {width: 100%;height: 250px;border: 1px dashed #00b7ee;border-radius: 10px;box-sizing: border-box;display: flex;flex-direction: column;justify-content: center;align-items: center;font-size: 18px;.click-txt {color: #00b7ee;cursor: pointer;}.btn-wrap {margin-top: 20px;}
}.min-h311 {min-height: 311px;
}.max-h311 {max-height: 311px;
}:deep(.el-dialog .el-dialog__header) {padding: 12px 16px;
}:deep(.el-dialog .el-dialog__body) {padding: 15px 30px 20px;
}</style>
4、vue 页面中使用:
<!-- 拖拽上传 -->
<DragUpload v-if="dragUpload.visible":data="dragUpload"@dragUploadAxiosFn="dragUploadAxiosFn"
/>
const dragUpload: any = reactive({visible: false,fileSize: 100, // 单位字节 MBimgType: ['png', 'jpg', 'jpeg'], // 图片类型限制fileName: 'SPU', // 超出后列表展示的文件名
});
5、上传到后端接口的参数:
6、效果图,如下:
7、额外补充,后端接收文件流的方法:
相关文章:

Vue3+TS 实现批量拖拽文件夹上传图片组件封装
1、html 代码: 代码中的表格引入了 vxe-table 插件 <Tag /> 是自己封装的说明组件 表格列表这块我使用了插槽来增加扩展性,可根据自己需求,在组件外部做调整 <template><div class"dragUpload"><el-dialo…...

二叉树的所有路径(力扣257)
因为题目要求路径是从上到下的,所以最好采用前序遍历。这样可以保证按从上到下的顺序将节点的值存入一个路径数组中。另外,此题还有一个难点就是如何求得所有路径。为了解决这个问题,我们需要用到回溯。回溯和递归不分家,每递归一…...

Python OrderedDict 实现 Least Recently used(LRU)缓存
OrderedDict 实现 Least Recently used(LRU)缓存 引言正文 引言 LRU 缓存是一种缓存替换策略,当缓存空间不足时,会移除最久未使用的数据以腾出空间存放新的数据。LRU 缓存的特点: 有限容量:缓存拥有固定的…...

LabVIEW项目中的工控机与普通电脑选择
工控机(Industrial PC)与普通电脑在硬件设计、性能要求、稳定性、环境适应性等方面存在显著差异。了解这些区别对于在LabVIEW项目中选择合适的硬件至关重要。下面将详细分析这两种设备的主要差异,并为LabVIEW项目中的选择提供指导。 硬件设…...

Ansys Speos | Speos Meshing 网格最佳实践
概述 网格划分是在各种计算应用中处理3D几何的基本步骤: 表面和体积:网格允许通过将复杂的表面和体积分解成更简单的几何元素(如三角形、四边形、四面体或六面体)来表示复杂的表面和体积。 模拟和渲染:网格是创建离散…...

elasticsearch segment数量对读写性能的影响
index.merge.policy.segments_per_tier 是一个配置选项,用于控制 Elasticsearch 中段(segment)合并策略的行为。它定义了在每一层的段合并过程中,允许存在的最大段数量。调整这个参数可以优化索引性能和资源使用。 假设你有一个索…...

全同态加密理论、生态现状与未来展望(中2)
《全同态加密理论、生态现状与未来展望》系列由lynndell2010gmail.com和mutourend2010gmail.com整理原创发布,分为上中下三个系列: 全同态加密理论、生态现状与未来展望(上):专注于介绍全同态加密理论知识。全同态加密…...

鸿蒙UI(ArkUI-方舟UI框架)-开发布局
返回主章节 → 鸿蒙UI(ArkUI-方舟UI框架) 开发布局 1、布局概述 1)布局结构 2)布局元素组成 3)如何选择布局 声明式UI提供了以下10种常见布局,开发者可根据实际应用场景选择合适的布局进行页面开发。 …...

RPC是什么?和HTTP区别?
RPC 是什么?HTTP 是什么? 作为一个程序员,假设我们需要从A电脑的进程发送一段数据到B电脑的进程,我们一般会在代码中使用 Socket 进行编程。 此时,可选性一般就是 TCP 和 UDP 二选一,由于 TCP 可靠、UDP 不…...

Linux C\C++编程-建立文件和内存映射
【图书推荐】《Linux C与C一线开发实践(第2版)》_linux c与c一线开发实践pdf-CSDN博客 《Linux C与C一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 Linu…...

行政纠错——pycorrector学习
pycorrector是一个开源中文文本纠错工具,它支持对中文文本进行音似、形似和语法错误的纠正。此工具是使用Python3进行开发的,并整合了Kenlm、ConvSeq2Seq、BERT、MacBERT、ELECTRA、ERNIE、Transformer等多种模型来实现文本纠错功能。pycorrector官方仓库…...

Go的defer原理
Go 的 defer 原理 defer 是 Go 语言中的一个关键字,用于延迟执行一个函数调用。它通常用于处理资源释放、连接关闭等操作,确保这些操作在函数返回之前执行。 1. 什么是 defer? defer 关键字用于延迟执行一个函数调用,直到包含它…...

Windows 下本地 Docker RAGFlow 部署指南
Windows 下本地 Docker RAGFlow 部署指南 环境要求部署步骤1. 克隆代码仓库2. 配置 Docker 镜像加速(可选)3. 修改端口配置(可选)4. 启动服务5. 验证服务状态6. 访问服务7. 登录系统8. 配置模型8.1 使用 Ollama 本地模型8.2 使用在线 API 服务9. 开始使用10. 常见问题处理端…...

专题三_穷举vs暴搜vs深搜vs回溯vs剪枝_全排列
dfs解决 全排列&子集 1.全排列 link:46. 全排列 - 力扣(LeetCode) 全局变量回溯 code class Solution { public:vector<vector<int>> ans;vector<int> cur;vector<bool> used;vector<vector<int>> permute…...

【IEEE Fellow 主讲报告| EI检索稳定】第五届机器学习与智能系统工程国际学术会议(MLISE 2025)
重要信息 会议时间地点:2025年6月13-15日 中国深圳 会议官网:http://mlise.org EI Compendex/Scopus稳定检索 会议简介 第五届机器学习与智能系统工程国际学术会议将于6月13-15日在中国深圳隆重召开。本次会议旨在搭建一个顶尖的学术交流平台…...

华为E9000刀箱服务器监控指标解读
美信监控易内置了数千种常见设备监测器,能够监测超过20万项指标。这些指标涵盖了从硬件设备到软件系统,从网络性能到安全状态等各个方面。如下基于美信监控易——IT基础监控模块,对华为E9000刀箱服务器部分监控指标进行解读。 一、华为E9000…...

【LC】2544. 交替数字和
题目描述: 给你一个正整数 n 。n 中的每一位数字都会按下述规则分配一个符号: 最高有效位 上的数字分配到 正 号。剩余每位上数字的符号都与其相邻数字相反。 返回所有数字及其对应符号的和。 示例 1: 输入:n 521 输出&…...

QT QTreeWidget控件 全面详解
本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…...

欧几里得算法求最小公倍数和最大公约数
一.最大公约数 gcd(a,b)gcd(b,a%b) 递归式,当且仅当b0,易得0和a的公约数为a.(可作为递归的出口) 证明: int gcd(int a, int b) {if (b 0) return a;else return gcd(b, a % b); } 二.最小公倍数 给定整数a b,求a b的最小公倍数 有图可知…...

Selenium配合Cookies实现网页免登录
文章目录 前言1 方案一:使用Chrome用户数据目录2 方案二:手动获取并保存Cookies,后续使用保存的Cookies3 注意事项 前言 在进行使用Selenium进行爬虫、网页自动化操作时,登录往往是一个必须解决的问题,但是Selenium每次…...

DeepSeek R1模型解读与使用
字节在春节前发布了doubao-1.5,它的官方介绍竟然是这样的: 这次发布了四个型号,doubao-1.5-pro-32k, doubao-1.5-pro-256k, doubao-1.5-lite-32k, doubao-1.5-vision-pro-32k,价格全部与上一个版本doubao模型一致,加量…...

Windows电脑不小心点击了关机,关机过程中如何阻止
如果电脑正在关机的过程中,想要阻止关机,可以尝试以下方法: 如果关机过程较慢,可以按下键盘组合键 Win R 打开运行窗口。输入 shutdown -a 后按回车键,这将中断关机操作(适用于 Windows 系统)…...

CNN-GRU卷积门控循环单元时间序列预测(Matlab完整源码和数据)
CNN-GRU卷积门控循环单元时间序列预测(Matlab完整源码和数据) 目录 CNN-GRU卷积门控循环单元时间序列预测(Matlab完整源码和数据)预测效果基本介绍CNN-GRU卷积门控循环单元时间序列预测一、引言1.1、研究背景与意义1.2、研究现状1…...

【吉林乡镇界】面图层shp格式arcgis数据乡镇名称和编码wgs84无偏移内容测评
标题中的“吉林省乡镇界面图层shp格式arcgis数据乡镇名称和编码wgs84无偏移”揭示了这是一个地理信息系统(GIS)相关的数据集,主要用于描绘吉林省的乡镇边界。这个数据集包含了一系列的文件,它们是ArcGIS软件能够识别和处理的Shape…...

fpga学习入门 串口rs232回环
奇偶检验位这里是省略了 做好回环后可以使用上位机做回环测试,top文件写的方式就是将rx(fpga端)接受到的模块(pc端)tx发送出去,这两个端口用杜邦线连接,同理模块的rx连接fpga的tx,…...

智启未来,AI筑梦科技新星”------华清远见成都中心2025冬令营圆满结束
2025年1月11日-16日,华清远见成都中心为期6天的“智启未来,AI筑梦科技新星”2025冬令营活动圆满结束。此次活动吸引了众多对人工智能和无人驾驶技术充满热情的学生参与,共同开启了一段点燃科技梦想的精彩旅程。 报道接待 以AI无人驾驶小车为核…...

接上篇基于Alertmanager 配置钉钉告警
Alertmanager 是一个用于处理和管理 Prometheus 警报的开源工具。它负责接收来自 Prometheus 服务器的警报,进行去重、分组、静默、抑制等操作,并通过电子邮件、PagerDuty、Slack 等多种渠道发送通知。 主要功能 去重:合并相同或相似的警报&a…...

DDD - 如何设计支持快速交付的DDD技术中台
文章目录 Pre概述打造快速交付团队烟囱式的开发团队(BAD)大前端技术中台(GOOD) 技术中台的特征简单易用的技术中台建设总结 Pre DDD - 软件退化原因及案例分析 DDD - 如何运用 DDD 进行软件设计 DDD - 如何运用 DDD 进行数据库设计 DDD - 服务、实体与值对象的两种设计思路…...

JAVA与数据结构-线性表
目录 一.线性表的概念 二.线性表的关系及分类 三.数组与顺序表 四.链表 1.静态链表(链表的的数组底层实现) 2.循环链表 3.双向链表 五.栈 1.栈的概念 2.栈的底层实现 3.共享空间栈 4.逆波兰表达式(后缀表达式) 5.栈与递归 六.…...

C++|开源日志库log4cpp和glog
文章目录 log4cpp 和 glog对比1. **功能对比**2. **易用性和配置**3. **性能**4. **线程安全**5. **日志输出**6. **功能扩展**7. **适用场景**8. **总结** 其它开源C日志库1. **spdlog**2. **easylogging**3. **Boost.Log**4. **loguru**5. **Poco Logging**6. **Qt Logging (…...