鴻蒙OSUniApp 實現圖片上傳與壓縮功能#三方框架 #Uniapp

UniApp 實現圖片上傳與壓縮功能

前言

在移動應用開發中,圖片上傳是一個非常常見的需求。無論是用戶頭像、朋友圈圖片還是商品圖片,都需要上傳到服務器。但移動設備拍攝的圖片往往尺寸較大,直接上傳會導致流量消耗過大、上傳時間過長,影響用戶體驗。因此,圖片壓縮成為了移動應用開發中的必備技能。

通過 UniApp 實現圖片上傳與壓縮功能,既能滿足用戶體驗需求,又能減輕服務器負擔。今天就來分享一下我在實際項目中使用的圖片上傳與壓縮方案,希望能對大家有所幫助。

技術方案分析

在 UniApp 中實現圖片上傳與壓縮,主要涉及以下幾個方面:

  1. 圖片選擇:通過 uni.chooseImage() 實現
  2. 圖片壓縮:通過 canvas 實現
  3. 圖片上傳:通過 uni.uploadFile() 實現

這個方案的優點是:

  • 壓縮在客戶端進行,減輕了服務器壓力
  • 減少了網絡流量,提高了上傳速度
  • 可以根據不同場景設置不同的壓縮參數

具體實現

1. 圖片選擇

首先實現圖片選擇功能:

