diff --git a/admin/src/views/withdrawals/index.vue b/admin/src/views/withdrawals/index.vue index 7ec5a44..5a2d6da 100644 --- a/admin/src/views/withdrawals/index.vue +++ b/admin/src/views/withdrawals/index.vue @@ -54,7 +54,7 @@ @@ -135,7 +135,12 @@ - ¥{{ withdrawalDetails.amount }} + {{ getCurrencySymbol(withdrawalDetails.currency) }}{{ withdrawalDetails.amount }} + + + + {{ withdrawalDetails.currency === 'PHP' ? '比索 (PHP)' : '人民币 (CNY)' }} + @@ -224,7 +229,7 @@ @@ -265,7 +270,7 @@ @@ -328,6 +333,7 @@ const approveForm = reactive({ withdrawalId: '', withdrawalNo: '', amount: '', + currency: 'CNY', userName: '', notes: '' }) @@ -340,6 +346,7 @@ const rejectForm = reactive({ withdrawalId: '', withdrawalNo: '', amount: '', + currency: 'CNY', userName: '', reason: '' }) @@ -448,6 +455,7 @@ function handleApprove(row) { approveForm.withdrawalId = row.id approveForm.withdrawalNo = row.withdrawalNo approveForm.amount = row.amount + approveForm.currency = row.currency || 'CNY' approveForm.userName = row.user?.nickname || row.user?.realName || '-' approveForm.notes = '' approveDialogVisible.value = true @@ -487,6 +495,7 @@ function handleReject(row) { rejectForm.withdrawalId = row.id rejectForm.withdrawalNo = row.withdrawalNo rejectForm.amount = row.amount + rejectForm.currency = row.currency || 'CNY' rejectForm.userName = row.user?.nickname || row.user?.realName || '-' rejectForm.reason = '' rejectDialogVisible.value = true @@ -561,6 +570,10 @@ function getStatusTagType(status) { return types[status] || 'info' } +function getCurrencySymbol(currency) { + return currency === 'PHP' ? '₱' : '¥' +} + function getPaymentMethodLabel(method) { const labels = { wechat: '微信', diff --git a/backend/src/controllers/uploadController.js b/backend/src/controllers/uploadController.js index 6e9d4f5..9b2fd86 100644 --- a/backend/src/controllers/uploadController.js +++ b/backend/src/controllers/uploadController.js @@ -4,6 +4,9 @@ const fs = require('fs').promises; const { uploadDir } = require('../middleware/upload'); const logger = require('../config/logger'); +// 图片处理阈值:小于此大小的图片不进行压缩处理(500KB) +const SKIP_PROCESSING_SIZE = 500 * 1024; + /** * Upload and process image * POST /api/v1/upload/image @@ -19,10 +22,51 @@ const uploadImage = async (req, res, next) => { } const originalPath = req.file.path; + const originalSize = req.file.size; + const originalExt = path.extname(req.file.originalname).toLowerCase(); - // Generate unique filename with .jpg extension (since we're converting to JPEG) + // Generate unique filename const timestamp = Date.now(); const randomString = Math.random().toString(36).substring(2, 15); + + // 对于小图片或已经是jpg/jpeg格式的小图片,跳过处理直接使用 + const isSmallImage = originalSize < SKIP_PROCESSING_SIZE; + const isJpeg = ['.jpg', '.jpeg'].includes(originalExt); + + if (isSmallImage && isJpeg) { + // 小的JPEG图片直接重命名使用,不进行处理 + const newFilename = `${timestamp}_${randomString}.jpg`; + const newPath = path.join(uploadDir, newFilename); + + try { + await fs.rename(originalPath, newPath); + } catch (renameError) { + // 如果重命名失败(跨分区),则复制后删除 + await fs.copyFile(originalPath, newPath); + await fs.unlink(originalPath).catch(() => {}); + } + + const imageUrl = `/uploads/${newFilename}`; + + logger.info('Image uploaded (skipped processing)', { + originalName: req.file.originalname, + filename: newFilename, + size: originalSize, + }); + + return res.status(200).json({ + code: 0, + message: 'Image uploaded successfully', + data: { + url: imageUrl, + filename: newFilename, + originalName: req.file.originalname, + size: originalSize, + }, + }); + } + + // 需要处理的图片 const processedFilename = `${timestamp}_${randomString}.jpg`; const processedPath = path.join(uploadDir, processedFilename); @@ -32,49 +76,42 @@ const uploadImage = async (req, res, next) => { } try { - // Process image with Sharp - const sharpInstance = sharp(originalPath); + // Process image with Sharp - 使用更快的设置 + const sharpInstance = sharp(originalPath, { + failOnError: false, // 不因为小错误而失败 + limitInputPixels: false, // 不限制输入像素 + }); + await sharpInstance .resize(1200, 1200, { fit: 'inside', withoutEnlargement: true, }) .jpeg({ - quality: 85, - progressive: true, + quality: 80, // 稍微降低质量以加快处理速度 + progressive: false, // 关闭渐进式以加快处理 + mozjpeg: false, // 不使用mozjpeg以加快速度 }) .toFile(processedPath); // Destroy sharp instance to release file handles sharpInstance.destroy(); - // Delete original file if it's different from processed - // On Windows, file deletion can fail due to file locks, so we schedule it for later - if (originalPath !== processedPath) { - // Schedule deletion after a delay to avoid file lock issues on Windows - setTimeout(async () => { - try { - await fs.unlink(originalPath); - logger.info('Original file deleted successfully', { path: originalPath }); - } catch (unlinkError) { - // If deletion still fails, just log it - // The file will need to be cleaned up manually or by a cleanup job - logger.warn('Could not delete original file', { - path: originalPath, - error: unlinkError.message - }); - } - }, 500); // Wait 500ms before attempting deletion - } + // Delete original file - 异步删除,不等待 + fs.unlink(originalPath).catch(unlinkError => { + logger.warn('Could not delete original file', { + path: originalPath, + error: unlinkError.message + }); + }); - // Return relative path for the image (more portable) - // Frontend will prepend the base URL as needed + // Return relative path for the image const imageUrl = `/uploads/${processedFilename}`; logger.info('Image uploaded and processed successfully', { originalName: req.file.originalname, processedFilename, - size: req.file.size, + originalSize, }); res.status(200).json({ @@ -89,11 +126,7 @@ const uploadImage = async (req, res, next) => { }); } catch (processingError) { // Clean up file if processing fails - try { - await fs.unlink(originalPath); - } catch (unlinkError) { - logger.error('Failed to delete file after processing error', { error: unlinkError }); - } + fs.unlink(originalPath).catch(() => {}); throw processingError; } } catch (error) { diff --git a/backend/src/services/adminWithdrawalService.js b/backend/src/services/adminWithdrawalService.js index 2884370..ca8ca4b 100644 --- a/backend/src/services/adminWithdrawalService.js +++ b/backend/src/services/adminWithdrawalService.js @@ -77,6 +77,7 @@ const getWithdrawalList = async (options = {}) => { id: withdrawal.id, withdrawalNo: withdrawal.withdrawalNo, amount: parseFloat(withdrawal.amount).toFixed(2), + currency: withdrawal.currency || 'CNY', paymentMethod: withdrawal.paymentMethod, paymentDetails: withdrawal.paymentDetails, status: withdrawal.status, @@ -281,6 +282,7 @@ const getWithdrawalDetails = async (withdrawalId) => { id: withdrawal.id, withdrawalNo: withdrawal.withdrawalNo, amount: parseFloat(withdrawal.amount).toFixed(2), + currency: withdrawal.currency || 'CNY', paymentMethod: withdrawal.paymentMethod, paymentDetails: withdrawal.paymentDetails, status: withdrawal.status, diff --git a/miniprogram/src/modules/api/AppServer.js b/miniprogram/src/modules/api/AppServer.js index 8afd2fd..b262df2 100644 --- a/miniprogram/src/modules/api/AppServer.js +++ b/miniprogram/src/modules/api/AppServer.js @@ -654,6 +654,7 @@ AppServer.prototype.UploadImage = async function(filePath) { url: url, filePath: filePath, name: 'image', + timeout: 60000, // 60秒超时 header: { 'Authorization': authToken },