Browse Source

实现视频上传和显示功能

pull/4/head
Trae AI 2 months ago
parent
commit
ea491c8d02
  1. 170
      Reject.js
  2. 4
      node_modules/.package-lock.json
  3. 4
      package-lock.json
  4. 195
      supply.html

170
Reject.js

@ -768,37 +768,48 @@ app.post('/api/supplies/create', async (req, res) => {
// 生成唯一的productId // 生成唯一的productId
const productId = `product_${Date.now()}_${Math.floor(Math.random() * 1000)}`; const productId = `product_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// 处理图片上传 // 处理媒体文件上传(图片和视频)
let uploadedImageUrls = []; let uploadedImageUrls = [];
if (Array.isArray(imageUrls) && imageUrls.length > 0) { if (Array.isArray(imageUrls) && imageUrls.length > 0) {
console.log('开始处理图片上传,共', imageUrls.length, '张图片'); console.log('开始处理媒体文件上传,共', imageUrls.length, '个文件');
for (const imageUrl of imageUrls) { for (const mediaUrl of imageUrls) {
if (imageUrl.startsWith('data:image/')) { if (mediaUrl.startsWith('data:image/') || mediaUrl.startsWith('data:video/')) {
// 处理DataURL // 处理DataURL
const base64Data = imageUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); let base64Data, ext, fileType;
if (mediaUrl.startsWith('data:image/')) {
// 图片类型
base64Data = mediaUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, '');
ext = mediaUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png';
fileType = 'image';
} else {
// 视频类型
base64Data = mediaUrl.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, '');
ext = mediaUrl.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4';
fileType = 'video';
}
let buffer = Buffer.from(base64Data, 'base64'); let buffer = Buffer.from(base64Data, 'base64');
const ext = imageUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png';
const filename = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`; const filename = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`;
try { try {
// 不再添加水印,前端已处理 // 不再添加水印,前端已处理
console.log('【水印处理】前端已添加水印,跳过后端水印处理'); console.log('【水印处理】前端已添加水印,跳过后端水印处理');
// 使用OSS上传带水印的图片 // 使用OSS上传媒体文件
const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, 'image'); const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, fileType);
uploadedImageUrls.push(ossUrl); uploadedImageUrls.push(ossUrl);
console.log('图片上传成功:', ossUrl); console.log(`${fileType}上传成功:`, ossUrl);
} catch (uploadError) { } catch (uploadError) {
console.error('图片上传失败:', uploadError.message); console.error(`${fileType}上传失败:`, uploadError.message);
// 继续上传其他图片,不中断流程 // 继续上传其他文件,不中断流程
} }
} else { } else {
// 已经是URL,直接使用 // 已经是URL,直接使用
uploadedImageUrls.push(imageUrl); uploadedImageUrls.push(mediaUrl);
} }
} }
console.log('图片处理完成,成功上传', uploadedImageUrls.length, '张图片'); console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件');
} }
// 创建商品数据 // 创建商品数据
@ -883,16 +894,86 @@ app.post('/api/supplies/create', async (req, res) => {
} }
}); });
// 图片上传API - /api/upload-image // 媒体文件上传API - /api/upload-media
console.log('正在注册媒体文件上传API路由: /api/upload-media');
app.post('/api/upload-media', async (req, res) => {
console.log('收到媒体文件上传请求:', req.body);
try {
const { fileData, fileName, folder = 'general' } = req.body;
// 验证参数
if (!fileData) {
return sendResponse(res, false, null, '文件数据不能为空');
}
let base64Data, ext, fileType;
if (fileData.startsWith('data:image/')) {
// 图片类型
base64Data = fileData.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, '');
ext = fileData.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png';
fileType = 'image';
} else if (fileData.startsWith('data:video/')) {
// 视频类型
base64Data = fileData.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, '');
ext = fileData.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4';
fileType = 'video';
} else {
return sendResponse(res, false, null, '不支持的文件类型');
}
let buffer = Buffer.from(base64Data, 'base64');
const filename = fileName || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`;
try {
// 使用OSS上传媒体文件
const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `uploads/${folder}`, fileType);
console.log(`${fileType}上传成功:`, ossUrl);
sendResponse(res, true, {
url: ossUrl,
fileType: fileType,
message: `${fileType}上传成功`
}, `${fileType}上传成功`);
} catch (uploadError) {
console.error(`${fileType}上传失败:`, uploadError.message);
sendResponse(res, false, null, `${fileType}上传失败: ${uploadError.message}`);
}
} catch (error) {
console.error('媒体文件上传API错误:', error.message);
sendResponse(res, false, null, `媒体文件上传失败: ${error.message}`);
}
});
// 图片上传API - /api/upload-image(兼容旧接口)
console.log('正在注册图片上传API路由: /api/upload-image'); console.log('正在注册图片上传API路由: /api/upload-image');
app.post('/api/upload-image', async (req, res) => { app.post('/api/upload-image', async (req, res) => {
console.log('收到图片上传请求'); console.log('收到图片上传请求,转发到媒体文件上传API');
// 注意:这里需要实现实际的图片上传逻辑,包括OSS配置 // 将请求转发到媒体文件上传API
// 由于当前缺少OSS配置,返回模拟数据 const uploadMediaHandler = app._router.stack.find(layer =>
sendResponse(res, true, { layer.route && layer.route.path === '/api/upload-media' && layer.route.methods['post']
imageUrl: 'https://example.com/image.jpg', );
message: '图片上传成功'
}, '图片上传成功'); if (uploadMediaHandler && uploadMediaHandler.route && uploadMediaHandler.route.stack[0]) {
return uploadMediaHandler.route.stack[0].handle(req, res);
} else {
sendResponse(res, false, null, '图片上传API内部错误');
}
});
// 视频上传API - /api/upload-video(专门的视频上传接口)
console.log('正在注册视频上传API路由: /api/upload-video');
app.post('/api/upload-video', async (req, res) => {
console.log('收到视频上传请求,转发到媒体文件上传API');
// 将请求转发到媒体文件上传API
const uploadMediaHandler = app._router.stack.find(layer =>
layer.route && layer.route.path === '/api/upload-media' && layer.route.methods['post']
);
if (uploadMediaHandler && uploadMediaHandler.route && uploadMediaHandler.route.stack[0]) {
return uploadMediaHandler.route.stack[0].handle(req, res);
} else {
sendResponse(res, false, null, '视频上传API内部错误');
}
}); });
// 获取审核失败原因API - /api/supplies/:id/reject-reason // 获取审核失败原因API - /api/supplies/:id/reject-reason
@ -1089,6 +1170,53 @@ app.put('/api/supplies/:id/edit', async (req, res) => {
} }
} }
<<<<<<< Updated upstream
=======
// 处理媒体文件上传(图片和视频)
let uploadedImageUrls = [];
if (Array.isArray(imageUrls) && imageUrls.length > 0) {
console.log('开始处理编辑媒体文件上传,共', imageUrls.length, '个文件');
for (const mediaUrl of imageUrls) {
if (mediaUrl.startsWith('data:image/') || mediaUrl.startsWith('data:video/')) {
// 处理DataURL
let base64Data, ext, fileType;
if (mediaUrl.startsWith('data:image/')) {
// 图片类型
base64Data = mediaUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, '');
ext = mediaUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png';
fileType = 'image';
} else {
// 视频类型
base64Data = mediaUrl.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, '');
ext = mediaUrl.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4';
fileType = 'video';
}
let buffer = Buffer.from(base64Data, 'base64');
const filename = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`;
try {
// 不再添加水印,前端已处理
console.log('【水印处理】前端已添加水印,跳过后端水印处理');
// 使用OSS上传媒体文件
const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, fileType);
uploadedImageUrls.push(ossUrl);
console.log(`${fileType}上传成功:`, ossUrl);
} catch (uploadError) {
console.error(`${fileType}上传失败:`, uploadError.message);
// 继续上传其他文件,不中断流程
}
} else {
// 已经是URL,直接使用
uploadedImageUrls.push(mediaUrl);
}
}
console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件');
}
>>>>>>> Stashed changes
// 更新货源信息 // 更新货源信息
const updateQuery = ` const updateQuery = `
UPDATE products UPDATE products

4
node_modules/.package-lock.json

@ -1,5 +1,9 @@
{ {
<<<<<<< Updated upstream
"name": "Review2", "name": "Review2",
=======
"name": "boss",
>>>>>>> Stashed changes
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

4
package-lock.json

@ -1,5 +1,9 @@
{ {
<<<<<<< Updated upstream
"name": "Review2", "name": "Review2",
=======
"name": "boss",
>>>>>>> Stashed changes
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

195
supply.html

@ -1052,13 +1052,13 @@
</div> </div>
</div> </div>
<!-- 商品图片 --> <!-- 商品图片/视频 -->
<div class="form-group"> <div class="form-group">
<label class="form-label">商品图片</label> <label class="form-label">商品图片/视频</label>
<div class="upload-area"> <div class="upload-area">
<div id="uploadImages" class="upload-images"></div> <div id="uploadImages" class="upload-images"></div>
<div class="add-image" onclick="triggerImageUpload()">+</div> <div class="add-image" onclick="triggerImageUpload()">+</div>
<input type="file" id="imageUpload" multiple accept="image/*" style="display: none;" onchange="handleImageUpload(event)"> <input type="file" id="imageUpload" multiple accept="image/*,video/*" style="display: none;" onchange="handleImageUpload(event)">
<div style="font-size: 12px; color: #666; margin-top: 10px; text-align: left; padding: 8px; background-color: #f5f5f5; border-radius: 4px;"> <div style="font-size: 12px; color: #666; margin-top: 10px; text-align: left; padding: 8px; background-color: #f5f5f5; border-radius: 4px;">
<div>图片要求:</div> <div>图片要求:</div>
<div>1. 箱子堆头图(不得出现地址、电话及货源信息,直营包场除外);</div> <div>1. 箱子堆头图(不得出现地址、电话及货源信息,直营包场除外);</div>
@ -1066,7 +1066,11 @@
<div>3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;</div> <div>3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;</div>
<div>4. 其他能佐证蛋重、品种的辅助图片。</div> <div>4. 其他能佐证蛋重、品种的辅助图片。</div>
</div> </div>
<<<<<<< Updated upstream
<div style="font-size: 12px; color: #999; margin-top: 10px; text-align: left;">最多上传5张图片</div> <div style="font-size: 12px; color: #999; margin-top: 10px; text-align: left;">最多上传5张图片</div>
=======
<div style="font-size: 12px; color: #999; margin-top: 10px; text-align: left;">最多上传2个媒体文件(图片或视频)</div>
>>>>>>> Stashed changes
</div> </div>
</div> </div>
@ -1398,13 +1402,21 @@
</div> </div>
</div> </div>
<!-- 商品图片 --> <!-- 商品图片/视频 -->
<div class="form-group"> <div class="form-group">
<label class="form-label">商品图片</label> <label class="form-label">商品图片/视频</label>
<div id="editUploadImages" class="upload-images" style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 15px;"> <div id="editUploadImages" class="upload-images" style="display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 15px;">
<!-- 图片将通过JavaScript动态加载 --> <!-- 图片将通过JavaScript动态加载 -->
</div> </div>
<<<<<<< Updated upstream
<div style="font-size: 12px; color: #999;">提示:图片暂不支持编辑</div> <div style="font-size: 12px; color: #999;">提示:图片暂不支持编辑</div>
=======
<div style="font-size: 12px; color: #999; margin-top: 10px;">最多上传2个媒体文件(图片或视频)</div>
<input type="file" id="editImageUpload" multiple accept="image/*,video/*" style="display: none;" onchange="handleEditImageUpload(event)">
<button type="button" onclick="triggerEditImageUpload()" style="margin-top: 10px; padding: 8px 16px; background-color: #f0f0f0; border: 1px solid #d9d9d9; border-radius: 4px; font-size: 14px; cursor: pointer;">
添加图片
</button>
>>>>>>> Stashed changes
</div> </div>
<!-- 货源类型 --> <!-- 货源类型 -->
@ -1860,14 +1872,17 @@
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async function(e) { reader.onload = async function(e) {
let imageUrl = e.target.result; let mediaUrl = e.target.result;
let isVideo = file.type.startsWith('video/');
// 为图片添加水印 // 只为图片添加水印,视频不添加
imageUrl = await addWatermarkToImage(imageUrl); if (!isVideo) {
mediaUrl = await addWatermarkToImage(mediaUrl);
}
supplyData.uploadedImages.push(imageUrl); supplyData.uploadedImages.push(mediaUrl);
renderUploadedImages(); renderUploadedImages();
// 图片上传后自动保存表单数据 // 媒体文件上传后自动保存表单数据
saveFormData(); saveFormData();
}; };
@ -3563,6 +3578,94 @@
document.getElementById('imageUpload').click(); document.getElementById('imageUpload').click();
} }
<<<<<<< Updated upstream
=======
// 触发编辑页面图片上传
function triggerEditImageUpload() {
document.getElementById('editImageUpload').click();
}
// 处理编辑页面图片上传
function handleEditImageUpload(event) {
const files = event.target.files;
const editUploadImages = document.getElementById('editUploadImages');
for (let i = 0; i < files.length; i++) {
if (editCurrentImages.length >= 2) {
alert('最多只能上传2个媒体文件(图片或视频)');
break;
}
const file = files[i];
const reader = new FileReader();
reader.onload = async function(e) {
let mediaUrl = e.target.result;
let isVideo = file.type.startsWith('video/');
try {
// 只为图片添加水印,视频不添加
if (!isVideo) {
mediaUrl = await addWatermarkToImage(mediaUrl);
}
// 添加到当前媒体列表
editCurrentImages.push(mediaUrl);
// 更新显示
updateEditImageDisplay();
} catch (error) {
console.error('媒体处理失败:', error);
alert('媒体处理失败,请重试');
}
};
reader.readAsDataURL(file);
}
// 清空文件输入,以便再次选择同一文件
event.target.value = '';
}
// 删除编辑页面图片
function deleteEditImage(imageUrl) {
// 从当前图片列表中删除
const index = editCurrentImages.indexOf(imageUrl);
if (index > -1) {
editCurrentImages.splice(index, 1);
}
// 更新显示
updateEditImageDisplay();
}
// 更新编辑页面媒体显示(图片或视频)
function updateEditImageDisplay() {
const editUploadImages = document.getElementById('editUploadImages');
editUploadImages.innerHTML = '';
editCurrentImages.forEach(mediaUrl => {
const imageElement = document.createElement('div');
imageElement.className = 'upload-image';
let mediaHtml = '';
if (mediaUrl.startsWith('data:video/') || mediaUrl.endsWith('.mp4') || mediaUrl.endsWith('.mov') || mediaUrl.endsWith('.avi') || mediaUrl.endsWith('.wmv') || mediaUrl.endsWith('.flv')) {
// 视频文件
mediaHtml = `<video src="${mediaUrl}" onclick="previewMedia('${mediaUrl}')" controls muted style="width: 100%; height: 100%; object-fit: cover;"></video>`;
} else {
// 图片文件
mediaHtml = `<img src="${mediaUrl}" alt="商品图片" onclick="previewImage('${mediaUrl}')">`;
}
imageElement.innerHTML = `
${mediaHtml}
<button class="delete-image-btn" onclick="deleteEditImage('${mediaUrl}')">×</button>
`;
editUploadImages.appendChild(imageElement);
});
}
>>>>>>> Stashed changes
// 为图片添加水印(前端Canvas实现) // 为图片添加水印(前端Canvas实现)
function addWatermarkToImage(imageUrl) { function addWatermarkToImage(imageUrl) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -3616,8 +3719,13 @@
const uploadArea = document.getElementById('uploadImages'); const uploadArea = document.getElementById('uploadImages');
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
<<<<<<< Updated upstream
if (supplyData.uploadedImages.length >= 5) { if (supplyData.uploadedImages.length >= 5) {
alert('最多只能上传5张图片'); alert('最多只能上传5张图片');
=======
if (supplyData.uploadedImages.length >= 2) {
alert('最多只能上传2个媒体文件(图片或视频)');
>>>>>>> Stashed changes
break; break;
} }
@ -3643,16 +3751,26 @@
event.target.value = ''; event.target.value = '';
} }
// 渲染已上传图片 // 渲染已上传媒体文件(图片和视频)
function renderUploadedImages() { function renderUploadedImages() {
const uploadArea = document.getElementById('uploadImages'); const uploadArea = document.getElementById('uploadImages');
uploadArea.innerHTML = ''; uploadArea.innerHTML = '';
supplyData.uploadedImages.forEach((imageUrl, index) => { supplyData.uploadedImages.forEach((mediaUrl, index) => {
const imageElement = document.createElement('div'); const imageElement = document.createElement('div');
imageElement.className = 'upload-image'; imageElement.className = 'upload-image';
let mediaHtml = '';
if (mediaUrl.startsWith('data:video/')) {
// 视频文件
mediaHtml = `<video src="${mediaUrl}" onclick="previewMedia('${mediaUrl}')" controls muted style="width: 100%; height: 100%; object-fit: cover;"></video>`;
} else {
// 图片文件
mediaHtml = `<img src="${mediaUrl}" alt="商品图片" onclick="previewImage('${mediaUrl}')">`;
}
imageElement.innerHTML = ` imageElement.innerHTML = `
<img src="${imageUrl}" alt="商品图片" onclick="previewImage('${imageUrl}')"> ${mediaHtml}
<div class="delete-image" onclick="deleteUploadedImage(${index})"></div> <div class="delete-image" onclick="deleteUploadedImage(${index})"></div>
`; `;
uploadArea.appendChild(imageElement); uploadArea.appendChild(imageElement);
@ -3942,28 +4060,65 @@
} }
const previewModal = document.getElementById('imagePreview'); const previewModal = document.getElementById('imagePreview');
const previewImage = document.getElementById('previewImage');
// 更新图片显示 // 更新媒体显示
updatePreviewImage(); updatePreviewImage();
// 重置缩放和拖动状态 // 重置缩放和拖动状态(仅图片需要)
const isVideo = imageUrl.startsWith('data:video/') || imageUrl.endsWith('.mp4') || imageUrl.endsWith('.mov') || imageUrl.endsWith('.avi') || imageUrl.endsWith('.wmv') || imageUrl.endsWith('.flv');
if (!isVideo) {
resetImageTransform(); resetImageTransform();
}
previewModal.classList.add('active'); previewModal.classList.add('active');
// 设置图片查看器事件 // 设置图片查看器事件(仅图片需要)
if (!isVideo) {
setupImageViewerEvents(); setupImageViewerEvents();
} }
}
// 专门的视频预览函数
function previewMedia(mediaUrl, mediaList = []) {
previewImage(mediaUrl, mediaList);
}
// 更新预览图片 // 更新预览媒体(图片或视频)
function updatePreviewImage() { function updatePreviewImage() {
// 获取预览元素
const previewImage = document.getElementById('previewImage'); const previewImage = document.getElementById('previewImage');
const previewVideo = document.createElement('video');
previewVideo.id = 'previewVideo';
previewVideo.className = 'preview-video';
previewVideo.style.maxWidth = '100%';
previewVideo.style.maxHeight = '100%';
previewVideo.controls = true;
previewVideo.muted = false;
const previewCounter = document.getElementById('previewCounter'); const previewCounter = document.getElementById('previewCounter');
const mediaUrl = currentPreviewImages[currentPreviewIndex];
// 判断是图片还是视频
const isVideo = mediaUrl.startsWith('data:video/') || mediaUrl.endsWith('.mp4') || mediaUrl.endsWith('.mov') || mediaUrl.endsWith('.avi') || mediaUrl.endsWith('.wmv') || mediaUrl.endsWith('.flv');
previewImage.src = currentPreviewImages[currentPreviewIndex]; // 移除旧的视频元素(如果存在)
const existingVideo = document.getElementById('previewVideo');
if (existingVideo) {
existingVideo.remove();
}
if (isVideo) {
// 视频预览
previewImage.style.display = 'none';
previewVideo.src = mediaUrl;
previewImage.parentElement.appendChild(previewVideo);
} else {
// 图片预览
previewImage.style.display = 'block';
previewImage.src = mediaUrl;
}
// 更新图片计数器 // 更新计数器
if (previewCounter) { if (previewCounter) {
previewCounter.textContent = `${currentPreviewIndex + 1}/${currentPreviewImages.length}`; previewCounter.textContent = `${currentPreviewIndex + 1}/${currentPreviewImages.length}`;
} }

Loading…
Cancel
Save