ofd文件轉pdf

主要后端使用Java實現,前端可隨意搭配http請求

添加依賴:

        <!-- OFD解析與轉換庫 --><dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>1.17.9</version></dependency><!-- PDFBox用于PDF生成 --><dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.29</version></dependency>

控制層代碼實現:

@CrossOrigin
@RestController
@RequestMapping("/tool")
public class ToolsController {@Autowiredprivate ToolsService toolsService;/*** 批量轉換OFD文件為PDF并打包下載*/@PostMapping("/batchofd2pdf")public ResponseEntity<byte[]> batchConvert(@RequestParam("files") MultipartFile[] ofdFiles) {if (ofdFiles == null || ofdFiles.length == 0) {return new ResponseEntity<>(HttpStatus.BAD_REQUEST);}try {// 構建文件名到輸入流的映射Map<String, InputStream> fileMap = new HashMap<>();for (MultipartFile file : ofdFiles) {if (!file.isEmpty() && file.getOriginalFilename().toLowerCase().endsWith(".ofd")) {fileMap.put(file.getOriginalFilename(), file.getInputStream());}}// 執行批量轉換Map<String, byte[]> pdfFiles = toolsService.batchConvert(fileMap);// 將所有PDF文件打包成ZIPtry (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();ZipOutputStream zipOut = new ZipOutputStream(byteOut)) {for (Map.Entry<String, byte[]> entry : pdfFiles.entrySet()) {zipOut.putNextEntry(new ZipEntry(entry.getKey()));zipOut.write(entry.getValue());zipOut.closeEntry();}zipOut.finish();// 設置響應頭,返回ZIP文件HttpHeaders headers = new HttpHeaders();headers.setContentDispositionFormData("attachment", "ofd_converted_pdfs.zip");headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);return new ResponseEntity<>(byteOut.toByteArray(), headers, HttpStatus.OK);}} catch (Exception e) {e.printStackTrace();return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);}}
}

service層代碼實現

package com.tool.service;import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ExecutionException;public interface ToolsService {Map<String,byte[]> batchConvert(Map<String, InputStream> fileMap) throws InterruptedException, ExecutionException;
}
@Service
public class ToolsServiceImpl implements ToolsService {// 線程池用于并行處理轉換任務private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);/*** 單個文件轉換:輸入流到輸出流*/public void convertOfdToPdf(InputStream ofdInputStream, OutputStream pdfOutputStream) throws Exception {ConvertHelper.toPdf(ofdInputStream, pdfOutputStream);}/*** 批量轉換多個OFD文件* @param fileMap 文件名到輸入流的映射* @return 文件名到PDF字節數組的映射*/@Overridepublic Map<String, byte[]> batchConvert(Map<String, InputStream> fileMap) throws InterruptedException, ExecutionException {Map<String, Future<byte[]>> futures = new HashMap<>();// 提交所有轉換任務到線程池for (Map.Entry<String, InputStream> entry : fileMap.entrySet()) {String fileName = entry.getKey();InputStream inputStream = entry.getValue();futures.put(fileName, executorService.submit(() -> {try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {convertOfdToPdf(inputStream, outputStream);return outputStream.toByteArray();} finally {inputStream.close();}}));}// 收集轉換結果Map<String, byte[]> results = new HashMap<>();for (Map.Entry<String, Future<byte[]>> entry : futures.entrySet()) {String fileName = entry.getKey().replace(".ofd", ".pdf");results.put(fileName, entry.getValue().get());}return results;}/*** 應用關閉時關閉線程池*/public void shutdownExecutor() {executorService.shutdown();}
}

前端實現:

<template><div class="container mx-auto px-4 py-8 max-w-6xl"><!-- 頁面標題 --><div class="text-center mb-8"><h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-gray-800 mb-2">OFD轉PDF批量轉換</h1><p class="text-gray-500">支持多文件上傳,一鍵批量轉換OFD文件為PDF格式</p></div><!-- 上傳區域 --><div class="p-6" style="margin: 10px 10px 10px 10px;"><el-uploadref="upload"class="upload-area"action="#":http-request="handleUpload":on-change="handleFileChange":on-remove="handleFileRemove":before-upload="beforeUpload":file-list="fileList":auto-upload="false"multipleaccept=".ofd"><el-button type="primary" :icon="Upload">選擇文件</el-button><template #tip><div class="el-upload__tip text-sm text-gray-500">支持上傳多個文件</div></template></el-upload></div><!-- 文件列表和進度 --><el-card v-if="fileList.length > 0" class="mb-6 transition-all duration-300 hover:shadow-md"><div class="p-4 border-b"><h2 class="font-semibold text-gray-800">文件列表</h2></div><el-table:data="fileList"bordersize="small"class="mb-0"><el-table-column prop="name" label="文件名" width="350"></el-table-column><el-table-column prop="size" label="大小" width="120"><template #default="scope">{{ formatFileSize(scope.row.size) }}</template></el-table-column><el-table-column prop="status" label="狀態" width="150"><template #default="scope"><el-tag:type="scope.row.status === 'ready' ? 'info' :scope.row.status === 'waiting' ? 'info' :scope.row.status === 'converting' ? 'warning' :scope.row.status === 'success' ? 'success' : 'danger'"size="small"><el-icon v-if="scope.row.status === 'converting'" class="mr-1"><Loading /></el-icon>{{ statusMap[scope.row.status] }}</el-tag></template></el-table-column><el-table-column label="進度" width="200"><template #default="scope"><el-progressv-if="scope.row.status === 'converting'":percentage="scope.row.progress"stroke-width="6"size="small"></el-progress><span v-else-if="scope.row.status === 'success'">100%</span><span v-else>-</span></template></el-table-column><el-table-column label="操作" width="120"><template #default="scope"><el-buttonv-if="scope.row.status === 'success'"type="text"size="small"text-color="#165DFF"@click="downloadFile(scope.row)"><el-icon class="mr-1"><Download /></el-icon>下載</el-button><el-buttonv-else-if="scope.row.status === 'waiting' || scope.row.status === 'error'"type="text"size="small"text-color="#F53F3F"@click="handleFileRemove(scope.row)"><el-icon class="mr-1"><Delete /></el-icon>刪除</el-button><span v-else>-</span></template></el-table-column></el-table></el-card><!-- 轉換進度彈窗 --><el-dialogtitle="轉換進度"v-model="showProgressDialog":close-on-click-modal="false":show-close="false"width="500px"><div class="mb-4"><p class="text-gray-600 mb-2">總進度:{{ totalProgress }}%</p><el-progress :percentage="totalProgress" stroke-width="8"></el-progress></div><div v-for="file in fileList" :key="file.uid" class="mb-2"><div class="flex justify-between text-sm mb-1"><span>{{ file.name }}</span><span>{{ file.progress }}%</span></div><el-progress :percentage="file.progress" stroke-width="4" size="small"></el-progress></div><template #footer><el-buttontype="default"@click="cancelConversion":disabled="!isCancellable">取消轉換</el-button></template></el-dialog><!-- 轉換完成提示 --><el-dialogtitle="轉換完成"v-model="showCompleteDialog"width="400px"><div class="text-center py-4">
<!--        <el-icon class="text-5xl text-success mb-4"><CheckCircle /></el-icon>--><p>所有文件轉換已完成</p><p class="text-gray-500 mt-2">成功:{{ successCount }} 個,失敗:{{ errorCount }} 個</p></div><template #footer><div class="text-center"><el-buttontype="primary"@click="downloadAllFiles":disabled="successCount === 0"><el-icon class="mr-1"><Download /></el-icon>下載全部</el-button><el-buttontype="default"@click="showCompleteDialog = false">關閉</el-button></div></template></el-dialog></div>
</template><script setup>
import { ref, computed, onBeforeUnmount } from 'vue';
import {DocumentAdd, Upload, Loading, Delete, Download
} from '@element-plus/icons-vue';
import {ElButton, ElMessage, ElNotification} from 'element-plus';
import axios from 'axios';// 文件列表
const fileList = ref([]);
// 上傳狀態
const isUploading = ref(false);
// 轉換狀態
const isConverting = ref(false);
// 進度彈窗顯示
const showProgressDialog = ref(false);
// 完成彈窗顯示
const showCompleteDialog = ref(false);
// 上傳組件引用
const upload = ref(null);
// 轉換請求取消令牌
const cancelTokenSource = ref(null);// 狀態映射
const statusMap = {ready: '等待轉換',waiting: '等待轉換',converting: '轉換中',success: '轉換成功',error: '轉換失敗'
};// 修復:轉換按鈕是否禁用的計算屬性
const isConvertDisabled = computed(() => {// 當沒有文件、正在上傳或正在轉換時禁用return fileList.value.length === 0 || isUploading.value || isConverting.value;
});// 計算屬性:總進度
const totalProgress = computed(() => {if (fileList.value.length === 0) return 0;const sum = fileList.value.reduce((acc, file) => acc + file.progress, 0);return Math.round(sum / fileList.value.length);
});// 計算屬性:成功和失敗數量
const successCount = computed(() => {return fileList.value.filter(file => file.status === 'success').length;
});const errorCount = computed(() => {return fileList.value.filter(file => file.status === 'error').length;
});// 計算屬性:是否可取消
const isCancellable = computed(() => {return isConverting.value && totalProgress.value < 100;
});// 文件大小格式化
const formatFileSize = (bytes) => {if (bytes === 0) return '0 B';const k = 1024;const sizes = ['B', 'KB', 'MB', 'GB'];const i = Math.floor(Math.log(bytes) / Math.log(k));return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};// 上傳前檢查
const beforeUpload = (file) => {// 檢查文件類型if (file.type !== '' && !file.name.toLowerCase().endsWith('.ofd')) {ElMessage.error('請上傳OFD格式的文件');return false;}// 檢查文件大小(限制50MB)const maxSize = 50 * 1024 * 1024;if (file.size > maxSize) {ElMessage.error('文件大小不能超過50MB');return false;}return true;
};// 文件變化處理 - 修復:確保文件正確添加到列表
const handleFileChange = (file, newFileList) => {// 同步更新文件列表fileList.value = newFileList;// 為新添加的文件設置初始狀態if (!file.status) {file.status = 'waiting';file.progress = 0;file.pdfUrl = null;}
};// 移除文件
const handleFileRemove = (file) => {fileList.value = fileList.value.filter(item => item.uid !== file.uid);
};// 清空文件列表
const clearFiles = () => {fileList.value = [];if (upload.value) {upload.value.clearFiles();}
};// 處理上傳(覆蓋默認上傳行為)
const handleUpload = () => {// 實際上傳由submitUpload處理,這里只是為了滿足組件要求
};// 提交轉換 - 修復:狀態管理更清晰
const submitUpload = async () => {console.log(1)if (fileList.value.length === 0) {ElMessage.warning('請先選擇文件');return;}console.log(2)// 重置文件狀態fileList.value.forEach(file => {file.status = 'converting';file.progress = 0;});console.log(3)// 更新狀態變量isUploading.value = true;isConverting.value = true;showProgressDialog.value = true;console.log(4)try {// 創建FormDataconst formData = new FormData();fileList.value.forEach(file => {formData.append('files', file.raw);});console.log(5)// 創建取消令牌cancelTokenSource.value = axios.CancelToken.source();console.log(6)// 模擬進度更新(實際項目中可以通過WebSocket或輪詢實現)const progressInterval = setInterval(() => {fileList.value.forEach(file => {if (file.status === 'converting' && file.progress < 100) {// 隨機增加進度,模擬真實場景const increment = Math.floor(Math.random() * 5) + 1;file.progress = Math.min(file.progress + increment, 100);}});}, 300);console.log(7)const postUrl = `http://10.60.128.250:8080/tool/batchofd2pdf`// 發送請求const response = await axios.post(postUrl, formData, {responseType: 'blob',cancelToken: cancelTokenSource.value.token,headers: {'Content-Type': 'multipart/form-data'}});console.log(8)// 清除進度模擬clearInterval(progressInterval);console.log(9)// 更新所有文件狀態為成功fileList.value.forEach(file => {file.status = 'success';file.progress = 100;// 創建下載URLfile.pdfUrl = URL.createObjectURL(response.data);});console.log(10)// 顯示完成彈窗showProgressDialog.value = false;showCompleteDialog.value = true;console.log(11)ElNotification.success({title: '轉換成功',message: `已成功轉換 ${fileList.value.length} 個文件`,duration: 3000});console.log(12)} catch (error) {if (axios.isCancel(error)) {// 取消操作fileList.value.forEach(file => {if (file.status === 'converting') {file.status = 'waiting';}});ElMessage.info('已取消轉換');} else {// 錯誤處理fileList.value.forEach(file => {if (file.status === 'converting') {file.status = 'error';}});ElMessage.error('轉換失敗:' + (error.response?.data?.message || error.message));}} finally {// 重置狀態變量console.log(14)isUploading.value = false;isConverting.value = false;showProgressDialog.value = false;console.log(15)}
};// 取消轉換
const cancelConversion = () => {if (cancelTokenSource.value) {cancelTokenSource.value.cancel('用戶取消了轉換');}
};// 下載單個文件
const downloadFile = (file) => {if (!file.pdfUrl) {ElMessage.warning('文件下載地址不存在');return;}// 創建a標簽下載const link = document.createElement('a');link.href = file.pdfUrl;link.download = file.name.replace('.ofd', '.pdf');document.body.appendChild(link);link.click();document.body.removeChild(link);
};// 下載所有文件
const downloadAllFiles = () => {// 這里應該下載ZIP包const firstPdfFile = fileList.value.find(file => file.status === 'success');if (firstPdfFile?.pdfUrl) {const link = document.createElement('a');link.href = firstPdfFile.pdfUrl;link.download = 'ofd_converted_pdfs.zip';document.body.appendChild(link);link.click();document.body.removeChild(link);showCompleteDialog.value = false;}
};// 組件卸載前清理
onBeforeUnmount(() => {// 釋放URL對象fileList.value.forEach(file => {if (file.pdfUrl) {URL.revokeObjectURL(file.pdfUrl);}});// 取消請求if (cancelTokenSource.value) {cancelTokenSource.value.cancel('組件已卸載');}
});
defineExpose({submitUpload,clearFiles
});
</script><style scoped>
.upload-area {border: 1px dashed #ccc;border-radius: 4px;padding: 20px;text-align: center;transition: border-color 0.3s;
}.upload-area:hover {border-color: #409eff;
}.upload-dropzone {transition: all 0.3s ease;
}::v-deep .el-progress__text {font-size: 12px !important;
}::v-deep .el-table__row:hover {background-color: #f5f7fa !important;
}
</style>

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

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

相關文章

4.應用層自定義協議與序列化

1.應用層程序員寫的一個個解決我們實際問題, 滿足我們日常需求的網絡程序, 都是在應用層1.1再談“協議”協議是一種 "約定". socket api 的接口, 在讀寫數據時, 都是按 "字符串" 的方式來發送接收的. 如果我們要傳輸一些 "結構化的數據" 怎么辦呢…

【QT搭建opencv環境】

本文參考以下文章&#xff1a; https://blog.csdn.net/weixin_43763292/article/details/112975207 https://blog.csdn.net/qq_44743171/article/details/124335100 使用軟件 QT 5.14.2下載地址&#xff1a;download.qt.io 選擇版本&#xff1a;Qt 5.14.2 Qt 5.14.2百度網盤鏈接…

golang--函數棧

一、函數棧的組成結構&#xff08;棧幀&#xff09; 每個函數調用對應一個棧幀&#xff0c;包含以下核心部分&#xff1a; 1. 參數區 (Arguments) 位置&#xff1a;棧幀頂部&#xff08;高地址端&#xff09;內容&#xff1a; 函數調用時傳入的參數按從右向左順序壓棧&#xff…

【FAQ】創建Dynamics 365 Sales環境

參考文章&#xff1a;5 分鐘內安裝 Dynamics 365 Sales 步驟 1&#xff1a;訪問 Power Platform 管理中心 導航到make.powerapps.com&#xff0c;然后點擊右上角的齒輪圖標。選擇管理中心&#xff0c;或者訪問aka.ms/ppac訪問 Power Platform 管理中心。 第 2 步&#xff1a…

【數據庫】使用Sql Server將分組后指定字段的行數據轉為一個字段顯示,并且以逗號隔開每個值,收藏不迷路

大家好&#xff0c;我是全棧小5&#xff0c;歡迎來到《小5講堂》。 這是《Sql Server》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。 溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01; 目錄前言示例數據集數…

7.項目起步(1)

1&#xff0c;項目起步-初始化項目并使用git管理創建項目并精細化配置src目錄調整git 管理項目2項目起步-配置別名路徑聯想提示什么是別名路徑聯想提示如何進行配置 &#xff08;自動配置了&#xff09;{"compilerOptions" : {"baseUrl" : "./",…

【C++詳解】深入解析繼承 類模板繼承、賦值兼容轉換、派生類默認成員函數、多繼承與菱形繼承

文章目錄一、繼承概念二、繼承定義定義格式繼承后基類成員訪問方式的變化類模板的繼承三、基類和派?類間的轉換(賦值兼容轉換)四、繼承中的作用域隱藏規則兩道筆試常考題五、派生類的默認成員函數四個常見默認成員函數實現?個不能被繼承的類六、繼承與友元七、繼承與靜態成員…

加法器 以及ALU(邏輯算術單元)

加法器框架&#xff0c;首先介紹原理&#xff0c;然后引入一位加法器最后再引入多位加法器最后引入帶符號的加法器這一節涉及到的硬件電路的知識理解就好&#xff0c;實在看不懂就跳過&#xff0c;但是封裝以后的功能必須看懂。這是一個一般的加法過程涉及到的必要元素圖中已經…

設計模式實戰:自定義SpringIOC(親手實踐)

上一篇&#xff1a;設計模式實戰&#xff1a;自定義SpringIOC&#xff08;理論分析&#xff09; 自定義SpringIOC&#xff08;親手實踐&#xff09; 上一篇文章&#xff0c;我們介紹了SpringIOC容器的核心組件及其作用&#xff0c;下面我們來動手仿寫一個SpringIOC容器&#…

力扣面試150(42/150)

7.28 20. 有效的括號 給定一個只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判斷字符串是否有效。 有效字符串需滿足&#xff1a; 左括號必須用相同類型的右括號閉合。左括號必須以正確的順序閉合。每個右括號都有一…

基于黑馬教程——微服務架構解析(二):雪崩防護+分布式事務

之前的兩篇文章我們介紹了微服務的基礎概念及其服務間通信機制。本篇將深入探討微服務的核心保障&#xff1a;服務保護與分布式事務。一、微服務保護問題描述&#xff1a; 在一個購物車的微服務中&#xff0c;倘若某一項服務&#xff08;服務A&#xff09;同一時刻訪問的數據十…

LeetCode: 429 N叉樹的層序遍歷

題目描述給定一個 N 叉樹&#xff0c;返回其節點值的層序遍歷&#xff08;即從左到右&#xff0c;逐層訪問每一層的所有節點&#xff09;。示例輸入格式&#xff08;層序序列化&#xff09;&#xff1a;輸入示意&#xff1a;1/ | \3 2 4/ \5 6輸出&#xff1a;[[1], [3,2,4…

使用phpstudy極簡快速安裝mysql

使用 phpStudy 極簡快速安裝 MySQL 的完整指南&#xff1a; 一、phpStudy 簡介 phpStudy 是一款 Windows 平臺下的 PHP 環境集成包&#xff0c;包含&#xff1a; Apache/Nginx PHP 5.x-7.x MySQL 5.5-8.0 phpMyAdmin 二、安裝步驟 1. 下載安裝包 訪問官網下載&#xf…

git lfs使用

apt install git lfs 或者下載二進制文件加到環境變量 https://github.com/git-lfs/git-lfs/releases git lfs install git lfs clone huggingface文件路徑 如果訪問不了hugggingface.co用hf-mirror.com替代&#xff0c;國內下載速度還是挺快的 先按照pip install modelscope m…

6、CentOS 9 安裝 Docker

&#x1f433; CentOS 9 安裝 Docker 最全圖文教程&#xff08;含鏡像源優化與常見問題解決&#xff09;標簽&#xff1a;CentOS 9、Docker、容器技術、開發環境、國內鏡像源 適合讀者&#xff1a;后端開發、運維工程師、Linux 初學者&#x1f4cc; 前言 在 CentOS 9 上安裝 Do…

SystemV消息隊列揭秘:原理與實戰

目錄 一、消息隊列的基本原理 1、基本概念 2、基本原理 3、消息類型的關鍵作用 4、重要特性總結 5、生命周期管理 6、典型應用場景 二、System V 消息隊列的內核數據結構 1、消息隊列的管理結構 msqid_ds&#xff08;消息隊列標識符結構&#xff09; 關鍵字段解析 2…

5 分鐘上手 Firecrawl

文章目錄Firecrawl 是什么&#xff1f;本地部署驗證mcp安裝palyground&#x1f525; 5 分鐘上手 FirecrawlFirecrawl 是什么&#xff1f; 一句話&#xff1a; 開源版的 “最強網頁爬蟲 清洗引擎” ? 自動把任意網頁 → 結構化 Markdown / JSON ? 支持遞歸整站抓取、JS 渲染…

算法訓練營day31 貪心算法⑤56. 合并區間、738.單調遞增的數字 、968.監控二叉樹

貪心算法的最后一篇博客&#xff01;前面兩道題都是比較簡單的思路&#xff0c;重點理解一下最后一道題即可。有一說一&#xff0c;進入到貪心算法這一章節之后&#xff0c;我的博客里和代碼注釋里的內容明顯少了很多&#xff0c;因為很多貪心的題目我覺得不需要很復雜的文字說…

Jenkins流水線部署+webhook2.0

文章目錄1. 環境2. 用到的插件3. 流水線部署腳本1. 環境 Centos7Jenkins2.5.0JDKopen17阿里云倉庫 注意&#xff1a;這個版本兼容需要特別注意&#xff0c;要不然會很麻煩 2. 用到的插件 Generic Webhook Trigger 3. 流水線部署腳本 兼容鉤子部署&#xff08;webhook&…

IDM下載失敗排查

網絡連接問題排查檢查網絡連接是否穩定&#xff0c;確保能夠正常訪問互聯網 測試其他下載工具或瀏覽器是否能夠正常下載 嘗試關閉防火墻或殺毒軟件&#xff0c;排除安全軟件攔截的可能性代理和VPN設置檢查確認IDM的代理設置是否正確&#xff0c;是否與系統代理一致 檢查是否使用…