🐛 修復 web ui 圖片上傳功能

This commit is contained in:
Minidoracat 2025-06-06 22:11:18 +08:00
parent da347c2108
commit ad44568c7c
2 changed files with 361 additions and 65 deletions

View File

@ -989,9 +989,11 @@ body {
.image-preview-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 4px;
margin-top: 12px;
justify-content: center;
/* 添加 padding 為刪除按鈕留出空間 */
padding: 8px;
}
.image-preview-container:empty {
@ -1003,10 +1005,12 @@ body {
position: relative;
display: inline-block;
border-radius: 6px;
overflow: hidden;
/* 移除 overflow: hidden 以確保刪除按鈕完全顯示 */
background: var(--bg-primary);
border: 1px solid var(--border-color);
transition: all 0.3s ease;
/* 添加一些 margin 為刪除按鈕留出空間 */
margin: 10px;
}
.image-preview-item:hover {
@ -1019,6 +1023,7 @@ body {
height: 80px;
object-fit: cover;
display: block;
border-radius: 6px;
}
/* 圖片刪除按鈕 */

View File

@ -259,12 +259,35 @@ class FeedbackApp {
this.commandOutput = document.getElementById('commandOutput');
this.runCommandBtn = document.getElementById('runCommandBtn');
// 圖片相關元素
this.imageInput = document.getElementById('imageInput');
this.imageUploadArea = document.getElementById('imageUploadArea');
this.imagePreviewContainer = document.getElementById('imagePreviewContainer');
this.imageSizeLimitSelect = document.getElementById('imageSizeLimit');
this.enableBase64DetailCheckbox = document.getElementById('enableBase64Detail');
// 動態初始化圖片相關元素
this.initImageElements();
}
/**
* 動態初始化圖片相關元素支援多佈局模式
*/
initImageElements() {
// 根據當前佈局模式確定元素前綴
const prefix = this.layoutMode && this.layoutMode.startsWith('combined') ? 'combined' : 'feedback';
console.log(`🖼️ 初始化圖片元素,使用前綴: ${prefix}`);
// 圖片相關元素 - 優先使用當前模式的元素
this.imageInput = document.getElementById(`${prefix}ImageInput`) || document.getElementById('imageInput');
this.imageUploadArea = document.getElementById(`${prefix}ImageUploadArea`) || document.getElementById('imageUploadArea');
this.imagePreviewContainer = document.getElementById(`${prefix}ImagePreviewContainer`) || document.getElementById('imagePreviewContainer');
this.imageSizeLimitSelect = document.getElementById(`${prefix}ImageSizeLimit`) || document.getElementById('imageSizeLimit');
this.enableBase64DetailCheckbox = document.getElementById(`${prefix}EnableBase64Detail`) || document.getElementById('enableBase64Detail');
// 記錄當前使用的前綴,用於後續操作
this.currentImagePrefix = prefix;
// 驗證關鍵元素是否存在
if (!this.imageInput || !this.imageUploadArea) {
console.warn(`⚠️ 圖片元素初始化失敗 - imageInput: ${!!this.imageInput}, imageUploadArea: ${!!this.imageUploadArea}`);
} else {
console.log(`✅ 圖片元素初始化成功 - 前綴: ${prefix}`);
}
}
initTabs() {
@ -349,44 +372,99 @@ class FeedbackApp {
this.handleCombinedMode();
}
// 重新初始化圖片處理(確保使用正確的佈局模式元素)
this.reinitializeImageHandling();
// 保存當前頁籤設定
this.saveSettings();
console.log(`切換到頁籤: ${tabName}`);
}
initImageHandling() {
if (!this.imageUploadArea || !this.imageInput) return;
/**
* 重新初始化圖片處理功能
*/
reinitializeImageHandling() {
console.log('🔄 重新初始化圖片處理功能...');
// 移除舊的事件監聽器
this.removeImageEventListeners();
// 重新初始化圖片元素
this.initImageElements();
// 如果有必要的元素,重新設置事件監聽器
if (this.imageUploadArea && this.imageInput) {
this.setupImageEventListeners();
console.log('✅ 圖片處理功能重新初始化完成');
} else {
console.warn('⚠️ 圖片處理重新初始化失敗 - 缺少必要元素');
}
// 更新圖片預覽(確保在新的容器中顯示)
this.updateImagePreview();
}
/**
* 設置圖片事件監聽器
*/
setupImageEventListeners() {
// 文件選擇事件
this.imageInput.addEventListener('change', (e) => {
this.imageChangeHandler = (e) => {
this.handleFileSelect(e.target.files);
});
};
this.imageInput.addEventListener('change', this.imageChangeHandler);
// 點擊上傳區域
this.imageUploadArea.addEventListener('click', () => {
this.imageClickHandler = () => {
this.imageInput.click();
});
};
this.imageUploadArea.addEventListener('click', this.imageClickHandler);
// 拖放事件
this.imageUploadArea.addEventListener('dragover', (e) => {
this.imageDragOverHandler = (e) => {
e.preventDefault();
this.imageUploadArea.classList.add('dragover');
});
};
this.imageUploadArea.addEventListener('dragover', this.imageDragOverHandler);
this.imageUploadArea.addEventListener('dragleave', (e) => {
this.imageDragLeaveHandler = (e) => {
e.preventDefault();
this.imageUploadArea.classList.remove('dragover');
});
};
this.imageUploadArea.addEventListener('dragleave', this.imageDragLeaveHandler);
this.imageUploadArea.addEventListener('drop', (e) => {
this.imageDropHandler = (e) => {
e.preventDefault();
this.imageUploadArea.classList.remove('dragover');
this.handleFileSelect(e.dataTransfer.files);
});
};
this.imageUploadArea.addEventListener('drop', this.imageDropHandler);
// 剪貼板貼上事件
document.addEventListener('paste', (e) => {
// 初始化圖片設定事件
this.initImageSettings();
}
initImageHandling() {
console.log('🖼️ 開始初始化圖片處理功能...');
// 重新初始化圖片元素(確保使用最新的佈局模式)
this.initImageElements();
if (!this.imageUploadArea || !this.imageInput) {
console.warn('⚠️ 圖片處理初始化失敗 - 缺少必要元素');
return;
}
// 清除舊的事件監聽器(如果存在)
this.removeImageEventListeners();
// 設置圖片事件監聽器
this.setupImageEventListeners();
// 設置全域剪貼板貼上事件(只設置一次)
if (!this.pasteHandler) {
this.pasteHandler = (e) => {
const items = e.clipboardData.items;
for (let item of items) {
if (item.type.indexOf('image') !== -1) {
@ -396,20 +474,101 @@ class FeedbackApp {
break;
}
}
});
};
document.addEventListener('paste', this.pasteHandler);
console.log('✅ 全域剪貼板貼上事件已設置');
}
// 圖片設定事件
console.log('✅ 圖片處理功能初始化完成');
}
/**
* 移除舊的圖片事件監聽器
*/
removeImageEventListeners() {
if (this.imageInput && this.imageChangeHandler) {
this.imageInput.removeEventListener('change', this.imageChangeHandler);
}
if (this.imageUploadArea) {
if (this.imageClickHandler) {
this.imageUploadArea.removeEventListener('click', this.imageClickHandler);
}
if (this.imageDragOverHandler) {
this.imageUploadArea.removeEventListener('dragover', this.imageDragOverHandler);
}
if (this.imageDragLeaveHandler) {
this.imageUploadArea.removeEventListener('dragleave', this.imageDragLeaveHandler);
}
if (this.imageDropHandler) {
this.imageUploadArea.removeEventListener('drop', this.imageDropHandler);
}
}
}
/**
* 初始化圖片設定事件
*/
initImageSettings() {
// 圖片大小限制設定
if (this.imageSizeLimitSelect) {
this.imageSizeLimitSelect.addEventListener('change', (e) => {
this.imageSizeLimit = parseInt(e.target.value);
this.saveSettings();
});
}
// Base64 詳細模式設定
if (this.enableBase64DetailCheckbox) {
this.enableBase64DetailCheckbox.addEventListener('change', (e) => {
this.enableBase64Detail = e.target.checked;
this.saveSettings();
});
}
// 同步設定到其他佈局模式
this.syncImageSettingsAcrossLayouts();
}
/**
* 同步圖片設定到所有佈局模式
*/
syncImageSettingsAcrossLayouts() {
const prefixes = ['feedback', 'combined'];
prefixes.forEach(prefix => {
const sizeSelect = document.getElementById(`${prefix}ImageSizeLimit`);
const base64Checkbox = document.getElementById(`${prefix}EnableBase64Detail`);
if (sizeSelect && sizeSelect !== this.imageSizeLimitSelect) {
sizeSelect.value = this.imageSizeLimit.toString();
sizeSelect.addEventListener('change', (e) => {
this.imageSizeLimit = parseInt(e.target.value);
// 同步到其他元素
prefixes.forEach(otherPrefix => {
const otherSelect = document.getElementById(`${otherPrefix}ImageSizeLimit`);
if (otherSelect && otherSelect !== e.target) {
otherSelect.value = e.target.value;
}
});
this.saveSettings();
});
}
if (base64Checkbox && base64Checkbox !== this.enableBase64DetailCheckbox) {
base64Checkbox.checked = this.enableBase64Detail;
base64Checkbox.addEventListener('change', (e) => {
this.enableBase64Detail = e.target.checked;
// 同步到其他元素
prefixes.forEach(otherPrefix => {
const otherCheckbox = document.getElementById(`${otherPrefix}EnableBase64Detail`);
if (otherCheckbox && otherCheckbox !== e.target) {
otherCheckbox.checked = e.target.checked;
}
});
this.saveSettings();
});
}
});
}
handleFileSelect(files) {
@ -455,22 +614,149 @@ class FeedbackApp {
}
updateImagePreview() {
if (!this.imagePreviewContainer) return;
// 更新所有佈局模式的圖片預覽容器
const previewContainers = [
document.getElementById('feedbackImagePreviewContainer'),
document.getElementById('combinedImagePreviewContainer'),
this.imagePreviewContainer // 當前主要容器
].filter(container => container); // 過濾掉不存在的容器
this.imagePreviewContainer.innerHTML = '';
if (previewContainers.length === 0) {
console.warn('⚠️ 沒有找到圖片預覽容器');
return;
}
console.log(`🖼️ 更新 ${previewContainers.length} 個圖片預覽容器`);
previewContainers.forEach(container => {
container.innerHTML = '';
this.images.forEach((image, index) => {
// 創建圖片預覽項目容器
const preview = document.createElement('div');
preview.className = 'image-preview';
preview.innerHTML = `
<img src="data:${image.type};base64,${image.data}" alt="${image.name}">
<div class="image-info">
<span class="image-name">${image.name}</span>
<span class="image-size">${this.formatFileSize(image.size)}</span>
</div>
<button class="image-remove" onclick="window.feedbackApp.removeImage(${index})">×</button>
`;
this.imagePreviewContainer.appendChild(preview);
preview.className = 'image-preview-item';
preview.style.position = 'relative';
preview.style.display = 'inline-block';
// 創建圖片元素
const img = document.createElement('img');
img.src = `data:${image.type};base64,${image.data}`;
img.alt = image.name;
img.style.width = '80px';
img.style.height = '80px';
img.style.objectFit = 'cover';
img.style.display = 'block';
img.style.borderRadius = '6px';
// 創建圖片信息容器
const imageInfo = document.createElement('div');
imageInfo.className = 'image-info';
imageInfo.style.position = 'absolute';
imageInfo.style.bottom = '0';
imageInfo.style.left = '0';
imageInfo.style.right = '0';
imageInfo.style.background = 'rgba(0, 0, 0, 0.7)';
imageInfo.style.color = 'white';
imageInfo.style.padding = '4px';
imageInfo.style.fontSize = '10px';
imageInfo.style.lineHeight = '1.2';
// 創建文件名元素
const imageName = document.createElement('div');
imageName.className = 'image-name';
imageName.textContent = image.name;
imageName.style.fontWeight = 'bold';
imageName.style.overflow = 'hidden';
imageName.style.textOverflow = 'ellipsis';
imageName.style.whiteSpace = 'nowrap';
// 創建文件大小元素
const imageSize = document.createElement('div');
imageSize.className = 'image-size';
imageSize.textContent = this.formatFileSize(image.size);
imageSize.style.fontSize = '9px';
imageSize.style.opacity = '0.8';
// 創建刪除按鈕
const removeBtn = document.createElement('button');
removeBtn.className = 'image-remove-btn';
removeBtn.textContent = '×';
removeBtn.title = '移除圖片';
removeBtn.style.position = 'absolute';
removeBtn.style.top = '-8px';
removeBtn.style.right = '-8px';
removeBtn.style.width = '20px';
removeBtn.style.height = '20px';
removeBtn.style.borderRadius = '50%';
removeBtn.style.background = '#f44336';
removeBtn.style.color = 'white';
removeBtn.style.border = 'none';
removeBtn.style.cursor = 'pointer';
removeBtn.style.fontSize = '12px';
removeBtn.style.fontWeight = 'bold';
removeBtn.style.display = 'flex';
removeBtn.style.alignItems = 'center';
removeBtn.style.justifyContent = 'center';
removeBtn.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.3)';
removeBtn.style.transition = 'all 0.3s ease';
removeBtn.style.zIndex = '10';
// 添加刪除按鈕懸停效果
removeBtn.addEventListener('mouseenter', () => {
removeBtn.style.background = '#d32f2f';
removeBtn.style.transform = 'scale(1.1)';
});
removeBtn.addEventListener('mouseleave', () => {
removeBtn.style.background = '#f44336';
removeBtn.style.transform = 'scale(1)';
});
// 添加刪除功能
removeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.removeImage(index);
});
// 組裝元素
imageInfo.appendChild(imageName);
imageInfo.appendChild(imageSize);
preview.appendChild(img);
preview.appendChild(imageInfo);
preview.appendChild(removeBtn);
container.appendChild(preview);
});
});
// 更新圖片計數顯示
this.updateImageCount();
}
/**
* 更新圖片計數顯示
*/
updateImageCount() {
const count = this.images.length;
const countElements = document.querySelectorAll('.image-count');
countElements.forEach(element => {
element.textContent = count > 0 ? `(${count})` : '';
});
// 更新上傳區域的顯示狀態
const uploadAreas = [
document.getElementById('feedbackImageUploadArea'),
document.getElementById('combinedImageUploadArea')
].filter(area => area);
uploadAreas.forEach(area => {
if (count > 0) {
area.classList.add('has-images');
} else {
area.classList.remove('has-images');
}
});
}
@ -1348,31 +1634,36 @@ class FeedbackApp {
}
clearFeedback() {
// 清空分離模式的回饋文字
if (this.feedbackText) {
this.feedbackText.value = '';
}
console.log('🧹 清空回饋內容...');
// 清空合併模式的回饋文字
const combinedFeedbackText = document.getElementById('combinedFeedbackText');
if (combinedFeedbackText) {
combinedFeedbackText.value = '';
}
// 清空所有模式的回饋文字
const feedbackInputs = [
document.getElementById('feedbackText'),
document.getElementById('combinedFeedbackText')
].filter(input => input);
feedbackInputs.forEach(input => {
input.value = '';
});
// 清空圖片數據
this.images = [];
// 更新所有圖片預覽容器updateImagePreview 現在會處理所有容器)
this.updateImagePreview();
// 同時清空合併模式的圖片預覽
const combinedImagePreviewContainer = document.getElementById('combinedImagePreviewContainer');
if (combinedImagePreviewContainer) {
combinedImagePreviewContainer.innerHTML = '';
}
// 重新啟用提交按鈕
if (this.submitBtn) {
this.submitBtn.disabled = false;
this.submitBtn.textContent = window.i18nManager ? window.i18nManager.t('buttons.submit') : '提交回饋';
}
const submitButtons = [
document.getElementById('submitBtn'),
document.getElementById('combinedSubmitBtn')
].filter(btn => btn);
submitButtons.forEach(button => {
button.disabled = false;
button.textContent = window.i18nManager ? window.i18nManager.t('buttons.submit') : '提交回饋';
});
console.log('✅ 回饋內容清空完成');
}
runCommand() {