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. 199
      supply.html

170
Reject.js

@ -768,37 +768,48 @@ app.post('/api/supplies/create', async (req, res) => {
// 生成唯一的productId
const productId = `product_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// 处理图片上传
// 处理媒体文件上传(图片和视频)
let uploadedImageUrls = [];
if (Array.isArray(imageUrls) && imageUrls.length > 0) {
console.log('开始处理图片上传,共', imageUrls.length, '张图片');
console.log('开始处理媒体文件上传,共', imageUrls.length, '个文件');
for (const imageUrl of imageUrls) {
if (imageUrl.startsWith('data:image/')) {
for (const mediaUrl of imageUrls) {
if (mediaUrl.startsWith('data:image/') || mediaUrl.startsWith('data:video/')) {
// 处理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');
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}`;
try {
// 不再添加水印,前端已处理
console.log('【水印处理】前端已添加水印,跳过后端水印处理');
// 使用OSS上传带水印的图片
const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, 'image');
// 使用OSS上传媒体文件
const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, fileType);
uploadedImageUrls.push(ossUrl);
console.log('图片上传成功:', ossUrl);
console.log(`${fileType}上传成功:`, ossUrl);
} catch (uploadError) {
console.error('图片上传失败:', uploadError.message);
// 继续上传其他图片,不中断流程
console.error(`${fileType}上传失败:`, uploadError.message);
// 继续上传其他文件,不中断流程
}
} else {
// 已经是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');
app.post('/api/upload-image', async (req, res) => {
console.log('收到图片上传请求');
// 注意:这里需要实现实际的图片上传逻辑,包括OSS配置
// 由于当前缺少OSS配置,返回模拟数据
sendResponse(res, true, {
imageUrl: 'https://example.com/image.jpg',
message: '图片上传成功'
}, '图片上传成功');
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/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
@ -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 = `
UPDATE products

4
node_modules/.package-lock.json

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

4
package-lock.json

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

199
supply.html

@ -1052,13 +1052,13 @@
</div>
</div>
<!-- 商品图片 -->
<!-- 商品图片/视频 -->
<div class="form-group">
<label class="form-label">商品图片</label>
<label class="form-label">商品图片/视频</label>
<div class="upload-area">
<div id="uploadImages" class="upload-images"></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>图片要求:</div>
<div>1. 箱子堆头图(不得出现地址、电话及货源信息,直营包场除外);</div>
@ -1066,7 +1066,11 @@
<div>3. 敲开鸡蛋后清晰显示蛋清、蛋黄状态,以体现新鲜度;</div>
<div>4. 其他能佐证蛋重、品种的辅助图片。</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;">最多上传2个媒体文件(图片或视频)</div>
>>>>>>> Stashed changes
</div>
</div>
@ -1398,13 +1402,21 @@
</div>
</div>
<!-- 商品图片 -->
<!-- 商品图片/视频 -->
<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;">
<!-- 图片将通过JavaScript动态加载 -->
</div>
<<<<<<< Updated upstream
<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>
<!-- 货源类型 -->
@ -1860,14 +1872,17 @@
const reader = new FileReader();
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();
// 图片上传后自动保存表单数据
// 媒体文件上传后自动保存表单数据
saveFormData();
};
@ -3563,6 +3578,94 @@
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实现)
function addWatermarkToImage(imageUrl) {
return new Promise((resolve, reject) => {
@ -3616,8 +3719,13 @@
const uploadArea = document.getElementById('uploadImages');
for (let i = 0; i < files.length; i++) {
<<<<<<< Updated upstream
if (supplyData.uploadedImages.length >= 5) {
alert('最多只能上传5张图片');
=======
if (supplyData.uploadedImages.length >= 2) {
alert('最多只能上传2个媒体文件(图片或视频)');
>>>>>>> Stashed changes
break;
}
@ -3643,16 +3751,26 @@
event.target.value = '';
}
// 渲染已上传图片
// 渲染已上传媒体文件(图片和视频)
function renderUploadedImages() {
const uploadArea = document.getElementById('uploadImages');
uploadArea.innerHTML = '';
supplyData.uploadedImages.forEach((imageUrl, index) => {
supplyData.uploadedImages.forEach((mediaUrl, index) => {
const imageElement = document.createElement('div');
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 = `
<img src="${imageUrl}" alt="商品图片" onclick="previewImage('${imageUrl}')">
${mediaHtml}
<div class="delete-image" onclick="deleteUploadedImage(${index})"></div>
`;
uploadArea.appendChild(imageElement);
@ -3942,28 +4060,65 @@
}
const previewModal = document.getElementById('imagePreview');
const previewImage = document.getElementById('previewImage');
// 更新图片显示
// 更新媒体显示
updatePreviewImage();
// 重置缩放和拖动状态
resetImageTransform();
// 重置缩放和拖动状态(仅图片需要)
const isVideo = imageUrl.startsWith('data:video/') || imageUrl.endsWith('.mp4') || imageUrl.endsWith('.mov') || imageUrl.endsWith('.avi') || imageUrl.endsWith('.wmv') || imageUrl.endsWith('.flv');
if (!isVideo) {
resetImageTransform();
}
previewModal.classList.add('active');
// 设置图片查看器事件
setupImageViewerEvents();
// 设置图片查看器事件(仅图片需要)
if (!isVideo) {
setupImageViewerEvents();
}
}
// 更新预览图片
// 专门的视频预览函数
function previewMedia(mediaUrl, mediaList = []) {
previewImage(mediaUrl, mediaList);
}
// 更新预览媒体(图片或视频)
function updatePreviewImage() {
// 获取预览元素
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 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) {
previewCounter.textContent = `${currentPreviewIndex + 1}/${currentPreviewImages.length}`;
}

Loading…
Cancel
Save