使用 HTML + JavaScript 實現圖片裁剪上傳功能

本文將詳細介紹一個基于 HTML 和 JavaScript 實現的圖片裁剪上傳功能。該功能支持文件選擇、拖放上傳、圖片預覽、區域選擇、裁剪操作以及圖片下載等功能,適用于需要進行圖片處理的 Web 應用場景。

效果演示

image-20250602112435691

image-20250602112543017

項目概述

本項目主要包含以下核心功能:

  • 文件選擇與拖放上傳
  • 裁剪框拖動與調整大小
  • 圖片裁剪
  • 圖片上傳(模擬)與下載

頁面結構

上傳區域

實現友好的文件選擇體驗,并支持拖放上傳。

<div class="upload-section"><input type="file" id="fileInput" accept="image/*"><label for="fileInput" class="file-label">選擇圖片</label><p>或拖放圖片到此處</p>
</div>
預覽區域

分為兩個部分,左側顯示原始圖片和裁剪框,右側展示裁剪后的結果。

<div class="preview-section"><div class="image-container"><div><h3>原始圖片</h3><img id="originalImage" style="max-width: 100%; display: none;"><div class="cropper-container" id="cropperContainer" style="display: none;"><canvas id="sourceCanvas"></canvas><div class="selection-box" id="selectionBox"><div class="resize-handle"></div></div></div><p class="instruction">拖動選擇框可移動位置,拖動右下角可調整大小</p></div><div><h3>裁剪結果</h3><canvas id="croppedCanvas" style="display: none;"></canvas><p id="noCropMessage">請先選擇圖片并設置裁剪區域</p></div></div>
</div>
操作按鈕

提供“裁剪”、“上傳”、“下載”和“重置”按鈕,方便用戶進行各種操作。

<div class="controls"><button id="cropBtn" disabled>裁剪圖片</button><button id="uploadBtn" disabled>上傳圖片</button><button id="downloadBtn" disabled>下載圖片</button><button id="resetBtn">重置</button>
</div>

核心功能實現

定義基礎變量

獲取DOM元素

const fileInput = document.getElementById('fileInput');
const originalImage = document.getElementById('originalImage');
const sourceCanvas = document.getElementById('sourceCanvas');
const croppedCanvas = document.getElementById('croppedCanvas');
const cropperContainer = document.getElementById('cropperContainer');
const selectionBox = document.getElementById('selectionBox');
const cropBtn = document.getElementById('cropBtn');
const uploadBtn = document.getElementById('uploadBtn');
const resetBtn = document.getElementById('resetBtn');
const noCropMessage = document.getElementById('noCropMessage');
const downloadBtn = document.getElementById('downloadBtn');

定義全局變量

let isDragging = false;
let isResizing = false;
let startX, startY;
let selection = {x: 0,y: 0,width: 0,height: 0,startX: 0,startY: 0,startWidth: 0,startHeight: 0
};
let imageRatio = 1;
文件選擇

使用 FileReader API 將選中的圖片讀取為 Data URL 并顯示在頁面上。

function handleFileSelect(event) {const file = event.target.files[0];if (!file || !file.type.match('image.*')) {alert('請選擇有效的圖片文件');return;}const reader = new FileReader();reader.onload = function(e) {originalImage.src = e.target.result;originalImage.onload = function() {initCropper();};};reader.readAsDataURL(file);
}
拖放上傳

通過監聽 dragover、dragleave 和 drop 事件實現拖放上傳功能。

const uploadSection = document.querySelector('.upload-section');
uploadSection.addEventListener('dragover', (e) => {e.preventDefault();uploadSection.style.borderColor = '#4CAF50';
});uploadSection.addEventListener('dragleave', () => {uploadSection.style.borderColor = '#ccc';
});uploadSection.addEventListener('drop', (e) => {e.preventDefault();uploadSection.style.borderColor = '#ccc';if (e.dataTransfer.files.length) {fileInput.files = e.dataTransfer.files;handleFileSelect({ target: fileInput });}
});
圖片預覽與裁剪框初始化

在圖片加載完成后,繪制到 canvas 上,并根據圖片尺寸調整畫布大小。初始化一個固定比例的裁剪框,居中顯示在畫布上。

