UniApp 實現pdf上傳和預覽

一、上傳

1、html

<template><button @click="takeFile">pdf上傳</button>
</template>

2、JS

takeFile() {// #ifdef H5// H5端使用input方式選擇文件const input = document.createElement('input');input.type = 'file';input.accept = '.pdf';input.onchange = (e) => {if (e.target.files && e.target.files[0]) {this.handleSelectedFile(e.target.files[0]);}};input.click();// #endif// #ifdef MP-WEIXIN// 微信小程序選擇文件wx.chooseMessageFile({count: 1,type: 'file',extension: ['.pdf'],success: (res) => {if (res.tempFiles && res.tempFiles[0]) {this.handleSelectedFile(res.tempFiles[0]);}},fail: (err) => {console.error('選擇文件失敗:', err);this.showToast('選擇文件失敗,請重試');}});// #endif// #ifdef APP-PLUSif (plus.os.name === 'Android') {this.selectPdfAndroid();} else {this.selectPdfIOS();}// #endif},// Android端使用Intent選擇PDFselectPdfAndroid() {try {const Intent = plus.android.importClass('android.content.Intent');const Activity = plus.android.runtimeMainActivity();const intent = new Intent(Intent.ACTION_GET_CONTENT);intent.setType('application/pdf');intent.addCategory(Intent.CATEGORY_OPENABLE);Activity.startActivityForResult(intent, 1001);Activity.onActivityResult = (requestCode, resultCode, data) => {if (requestCode === 1001 && resultCode === Activity.RESULT_OK && data) {const uri = data.getData();this.handleAndroidUri(uri);}};} catch (error) {console.log('Android Intent失敗:', error);this.showToast('當前設備不支持文件選擇');}},// 處理Android URI - 簡化版本handleAndroidUri(uri) {try {const uriString = uri.toString();console.log('選擇的文件URI:', uriString);// 簡化處理:生成默認文件名,直接上傳const timestamp = Date.now();const fileName = `document_${timestamp}.pdf`;console.log('準備上傳文件:', { fileName, uriString });// 直接使用URI進行上傳,不獲取詳細信息this.selectedFile = {path: uriString,name: fileName,size: 0 // 不獲取大小,讓服務器端驗證};this.uploadPdf();} catch (error) {console.log('處理URI失敗:', error);this.showToast('文件處理失敗,請重試');}},// iOS端選擇PDFselectPdfIOS() {// iOS可以使用plus.gallery,設置過濾器plus.gallery.pick((path) => {const fileName = path.split('/').pop() || 'document.pdf';if (fileName.toLowerCase().endsWith('.pdf')) {this.selectedFile = {path: path,name: fileName,size: 0};this.uploadPdf();} else {this.showToast('請選擇PDF文件');}},(error) => {console.log('iOS選擇失敗:', error);this.showToast('文件選擇失敗');},{filter: 'file'});},/*** 處理選中的文件*/handleSelectedFile(file) {// 驗證文件類型if (!this.isPdfFile(file)) {this.showToast('請選擇PDF格式的文件');return;}// 驗證文件大小 (5MB)if (file.size > 5 * 1024 * 1024) {this.showToast('文件大小不能超過5MB');return;}console.log('pdf', file)this.selectedFile = file;// this.uploadProgress = 0;this.uploadPdf();},/*** 驗證是否為PDF文件*/isPdfFile(file) {// #ifdef H5return file.type === 'application/pdf' || file.name.endsWith('.pdf');// #endif// #ifdef MP-WEIXIN || APP-PLUSreturn file.name.endsWith('.pdf');// #endif},/*** 顯示提示消息*/showToast(message) {uni.showToast({title: message,icon: 'none'})}

3、打包配置

  /* android打包配置 */"android" : {"permissions" : ["<uses-feature android:name=\"android.hardware.camera\"/>","<uses-feature android:name=\"android.hardware.camera.autofocus\"/>","<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>","<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>","<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.CALL_PHONE\"/>","<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>","<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>","<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>","<uses-permission android:name=\"android.permission.INTERNET\"/>","<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>","<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>","<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>","<uses-permission android:name=\"android.permission.READ_LOGS\"/>","<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>","<uses-permission android:name=\"android.permission.VIBRATE\"/>","<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>","<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>","<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>","<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>","<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />","<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>","<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"/>","<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>"],"abiFilters" : []},/* ios打包配置 */"ios" : {"idfa" : false,"privacyDescription" : {"NSMicrophoneUsageDescription" : "用戶上傳視頻時需使用音頻信息","NSSpeechRecognitionUsageDescription" : "請允許使用您的語音識別,以便更好的體驗該應用","NSPhotoLibraryUsageDescription" : "需要用與評論上傳,頭像上傳功能","NSPhotoLibraryAddUsageDescription" : "保存圖片到本地","NSFaceIDUsageDescription" : "使用面部識別進行登錄","NSCameraUsageDescription" : "需要用與掃描二維碼和圖片拍攝","NSLocationWhenInUseUsageDescription" : "獲得地理位置,為您推薦最近的好友,允許嗎?","NSLocationAlwaysUsageDescription" : "需要您同意才能獲得您的地理位置,允許嗎?","NSLocationAlwaysAndWhenInUseUsageDescription" : "需要一直獲取您的地理位置信息,允許嗎?"},"dSYMs" : false},

二、預覽

1、預覽組件

<template><view class="container"><!-- 導航欄 --><!-- <view class="navbar"><view class="back-btn" @click="handleBack"><uni-icons type="left" size="24" color="#333"></uni-icons></view><view class="title">{{ title || 'PDF預覽' }}</view><view class="right-space"></view></view> --><!-- PDF      web-view組件:- 用于加載PDF預覽頁面- 注意:src需要在manifest.json中配置web-view的域名白名單--><web-view :src="webviewUrl" @message="handleWebviewMessage" class="webview":webview-styles="webviewStyles"></web-view><!-- 加載提示 --><!-- <view v-if="loading" class="loading-mask"><view class="loading-container"><uni-loading type="spinner" size="24"></uni-loading><view class="loading-text">加載中...</view></view></view> --></view>
</template><script>
export default {data() {return {title: "", // PDF標題pdfUrl: "", // PDF文件地址webviewUrl: "", // web-view加載的URLloading: true, // 加載狀態webviewStyles: {// width: '100%',// height: '90%'},};},onLoad(options) {// 獲取頁面參數this.title = options.title || "";this.pdfUrl = decodeURIComponent(options.url || "");// 驗證PDF URLif (!this.pdfUrl) {uni.showToast({title: "缺少PDF文件地址",icon: "none",duration: 2000,});setTimeout(() => {this.handleBack();}, 2000);return;}// #ifdef H5window.addEventListener('message', (e) => {// console.log(e,e.data.data[0].action)this.dealMessage(e.data.data[0])})// #endif// 初始化web-view URLthis.initWebviewUrl();},methods: {/*** 初始化web-view加載的URL*/initWebviewUrl() {// 構建PDF預覽頁面的URL(使用hybrid中的html頁面)// 注意:根據實際項目目錄結構調整路徑let viewerUrl = "/static/html/pdfViewer.html";// 處理不同平臺的路徑// #ifdef H5// 添加參數:PDF地址和標題const params = new URLSearchParams();params.append("url", this.pdfUrl);params.append("title", this.title);console.log(params);// H5平臺需要完整路徑viewerUrl = location.origin + viewerUrl + "?" + params.toString();// #endif// #ifndef H5// 非H5平臺直接拼接參數// 添加參數:PDF地址和標題viewerUrl =viewerUrl + "?" + "url=" + this.pdfUrl + "&title=" + this.title;// #endifthis.webviewUrl = viewerUrl;},/*** 處理web-view發送的消息*/handleWebviewMessage(e) {console.log(e)this.dealMessage(e.detail.data[0][0])},dealMessage(data) {if (data.action === "back") {// 處理返回事件this.handleBack();} else if (data.action === "loaded") {// 處理PDF加載完成事件this.loading = false;} else if (data.action === "error") {// 處理PDF加載錯誤this.loading = false;uni.showToast({title: "PDF加載失敗",icon: "none",duration: 2000,});}},/*** 處理返回按鈕點擊*/handleBack() {uni.navigateBack({delta: 1, // 返回的頁面數,1表示返回上一級success: function () {console.log("返回上一頁成功");},fail: function (error) {console.error("返回上一頁失敗", error);},});},},
};
</script><style lang="scss" scoped>
.container {display: flex;flex-direction: column;height: 100vh;
}.navbar {display: flex;align-items: center;height: 44px;background-color: #fff;border-bottom: 1px solid #eee;padding: 0 16px;/* #ifndef H5 */padding-top: 44px;height: 88px;/* #endif */box-sizing: border-box;
}.back-btn {width: 44px;height: 44px;display: flex;align-items: center;justify-content: center;
}.title {flex: 1;text-align: center;font-size: 17px;color: #333;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;
}.right-space {width: 44px;height: 44px;
}.webview {flex: 1;width: 100%;
}.loading-mask {position: fixed;top: 44px;left: 0;right: 0;bottom: 0;background-color: rgba(255, 255, 255, 0.8);display: flex;align-items: center;justify-content: center;z-index: 999;
}.loading-container {display: flex;flex-direction: column;align-items: center;
}.loading-text {margin-top: 12px;font-size: 14px;color: #666;
}
</style>

2、預覽頁面

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><metaname="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/><title>PDF預覽</title><!-- 引入PDF.js庫 --><script src="./pdf.min.js"></script><script type="text/javascript" src="./uni.webview.1.5.6.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {background-color: #f5f5f5;overflow-x: hidden;}#pdfContainer {padding: 16px;margin-top: 88px;}.pdf-page {width: 100%;margin-bottom: 16px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);background-color: #fff;border-radius: 4px;}.loading {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);text-align: center;}.loading-text {margin-top: 16px;font-size: 14px;color: #333;}.error-container {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);text-align: center;padding: 20px;}.error-text {font-size: 16px;color: #f66;margin-bottom: 20px;}.retry-btn {padding: 8px 16px;background: linear-gradient(300deg, #02c6ea 0%, #67f2b1 100%);color: white;border: none;border-radius: 20px;font-size: 14px;cursor: pointer;}.navbar {position: fixed;top: 0;left: 0;width: 100%;z-index: 99999;display: flex;width: 100%;align-items: center;background-color: #fff;border-bottom: 1px solid #eee;/* #ifndef H5 */padding-top: 44px;height: 88px;/* #endif */box-sizing: border-box;}.navbar.h5 {height: 44px;padding: 0 16px;}.back-btn {width: 44px;height: 44px;display: flex;align-items: center;justify-content: center;}.back-icon {width: 9px;height: 15px;background-image: url(./back.png);background-repeat: no-repeat;background-position: 0 0;background-size: 100% 100%;}.title {flex: 1;text-align: center;font-size: 17px;color: #333;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}.title > div {padding-right: 22px;}.right-space {width: 44px;height: 44px;}</style></head><body><div class="navbar" id="nav"><div class="back-btn" id="back"><div class="back-icon"></div></div><div class="title"><div>PDF預覽</div></div></div><div id="loading" class="loading"><div class="loading-text">加載中...</div></div><div id="error" class="error-container" style="display: none"><div class="error-text">PDF加載失敗</div><button class="retry-btn" id="retryBtn">重試</button></div><div id="pdfContainer"></div><script>let env = null;document.addEventListener("UniAppJSBridgeReady", function () {uni.getEnv(function (res) {env = res;console.log("當前環境:" + JSON.stringify(res));if (res.h5) {const cls = document.getElementById("nav").className;document.getElementById("nav").setAttribute("class", cls + " h5");//  console.log(cls)}});});// 向uni-app發送消息function sendMessage(action, data = {}) {console.log(env);if (env.h5) {// H5環境window.parent.postMessage({data: [{ action, ...data }],},"*");} else {uni.postMessage({data: [{ action, ...data }],});}}// 獲取URL參數function getUrlParams() {const params = {};const search = window.location.search.substring(1);const pairs = search.split("&");pairs.forEach((pair) => {const [key, value] = pair.split("=");if (key) {params[key] = decodeURIComponent(value || "");}});return params;}// 初始化PDF預覽async function initPdfViewer() {const params = getUrlParams();let pdfUrl = params.url;if (!pdfUrl) {showError();return;}try {// 處理本地文件路徑(App平臺)if (pdfUrl.startsWith("_doc/") || pdfUrl.startsWith("_downloads/")) {pdfUrl = window.location.origin + pdfUrl;}console.log(pdfUrl);// 配置PDF.jsconst pdfjsLib = window["pdfjs-dist/build/pdf"];pdfjsLib.GlobalWorkerOptions.workerSrc = "./pdf.worker.min.js";// 加載PDF文檔const loadingTask = pdfjsLib.getDocument(pdfUrl);const pdfDoc = await loadingTask.promise;console.log(pdfDoc);// 隱藏加載狀態,通知uni-app加載完成document.getElementById("loading").style.display = "none";sendMessage("loaded");// 渲染所有頁面for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {const page = await pdfDoc.getPage(pageNum);// 設置縮放比例以適應移動設備const viewport = page.getViewport({ scale: 1.2 });// 創建畫布元素const canvas = document.createElement("canvas");canvas.className = "pdf-page";document.getElementById("pdfContainer").appendChild(canvas);const context = canvas.getContext("2d");canvas.height = viewport.height;canvas.width = viewport.width;// 渲染頁面const renderContext = {canvasContext: context,viewport: viewport,};await page.render(renderContext).promise;}} catch (error) {console.error("PDF加載失敗:", error);showError();sendMessage("error", { message: error.message });}}// 顯示錯誤狀態function showError() {document.getElementById("loading").style.display = "none";document.getElementById("error").style.display = "block";}// 重試按鈕事件document.getElementById("retryBtn").addEventListener("click", () => {document.getElementById("error").style.display = "none";document.getElementById("loading").style.display = "block";document.getElementById("pdfContainer").innerHTML = "";initPdfViewer();});// 頁面加載完成后初始化window.addEventListener("DOMContentLoaded", initPdfViewer);window.onload = () => {document.getElementById("back").addEventListener("click", () => {console.log("back");sendMessage("back");});};// 監聽頁面返回事件(Android物理返回鍵)document.addEventListener("backbutton",() => {sendMessage("back");},false);</script></body>
</html>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/93662.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/93662.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/93662.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《用Proxy解構前端壁壘:跨框架狀態共享庫的從零到優之路》

一個項目中同時出現React的函數式組件、Vue的模板語法、Angular的依賴注入時,數據在不同框架體系間的流轉便成了開發者不得不面對的難題—狀態管理,這個本就復雜的命題,在跨框架場景下更顯棘手。而Proxy,作為JavaScript語言賦予開發者的“元編程利器”,正為打破這道壁壘提…

MOESI FSM的全路徑測試用例

MOESI FSM的全路徑測試用例摘要&#xff1a;本文首先提供一個UVM版本的測試序列&#xff08;基于SystemVerilog和UVM框架&#xff09;&#xff0c;設計為覆蓋MOESI FSM的全路徑&#xff1b;其次詳細解釋如何使用覆蓋組&#xff08;covergroup&#xff09;來量化測試的覆蓋率&am…

git倉庫和分支的關系

1?? 倉庫分支&#xff08;Repository Branch&#xff09;每個 Git 倉庫都有自己的分支結構。分支決定你當前倉庫看到的代碼版本。示例&#xff1a;倉庫分支只是局部修改&#xff0c;項目分支才是全局管理所有倉庫分支的概念。wifi_camera 倉庫&#xff1a; - main - dev - fe…

Linux的基本操作

Linux 系統基礎操作完整指南一、文件與目錄操作1. 導航與查看pwd (Print Working Directory)作用&#xff1a;顯示當前所在目錄的完整路徑示例&#xff1a;pwd → 輸出 /home/user/documents使用場景&#xff1a;當你在多層目錄中迷失時快速定位當前位置ls (List)常用選項&…

npm設置了鏡像 pnpm還需要設置鏡像嗎

npm配置鏡像后是否需要為pnpm單獨設置鏡像&#xff1f; 是的&#xff0c;即使您已經為npm設置了鏡像源&#xff08;如淘寶鏡像&#xff09;&#xff0c;仍然需要單獨為pnpm配置鏡像源。這是因為npm和pnpm是兩個獨立的包管理工具&#xff0c;它們的配置系統和環境變量是分離的&a…

Linux管道

預備知識&#xff1a;進程通信進程需要某種協同&#xff0c;協同的前提條件是通信。有些數據是用來通知就緒的&#xff0c;有些是單純的傳輸數據&#xff0c;還有一些是控制相關信息。進程具有獨立性&#xff0c;所以通信的成本可能稍微高一點&#xff1b;進程間通信前提是讓不…

基于Spring Boot的快遞物流倉庫管理系統 商品庫存管理系統

&#x1f525;作者&#xff1a;it畢設實戰小研&#x1f525; &#x1f496;簡介&#xff1a;java、微信小程序、安卓&#xff1b;定制開發&#xff0c;遠程調試 代碼講解&#xff0c;文檔指導&#xff0c;ppt制作&#x1f496; 精彩專欄推薦訂閱&#xff1a;在下方專欄&#x1…

腳手架開發-Common封裝基礎通用工具類<基礎工具類>

書接上文 java一個腳手架搭建_redission java腳手架-CSDN博客 以微服務為基礎搭建一套腳手架開始前的介紹-CSDN博客 腳手架開發-準備配置-進行數據初始化-配置文件的準備-CSDN博客 腳手架開發-準備配置-配置文件的準備項目的一些中間件-CSDN博客 腳手架開發-Nacos集成-CSD…

軟件系統運維常見問題

系統部署常見問題 環境配置、兼容性問題。生產與測試環境的操作系統、庫版本、中間件版本不一致&#xff0c;運行環境軟件版本不匹配。新舊版本代碼/依賴不兼容。依賴缺失或沖突問題。后端包啟動失敗&#xff0c;提示類/方法/第三方依賴庫找不到或者版本沖突。配置錯誤。系統啟…

2021 IEEE【論文精讀】用GAN讓音頻隱寫術騙過AI檢測器 - 對抗深度學習的音頻信息隱藏

使用GAN生成音頻隱寫術的隱寫載體 本文為個人閱讀GAN音頻隱寫論文&#xff0c;部分內容注解&#xff0c;由于原文篇幅較長這里就不再一一粘貼&#xff0c;僅對原文部分內容做注解&#xff0c;僅供參考詳情參考原文鏈接 原文鏈接&#xff1a;https://ieeexplore.ieee.org/abstra…

PWA技術》》漸進式Web應用 Push API 和 WebSocket 、webworker 、serviceworker

PWA # 可離線 # 高性能 # 無需安裝 # 原生體驗Manifest {"name": "天氣助手", // 應用全名"short_name": "天氣", // 短名稱&#xff08;主屏幕顯示&#xff09;"start_url": "/index.html&…

數據結構——棧和隊列oj練習

225. 用隊列實現棧 - 力扣&#xff08;LeetCode&#xff09; 這一題需要我們充分理解隊列和棧的特點。 隊列&#xff1a;隊頭出數據&#xff0c;隊尾入數據。 棧&#xff1a;棧頂出數據和入數據。 我們可以用兩個隊列實現棧&#xff0c;在這過程中&#xff0c;我們總要保持其…

Java基礎 8.19

目錄 1.局部內部類的使用 總結 1.局部內部類的使用 說明&#xff1a;局部內部類是定義在外部類的局部位置&#xff0c;比如方法中&#xff0c;并且有類名可以直接訪問外部類的所有成員&#xff0c;包含私有的不能添加訪問修飾符&#xff0c;因為它的地位就是一個局部變量。局…

從父類到子類:C++ 繼承的奇妙旅程(2)

前言&#xff1a;各位代碼航海家&#xff0c;歡迎回到C繼承宇宙&#xff01;上回我們解鎖了繼承的「基礎裝備包」&#xff0c;成功馴服了public、protected和花式成員隱藏術。但——??前方高能預警&#xff1a; 繼承世界的暗流涌動遠不止于此&#xff01;今天我們將勇闖三大神…

【圖像算法 - 16】庖丁解牛:基于YOLO12與OpenCV的車輛部件級實例分割實戰(附完整代碼)

庖丁解牛&#xff1a;基于YOLO12與OpenCV的車輛部件級實例分割實戰&#xff08;附完整代碼&#xff09; 摘要&#xff1a; 告別“只見整車不見細節”&#xff01;本文將帶您深入實戰&#xff0c;利用YOLO12-seg訓練實例分割模型&#xff0c;結合OpenCV的強大圖像處理能力&…

ubuntu22.04配置遠程桌面

文章目錄前言檢查桌面類型xorg遠程桌面(xrdp)安裝xrdpxrdp添加到ssl-certwayland遠程桌面(gnome-remote-desktop)檢查安裝開啟開啟狀況檢查自動登錄奇技淫巧前言 在windows上使用遠程桌面服務&#xff0c;連接ubuntu主機的遠程桌面 檢查桌面類型 查看桌面類型、協議 echo $…

SQL Server 中子查詢、臨時表與 CTE 的選擇與對比

在 SQL Server 的實際開發過程中&#xff0c;我們常常需要將復雜的查詢邏輯分解為多個階段進行處理。實現這一目標的常見手段有 子查詢 (Subquery)、臨時表 (Temporary Table) 和 CTE (Common Table Expression)。這三者在語法、執行效率以及可維護性方面各有優勢與局限。如何選…

肖臻《區塊鏈技術與應用》第20-22講 - 以太坊難度調整、權益證明和智能合約

以太坊的“冰河時代”:詳解難度調整算法與“難度炸彈” 摘要: 為了實現遠快于比特幣的十幾秒出塊速度,以太坊必須設計一套更為靈敏和復雜的挖礦難度調整算法。本文基于北京大學肖臻老師的公開課內容,深入剖析了以太坊獨特的逐塊難度調整機制。文章首先解釋了其維持15秒平均…

C++中內存池(Memory Pool)詳解和完整示例

1. 什么是內存池&#xff1f; 內存池&#xff08;Memory Pool / Pool Allocator&#xff09; 是一種內存管理機制&#xff0c;提前向系統申請一大塊內存&#xff0c;再在這塊內存里切分、分配和回收。 它相當于在用戶空間建立了一層 “小型堆管理器”&#xff0c;避免頻繁調用系…

測試 Next.js 應用:工具與策略

1. 引言 Next.js 作為一個基于 React 的全棧框架&#xff0c;在構建復雜 Web 應用時&#xff0c;測試是確保代碼質量、功能穩定性和用戶體驗的關鍵步驟。測試可以分為單元測試、集成測試和端到端測試三種類型&#xff0c;每種類型針對不同的層面&#xff1a;單元測試驗證單個組…