当前位置: 首页 > news >正文

Vue3 -- PDF展示、添加签名(带笔锋)、导出

文章目录

    • 笔锋签名
    • 方案一
      • 实现要点
      • 实现过程
        • 组件引用
        • 页面元素
        • 添加引用
        • 实现代码
        • 效果展示
        • 缺点
    • 方案二
      • 修改页面元素
      • 替换引用
      • 修改代码
      • 效果展示
    • 完整代码地址

实现功能的时候采用了两个方案,主要是第一个方案最后的实现效果并不太理想,但实现起来比较简单,要求不高时可以使用。
DEMO 会一次性加载并展示所有的 PDF 页面,目的是方便在手机上观看时上下滑动,如果要做成上一页下一页的效果,需要自行实现。

笔锋签名

我是用开源项目 smooth-signature 实现带笔锋签名的功能。
Gitee 地址是 https://github.com/linjc/smooth-signature

npm install --save smooth-signature

使用起来也比较简单,首先获取到需要操作的画布 canvas ,然后生成一个笔锋签名对象 SmoothSignature,optionSign 是初始化的一些简单属性。

const signature = new SmoothSignature(canvas, optionSign);

这样一来,我们的 canvas 就可以画线条了,同时我们可以通过 signature 去做一些操作,比如清空签名、撤回一步的操作等。

方案一

实现要点

  1. 读取 PDF 文件,并将 PDF 页面渲染到 Canvas 画布上,这里需要动态生成 Canvas
  2. 将每一个 Canvas 都包装成 SmoothSignature
  3. 添加一个标识,判断是否允许在 Canvas 上画线(手机滑动会和签名画线冲突,用按钮来控制什么时候允许画线)。
  4. 保存 PDF 时,先将每一个 Canvas 中的内容转化成图片格式 image/JPEG ,或者 image/PNG ,PNG格式的文件可能会比较大。
  5. 最后用生成的图片导出一个新的 PDF (实质上 PDF 每一页都是一张图片)。

实现过程

组件引用

smooth-signature笔锋签名
pdfjs-distPDF展示等功能
jspdfPDF导出相关功能
npm install --save smooth-signature
npm install --save pdfjs-dist@2.0.943
npm install --save jspdf 

页面元素

主要是读取文件、切至签名功能、切回预览功能、撤回签名、清除所有签名以及下载PDF的功能。

<template><div :class="`tab-header`"><div id="editor"><Input:class="`button-common`"type="file"ref="fielinput"accept=".pdf"id="fielinput"@change="uploadFile"/><Button :class="`button-common`" v-if="isSign" @click="handleSign">切回预览</Button><Button :class="`button-common`" v-else @click="handleSign">切至签名</Button><Button :class="`button-common`" @click="handleUndo">撤回</Button><Button :class="`button-common`" @click="handleClear">清除</Button><Button :class="`button-common`" @click="savePDF">下载PDF</Button></div><div><div id="parentDiv"><div ref="contentDiv" id="contentDiv"></div></div></div></div>
</template>
<script lang="ts">
引用
...... 
实现代码
......
</script>
<style lang="less" scoped>.tab-header {background: rgb(146, 175, 138);padding-left: 1%;padding-right: 1%;}.button-common {margin-right: 2px;max-width: 200px;}#contentDiv {// display: inline-block;}#parentDiv {position: absolute;overflow: auto;top: 5%;bottom: 1%;display: inline-block;}#signShower {position: absolute;left: 50%;top: 5%;bottom: 1%;display: inline-block;}
</style>

添加引用

这里要注意的是,需要给 pdfJS 指定工作路径

  import { Button, Input } from 'ant-design-vue';import { defineComponent, ref } from 'vue';import SmoothSignature from 'smooth-signature';import * as pdfJS from 'pdfjs-dist';import * as pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';import JsPDF from 'jspdf';pdfJS.GlobalWorkerOptions.workerSrc = pdfjsWorker;

实现代码