function initCropper() {// 顯示原始圖片和裁剪區域originalImage.style.display = 'none';cropperContainer.style.display = 'inline-block';// 設置canvas尺寸const maxWidth = 500;imageRatio = originalImage.naturalWidth / originalImage.naturalHeight;let canvasWidth, canvasHeight;if (originalImage.naturalWidth > maxWidth) {canvasWidth = maxWidth;canvasHeight = maxWidth / imageRatio;} else {canvasWidth = originalImage.naturalWidth;canvasHeight = originalImage.naturalHeight;}sourceCanvas.width = canvasWidth;sourceCanvas.height = canvasHeight;// 繪制圖片到canvasconst ctx = sourceCanvas.getContext('2d');ctx.drawImage(originalImage, 0, 0, canvasWidth, canvasHeight);// 初始化選擇框 (1:1比例)const boxSize = Math.min(canvasWidth, canvasHeight) * 0.6;selection = {x: Math.max(0, Math.min((canvasWidth - boxSize) / 2, canvasWidth - boxSize)), // 確保初始位置在畫布范圍內y: Math.max(0, Math.min((canvasHeight - boxSize) / 2, canvasHeight - boxSize)), // 確保初始位置在畫布范圍內width: boxSize,height: boxSize,startX: 0,startY: 0,startWidth: 0,startHeight: 0};updateSelectionBox();cropBtn.disabled = false;
}
裁剪框拖動與調整大小

通過監聽鼠標事件(mousedown、mousemove、mouseup)實現裁剪框的拖動和調整大小功能。確保裁剪框始終位于畫布范圍內,并保持指定的比例。

