690 lines
29 KiB
PHP
Executable File
690 lines
29 KiB
PHP
Executable File
<!-- 页面标题 -->
|
|
<h1 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
|
|
<i class="fas fa-image text-primary mr-3"></i>图片图库管理
|
|
</h1>
|
|
<!-- 主内容区 -->
|
|
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
|
<!-- 标签页导航 -->
|
|
<div class="border-b border-gray-200">
|
|
<div class="flex">
|
|
<button id="gallery-tab-gallery" class="px-6 py-4 font-medium text-primary border-b-2 border-primary" data-tab="gallery"> <i class="fa fa-th-large mr-2"></i>图库 </button>
|
|
<button id="gallery-upload-modal-trigger" class="px-6 py-4 font-medium text-gray-500 hover:text-gray-700"> <i class="fa fa-upload mr-2"></i>上传 </button>
|
|
</div>
|
|
</div>
|
|
<!-- 图库内容 -->
|
|
<div id="gallery-content-gallery" class="p-6" data-tab="gallery">
|
|
<!-- 批量删除按钮 -->
|
|
<button id="gallery-batch-delete" class="mb-6 px-4 py-2 bg-red-500 text-white rounded-md flex items-center opacity-50 cursor-not-allowed" disabled> <i class="fa fa-trash mr-2"></i> 删除选中的图片 </button>
|
|
<!-- 图片网格 -->
|
|
<div id="gallery-media-grid" class="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 mb-6"></div>
|
|
<!-- 加载更多按钮 -->
|
|
<button id="gallery-load-more" class="w-full py-2 px-4 bg-gray-100 text-gray-800 rounded-md flex items-center justify-center hover:bg-gray-200 transition-colors"> <i class="fa fa-refresh mr-2"></i> 加载更多 </button>
|
|
</div>
|
|
</div>
|
|
<!-- 上传弹出窗口 -->
|
|
<div id="gallery-upload-modal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center">
|
|
<div class="bg-white rounded-lg shadow-xl w-full max-w-md max-h-[90vh] overflow-y-auto">
|
|
<div class="p-6 border-b border-gray-200 flex justify-between items-center">
|
|
<h2 class="text-xl font-bold text-gray-800">上传图片</h2>
|
|
<button id="gallery-close-upload-modal" class="text-gray-500 hover:text-gray-700"> <i class="fa fa-times text-xl"></i> </button>
|
|
</div>
|
|
<div class="p-6">
|
|
<div class="space-y-6">
|
|
<!-- 上传参数设置 -->
|
|
<div class="space-y-4">
|
|
<!-- 质量设置 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">质量设置 (1-100)</label>
|
|
<div class="flex items-center space-x-4">
|
|
<input type="range" id="gallery-quality" min="35" max="100" value="60" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-500">
|
|
<span id="gallery-quality-value" class="text-sm font-medium min-w-[3rem] text-center">60</span>
|
|
</div>
|
|
</div>
|
|
<!-- 宽度设置 -->
|
|
<div>
|
|
<label for="gallery-width" class="block text-sm font-medium text-gray-700 mb-2">转换宽度</label>
|
|
<input type="number" id="gallery-width" placeholder="留空为自动" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
<!-- 高度设置 -->
|
|
<div>
|
|
<label for="gallery-height" class="block text-sm font-medium text-gray-700 mb-2">转换高度</label>
|
|
<input type="number" id="gallery-height" placeholder="留空为自动" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
</div>
|
|
<!-- 上传按钮 -->
|
|
<button id="gallery-upload-btn" class="w-full py-3 bg-blue-500 text-white rounded-md flex items-center justify-center hover:bg-blue-600 transition-colors">
|
|
<i class="fa fa-cloud-upload mr-2"></i>
|
|
<span id="gallery-upload-status">开始上传</span>
|
|
</button>
|
|
<input type="file" id="gallery-upload-input" multiple accept="image/*" class="hidden">
|
|
<!-- 上传结果 -->
|
|
<div id="gallery-upload-results" class="mt-6 space-y-4"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 图片预览模态框 -->
|
|
<div id="gallery-preview-modal" class="fixed inset-0 bg-black/90 z-50 hidden items-center justify-center p-4">
|
|
<div class="relative max-w-5xl ">
|
|
<!-- 图片容器 - 用于定位关闭按钮 -->
|
|
<div class="relative inline-block">
|
|
<img src="" alt="预览图片" class="max-w-full max-h-[80vh] bg-white mx-auto object-contain">
|
|
<!-- 关闭按钮 - 绝对定位在图片右上角 -->
|
|
<button id="gallery-preview-close" class="absolute -top-8 bg-white w-8 h-8 p-0 rounded-full -right-8 text-red text-2xl hover:text-gray-300 transition-colors">
|
|
<i class="fa fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
// 组件状态
|
|
const galleryState = {
|
|
targetInputId: null,
|
|
currentPage: 1,
|
|
totalItems: 0,
|
|
totalPages: 0,
|
|
mediaItems: [],
|
|
isLoading: false,
|
|
activeTab: 'gallery',
|
|
selectedImageIds: []
|
|
};
|
|
|
|
// DOM元素缓存 - 使用唯一ID避免冲突
|
|
const galleryElements = {
|
|
mediaGrid: document.getElementById('gallery-media-grid'),
|
|
loadMoreBtn: document.getElementById('gallery-load-more'),
|
|
batchDeleteBtn: document.getElementById('gallery-batch-delete'),
|
|
previewModal: document.getElementById('gallery-preview-modal'),
|
|
previewImage: document.querySelector('#gallery-preview-modal img'),
|
|
previewCloseBtn: document.getElementById('gallery-preview-close'),
|
|
uploadModal: document.getElementById('gallery-upload-modal'),
|
|
uploadModalTrigger: document.getElementById('gallery-upload-modal-trigger'),
|
|
closeUploadModal: document.getElementById('gallery-close-upload-modal'),
|
|
uploadResults: document.getElementById('gallery-upload-results')
|
|
};
|
|
|
|
// 初始化函数
|
|
function initGallery() {
|
|
setupGalleryEventListeners();
|
|
fetchGalleryMediaList();
|
|
}
|
|
|
|
// 格式化文件大小
|
|
function galleryFormatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(0)) + ' ' + sizes[i];
|
|
}
|
|
|
|
// 验证图片文件
|
|
function galleryIsImageFile(file) {
|
|
const extension = file.name.split('.').pop().toLowerCase();
|
|
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'heic', 'heif', 'avif'];
|
|
return imageExtensions.includes(extension) || /image\/.*/.test(file.type);
|
|
}
|
|
|
|
// 本地存储表单状态 - 使用唯一键名
|
|
function gallerySaveFormState(width, height) {
|
|
localStorage.setItem('galleryFormState', JSON.stringify({ width, height }));
|
|
}
|
|
|
|
function galleryLoadFormState() {
|
|
const state = localStorage.getItem('galleryFormState');
|
|
return state ? JSON.parse(state) : { width: '', height: '' };
|
|
}
|
|
|
|
// 设置事件监听 - 全部使用唯一ID
|
|
function setupGalleryEventListeners() {
|
|
// 质量滑块事件
|
|
const qualityInput = document.getElementById('gallery-quality');
|
|
const qualityValue = document.getElementById('gallery-quality-value');
|
|
if (qualityInput && qualityValue) {
|
|
qualityInput.addEventListener('input', function() {
|
|
qualityValue.textContent = this.value;
|
|
});
|
|
}
|
|
|
|
// 上传模态框控制
|
|
galleryElements.uploadModalTrigger.addEventListener('click', () => {
|
|
galleryElements.uploadModal.classList.remove('hidden');
|
|
galleryElements.uploadModal.classList.add('flex');
|
|
// 加载保存的表单状态
|
|
const { width, height } = galleryLoadFormState();
|
|
document.getElementById('gallery-width').value = width;
|
|
document.getElementById('gallery-height').value = height;
|
|
});
|
|
|
|
galleryElements.closeUploadModal.addEventListener('click', () => {
|
|
galleryElements.uploadModal.classList.add('hidden');
|
|
galleryElements.uploadModal.classList.remove('flex');
|
|
});
|
|
|
|
// 点击模态框背景关闭
|
|
galleryElements.uploadModal.addEventListener('click', (e) => {
|
|
if (e.target === galleryElements.uploadModal) {
|
|
galleryElements.uploadModal.classList.add('hidden');
|
|
galleryElements.uploadModal.classList.remove('flex');
|
|
}
|
|
});
|
|
|
|
// 加载更多按钮
|
|
galleryElements.loadMoreBtn.addEventListener('click', galleryLoadMoreImages);
|
|
|
|
// 批量删除按钮
|
|
galleryElements.batchDeleteBtn.addEventListener('click', () => {
|
|
if (galleryState.selectedImageIds.length > 0) {
|
|
galleryConfirmDeleteImage(galleryState.selectedImageIds);
|
|
}
|
|
});
|
|
|
|
// 图片网格事件委托 - 使用数据属性识别元素类型
|
|
galleryElements.mediaGrid.addEventListener('click', (e) => {
|
|
// 预览图片 - 使用数据属性选择
|
|
const previewBtn = e.target.closest('[data-action="preview"]');
|
|
if (previewBtn) {
|
|
const url = previewBtn.dataset.url;
|
|
galleryPreviewFile(url);
|
|
return;
|
|
}
|
|
|
|
// 复制链接 - 使用数据属性选择
|
|
const copyBtn = e.target.closest('[data-action="copy"]');
|
|
if (copyBtn) {
|
|
const url = copyBtn.dataset.url;
|
|
galleryCopyToClipboard(url);
|
|
return;
|
|
}
|
|
|
|
// 图片复选框 - 使用数据属性选择
|
|
const checkbox = e.target.closest('[data-type="image-checkbox"]');
|
|
if (checkbox) {
|
|
const imageId = checkbox.dataset.id;
|
|
galleryToggleImageSelection(imageId, checkbox);
|
|
return;
|
|
}
|
|
});
|
|
|
|
// 上传结果区域事件委托
|
|
galleryElements.uploadResults.addEventListener('click', (e) => {
|
|
const retryBtn = e.target.closest('[data-action="retry-upload"]');
|
|
if (retryBtn) {
|
|
document.getElementById('gallery-upload-input').click();
|
|
return;
|
|
}
|
|
});
|
|
|
|
// 上传按钮
|
|
document.getElementById('gallery-upload-btn').addEventListener('click', () => {
|
|
document.getElementById('gallery-upload-input').click();
|
|
});
|
|
|
|
// 文件选择事件
|
|
document.getElementById('gallery-upload-input').addEventListener('change', galleryHandleFileSelect);
|
|
|
|
// 预览模态框关闭
|
|
galleryElements.previewCloseBtn.addEventListener('click', () => {
|
|
galleryElements.previewModal.classList.remove('flex');
|
|
galleryElements.previewModal.classList.add('hidden');
|
|
});
|
|
|
|
galleryElements.previewModal.addEventListener('click', (e) => {
|
|
if (e.target === galleryElements.previewModal) {
|
|
galleryElements.previewModal.classList.remove('flex');
|
|
galleryElements.previewModal.classList.add('hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
// 切换图片选择状态
|
|
function galleryToggleImageSelection(imageId, checkbox) {
|
|
const index = galleryState.selectedImageIds.indexOf(imageId);
|
|
|
|
if (index === -1) {
|
|
// 选中
|
|
galleryState.selectedImageIds.push(imageId);
|
|
checkbox.checked = true;
|
|
// 使用DOM导航找到父容器并应用样式
|
|
checkbox.closest('[data-type="image-item"]').classList.add('ring-2', 'ring-blue-500', 'ring-offset-2');
|
|
} else {
|
|
// 取消选中
|
|
galleryState.selectedImageIds.splice(index, 1);
|
|
checkbox.checked = false;
|
|
checkbox.closest('[data-type="image-item"]').classList.remove('ring-2', 'ring-blue-500', 'ring-offset-2');
|
|
}
|
|
|
|
// 更新批量删除按钮状态
|
|
if (galleryState.selectedImageIds.length > 0) {
|
|
galleryElements.batchDeleteBtn.disabled = false;
|
|
galleryElements.batchDeleteBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
galleryElements.batchDeleteBtn.classList.add('hover:bg-red-600');
|
|
} else {
|
|
galleryElements.batchDeleteBtn.disabled = true;
|
|
galleryElements.batchDeleteBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
galleryElements.batchDeleteBtn.classList.remove('hover:bg-red-600');
|
|
}
|
|
}
|
|
|
|
// 确认删除
|
|
function galleryConfirmDeleteImage(ids) {
|
|
const isBatch = ids.length > 1;
|
|
if (confirm(`确定要${isBatch ? '批量删除选中的' : '删除这张'}图片吗?此操作不可撤销。`)) {
|
|
galleryDeleteImages(ids);
|
|
}
|
|
}
|
|
|
|
// 删除图片函数
|
|
function galleryDeleteImages(ids) {
|
|
galleryState.isLoading = true;
|
|
galleryShowLoading();
|
|
|
|
fetch('/admin/images/delete', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ ids })
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP错误: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
showMessage(`成功删除${ids.length}张图片`, 'success');
|
|
galleryState.currentPage = 1;
|
|
galleryState.selectedImageIds = [];
|
|
galleryState.isLoading = false;
|
|
fetchGalleryMediaList();
|
|
} else {
|
|
showMessage(`删除失败:${data.message}`, 'error');
|
|
galleryState.isLoading = false;
|
|
galleryRenderMediaGrid();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('删除失败:', error);
|
|
showMessage('网络错误,删除失败', 'error');
|
|
galleryState.isLoading = false;
|
|
galleryRenderMediaGrid();
|
|
});
|
|
}
|
|
|
|
// 获取图片列表
|
|
function fetchGalleryMediaList() {
|
|
if (galleryState.isLoading) return;
|
|
galleryState.isLoading = true;
|
|
|
|
const isInitialLoad = galleryState.currentPage === 1;
|
|
if (isInitialLoad) {
|
|
galleryShowLoading();
|
|
} else {
|
|
const loadingIndicator = galleryCreateLoadingIndicator();
|
|
galleryElements.mediaGrid.appendChild(loadingIndicator);
|
|
}
|
|
|
|
const itemsPerPage = 20;
|
|
const params = new URLSearchParams({
|
|
page: galleryState.currentPage,
|
|
limit: itemsPerPage
|
|
});
|
|
|
|
fetch(`/admin/images/list?${params}`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`列表请求失败: ${response.status}`);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
galleryState.currentPage = data.page;
|
|
galleryState.totalItems = data.total;
|
|
galleryState.totalPages = Math.ceil(data.total / itemsPerPage);
|
|
|
|
galleryState.mediaItems = isInitialLoad ? data.data : [...galleryState.mediaItems, ...data.data];
|
|
galleryRenderMediaGrid();
|
|
|
|
if (galleryState.currentPage >= galleryState.totalPages) {
|
|
galleryElements.loadMoreBtn.innerHTML = `<i class="fa fa-check mr-2"></i> 没有更多图片了`;
|
|
galleryElements.loadMoreBtn.disabled = true;
|
|
galleryElements.loadMoreBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
} else {
|
|
galleryElements.loadMoreBtn.innerHTML = `<i class="fa fa-refresh mr-2"></i> 加载更多`;
|
|
galleryElements.loadMoreBtn.disabled = false;
|
|
galleryElements.loadMoreBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
}
|
|
} else {
|
|
showMessage(data.message, 'error');
|
|
galleryRenderMediaGrid();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('获取图片列表失败:', error);
|
|
showMessage('网络错误,请重试', 'error');
|
|
galleryRenderMediaGrid();
|
|
})
|
|
.finally(() => {
|
|
galleryState.isLoading = false;
|
|
});
|
|
}
|
|
|
|
// 显示加载状态
|
|
function galleryShowLoading() {
|
|
galleryElements.mediaGrid.innerHTML = '';
|
|
const loadingIndicator = galleryCreateLoadingIndicator();
|
|
galleryElements.mediaGrid.appendChild(loadingIndicator);
|
|
}
|
|
|
|
// 加载更多图片
|
|
function galleryLoadMoreImages() {
|
|
if (galleryState.isLoading || galleryState.currentPage >= galleryState.totalPages) return;
|
|
galleryState.currentPage++;
|
|
fetchGalleryMediaList();
|
|
}
|
|
|
|
// 创建加载指示器
|
|
function galleryCreateLoadingIndicator() {
|
|
const indicator = document.createElement('div');
|
|
indicator.className = 'col-span-full flex flex-col items-center justify-center py-12';
|
|
indicator.innerHTML = `
|
|
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500 mb-4"></div>
|
|
<p class="text-gray-500">加载中...</p>
|
|
`;
|
|
return indicator;
|
|
}
|
|
|
|
// 渲染图片网格
|
|
function galleryRenderMediaGrid() {
|
|
galleryElements.mediaGrid.innerHTML = '';
|
|
const itemsToRender = galleryState.mediaItems;
|
|
|
|
if (itemsToRender.length === 0) {
|
|
galleryElements.mediaGrid.innerHTML = `
|
|
<div class="col-span-full flex flex-col items-center justify-center py-12 text-center px-4">
|
|
<i class="fa fa-picture-o text-4xl text-gray-300 mb-4"></i>
|
|
<p class="text-gray-500">没有找到图片</p>
|
|
<button onclick="document.getElementById('gallery-upload-modal-trigger').click()" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors">
|
|
<i class="fa fa-upload mr-2"></i>上传图片
|
|
</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// 渲染图片项 - 使用数据属性代替自定义类名
|
|
itemsToRender.forEach(item => {
|
|
const isSelected = galleryState.selectedImageIds.includes(item.id.toString());
|
|
const itemElement = document.createElement('div');
|
|
// 使用数据属性标识元素类型,而非自定义类名
|
|
itemElement.dataset.type = "image-item";
|
|
itemElement.className = `bg-white rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow ${isSelected ? 'ring-2 ring-blue-500 ring-offset-2' : ''}`;
|
|
itemElement.innerHTML = `
|
|
<div class="relative aspect-[4/3] bg-gray-100 overflow-hidden">
|
|
<!-- 复选框 - 使用数据属性标识 -->
|
|
<input type="checkbox" data-type="image-checkbox" data-id="${item.id}"
|
|
class="absolute top-2 left-2 z-10 w-4 h-4 rounded border-gray-300 text-blue-500 focus:ring-blue-500"
|
|
${isSelected ? 'checked' : ''}>
|
|
|
|
<!-- 缩略图 -->
|
|
<img src="${item.url}" alt="${item.name}" class="w-full h-full object-cover" loading="lazy">
|
|
|
|
<!-- 操作按钮 - 使用数据属性标识操作类型 -->
|
|
<div class="absolute inset-0 bg-black/50 opacity-0 hover:opacity-100 transition-opacity flex items-center justify-center gap-2 p-2">
|
|
<button data-action="copy" data-url="${item.url}"
|
|
class="bg-white w-8 h-8 p-0 rounded-full flex items-center justify-center hover:bg-gray-100 transition-colors"
|
|
title="复制链接">
|
|
<i class="fa fa-copy text-gray-800"></i>
|
|
</button>
|
|
<button data-action="preview" data-url="${item.url}"
|
|
class="bg-white w-8 h-8 p-0 rounded-full flex items-center justify-center hover:bg-gray-100 transition-colors"
|
|
title="预览图片">
|
|
<i class="fa fa-eye text-gray-800"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 图片信息 -->
|
|
<div class="p-2">
|
|
<div class="text-xs font-medium text-gray-800 truncate mb-1" title="${item.name}">${item.name}</div>
|
|
<div class="flex justify-between items-center text-xs text-gray-500">
|
|
<span>${galleryFormatFileSize(item.size)}</span>
|
|
<span>${item.width}*${item.height}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
galleryElements.mediaGrid.appendChild(itemElement);
|
|
});
|
|
|
|
// 添加统计信息
|
|
const statsElement = document.createElement('div');
|
|
statsElement.className = 'col-span-full mt-6 pt-4 border-t border-gray-100 text-sm text-gray-500 flex justify-between items-center';
|
|
statsElement.innerHTML = `
|
|
<div>
|
|
共计 <span class="font-semibold text-gray-800">${galleryState.totalItems}</span> 张图片
|
|
</div>
|
|
<div>
|
|
已显示 <span class="font-semibold text-gray-800">${galleryState.mediaItems.length}</span> 张图片
|
|
</div>
|
|
`;
|
|
galleryElements.mediaGrid.appendChild(statsElement);
|
|
|
|
// 更新批量删除按钮状态
|
|
if (galleryState.selectedImageIds.length > 0) {
|
|
galleryElements.batchDeleteBtn.disabled = false;
|
|
galleryElements.batchDeleteBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
galleryElements.batchDeleteBtn.classList.add('hover:bg-red-600');
|
|
} else {
|
|
galleryElements.batchDeleteBtn.disabled = true;
|
|
galleryElements.batchDeleteBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
galleryElements.batchDeleteBtn.classList.remove('hover:bg-red-600');
|
|
}
|
|
}
|
|
|
|
// 处理文件选择
|
|
function galleryHandleFileSelect(e) {
|
|
const files = e.target.files;
|
|
if (!files.length) return;
|
|
|
|
const validFiles = [];
|
|
const invalidFiles = [];
|
|
|
|
Array.from(files).forEach(file => {
|
|
if (!galleryIsImageFile(file)) {
|
|
invalidFiles.push({ file, reason: '不支持的文件类型,仅支持图片' });
|
|
return;
|
|
}
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
invalidFiles.push({ file, reason: '文件过大,最大支持10MB' });
|
|
return;
|
|
}
|
|
validFiles.push(file);
|
|
});
|
|
|
|
galleryElements.uploadResults.innerHTML = '';
|
|
|
|
if (invalidFiles.length > 0) {
|
|
invalidFiles.forEach(({ file, reason }) => {
|
|
const errorItem = document.createElement('div');
|
|
errorItem.className = 'p-4 border border-red-200 bg-red-50 rounded-md';
|
|
errorItem.innerHTML = `
|
|
<div class="flex justify-between items-start mb-1">
|
|
<span class="font-medium text-red-800 text-sm">${file.name}</span>
|
|
<span class="text-red-600 text-xs">错误</span>
|
|
</div>
|
|
<p class="text-red-700 text-xs">${reason}</p>
|
|
`;
|
|
galleryElements.uploadResults.appendChild(errorItem);
|
|
});
|
|
}
|
|
|
|
if (validFiles.length > 0) {
|
|
galleryUploadFiles(validFiles);
|
|
}
|
|
}
|
|
|
|
// 上传文件
|
|
function galleryUploadFiles(files) {
|
|
const quality = document.getElementById('gallery-quality').value;
|
|
const width = document.getElementById('gallery-width').value;
|
|
const height = document.getElementById('gallery-height').value;
|
|
const uploadStatus = document.getElementById('gallery-upload-status');
|
|
|
|
gallerySaveFormState(width, height);
|
|
|
|
// 添加上传中的指示器
|
|
files.forEach(file => {
|
|
const progressItem = document.createElement('div');
|
|
progressItem.className = 'p-4 border border-gray-200 rounded-md overflow-hidden';
|
|
progressItem.innerHTML = `
|
|
<div class="flex justify-between items-start mb-2">
|
|
<span class="font-medium text-gray-800 text-sm">${file.name}</span>
|
|
<span class="text-blue-500 text-xs">上传中</span>
|
|
</div>
|
|
<div class="w-full bg-gray-200 rounded-full h-1.5">
|
|
<div data-type="upload-progress" class="bg-blue-500 h-1.5 rounded-full w-0 transition-all duration-300"></div>
|
|
</div>
|
|
`;
|
|
galleryElements.uploadResults.appendChild(progressItem);
|
|
});
|
|
|
|
files.forEach((file, index) => {
|
|
const formData = new FormData();
|
|
formData.append('image', file);
|
|
formData.append('quality', quality);
|
|
if (width) formData.append('width', width);
|
|
if (height) formData.append('height', height);
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '/admin/images/upload', true);
|
|
xhr.timeout = 120000;
|
|
|
|
xhr.ontimeout = function() {
|
|
const progressItems = galleryElements.uploadResults.querySelectorAll('.p-4');
|
|
const progressItem = progressItems[index];
|
|
galleryHandleUploadError(file, '等待返回超时,已在后台处理,稍后到图片列表中查看', progressItem);
|
|
};
|
|
|
|
xhr.upload.addEventListener('progress', (e) => {
|
|
if (e.lengthComputable) {
|
|
const percentComplete = (e.loaded / e.total) * 100;
|
|
uploadStatus.textContent = `上传中 ${Math.round(percentComplete)}%`;
|
|
|
|
const progressItems = galleryElements.uploadResults.querySelectorAll('.p-4');
|
|
const progressItem = progressItems[index];
|
|
const progressBar = progressItem.querySelector('[data-type="upload-progress"]');
|
|
progressBar.style.width = `${percentComplete}%`;
|
|
}
|
|
});
|
|
|
|
xhr.onload = function() {
|
|
uploadStatus.textContent = `开始上传`;
|
|
|
|
const progressItems = galleryElements.uploadResults.querySelectorAll('.p-4');
|
|
const progressItem = progressItems[index];
|
|
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
try {
|
|
const response = JSON.parse(xhr.responseText);
|
|
if (response.status === "error") {
|
|
galleryHandleUploadError(file, response.message, progressItem);
|
|
} else {
|
|
galleryHandleUploadSuccess(response, progressItem);
|
|
}
|
|
} catch (error) {
|
|
console.error('解析响应失败:', error);
|
|
galleryHandleUploadError(file, '解析服务器响应失败', progressItem);
|
|
}
|
|
} else {
|
|
try {
|
|
const responseData = JSON.parse(xhr.responseText);
|
|
galleryHandleUploadError(file, responseData.message || '服务器错误', progressItem);
|
|
} catch (parseError) {
|
|
galleryHandleUploadError(file, `服务器错误 (${xhr.status})`, progressItem);
|
|
}
|
|
}
|
|
};
|
|
|
|
xhr.onerror = function() {
|
|
uploadStatus.textContent = `开始上传`;
|
|
const progressItems = galleryElements.uploadResults.querySelectorAll('.p-4');
|
|
const progressItem = progressItems[index];
|
|
galleryHandleUploadError(file, '网络错误,请重试', progressItem);
|
|
};
|
|
|
|
xhr.send(formData);
|
|
});
|
|
}
|
|
|
|
// 处理上传成功
|
|
function galleryHandleUploadSuccess(response, progressItem) {
|
|
if (!response.data || !response.data.url) {
|
|
console.error('上传成功但缺少URL:', response);
|
|
return;
|
|
}
|
|
const newItem = {
|
|
id: response.data.id || Date.now(),
|
|
name: response.data.name,
|
|
url: response.data.url,
|
|
size: response.data.size || 0,
|
|
width: response.data.width || 0,
|
|
height: response.data.height || 0
|
|
};
|
|
|
|
// 添加到图库列表
|
|
galleryState.mediaItems.unshift(newItem);
|
|
galleryState.totalItems = galleryState.mediaItems.length;
|
|
fetchGalleryMediaList();
|
|
showMessage('图片上传成功', 'success');
|
|
setTimeout(() => {
|
|
galleryElements.uploadModal.classList.add('hidden');
|
|
galleryElements.uploadModal.classList.remove('flex');
|
|
galleryElements.uploadResults.innerHTML = '';
|
|
const fileInput = document.getElementById('gallery-upload-input');
|
|
if (fileInput) {
|
|
fileInput.value = '';
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
// 处理上传失败
|
|
function galleryHandleUploadError(file, message, progressItem) {
|
|
progressItem.innerHTML = `
|
|
<div class="flex justify-between items-start mb-1">
|
|
<span class="font-medium text-red-800 text-sm">${file.name}</span>
|
|
<span class="text-red-600 text-xs">失败</span>
|
|
</div>
|
|
<p class="text-red-700 text-sm">${message}</p>
|
|
<button data-action="retry-upload"
|
|
class="mt-2 text-xs px-3 py-1 bg-gray-200 text-gray-800 rounded hover:bg-gray-300 transition-colors">
|
|
重试
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
// 预览图片
|
|
function galleryPreviewFile(url) {
|
|
galleryElements.previewImage.src = url;
|
|
galleryElements.previewImage.alt = '图片预览';
|
|
galleryElements.previewModal.classList.remove('hidden');
|
|
galleryElements.previewModal.classList.add('flex');
|
|
}
|
|
|
|
// 复制到剪贴板
|
|
function galleryCopyToClipboard(text) {
|
|
navigator.clipboard.writeText(text).then(() => {
|
|
showMessage('链接已复制', 'success');
|
|
}).catch(err => {
|
|
console.error('无法复制文本: ', err);
|
|
showMessage('复制失败,请手动复制', 'error');
|
|
});
|
|
}
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
document.addEventListener('DOMContentLoaded', initGallery);
|
|
|
|
// 暴露全局函数(如果需要)
|
|
window.fetchGalleryMediaList = fetchGalleryMediaList;
|
|
</script> |