代码中添加了主要的注释,可以查看下述代码

  export default defineComponent({components: { Button, Input },setup() {const fielinput = ref(null);const contentDiv = ref(null);//签名相关const isSign = ref(false); //控制是否允许签名const canvass = ref([]); //保存所有画布元素const signatures = ref([]); //所有签名对象const historys = ref([]); //签名历史 用于回退或者清除,因为是一次性展示多个页面,会存在多个包装好的签名对象,存放历史列表方便操作//PDF展示相关const pdfData = ref(null); // PDF 内容const scale = ref(2); //放大比例 ,有的时候展示可能会比较模糊,可以放大展示//上传控件选择事件,加载选中的 PDF 文件const uploadFile = (e: Event) => {// 断言为HTMLInputElementconst target = e.target as HTMLInputElement;const files = target.files;let reader = new FileReader();reader.readAsDataURL(files[0]);reader.onload = () => {let data = atob(reader.result.substring(reader.result.indexOf(',') + 1));loadPdfData(data);};};//加载PDFfunction loadPdfData(data) {//移除所有旧的 Canvas 画布元素removeChild();//重置对象状态isSign.value = false;canvass.value = [];signatures.value = [];// 引入pdf.js的字体,如果没有引用的话字体可能会不显示let CMAP_URL = 'https://unpkg.com/pdfjs-dist@2.0.943/cmaps/';//读取base64的pdf流文件pdfData.value = pdfJS.getDocument({data: data, // PDF base64编码cMapUrl: CMAP_URL,cMapPacked: true,});//渲染全部页面renderAllPages();}//移除页面上旧的元素function removeChild() {var content = contentDiv.value;var child = content.lastElementChild;while (child) {content.removeChild(child);child = content.lastElementChild;}}//渲染全部页面function renderAllPages() {pdfData.value.promise.then((pdf) => {for (let i = 1; i <= pdf.numPages; i++) {pdf.getPage(i).then((page) => {let viewport = page.getViewport(scale.value);//动态生成 Canvas 画布并设置宽高var canvas = document.createElement('canvas');canvas.height = viewport.height;canvas.width = viewport.width;let ctx = canvas.getContext('2d');let renderContext = {canvasContext: ctx,viewport: viewport,};//将 PDF 页面渲染到 Canvas 上page.render(renderContext).then(() => {});//将画布包装成 SmoothSignatureinitSignatureCanvas(canvas);//将画布元素放入到 div 容器中展示canvass.value.push(canvas);contentDiv.value.appendChild(canvas);});}});}//初始化签名对象const initSignatureCanvas = (canvas) => {const optionSign = {width: canvas.width,height: canvas.height,maxHistoryLength: 100, //最大历史记录};const signature = new SmoothSignature(canvas, optionSign);//初始化时 先移除它内部添加的监听事件,默认不能签名signature.removeListener();//签名对象 addHistory 方法做一下修改,在原来逻辑的基础上添加这一行// historys.value.push(signature); 方便处理历史签名记录signature.addHistory = function () {if (!signature.maxHistoryLength || !signature.canAddHistory) return;signature.canAddHistory = false;signature.historyList.push(signature.canvas.toDataURL());signature.historyList = signature.historyList.slice(-signature.maxHistoryLength);historys.value.push(signature);};signatures.value.push(signature);};/*** 签名预览转换* 允许签名:调用 signature 对象中的 addListener 方法,添加监听事件* 不允许签名:调用 signature 对象中的 removeListener 方法,移除监听事件*/const handleSign = () => {isSign.value = !isSign.value;if (signatures.value && signatures.value.length > 0) {if (isSign.value) {for (let i = 0; i < signatures.value.length; i++) {signatures.value[i].addListener();}} else {for (let i = 0; i < signatures.value.length; i++) {signatures.value[i].removeListener();}}}};/*** 后退操作* 调用历史签名记录中的 signature 对象中的 undo 方法会撤回当前对象中的最后一次的画线记录* 注意:后退后不要忘记将列表中最后一个元素移除*/const handleUndo = () => {if (historys.value && historys.value.length > 0) {const signatureList = historys.value;let signature = signatureList.pop();signature.undo();historys.value = signatureList;}};// 清除所有 循环把所有签名历史都处理了const handleClear = async () => {while (historys.value && historys.value.length > 0) {handleUndo();}};// 下载PDFconst savePDF = () => {//生成新的 PDFlet pdf = new JsPDF('', 'pt', 'a4');if (canvass.value.length > 0) {//将 canvas 内容转化成 JPEGfor (let i = 0; i < canvass.value.length; i++) {const ccccc = canvass.value[i];let pageData = ccccc.toDataURL('image/JPEG');if (i > 0) {pdf.addPage();}pdf.addImage(pageData,'JPEG',0,0,ccccc.width / scale.value,ccccc.height / scale.value,);}//到处新的PDF return pdf.save('TestPdf.pdf');}};return {fielinput,uploadFile,contentDiv,isSign,handleSign,handleUndo,handleClear,savePDF,};},mounted() {},});

效果展示

在这里插入图片描述
在这里插入图片描述

缺点

1、生成的新的PDF每一页都是一个图片,这就表示 PDF 中的内容无法被解析,文字再也无法被选中了。
2、因为生成的是图片,所以最终效果可能会变模糊,可以通过放大比例去优化展示效果,但是始终不是一个最优的解决方案。

方案二

方案二使用一个新的组件 pdf-lib 来处理最后生成的 PDF
方案二仍旧使用 pdfjs-distCanvas 上展示 PDF,并使用 smooth-signature 使得画布拥有笔锋签名效果。
不同的是,这一次签名画布和 PDF 展示画布并不再是同一个画布,而是上下重叠的两个分离的画布
这样一来,我们可以将签名画布上的内容生成一个透明背景的 PNG 图片,然后以水印的方式添加到原来的 PDF 文件中。

修改页面元素

需要两个 Div 容器 ,父容器的滚动条需要同步滚动,否则会出现签名在滚动,但是 PDF 页面不动的情况

<template><div :class="`tab-header`"><div id="editor"><Input:class="`button-common`"type="file"ref="fielinput"accept=".pdf"id="fielinput"@change="uploadFile"/><Button :class="`button-common`" v-if="isSign" @click="handleSign">点击预览</Button><Button :class="`button-common`" v-else @click="handleSign">点击签名</Button><Button :class="`button-common`" @click="handleUndo">撤回</Button><Button :class="`button-common`" @click="handleClear">清除</Button><Button :class="`button-common`" @click="savePDF">下载PDF</Button></div><div><div id="parentDiv1"><div ref="contentDiv" id="contentDiv"></div></div><div id="parentDiv2"><div ref="signContentDiv" id="signContentDiv"></div></div></div></div>
</template>

替换引用

 //import JsPDF from 'jspdf';import { PDFDocument } from 'pdf-lib';

修改代码

文章底部附完整代码

...
const signCanvass = ref([]); //保存所有签名画布
const base64 = ref(null);	//读取的pdf的base64数据

上传文件的方法中添加一行保存PDF base64 ,生成新的 PDF 时使用

const uploadFile = (e: Event) => {...reader.onload = () => {base64.value = reader.result;...};
};

加载 PDF 时,我们要重置的对象增加了,而且加载完之后我们要让两个父容器滚动同步

function loadPdfData(data) {removeChild();...signCanvass.value = []; //重置...renderAllPages();//两个DIV协同滚动var div1 = document.getElementById('parentDiv1');var div2 = document.getElementById('parentDiv2');div1.addEventListener('scroll', function () {div2.scrollLeft = div1.scrollLeft;div2.scrollTop = div1.scrollTop;});div2.addEventListener('scroll', function () {div1.scrollLeft = div2.scrollLeft;div1.scrollTop = div2.scrollTop;});
}

移除页面元素的时候,我们要将两个 div 容器中的元素都移除掉

function removeChild() {var content = contentDiv.value;var child = content.lastElementChild;while (child) {content.removeChild(child);child = content.lastElementChild;}var signContent = signContentDiv.value;var child2 = signContent.lastElementChild;while (child2) {signContent.removeChild(child2);child2 = signContent.lastElementChild;}
}

渲染 PDF 页面的时候,每一个页面都会生成两个相同大小的画布,一个用来展示,一个用来签名,两个画布是重叠的。

function renderAllPages() {pdfData.value.promise.then((pdf) => {for (let i = 1; i <= pdf.numPages; i++) {pdf.getPage(i).then((page) => {// 获取DOM中为预览PDF准备好的canvasDOM对象let viewport = page.getViewport(scale.value);var canvas = document.createElement('canvas');//用来展示var sighCanvas = document.createElement('canvas');//用来签名canvas.height = viewport.height;canvas.width = viewport.width;sighCanvas.height = viewport.height;sighCanvas.width = viewport.width;let ctx = canvas.getContext('2d');let renderContext = {canvasContext: ctx,viewport: viewport,};page.render(renderContext).then(() => {});initSignatureCanvas(sighCanvas);canvass.value.push(canvas);signCanvass.value.push(sighCanvas);contentDiv.value.appendChild(canvas);signContentDiv.value.appendChild(sighCanvas);});}});
}

主要是保存 PDF 的功能与原来完全不一样。
因为我们前面说的签名画布和 PDF 页是同步生成的,所以页码(下标)也是相对应的。
所以我们只要把签名页面转成一个透明背景的 PNG ,然后添加到 PDF 对应页码的页面上,新的 PDF 文件就是我们需要的签名文件 。

const savePDF = async () => {const pdfDoc = await PDFDocument.load(base64.value);const pages = pdfDoc.getPages();for (let i = 0; i < pages.length; i++) {//对应下标的 签名画布中的内容生成 png图片const eleImgCover = await pdfDoc.embedPng(signCanvass.value[i].toDataURL('image/PNG'));//页面中添加水印pages[i].drawImage(eleImgCover, {x: 0,y: 0,width: eleImgCover.width / scale.value, //这里要进行缩放,因为之前的画布我们是放大过的height: eleImgCover.height / scale.value, //这里要进行缩放,因为之前的画布我们是放大过的});}//生成blob流const pdfBytes = await pdfDoc.save();saveBlob(pdfBytes, 'TestPdf');
};
//网上找的 保存 bolb流 的方法
function saveBlob(data, fileName) {if (typeof window.navigator.msSaveBlob !== 'undefined') {window.navigator.msSaveBlob(new Blob([data], { type: 'application/pdf' }),fileName + '.pdf',);} else {let url = window.URL.createObjectURL(new Blob([data], { type: 'application/pdf' })); //定义下载的链接let link = document.createElement('a'); //创建一个超链接元素link.style.display = 'none'; //隐藏该元素link.href = url; //创建下载的链接link.setAttribute('download', fileName + '.pdf');document.body.appendChild(link);link.click(); //点击下载document.body.removeChild(link); //下载完成移除元素window.URL.revokeObjectURL(url); //释放掉blob对象}
}

效果展示

文字内容可以解析、能够被选中
在这里插入图片描述
在这里插入图片描述

完整代码地址

方案一
方案二

相关文章:

Vue3 -- PDF展示、添加签名(带笔锋)、导出

文章目录笔锋签名方案一实现要点实现过程组件引用页面元素添加引用实现代码效果展示缺点方案二修改页面元素替换引用修改代码效果展示完整代码地址实现功能的时候采用了两个方案&#xff0c;主要是第一个方案最后的实现效果并不太理想&#xff0c;但实现起来比较简单&#xff0…...

行测-判断推理-图形推理-样式规律-属性规律-曲直性

左边的图全是由曲线构成的选C1 3 5全是由曲线构成的2 4 6全是由直线构成的第三行的图形有曲有直选A1 3 5有曲有直2 4 6全是直线选D图形有曲有直&#xff0c;排除B D外曲内直->内曲外直->外曲内直->内曲外直->外曲内直->内曲外直所以问号出的图形应该是内曲外直选…...

idea集成Alibaba Cloud Toolkit插件

idea集成Alibaba Cloud Toolkit插件 ​ 使用该插件主要是简化打包、上传、启动服务的相关操作。 ​ 很早之前的方式是使用开发工具&#xff08;eclipse,idea&#xff09;&#xff0c;使用maven命令完成项目打包&#xff08;这里指jar&#xff09;&#xff0c;然后通过shell工…...

Win11 文件夹打开慢或卡顿解决方案

问题 目前是 2023/2/27, 我的 Win11 系统点开一个文件夹要等待 2-3 秒才能加载出来, 使用体验极差。网上查阅大量资料, 有些人在系统更新后这个情况就消失了, 但是我这一直存在, 系统也是当前的最新版, 没有修复。 目前得出的结论是, 因为 Win11 的工具栏占用了过多的资源, 需…...

【PostgreSQL的idle in transaction连接状态】

在平时查询pg_stat_activity这个视图的时候&#xff0c;每一行包含了一个进程的相关信息&#xff0c;包含当前正在执行的SQL&#xff0c;或者会话的状态等等&#xff0c;state字段表示当前进程的状态。在PostgreSQL数据库里&#xff0c;其实代码里总共定义了7种BackendState&am…...

cityengine自定义纹理库资源

背景 cityengine虽然可以将shp生成带纹理的三维模型,但是纹理不一定满足我们的要求,这时候我们就想用我们自己制作的纹理 粗略了解规则文件 了解Building_From_Footprint.cga这个规则文件,具体文件位置默认在 “C:\Users[电脑用户名:如Administrator]\Documents\CityEng…...

taobao.top.secret.bill.detail( 服务商的商家解密账单详情查询 )

&#xffe5;免费必须用户授权 服务商的商家解密账单详情查询&#xff0c;仅对90天内的账单提供SLA保障。 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient…...

2023软件测试金三银四常见的软件测试面试题-【抓包和网络协议篇】

八、抓包与网络协议 8.1 抓包工具怎么用 我原来的公司对于抓包这块&#xff0c;在App的测试用得比较多。我们会使用fiddler抓取数据检查结果&#xff0c;定位问题&#xff0c;测试安全&#xff0c;制造弱网环境; 如&#xff1a;抓取数据通过查看请求数据&#xff0c;请求行&…...

vue脚手架多页自动化生成实践

前言 在前端开发过程中&#xff0c;常常面对多种业务场景。到目前为止&#xff0c;前端对于不同场景的处理通常会采用不同的渲染方案来组合处理&#xff0c;常见的渲染方案包括&#xff1a;CSR(Client Side Rendering)、SSR(Server Side Rendering)、SSG(Static Site Generati…...

【SQL语句优化】

SQL语句优化是提高数据库查询性能的重要手段之一&#xff0c;下面是几种常见的SQL语句优化方法和案例&#xff1a; 减少查询的数据量 减少查询的数据量&#xff1a;使用 WHERE 子句和索引来限制检索行数&#xff0c;只检索需要的行&#xff0c;避免检索全部行数据。 例子&am…...

阿里P8:做测试10年我的一些经验分享,希望你们少走弯路

我是在2015年毕业的&#xff0c;当时是读的普通本科&#xff0c;不上不下的专业水平&#xff0c;毕业的时候&#xff0c;恰好遇到了金融危机。校园招聘里阴差阳错的巧合&#xff0c;让我走上了软件测试工程师的道路。 入职第一天&#xff0c;来了个高大上的讲师&#xff0c;记…...

栈在括号匹配中的应用(栈/链栈 纯C实现)

目录 1 问题背景 2 具体思路 3 代码实现 3.1 顺序栈实现 3.2 链栈实现 1 问题背景 栈的括号匹配问题是指在给定一个字符串&#xff08;包含多种括号&#xff09;&#xff0c;判断其中的括号是否能够正确匹配&#xff0c;即每个左括号是否有一个对应的右括号与之匹配&#x…...

C语言Switch语句用法

C switch 语句 一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case&#xff0c;且被测试的变量会对每个 switch case 进行检查。 语法 C 语言中 switch 语句的语法&#xff1a; switch(expression){case constant-expression :statement(s);break;…...

Curl编码请求参数,API接口请求示例参数

请求参数请求参数&#xff1a;num_iid610947572360 参数说明&#xff1a;num_iid:1688商品ID sales_data:&sales_data1 获取近30天成交数据 agent:&agent1 获取1688分销代发价格数据请求示例 测试入口 Curl PHP PHPsdk JAVA C# Python-- 请求示例 url 默认请求参数已经…...

【C/C++】类型限定符extern、const、Volatile、register

1、extern&#xff1a; 声明一个变量&#xff0c;extern声明的变量没有建立存储空间。 extern int a ; //变量在定义的时候创建存储空间。 ①当我们在编译器中试图运行以下代码&#xff0c;系统会报错。 错误原因是“无法解析外部符号_a”.系统认为变量a是没有开辟内存空间的…...

day54【代码随想录】二刷数组

文章目录前言一、二分查找&#xff08;力扣724&#xff09;二、移除元素&#xff08;力扣27&#xff09;【双指针】三、有序数组的平方&#xff08;力扣977&#xff09;【双指针】四、合并两个有序数组&#xff08;力扣88&#xff09;五、长度最小的子数组&#xff08;力扣209&…...

哪个品牌蓝牙耳机性价比高?性价比高的平价蓝牙耳机推荐

现如今&#xff0c;随着蓝牙技术的进步&#xff0c;蓝牙耳机在人们日常生活中的便捷性更胜从前。越来越多的蓝牙耳机品牌被大众看见、认可。那么&#xff0c;哪个品牌的蓝牙耳机性价比高&#xff1f;接下来&#xff0c;我给大家推荐几款性价比高的平价蓝牙耳机&#xff0c;一起…...

揭秘关于TFRcord的五脏六腑

揭秘关于TFRcord的五脏六腑 前言&#xff1a;本篇文章将演示如何创建、解析和使用tf.Example消息&#xff0c;以及如何在.tfrecord文件之间对tf.Example消息进行序列化、写入和读取。 教程讲解使用的都是结构化数据&#xff0c;文章最后还会演示如果将图片写成.tfrecord文件&am…...

【Shell学习笔记】3.Shell 传递参数及数组

前言 本章介绍Shell的传递参数和数组。 Shell 传递参数 我们可以在执行 Shell 脚本时&#xff0c;向脚本传递参数&#xff0c;脚本内获取参数的格式为&#xff1a;$n。n 代表一个数字&#xff0c;1 为执行脚本的第一个参数&#xff0c;2 为执行脚本的第二个参数&#xff0c;…...

【终结Bug】ModuleNotFoundError: No module named ‘cv2’

解决方案&#xff1a; 打开 cmd键入 pip install opencv_python -i https://pypi.tuna.tsinghua.edu.cn/simple...

GNN实战:Cora、Citeseer、PubMed三大文献数据集保姆级使用指南(附代码)

GNN实战&#xff1a;Cora、Citeseer、PubMed三大文献数据集深度解析与工程实践 引言&#xff1a;为什么这三个数据集成为GNN研究的"黄金标准"&#xff1f; 在探索图神经网络&#xff08;GNN&#xff09;的浩瀚宇宙中&#xff0c;Cora、Citeseer和PubMed如同三颗璀璨的…...

从EWA Splatting到3DGS:一阶泰勒展开如何保住高斯的“椭圆”形状?

从EWA Splatting到3DGS&#xff1a;一阶泰勒展开如何保住高斯的“椭圆”形状&#xff1f; 在计算机图形学的演进历程中&#xff0c;三维高斯分布&#xff08;3D Gaussian&#xff09;的投影问题一直是个既基础又关键的挑战。想象一下&#xff0c;当你试图将一个完美的三维椭球投…...

OpenClaw多场景实战:Qwen3-VL:30B在飞书中的5个应用案例

OpenClaw多场景实战&#xff1a;Qwen3-VL:30B在飞书中的5个应用案例 1. 为什么选择OpenClawQwen3-VL:30B组合 去年我在团队内部尝试搭建智能助手时&#xff0c;发现市面上大多数方案要么需要将敏感数据上传到第三方平台&#xff0c;要么功能过于单一。直到遇到OpenClaw这个开…...

AHT20传感器数据漂移?STM32硬件I2C与软件模拟的稳定性对比测试

STM32硬件I2C与软件模拟I2C在AHT20传感器应用中的稳定性深度解析 工业级环境监测系统对温湿度数据的可靠性有着严苛要求。AHT20作为一款高精度温湿度传感器&#xff0c;其数据采集的稳定性直接关系到整个系统的可信度。本文将深入探讨STM32平台下硬件I2C与GPIO模拟I2C两种实现方…...

DAMO-YOLO部署教程:SSL证书配置与HTTP自动跳转HTTPS设置

DAMO-YOLO部署教程&#xff1a;SSL证书配置与HTTP自动跳转HTTPS设置 1. 引言 当你成功部署了DAMO-YOLO智能视觉探测系统后&#xff0c;可能会发现浏览器提示"不安全"的警告。这是因为默认的HTTP协议缺乏加密保护&#xff0c;对于涉及图像处理的AI系统来说&#xff…...

这次终于选对了!高效论文写作全流程AI论文写作软件推荐(2026 最新)

2026年AI论文写作软件已全面升级&#xff0c;论文写作全流程可拆解为文献调研→选题/开题→大纲/初稿→文献综述→降重/去AI味→润色/格式→查重/投稿七大环节&#xff0c;以下工具按环节精准匹配&#xff0c;兼顾中文适配、降重能力、去AI痕迹、学术合规四大核心需求&#xff…...

【跟韩工学Ubuntu第5课】-第5章 网络管理:Netplan、路由与防火墙-004篇-Ubuntu Server 网络管理:进阶配置、优化与实战诊断

文章目录 Ubuntu Server 网络管理:进阶配置、优化与实战诊断 (扩容优化版 | 适配高校教学+生产实战 | 30页核心内容) 5.1 网络基础:深入理解与实践查看(扩容+优化) 一、核心概念进阶(新增计算案例+场景区分) 二、必备诊断命令(新增高频参数+中文注释) 三、IPv6 完整配…...

VLSI设计实战:手把手教你用SPICE模型搭建9种基础电路(附完整代码)

VLSI设计实战&#xff1a;手把手教你用SPICE模型搭建9种基础电路&#xff08;附完整代码&#xff09; 在集成电路设计的浩瀚宇宙中&#xff0c;SPICE模型就像工程师手中的瑞士军刀。我第一次接触SPICE仿真时&#xff0c;面对密密麻麻的网表文件完全不知所措——直到导师扔给我一…...

2026论文写作工具红黑榜:AI论文网站怎么选?这份榜单够用!

红榜优先选千笔AI、ThouPen、豆包&#xff0c;适配国内学术规范&#xff1b;黑榜避开低质免费工具、无真实引用平台、过度依赖全文生成的工具&#xff0c;优先按需求匹配三维模型&#xff08;需求匹配度 - 数据可信度 - 成本承受力&#xff09;。 一、红榜&#xff1a;10 款高分…...

Phi-3-Mini-128K应用场景:新能源电池BMS固件日志智能归因与故障预测

Phi-3-Mini-128K应用场景&#xff1a;新能源电池BMS固件日志智能归因与故障预测 想象一下&#xff0c;你是一家新能源车企的BMS&#xff08;电池管理系统&#xff09;软件工程师。凌晨三点&#xff0c;你的手机响了&#xff0c;生产线告警&#xff1a;一批电池包的固件在测试中…...