// 選擇圖片
chooseImage() {return new Promise((resolve, reject) => {uni.chooseImage({count: 9, // 最多可選擇的圖片張數sizeType: ['original', 'compressed'], // 可選擇原圖或壓縮后的圖片sourceType: ['album', 'camera'], // 從相冊選擇或使用相機拍攝success: (res) => {resolve(res.tempFilePaths);},fail: (err) => {reject(err);}});});
}

2. 圖片壓縮實現

壓縮圖片是整個功能的核心,我們使用 canvas 來實現:

/*** 圖片壓縮* @param {String} src 圖片路徑* @param {Number} quality 壓縮質量(0-1)* @param {Number} maxWidth 最大寬度* @param {Number} maxHeight 最大高度*/
compressImage(src, quality = 0.8, maxWidth = 800, maxHeight = 800) {return new Promise((resolve, reject) => {// 獲取圖片信息uni.getImageInfo({src: src,success: (imgInfo) => {// 計算壓縮后的尺寸let width = imgInfo.width;let height = imgInfo.height;// 等比例縮放if (width > maxWidth || height > maxHeight) {const ratio = Math.max(width / maxWidth, height / maxHeight);width = Math.floor(width / ratio);height = Math.floor(height / ratio);}// 創建canvas上下文const ctx = uni.createCanvasContext('compressCanvas', this);// 繪制圖片到canvasctx.drawImage(src, 0, 0, width, height);// 將canvas轉為圖片ctx.draw(false, () => {setTimeout(() => {uni.canvasToTempFilePath({canvasId: 'compressCanvas',fileType: 'jpg',quality: quality,success: (res) => {// 獲取壓縮后的圖片路徑resolve(res.tempFilePath);},fail: (err) => {reject(err);}}, this);}, 300); // 延遲確保canvas繪制完成});},fail: (err) => {reject(err);}});});
}

在頁面中需要添加對應的 canvas 元素:

<canvas canvas-id="compressCanvas" style="width: 0px; height: 0px; position: absolute; left: -1000px; top: -1000px;"></canvas>

3. 圖片上傳實現

圖片上傳時,我們往往需要添加額外的參數,比如表單字段、用戶 token 等:

/*** 上傳圖片到服務器* @param {String} filePath 圖片路徑* @param {String} url 上傳地址* @param {Object} formData 附加數據*/
uploadFile(filePath, url, formData = {}) {return new Promise((resolve, reject) => {uni.uploadFile({url: url,filePath: filePath,name: 'file', // 服務器接收的字段名formData: formData,header: {// 可以添加自定義 header,如 token'Authorization': 'Bearer ' + uni.getStorageSync('token')},success: (res) => {// 這里需要注意,返回的數據是字符串,需要手動轉為 JSONlet data = JSON.parse(res.data);resolve(data);},fail: (err) => {reject(err);}});});
}

4. 完整的上傳流程

將以上三個步驟組合,實現完整的圖片上傳流程:

// 實現完整的上傳流程
async handleUpload() {try {// 顯示加載提示uni.showLoading({title: '上傳中...',mask: true});// 選擇圖片const imagePaths = await this.chooseImage();// 用于存儲上傳結果const uploadResults = [];// 循環處理每張圖片for (let i = 0; i < imagePaths.length; i++) {// 壓縮圖片const compressedPath = await this.compressImage(imagePaths[i],0.7,  // 壓縮質量800,  // 最大寬度800   // 最大高度);// 獲取原圖和壓縮后的圖片大小進行對比const originalInfo = await this.getFileInfo(imagePaths[i]);const compressedInfo = await this.getFileInfo(compressedPath);console.log(`原圖大小: ${(originalInfo.size / 1024).toFixed(2)}KB, 壓縮后: ${(compressedInfo.size / 1024).toFixed(2)}KB`);// 上傳壓縮后的圖片const uploadResult = await this.uploadFile(compressedPath,'https://your-api.com/upload',{type: 'avatar', // 附加參數userId: this.userId});uploadResults.push(uploadResult);}// 隱藏加載提示uni.hideLoading();// 提示上傳成功uni.showToast({title: '上傳成功',icon: 'success'});// 返回上傳結果return uploadResults;} catch (error) {uni.hideLoading();uni.showToast({title: '上傳失敗',icon: 'none'});console.error('上傳錯誤:', error);}
}// 獲取文件信息
getFileInfo(filePath) {return new Promise((resolve, reject) => {uni.getFileInfo({filePath: filePath,success: (res) => {resolve(res);},fail: (err) => {reject(err);}});});
}

進階優化

以上代碼已經可以基本滿足圖片上傳與壓縮需求,但在實際項目中,我們還可以進一步優化:

1. 添加圖片預覽功能

在上傳前,通常需要讓用戶預覽選擇的圖片:

// 預覽圖片
previewImage(current, urls) {uni.previewImage({current: current, // 當前顯示圖片的路徑urls: urls, // 需要預覽的圖片路徑列表indicator: 'number',loop: true});
}

2. 使用 uniCloud 上傳

如果你使用 uniCloud 作為后端服務,可以利用其提供的云存儲功能簡化上傳流程:

// 使用 uniCloud 上傳
async uploadToUniCloud(filePath) {try {const result = await uniCloud.uploadFile({filePath: filePath,cloudPath: 'images/' + Date.now() + '.jpg'});return result.fileID; // 返回文件ID} catch (error) {throw error;}
}

3. 添加上傳進度顯示

對于大圖片,添加上傳進度能提升用戶體驗:

uploadFileWithProgress(filePath, url, formData = {}) {return new Promise((resolve, reject) => {const uploadTask = uni.uploadFile({url: url,filePath: filePath,name: 'file',formData: formData,success: (res) => {let data = JSON.parse(res.data);resolve(data);},fail: (err) => {reject(err);}});uploadTask.onProgressUpdate((res) => {console.log('上傳進度', res.progress);// 更新進度條this.uploadProgress = res.progress;});});
}

4. 針對鴻蒙系統的適配

隨著國產操作系統鴻蒙的普及,我們也需要考慮在鴻蒙系統上的兼容性。雖然目前 UniApp 官方還沒有專門針對鴻蒙系統的適配文檔,但我們可以通過一些方法來優化:

// 檢測當前系統
checkSystem() {const systemInfo = uni.getSystemInfoSync();console.log('當前系統:', systemInfo.platform);// 鴻蒙系統目前會被識別為 android,可以通過 brand 和 model 輔助判斷const isHarmonyOS = systemInfo.brand === 'HUAWEI' && /HarmonyOS/i.test(systemInfo.system);if (isHarmonyOS) {console.log('當前是鴻蒙系統');// 針對鴻蒙系統進行特殊處理// 例如:調整壓縮參數、使用不同的 API 等}return systemInfo;
}

根據我的測試,在鴻蒙系統上,有時 canvas 繪制需要更長的延遲時間,可以適當調整:

// 針對鴻蒙系統的 canvas 延遲調整
const delay = isHarmonyOS ? 500 : 300;
setTimeout(() => {uni.canvasToTempFilePath({// 配置項...});
}, delay);

實際案例

下面是一個完整的實際案例,用于實現商品發布頁面的圖片上傳功能:

<template><view class="container"><view class="image-list"><!-- 已選圖片預覽 --><view class="image-item" v-for="(item, index) in imageList" :key="index"><image :src="item.path" mode="aspectFill" @tap="previewImage(index)"></image><view class="delete-btn" @tap.stop="deleteImage(index)">×</view></view><!-- 添加圖片按鈕 --><view class="add-image" @tap="handleAddImage" v-if="imageList.length < 9"><text class="add-icon">+</text><text class="add-text">添加圖片</text></view></view><!-- 上傳按鈕 --><button class="upload-btn" @tap="submitUpload" :disabled="imageList.length === 0">上傳圖片 ({{imageList.length}}/9)</button><!-- 壓縮畫布(隱藏) --><canvas canvas-id="compressCanvas" style="width: 0px; height: 0px; position: absolute; left: -1000px; top: -1000px;"></canvas><!-- 上傳進度條 --><view class="progress-bar" v-if="isUploading"><view class="progress-inner" :style="{width: uploadProgress + '%'}"></view><text class="progress-text">{{uploadProgress}}%</text></view></view>
</template><script>
export default {data() {return {imageList: [], // 已選圖片列表isUploading: false, // 是否正在上傳uploadProgress: 0, // 上傳進度isHarmonyOS: false // 是否鴻蒙系統};},onLoad() {// 檢測系統const systemInfo = this.checkSystem();this.isHarmonyOS = systemInfo.brand === 'HUAWEI' && /HarmonyOS/i.test(systemInfo.system);},methods: {// 添加圖片async handleAddImage() {try {const imagePaths = await this.chooseImage();// 添加到圖片列表for (let path of imagePaths) {this.imageList.push({path: path,compressed: false,compressedPath: '',uploaded: false,fileID: ''});}} catch (error) {console.error('選擇圖片失敗:', error);}},// 預覽圖片previewImage(index) {const urls = this.imageList.map(item => item.path);uni.previewImage({current: this.imageList[index].path,urls: urls});},// 刪除圖片deleteImage(index) {this.imageList.splice(index, 1);},// 提交上傳async submitUpload() {if (this.imageList.length === 0) {uni.showToast({title: '請至少選擇一張圖片',icon: 'none'});return;}this.isUploading = true;this.uploadProgress = 0;uni.showLoading({title: '準備上傳...',mask: true});try {// 上傳結果const uploadResults = [];// 總進度let totalProgress = 0;// 遍歷所有圖片進行壓縮和上傳for (let i = 0; i < this.imageList.length; i++) {let item = this.imageList[i];// 如果還沒壓縮過,先壓縮if (!item.compressed) {uni.showLoading({title: `壓縮第 ${i+1}/${this.imageList.length} 張圖片`,mask: true});try {const compressedPath = await this.compressImage(item.path,0.7,800,800);// 更新圖片信息this.imageList[i].compressed = true;this.imageList[i].compressedPath = compressedPath;// 獲取壓縮前后的大小對比const originalInfo = await this.getFileInfo(item.path);const compressedInfo = await this.getFileInfo(compressedPath);console.log(`圖片 ${i+1}: 原圖 ${(originalInfo.size / 1024).toFixed(2)}KB, 壓縮后 ${(compressedInfo.size / 1024).toFixed(2)}KB, 壓縮率 ${((1 - compressedInfo.size / originalInfo.size) * 100).toFixed(2)}%`);} catch (error) {console.error(`壓縮第 ${i+1} 張圖片失敗:`, error);// 如果壓縮失敗,使用原圖this.imageList[i].compressedPath = item.path;this.imageList[i].compressed = true;}}// 準備上傳uni.showLoading({title: `上傳第 ${i+1}/${this.imageList.length} 張圖片`,mask: true});try {// 使用壓縮后的圖片路徑,如果沒有則使用原圖const fileToUpload = item.compressedPath || item.path;// 上傳圖片const result = await this.uploadFileWithProgress(fileToUpload,'https://your-api.com/upload',{type: 'product',index: i});// 更新圖片信息this.imageList[i].uploaded = true;this.imageList[i].fileID = result.fileID || result.url;uploadResults.push(result);// 更新總進度totalProgress = Math.floor((i + 1) / this.imageList.length * 100);this.uploadProgress = totalProgress;} catch (error) {console.error(`上傳第 ${i+1} 張圖片失敗:`, error);uni.showToast({title: `第 ${i+1} 張圖片上傳失敗`,icon: 'none'});}}uni.hideLoading();this.isUploading = false;uni.showToast({title: '所有圖片上傳完成',icon: 'success'});// 返回上傳結果,可以傳給父組件或進行后續處理this.$emit('uploadComplete', uploadResults);} catch (error) {uni.hideLoading();this.isUploading = false;console.error('上傳過程出錯:', error);uni.showToast({title: '上傳失敗,請重試',icon: 'none'});}},// 其他方法實現(chooseImage, compressImage, uploadFile等,同前面的實現)}
};
</script><style lang="scss">
.container {padding: 20rpx;
}.image-list {display: flex;flex-wrap: wrap;
}.image-item {width: 220rpx;height: 220rpx;margin: 10rpx;position: relative;border-radius: 8rpx;overflow: hidden;image {width: 100%;height: 100%;}.delete-btn {position: absolute;top: 0;right: 0;width: 44rpx;height: 44rpx;background-color: rgba(0, 0, 0, 0.5);color: #ffffff;text-align: center;line-height: 44rpx;font-size: 32rpx;z-index: 10;}
}.add-image {width: 220rpx;height: 220rpx;margin: 10rpx;background-color: #f5f5f5;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 8rpx;border: 1px dashed #dddddd;.add-icon {font-size: 60rpx;color: #999999;}.add-text {font-size: 24rpx;color: #999999;margin-top: 10rpx;}
}.upload-btn {margin-top: 40rpx;background-color: #007aff;color: #ffffff;border-radius: 8rpx;&:disabled {background-color: #cccccc;}
}.progress-bar {margin-top: 30rpx;height: 40rpx;background-color: #f5f5f5;border-radius: 20rpx;overflow: hidden;position: relative;.progress-inner {height: 100%;background-color: #007aff;transition: width 0.3s;}.progress-text {position: absolute;top: 0;left: 0;right: 0;bottom: 0;display: flex;justify-content: center;align-items: center;font-size: 24rpx;color: #ffffff;}
}
</style>

鴻蒙系統適配經驗

前面已經簡單提到了鴻蒙系統的適配,下面來詳細說一下我在實際項目中遇到的問題和解決方案:

  1. 畫布延遲問題:在鴻蒙系統上,canvas 繪制后轉圖片需要更長的延遲時間,建議延長 setTimeout 時間。

  2. 文件系統差異:有些文件路徑的處理方式可能與 Android 有所不同,建議使用 UniApp 提供的 API 進行文件操作,而不要直接操作路徑。

  3. 圖片格式支持:在鴻蒙系統上,對 WebP 等格式的支持可能有限,建議統一使用 JPG 或 PNG 格式。

  4. 內存管理:鴻蒙系統對內存的管理略有不同,處理大圖片時需要注意內存釋放。可以在完成上傳后,主動清空臨時圖片:

// 清空臨時文件
clearTempFiles() {for (let item of this.imageList) {// 如果存在壓縮后的臨時文件,嘗試刪除if (item.compressedPath && item.compressedPath !== item.path) {uni.removeSavedFile({filePath: item.compressedPath,complete: () => {console.log('清理臨時文件');}});}}
}

總結

通過本文介紹的方法,我們可以在 UniApp 中實現圖片上傳與壓縮功能,主要包括以下幾個步驟:

  1. 使用 uni.chooseImage() 選擇圖片
  2. 使用 canvas 進行圖片壓縮
  3. 使用 uni.uploadFile() 上傳圖片
  4. 添加進度顯示和預覽功能
  5. 針對鴻蒙系統做特殊適配

在實際項目中,可以根據需求調整壓縮參數,比如對頭像類圖片可以壓縮得更小,而對需要展示細節的商品圖片,可以保留更高的質量。

希望本文能夠幫助大家更好地實現 UniApp 中的圖片上傳與壓縮功能。如果有任何問題或建議,歡迎在評論區交流討論!

參考資料

  1. UniApp 官方文檔:https://uniapp.dcloud.io/
  2. Canvas API 參考:https://uniapp.dcloud.io/api/canvas/CanvasContext

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

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

相關文章

已經裝了pygame但pycharm顯示沒有該模塊/軟件包無法加載出來下載pygame

首先&#xff0c;如果你已經通過pip install pygame或者其他什么命令下載好了pygame &#xff08;可以通過pip list查看&#xff0c;有pygame說明pygame已經成功安裝在當前python環境中&#xff09;。然而&#xff0c;如果你在 PyCharm 中仍然看不到 pygame&#xff0c;可能是因…

第6章 實戰案例:基于 STEVAL-IDB011V1 板級 CI/CD 全流程

在前五章中,我們完成了嵌入式 CI/CD 從環境搭建、編譯自動化、測試自動化、發布分發到監控回歸的全技術鏈條。本章將以 STEVAL-IDB011V1(搭載 BlueNRG-355)評估板為實戰載體,手把手演示如何在 GitLab CI(或 Jenkins)上,構建一條從 Git Push → 編譯 → 測試 → 刷寫 → …

系統架構設計(十四):解釋器風格

概念 解釋器風格是一種將程序的每個語句逐條讀取并解釋執行的體系結構風格。程序在運行時不會先被編譯為機器碼&#xff0c;而是動態地由解釋器分析并執行其語義。 典型應用&#xff1a;Python 解釋器、JavaScript 引擎、Bash Shell、SQL 引擎。 組成結構 解釋器風格系統的…

1-機器學習的基本概念

文章目錄 一、機器學習的步驟Step1 - Function with unknownStep2 - Define Loss from Training DataStep3 - Optimization 二、機器學習的改進Q1 - 線性模型有一些缺點Q2 - 重新詮釋機器學習的三步Q3 - 機器學習的擴展Q4 - 過擬合問題&#xff08;Overfitting&#xff09; 一、…

SQL里where條件的順序影響索引使用嗎?

大家好&#xff0c;我是鋒哥。今天分享關于【SQL里where條件的順序影響索引使用嗎&#xff1f;】面試題。希望對大家有幫助&#xff1b; SQL里where條件的順序影響索引使用嗎&#xff1f; 1000道 互聯網大廠Java工程師 精選面試題-Java資源分享網 在 SQL 查詢中&#xff0c;W…

計算機科技筆記: 容錯計算機設計05 n模冗余系統 TMR 三模冗余系統

NMR&#xff08;N-Modular Redundancy&#xff0c;N 模冗余&#xff09;是一種通用的容錯設計架構&#xff0c;通過引入 N 個冗余模塊&#xff08;N ≥ 3 且為奇數&#xff09;&#xff0c;并采用多數投票機制&#xff0c;來提升系統的容錯能力與可靠性。單個模塊如果可靠性小于…

中級網絡工程師知識點7

1.存儲區城網絡SAN可分為IP-SAN,FC-SAN兩種&#xff0c;從部署成本和傳輸效率兩個方面比較兩種SAN&#xff0c;比較結果為FCSAN部署成本更高 2.RAID2.0技術的優勢&#xff1a; &#xff08;1&#xff09;自動負載均衡&#xff0c;降低了存儲系統故障率 &#xff08;2&#x…

在Ubuntu24.04中配置開源直線特征提取軟件DeepLSD

在Ubuntu24.04中配置開源直線特征提取軟件DeepLSD 本文提供在Ubuntu24.04中配置開源直線特征提取軟件DeepLSD的基礎環境配置、列出需要修改的文件內容&#xff0c;以及報錯解決方案集錦。 基礎的編譯安裝環境 python3.8.12CUDA12gcc/g 9.5&#xff08;系統自帶的g-13版本太新…

Nginx+Lua 實戰避坑:從模塊加載失敗到版本沖突的深度剖析

Nginx 集成 Lua (通常通過 ngx_http_lua_module 或 OpenResty) 為我們提供了在 Web 服務器層面實現動態邏輯的強大能力。然而,在享受其高性能和靈活性的同時,配置和使用過程中也常常會遇到各種令人頭疼的問題。本文將結合實際案例,深入分析在 Nginx+Lua 環境中常見的技術問題…

Vue.js組件開發進階

Vue.js 是一個漸進式 JavaScript 框架&#xff0c;廣泛用于構建用戶界面。組件是 Vue.js 的核心概念之一&#xff0c;允許開發者將 UI 拆分為獨立、可復用的模塊。本文將深入探討 Vue.js 組件的開發&#xff0c;涵蓋從基礎到高級的各個方面。 組件的基本概念 在 Vue.js 中&am…

藍橋杯19682 完全背包

問題描述 有 N 件物品和一個體積為 M 的背包。第 i 個物品的體積為 vi?&#xff0c;價值為 wi?。每件物品可以使用無限次。 請問可以通過什么樣的方式選擇物品&#xff0c;使得物品總體積不超過 M 的情況下總價值最大&#xff0c;輸出這個最大價值即可。 輸入格式 第一行…

深度學習之用CelebA_Spoof數據集搭建一個活體檢測-一些模型訓練中的改動帶來的改善

實驗背景 在前面的深度學習之用CelebA_Spoof數據集搭建一個活體檢測-模型搭建和訓練&#xff0c;我們基于CelebA_Spoof數據集構建了一個用SqueezeNe框架進行訓練的活體2D模型&#xff0c;采用了蒸餾法進行了一些簡單的工作。在前面提供的訓練參數中&#xff0c;主要用了以下幾…

2025年PMP 學習二十 第13章 項目相關方管理

第13章 項目相關方管理 序號過程過程組過程組1識別相關方啟動2規劃相關方管理規劃3管理相關方參與與執行4監控相關方參與與監控 相關方管理&#xff0c;針對于團隊之外的相關方的&#xff0c;核心目標是讓對方為了支持項目&#xff0c;以達到項目目標。 文章目錄 第13章 項目相…

GO語言語法---For循環、break、continue

文章目錄 1. 基本for循環&#xff08;類似其他語言的while&#xff09;2. 經典for循環&#xff08;初始化;條件;后續操作&#xff09;3. 無限循環4. 使用break和continue5 . 帶標簽的循環&#xff08;可用于break/continue指定循環&#xff09;1、break帶標簽2、continue帶標簽…

CSS- 4.4 固定定位(fixed) 咖啡售賣官網實例

本系列可作為前端學習系列的筆記&#xff0c;代碼的運行環境是在HBuilder中&#xff0c;小編會將代碼復制下來&#xff0c;大家復制下來就可以練習了&#xff0c;方便大家學習。 HTML系列文章 已經收錄在前端專欄&#xff0c;有需要的寶寶們可以點擊前端專欄查看&#xff01; 點…

分布式微服務系統架構第132集:Python大模型,fastapi項目-Jeskson文檔-微服務分布式系統架構

加群聯系作者vx&#xff1a;xiaoda0423 倉庫地址&#xff1a;https://webvueblog.github.io/JavaPlusDoc/ https://1024bat.cn/ https://github.com/webVueBlog/fastapi_plus 這個錯誤是由于 Python 3 中已經將線程的 isAlive() 方法更名為 is_alive()&#xff0c;但你的調試工…

react路由中Suspense的介紹

好的&#xff0c;我們來詳細解釋一下這個 AppRouter 組件的代碼。 這個組件是一個在現代 React 應用中非常常見的模式&#xff0c;特別是在使用 React Router v6 進行路由管理和結合代碼分割&#xff08;Code Splitting&#xff09;來優化性能時。 JavaScript const AppRout…

C語言內存函數與數據在內存中的存儲

一、c語言內存函數 1、memcpy函數是一個標準庫函數&#xff0c;用于內存復制。功能上是用來將一塊內存中的內容復制到另一塊內存中。用戶需要提供目標地址、源地址以及要復制的字節數。例如結構體之間的復制。 memcpy函數的原型是&#xff1a;void* memcpy&#xff08;void* …

層次原理圖

層次原理圖簡介 層次原理圖&#xff08;Hierarchical Schematic&#xff09;是一種常用于電子工程與系統設計的可視化工具&#xff0c;通過分層結構將復雜系統分解為多個可管理的子模塊。它如同“設計藍圖”&#xff0c;以樹狀結構呈現整體與局部的關系&#xff1a;頂層展現系…

流程編輯器Bpmn與LogicFlow學習

工作流技術如何與用戶交互結合&#xff08;如動態表單、任務分配&#xff09;處理過 XML 與 JSON 的轉換自定義過 bpmn.js 的樣式&#xff08;如修改節點顏色、形狀、圖標&#xff09;擴展過上下文菜單&#xff08;Palette&#xff09;或屬性面板&#xff08;Properties Panel&…