selectionBox.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', endDrag);
const resizeHandle = document.querySelector('.resize-handle');
resizeHandle.addEventListener('mousedown', (e) => {e.stopPropagation();startResize(e);
});
function startDrag(e) {if (e.target.classList.contains('resize-handle')) {return; // 忽略調整大小手柄的點擊}isDragging = true;startX = e.clientX;startY = e.clientY;// 存儲初始位置selection.startX = selection.x;selection.startY = selection.y;e.preventDefault();
}
function startResize(e) {isResizing = true;startX = e.clientX;startY = e.clientY;// 存儲初始尺寸和位置selection.startX = selection.x;selection.startY = selection.y;selection.startWidth = selection.width;selection.startHeight = selection.height;e.preventDefault();
}
function handleDrag(e) {if (!isDragging && !isResizing) return;const dx = e.clientX - startX;const dy = e.clientY - startY;if (isDragging) {// 處理移動選擇框let newX = selection.startX + dx;let newY = selection.startY + dy;// 限制在canvas范圍內newX = Math.max(0, Math.min(newX, sourceCanvas.width - selection.width));newY = Math.max(0, Math.min(newY, sourceCanvas.height - selection.height));selection.x = newX;selection.y = newY;} else if (isResizing) {// 處理調整大小 (保持1:1比例)let newSize = Math.max(10, Math.min(selection.startWidth + (dx + dy) / 2, // 取dx和dy的平均值使調整更平滑Math.min(sourceCanvas.width - selection.startX,sourceCanvas.height - selection.startY)));// 應用新尺寸 (保持正方形)selection.width = newSize;selection.height = newSize;// 確保裁剪框不會超出畫布范圍if (selection.x + selection.width > sourceCanvas.width) {selection.x = sourceCanvas.width - selection.width;}if (selection.y + selection.height > sourceCanvas.height) {selection.y = sourceCanvas.height - selection.height;}}updateSelectionBox();
}
function endDrag() {isDragging = false;isResizing = false;
}
圖片裁剪與結果展示

使用 drawImage 方法從源畫布中裁剪出指定區域,并將其繪制到目標畫布上。

function cropImage() {const ctx = croppedCanvas.getContext('2d');// 設置裁剪后canvas的尺寸 (1:1)croppedCanvas.width = selection.width;croppedCanvas.height = selection.height;// 執行裁剪ctx.drawImage(sourceCanvas,selection.x, selection.y, selection.width, selection.height, // 源圖像裁剪區域0, 0, selection.width, selection.height                     // 目標canvas繪制區域);// 顯示裁剪結果croppedCanvas.style.display = 'block';noCropMessage.style.display = 'none';uploadBtn.disabled = false;downloadBtn.disabled = false;
}
圖片上傳與下載

提供模擬的上傳功能,使用 toBlob 方法獲取裁剪后的圖片數據。支持將裁剪后的圖片下載為 JPEG 格式的文件。

function uploadImage() {// 在實際應用中,這里應該將圖片數據發送到服務器croppedCanvas.toBlob((blob) => {// 創建FormData對象并添加圖片const formData = new FormData();formData.append('croppedImage', blob, 'cropped-image.jpg');// 模擬上傳延遲setTimeout(() => {alert('圖片上傳成功!(模擬)');console.log('上傳的圖片數據:', blob);// 在實際應用中,你可能需要處理服務器響應}, 1000);}, 'image/jpeg', 0.9);
}
function downloadImage() {if (!croppedCanvas.width || !croppedCanvas.height) {alert('請先裁剪圖片');return;}// 創建一個臨時的a標簽用于觸發下載const link = document.createElement('a');link.href = croppedCanvas.toDataURL('image/jpeg', 0.9);link.download = 'cropped-image.jpg'; // 設置下載文件名link.click();
}

擴展建議

  • 支持多種裁剪比例:可以擴展代碼以支持不同的裁剪比例(如 4:3、16:9),并通過 UI 控件讓用戶選擇。
  • 圖像縮放功能:添加對圖片縮放的支持,允許用戶放大或縮小圖片以便更精確地選擇裁剪區域。
  • 服務器端集成:實際應用中,應將裁剪后的圖片發送到服務器進行存儲和處理,可以通過請求實現。

完整代碼

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>圖片裁剪上傳</title><style>body {max-width: 1200px;margin: 0 auto;padding: 20px;}.container {display: flex;flex-direction: column;gap: 20px;}h1 {text-align: center;}.upload-section {padding: 20px;text-align: center;border-radius: 5px;background: #f8f9fa;border: 2px dashed #dee2e6;transition: all 0.3s ease;cursor: pointer;}.upload-section:hover {border-color: #4CAF50;background: rgba(76, 175, 80, 0.05);}.file-label {background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);color: white;border-radius: 25px;padding: 12px 24px;transition: transform 0.2s;}.file-label:hover {transform: translateY(-2px);box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);}.preview-section {display: flex;flex-direction: column;gap: 20px;background: #ffffff;border-radius: 12px;padding: 20px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);}canvas {max-width: 100%;border: 1px solid #eee;display: block;}.cropper-container {position: relative;display: inline-block;}.selection-box {position: absolute;border: 2px dashed #000;background: rgba(255, 255, 255, 0.3);cursor: move;box-sizing: border-box;}.resize-handle {position: absolute;width: 10px;height: 10px;background: #fff;border: 2px solid #000;border-radius: 50%;bottom: -5px;right: -5px;cursor: se-resize;}button {padding: 10px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;font-size: 16px;}button:hover {background-color: #45a049;}button {background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);border-radius: 25px;padding: 12px 24px;font-weight: 500;box-shadow: 0 4px 6px rgba(76, 175, 80, 0.1);transition: all 0.3s ease;}button:hover {transform: translateY(-2px);box-shadow: 0 6px 12px rgba(76, 175, 80, 0.2);}button:disabled {opacity: 0.6;cursor: not-allowed;}input[type="file"] {display: none;}.file-label {display: inline-block;padding: 10px 15px;background-color: #f0f0f0;border-radius: 4px;cursor: pointer;margin-bottom: 10px;}.controls {display: flex;gap: 10px;margin-top: 10px;justify-content: center;}.instruction {font-size: 14px;color: #666;margin-top: 10px;}.image-container {display: flex;gap: 20px;}.image-container > div {width: 500px;}</style>
</head>
<body>
<div class="container"><h1>圖片裁剪上傳</h1><div class="upload-section"><input type="file" id="fileInput" accept="image/*"><label for="fileInput" class="file-label">選擇圖片</label><p>或拖放圖片到此處</p></div><div class="preview-section"><div class="image-container"><div><h3>原始圖片</h3><img id="originalImage" style="max-width: 100%; display: none;"><div class="cropper-container" id="cropperContainer" style="display: none;"><canvas id="sourceCanvas"></canvas><div class="selection-box" id="selectionBox"><div class="resize-handle"></div></div></div><p class="instruction">拖動選擇框可移動位置,拖動右下角可調整大小</p></div><div><h3>裁剪結果</h3><canvas id="croppedCanvas" style="display: none;"></canvas><p id="noCropMessage">請先選擇圖片并設置裁剪區域</p></div></div></div><div class="controls"><button id="cropBtn" disabled>裁剪圖片</button><button id="uploadBtn" disabled>上傳圖片</button><button id="downloadBtn" disabled>下載圖片</button><button id="resetBtn">重置</button></div>
</div><script>// 獲取DOM元素const fileInput = document.getElementById('fileInput');const originalImage = document.getElementById('originalImage');const sourceCanvas = document.getElementById('sourceCanvas');const croppedCanvas = document.getElementById('croppedCanvas');const cropperContainer = document.getElementById('cropperContainer');const selectionBox = document.getElementById('selectionBox');const cropBtn = document.getElementById('cropBtn');const uploadBtn = document.getElementById('uploadBtn');const resetBtn = document.getElementById('resetBtn');const noCropMessage = document.getElementById('noCropMessage');const downloadBtn = document.getElementById('downloadBtn');// 全局變量let isDragging = false;let isResizing = false;let startX, startY;let selection = {x: 0,y: 0,width: 0,height: 0,startX: 0,startY: 0,startWidth: 0,startHeight: 0};let imageRatio = 1;// 監聽文件選擇fileInput.addEventListener('change', handleFileSelect);// 拖放功能const uploadSection = document.querySelector('.upload-section');uploadSection.addEventListener('dragover', (e) => {e.preventDefault();uploadSection.style.borderColor = '#4CAF50';});uploadSection.addEventListener('dragleave', () => {uploadSection.style.borderColor = '#ccc';});uploadSection.addEventListener('drop', (e) => {e.preventDefault();uploadSection.style.borderColor = '#ccc';if (e.dataTransfer.files.length) {fileInput.files = e.dataTransfer.files;handleFileSelect({ target: fileInput });}});// 選擇框鼠標事件selectionBox.addEventListener('mousedown', startDrag);document.addEventListener('mousemove', handleDrag);document.addEventListener('mouseup', endDrag);// 調整大小手柄事件const resizeHandle = document.querySelector('.resize-handle');resizeHandle.addEventListener('mousedown', (e) => {e.stopPropagation();startResize(e);});// 下載圖片function downloadImage() {if (!croppedCanvas.width || !croppedCanvas.height) {alert('請先裁剪圖片');return;}// 創建一個臨時的a標簽用于觸發下載const link = document.createElement('a');link.href = croppedCanvas.toDataURL('image/jpeg', 0.9);link.download = 'cropped-image.jpg'; // 設置下載文件名link.click();}// 按鈕事件cropBtn.addEventListener('click', cropImage);uploadBtn.addEventListener('click', uploadImage);resetBtn.addEventListener('click', resetAll);downloadBtn.addEventListener('click', downloadImage);// 處理文件選擇function handleFileSelect(event) {const file = event.target.files[0];if (!file || !file.type.match('image.*')) {alert('請選擇有效的圖片文件');return;}const reader = new FileReader();reader.onload = function(e) {originalImage.src = e.target.result;originalImage.onload = function() {initCropper();};};reader.readAsDataURL(file);}// 初始化裁剪器function initCropper() {// 顯示原始圖片和裁剪區域originalImage.style.display = 'none';cropperContainer.style.display = 'inline-block';// 設置canvas尺寸const maxWidth = 500;imageRatio = originalImage.naturalWidth / originalImage.naturalHeight;let canvasWidth, canvasHeight;if (originalImage.naturalWidth > maxWidth) {canvasWidth = maxWidth;canvasHeight = maxWidth / imageRatio;} else {canvasWidth = originalImage.naturalWidth;canvasHeight = originalImage.naturalHeight;}sourceCanvas.width = canvasWidth;sourceCanvas.height = canvasHeight;// 繪制圖片到canvasconst ctx = sourceCanvas.getContext('2d');ctx.drawImage(originalImage, 0, 0, canvasWidth, canvasHeight);// 初始化選擇框 (1:1比例)const boxSize = Math.min(canvasWidth, canvasHeight) * 0.6;selection = {x: Math.max(0, Math.min((canvasWidth - boxSize) / 2, canvasWidth - boxSize)), // 確保初始位置在畫布范圍內y: Math.max(0, Math.min((canvasHeight - boxSize) / 2, canvasHeight - boxSize)), // 確保初始位置在畫布范圍內width: boxSize,height: boxSize,startX: 0,startY: 0,startWidth: 0,startHeight: 0};updateSelectionBox();cropBtn.disabled = false;}// 更新選擇框位置和尺寸function updateSelectionBox() {selectionBox.style.left = `${selection.x}px`;selectionBox.style.top = `${selection.y}px`;selectionBox.style.width = `${selection.width}px`;selectionBox.style.height = `${selection.height}px`;}// 開始拖動function startDrag(e) {if (e.target.classList.contains('resize-handle')) {return; // 忽略調整大小手柄的點擊}isDragging = true;startX = e.clientX;startY = e.clientY;// 存儲初始位置selection.startX = selection.x;selection.startY = selection.y;e.preventDefault();}// 處理拖動function handleDrag(e) {if (!isDragging && !isResizing) return;const dx = e.clientX - startX;const dy = e.clientY - startY;if (isDragging) {// 處理移動選擇框let newX = selection.startX + dx;let newY = selection.startY + dy;// 限制在canvas范圍內newX = Math.max(0, Math.min(newX, sourceCanvas.width - selection.width));newY = Math.max(0, Math.min(newY, sourceCanvas.height - selection.height));selection.x = newX;selection.y = newY;} else if (isResizing) {// 處理調整大小 (保持1:1比例)let newSize = Math.max(10, Math.min(selection.startWidth + (dx + dy) / 2, // 取dx和dy的平均值使調整更平滑Math.min(sourceCanvas.width - selection.startX,sourceCanvas.height - selection.startY)));// 應用新尺寸 (保持正方形)selection.width = newSize;selection.height = newSize;// 確保裁剪框不會超出畫布范圍if (selection.x + selection.width > sourceCanvas.width) {selection.x = sourceCanvas.width - selection.width;}if (selection.y + selection.height > sourceCanvas.height) {selection.y = sourceCanvas.height - selection.height;}}updateSelectionBox();}// 結束拖動或調整大小function endDrag() {isDragging = false;isResizing = false;}// 開始調整大小function startResize(e) {isResizing = true;startX = e.clientX;startY = e.clientY;// 存儲初始尺寸和位置selection.startX = selection.x;selection.startY = selection.y;selection.startWidth = selection.width;selection.startHeight = selection.height;e.preventDefault();}// 裁剪圖片function cropImage() {const ctx = croppedCanvas.getContext('2d');// 設置裁剪后canvas的尺寸 (1:1)croppedCanvas.width = selection.width;croppedCanvas.height = selection.height;// 執行裁剪ctx.drawImage(sourceCanvas,selection.x, selection.y, selection.width, selection.height, // 源圖像裁剪區域0, 0, selection.width, selection.height                     // 目標canvas繪制區域);// 顯示裁剪結果croppedCanvas.style.display = 'block';noCropMessage.style.display = 'none';uploadBtn.disabled = false;downloadBtn.disabled = false;}// 上傳圖片function uploadImage() {// 在實際應用中,這里應該將圖片數據發送到服務器croppedCanvas.toBlob((blob) => {// 創建FormData對象并添加圖片const formData = new FormData();formData.append('croppedImage', blob, 'cropped-image.jpg');// 模擬上傳延遲setTimeout(() => {alert('圖片上傳成功!(模擬)');console.log('上傳的圖片數據:', blob);// 在實際應用中,你可能需要處理服務器響應}, 1000);}, 'image/jpeg', 0.9);}// 重置所有function resetAll() {// 隱藏元素cropperContainer.style.display = 'none';croppedCanvas.style.display = 'none';noCropMessage.style.display = 'block';originalImage.style.display = 'none';// 重置按鈕狀態cropBtn.disabled = true;uploadBtn.disabled = true;downloadBtn.disabled = true;// 清除文件輸入fileInput.value = '';// 清除畫布const ctx = sourceCanvas.getContext('2d');ctx.clearRect(0, 0, sourceCanvas.width, sourceCanvas.height);const croppedCtx = croppedCanvas.getContext('2d');croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);}
</script>
</body>
</html>

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

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

相關文章

GO+RabbitMQ+Gin+Gorm+docker 部署 demo

更多個人筆記見&#xff1a; &#xff08;注意點擊“繼續”&#xff0c;而不是“發現新項目”&#xff09; github個人筆記倉庫 https://github.com/ZHLOVEYY/IT_note gitee 個人筆記倉庫 https://gitee.com/harryhack/it_note 個人學習&#xff0c;學習過程中還會不斷補充&…

【安全】VulnHub靶場 - W1R3S

【安全】VulnHub靶場 - W1R3S 備注一、故事背景二、Web滲透1.主機發現端口掃描2.ftp服務3.web服務 三、權限提升 備注 2025/05/22 星期四 簡單的打靶記錄 一、故事背景 您受雇對 W1R3S.inc 個人服務器進行滲透測試并報告所有發現。 他們要求您獲得 root 訪問權限并找到flag&…

WEB安全--SQL注入--MSSQL注入

一、SQLsever知識點了解 1.1、系統變量 版本號&#xff1a;version 用戶名&#xff1a;USER、SYSTEM_USER 庫名&#xff1a;DB_NAME() SELECT name FROM master..sysdatabases 表名&#xff1a;SELECT name FROM sysobjects WHERE xtypeU 字段名&#xff1a;SELECT name …

工作流引擎-18-開源審批流項目之 plumdo-work 工作流,表單,報表結合的多模塊系統

工作流引擎系列 工作流引擎-00-流程引擎概覽 工作流引擎-01-Activiti 是領先的輕量級、以 Java 為中心的開源 BPMN 引擎&#xff0c;支持現實世界的流程自動化需求 工作流引擎-02-BPM OA ERP 區別和聯系 工作流引擎-03-聊一聊流程引擎 工作流引擎-04-流程引擎 activiti 優…

Docker 筆記 -- 借助AI工具強勢輔助

常用命令 鏡像管理命令&#xff1a; docker images&#xff08;列出鏡像&#xff09; docker pull&#xff08;拉取鏡像&#xff09; docker build&#xff08;構建鏡像&#xff09; docker save/load&#xff08;保存/加載鏡像&#xff09; 容器操作命令 docker run&#…

5G-A時代與p2p

5G-A時代正在走來&#xff0c;那么對P2P的影響有多大。 5G-A作為5G向6G過渡的關鍵技術&#xff0c;將數據下載速率從千兆提升至萬兆&#xff0c;上行速率從百兆提升至千兆&#xff0c;時延降至毫秒級。這種網絡性能的跨越式提升&#xff0c;為P2P提供了更強大的底層支撐&#x…

Redis-6.2.9 主從復制配置和詳解

1 主從架構圖 192.168.254.120 u24-redis-120 #主庫 192.168.254.121 u24-redis-121 #從庫 2 redis軟件版本 rootu24-redis-121:~# redis-server --version Redis server v6.2.9 sha00000000:0 malloclibc bits64 build56edd385f7ce4c9b 3 主庫redis配置文件(192.168.254.1…

004 flutter基礎 初始文件講解(3)

之前&#xff0c;我們正向的學習了一些flutter的基礎&#xff0c;如MaterialApp&#xff0c;Scaffold之類的東西&#xff0c;那么接下來&#xff0c;我們將正式接觸原代碼&#xff1a; import package:flutter/material.dart;void main() {runApp(const MyApp()); }class MyAp…

Linux 系統 Docker Compose 安裝

個人博客地址&#xff1a;Linux 系統 Docker Compose 安裝 | 一張假鈔的真實世界 本文方法是直接下載 GitHub 項目的 release 版本。項目地址&#xff1a;GitHub - docker/compose: Define and run multi-container applications with Docker。 執行以下命令將發布程序加載至…

Tree 樹形組件封裝

整體思路 數據結構設計 使用遞歸的數據結構&#xff08;TreeNode&#xff09;表示樹形數據每個節點包含id、name、可選的children數組和selected狀態 狀態管理 使用useState在組件內部維護樹狀態的副本通過deepCopyTreeData函數進行深拷貝&#xff0c;避免直接修改原始數據 核…

tortoisegit 使用rebase修改歷史提交

在 TortoiseGit 中使用 rebase 修改歷史提交&#xff08;如修改提交信息、合并提交或刪除提交&#xff09;的步驟如下&#xff1a; --- ### **一、修改最近一次提交** 1. **操作**&#xff1a; - 右鍵項目 → **TortoiseGit** → **提交(C)** - 勾選 **"Amend…

中科院報道鐵電液晶:從實驗室突破到多場景應用展望

2020年的時候&#xff0c;相信很多關注科技前沿的朋友都注意到&#xff0c;中國科學院一篇報道聚焦一項有望改寫顯示產業格局的新技術 —— 鐵電液晶&#xff08;FeLC&#xff09;。這項被業內稱為 "下一代顯示核心材料" 的研究&#xff0c;究竟取得了哪些實質性進展…

論文閱讀(六)Open Set Video HOI detection from Action-centric Chain-of-Look Prompting

論文來源&#xff1a;ICCV&#xff08;2023&#xff09; 項目地址&#xff1a;https://github.com/southnx/ACoLP 1.研究背景與問題 開放集場景下的泛化性&#xff1a;傳統 HOI 檢測假設訓練集包含所有測試類別&#xff0c;但現實中存在大量未見過的 HOI 類別&#xff08;如…

74道Node.js高頻題整理(附答案背誦版)

簡述 Node. js 基礎概念 &#xff1f; Node.js是一個基于Chrome V8引擎的JavaScript運行環境。它使得JavaScript可以在服務器端運行&#xff0c;從而進行網絡編程&#xff0c;如構建Web服務器、處理網絡請求等。Node.js采用事件驅動、非阻塞I/O模型&#xff0c;使其輕量且高效…

年齡是多少

有5個人坐在一起&#xff0c;問第五個人多少歲&#xff1f;他說比第四個人大兩歲。問第四個人歲數&#xff0c;他說比第三個人大兩歲。問第三個人&#xff0c;又說比第二個人大兩歲。問第二個人&#xff0c;說比第一個人大兩歲。最后問第一個人&#xff0c;他說是10歲。請問他們…

華為OD機試真題——模擬消息隊列(2025A卷:100分)Java/python/JavaScript/C++/C語言/GO六種最佳實現

2025 A卷 100分 題型 本文涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、測試用例以及綜合分析; 并提供Java、python、JavaScript、C++、C語言、GO六種語言的最佳實現方式! 2025華為OD真題目錄+全流程解析/備考攻略/經驗分享 華為OD機試真題《模擬消息隊列》: 目錄 題…

LangChain-結合GLM+SQL+函數調用實現數據庫查詢(三)

針對 LangChain-結合GLM+SQL+函數調用實現數據庫查詢(二)-CSDN博客 進一步簡化 通過 LangChain 和大語言模型(GLM-4)實現了一個 AI 代理,能夠根據自然語言提問自動生成 SQL 查詢語句,并連接 MySQL 數據庫執行查詢,最終返回結果。 整個流程如下: 用戶提問 → AI 生成 SQ…

ZLG ZCANPro,ECU刷新,bug分享

文章目錄 摘要 ??問題的起因bug分享 ?思考&反思 ??摘要 ?? ZCANPro想必大家都不陌生,買ZLG的CAN卡,必須要用的上位機軟件。在汽車行業中,有ECU軟件升級的需求,通常都通過UDS協議實現程序的更新,滿足UDS升級的上位機要么自己開發,要么用CANoe或者VFlash,最近…

第2期:APM32微控制器鍵盤PCB設計實戰教程

第2期&#xff1a;APM32微控制器鍵盤PCB設計實戰教程 一、APM32小系統介紹 使用apm32鍵盤小系統開源工程操作 APM32是一款與STM32兼容的微控制器&#xff0c;可以直接替代STM32進行使用。本教程基于之前開源的APM32小系統&#xff0c;鏈接將放在錄播評論區中供大家參考。 1…

單元測試-斷言常見注解

目錄 1.斷言 2.常見注解 3.依賴范圍 1.斷言 斷言練習 package com.gdcp;import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test;//測試類 public class UserServiceTest {Testpublic void testGetGender(){UserService userService new UserService…