2026-03-24 11:45:13 +08:00
|
|
|
|
import request from "./request";
|
|
|
|
|
|
let loading = false;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 核心新增:校验图片比例是否符合 4:3 规范(带容错)
|
|
|
|
|
|
* @param {Number} width 图片宽度
|
|
|
|
|
|
* @param {Number} height 图片高度
|
|
|
|
|
|
* @param {Number} tolerance 容错值,默认0.05,摇摆空间足够
|
|
|
|
|
|
* @returns {Boolean} 是否符合比例
|
|
|
|
|
|
*/
|
|
|
|
|
|
const checkImageRatio = (width, height, tolerance = 0.05) => {
|
|
|
|
|
|
const standardRatio = 4 / 3; // 标准4:3比例
|
|
|
|
|
|
const actualRatio = width / height; // 实际图片宽高比
|
|
|
|
|
|
// 支持 横版4:3 或 竖版3:4 两种情况
|
|
|
|
|
|
console.log(actualRatio, standardRatio)
|
|
|
|
|
|
//const isStandardHorizontal = Math.abs(actualRatio - standardRatio) <= tolerance;
|
|
|
|
|
|
// const isStandardVertical = Math.abs(actualRatio - (3 / 4)) <= tolerance;
|
|
|
|
|
|
// return isStandardHorizontal || isStandardVertical;
|
2026-03-25 13:29:04 +08:00
|
|
|
|
const isStandardHorizontal = standardRatio - actualRatio <= 0.334 || actualRatio - standardRatio <= tolerance;
|
2026-03-24 11:45:13 +08:00
|
|
|
|
return isStandardHorizontal;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 触发上传
|
|
|
|
|
|
const handleUpload = async (userId, type, sourceType = ['album', 'camera'], uploadType = 'image') => {
|
|
|
|
|
|
if (loading) return
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
loading = true
|
|
|
|
|
|
let file = null
|
|
|
|
|
|
if (uploadType == 'image') {
|
|
|
|
|
|
const res = await uni.chooseImage({
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
sizeType: ['original', 'compressed'],
|
|
|
|
|
|
sourceType: sourceType
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
console.log('res======', res)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查用户是否真的选择了图片
|
|
|
|
|
|
if (!res || !res.tempFiles || res.tempFiles.length === 0) {
|
|
|
|
|
|
console.log('用户取消了图片选择')
|
|
|
|
|
|
return null // 明确返回 null 表示用户取消
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
file = res.tempFiles[0]
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 【核心新增:图片比例校验 开始】 ==========
|
|
|
|
|
|
// 获取图片宽高信息(uniapp多端兼容API,APP/小程序/H5都支持)
|
|
|
|
|
|
const imageInfo = await uni.getImageInfo({
|
|
|
|
|
|
src: file.path
|
|
|
|
|
|
});
|
|
|
|
|
|
const {
|
|
|
|
|
|
width,
|
|
|
|
|
|
height
|
|
|
|
|
|
} = imageInfo;
|
|
|
|
|
|
console.log('图片宽高:', width, height, '实际比例:', (width / height).toFixed(4));
|
|
|
|
|
|
// 校验4:3比例,容错0.05
|
|
|
|
|
|
const isRatioValid = checkImageRatio(width, height, 0.05);
|
|
|
|
|
|
if (!isRatioValid) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '图片比例需为4:3,允许轻微偏差',
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
duration: 2000
|
|
|
|
|
|
});
|
|
|
|
|
|
return null; // 比例不符,终止上传流程
|
|
|
|
|
|
}
|
|
|
|
|
|
// ========== 【核心新增:图片比例校验 结束】 ==========
|
|
|
|
|
|
} else if (uploadType == 'video') {
|
|
|
|
|
|
const res = await uni.chooseVideo({
|
|
|
|
|
|
count: 1,
|
|
|
|
|
|
sourceType: ['album', 'camera'],
|
|
|
|
|
|
compressed: false
|
|
|
|
|
|
})
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
file = res.tempFile;
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef APP-PLUS || MP-WEIXIN
|
|
|
|
|
|
file = res;
|
|
|
|
|
|
// #endif
|
2026-05-06 15:01:38 +08:00
|
|
|
|
const duration = res.duration;
|
|
|
|
|
|
if (duration < 3) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "视频时长不能少于3秒",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (duration > 59.9) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "视频时长不能超过1分钟",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2026-03-24 11:45:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const result = await uploadFile(file, userId, type, uploadType)
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('上传出错:', error)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是用户取消操作,不抛出错误,返回 null
|
2026-04-29 18:17:50 +08:00
|
|
|
|
// if (error.errMsg && error.errMsg.includes('cancel')) {
|
|
|
|
|
|
// console.log('用户取消了操作')
|
|
|
|
|
|
// return null
|
|
|
|
|
|
// }
|
2026-03-24 11:45:13 +08:00
|
|
|
|
|
|
|
|
|
|
throw error
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// iOS专用:HEIC转JPG
|
|
|
|
|
|
const convertHEICtoJPG = async (heicPath) => {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
plus.zip.compressImage({
|
|
|
|
|
|
src: heicPath,
|
|
|
|
|
|
dst: heicPath.replace(/\.heic$|\.heif$/i, '.jpg'),
|
|
|
|
|
|
format: 'jpg',
|
|
|
|
|
|
quality: 90
|
|
|
|
|
|
}, resolve, reject)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
// 获取二进制数据(兼容iOS)
|
|
|
|
|
|
const getFileBinary = async (filePath) => {
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
|
const reader = new plus.io.FileReader()
|
|
|
|
|
|
reader.onloadend = (e) => {
|
|
|
|
|
|
resolve(e.target.result)
|
|
|
|
|
|
}
|
|
|
|
|
|
reader.readAsArrayBuffer(filePath)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
const compressAndUpload = async (file, userId, type) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('file=====', file)
|
|
|
|
|
|
// 压缩图片
|
|
|
|
|
|
const compressedResult = await uni.compressImage({
|
|
|
|
|
|
src: file.path,
|
|
|
|
|
|
quality: 80, // 压缩质量,范围0-100
|
|
|
|
|
|
format: 'jpg', // 指定输出为jpg格式
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取压缩后的临时文件路径
|
|
|
|
|
|
const compressedPath = compressedResult.tempFilePath;
|
|
|
|
|
|
console.log('compressedPath', compressedPath)
|
|
|
|
|
|
file.path = compressedPath;
|
|
|
|
|
|
console.log('file.path', file.path)
|
|
|
|
|
|
// 上传图片
|
|
|
|
|
|
const result = await uploadFile(file, userId, type)
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('压缩或上传失败:', error);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '图片处理失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 上传文件到OSS
|
|
|
|
|
|
const uploadFile = async (file, userId, type, uploadType) => {
|
|
|
|
|
|
console.log('file=======', file)
|
|
|
|
|
|
console.log('userId=======', userId)
|
|
|
|
|
|
// 检查文件大小
|
2026-05-06 15:01:38 +08:00
|
|
|
|
if (uploadType == "image") {
|
|
|
|
|
|
if (file.size > 5 * 1024 * 1024) {
|
|
|
|
|
|
return Promise.reject({errMsg: "文件大小不能超过5MB"});
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (uploadType == "video") {
|
|
|
|
|
|
if (file.size > 500 * 1024 * 1024) {
|
|
|
|
|
|
return Promise.reject({errMsg: "文件大小不能超过500MB"});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-24 11:45:13 +08:00
|
|
|
|
|
|
|
|
|
|
let date = new Date().getTime()
|
|
|
|
|
|
console.log('file', file)
|
|
|
|
|
|
// let imageType = file.path.split('.')
|
|
|
|
|
|
let imageType;
|
|
|
|
|
|
if (uploadType == 'image') {
|
2026-03-25 13:29:04 +08:00
|
|
|
|
//imageType = file.path.split('.')
|
2026-04-09 16:10:17 +08:00
|
|
|
|
imageType = file.path.split('.')
|
2026-03-24 11:45:13 +08:00
|
|
|
|
} else if (uploadType == 'video') {
|
|
|
|
|
|
// #ifdef H5
|
2026-04-09 17:23:40 +08:00
|
|
|
|
imageType = file.url.split('.')
|
2026-03-24 11:45:13 +08:00
|
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef APP-PLUS || MP-WEIXIN
|
|
|
|
|
|
imageType = file.tempFilePath.split('.')
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
}
|
|
|
|
|
|
let artisan = type == 1 ? 'yh' : type == 2 ? 'syr' : type == 3 ? 'sj' : '';
|
|
|
|
|
|
let name = `${userId}_${artisan}_${date}.${imageType[imageType.length - 1]}`
|
|
|
|
|
|
console.log('file.path', file.path)
|
|
|
|
|
|
console.log('imageType', imageType)
|
|
|
|
|
|
console.log('name', name)
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 获取OSS上传凭证
|
|
|
|
|
|
const ossConfig = await getOssConfig(type)
|
|
|
|
|
|
// 2. 上传文件
|
|
|
|
|
|
const uploadRes = await uploadToOss(file, ossConfig, name, uploadType)
|
|
|
|
|
|
// 3. 添加到文件列表
|
|
|
|
|
|
const fileUrl = `${ossConfig.host}/${ossConfig.dir}${name}`
|
|
|
|
|
|
return fileUrl
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('上传失败:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取OSS配置
|
|
|
|
|
|
const getOssConfig = async (type) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await request.post('/user/getalioss', {
|
|
|
|
|
|
type: type,
|
|
|
|
|
|
kind: 1
|
|
|
|
|
|
})
|
|
|
|
|
|
return res
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取OSS配置失败:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 上传到OSS
|
|
|
|
|
|
const uploadToOss = (file, ossConfig, name, uploadType) => {
|
2026-04-29 18:17:50 +08:00
|
|
|
|
|
2026-03-24 11:45:13 +08:00
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
const formData = new FormData()
|
|
|
|
|
|
formData.append('name', name);
|
|
|
|
|
|
formData.append('policy', ossConfig.policy);
|
|
|
|
|
|
formData.append('OSSAccessKeyId', ossConfig.ossAccessKeyId);
|
|
|
|
|
|
formData.append('success_action_status', '200');
|
|
|
|
|
|
formData.append('signature', ossConfig.signature);
|
|
|
|
|
|
formData.append('key', ossConfig.dir + name);
|
|
|
|
|
|
// file必须为最后一个表单域,除file以外的其他表单域无顺序要求。
|
|
|
|
|
|
formData.append('file', file);
|
|
|
|
|
|
fetch(ossConfig.host, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: formData
|
|
|
|
|
|
}, ).then((res) => {
|
|
|
|
|
|
resolve(`${ossConfig.host}/${ossConfig.dir}${name}`)
|
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
|
reject(new Error(`上传失败: ${res.statusCode}`))
|
|
|
|
|
|
})
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef APP-PLUS || MP-WEIXIN
|
|
|
|
|
|
let formData = {};
|
|
|
|
|
|
formData.name = name;
|
|
|
|
|
|
formData.policy = ossConfig.policy;
|
|
|
|
|
|
formData.OSSAccessKeyId = ossConfig.ossAccessKeyId;
|
|
|
|
|
|
formData.success_action_status = '200';
|
|
|
|
|
|
formData.signature = ossConfig.signature;
|
|
|
|
|
|
formData.key = ossConfig.dir + name;
|
|
|
|
|
|
formData.file = file;
|
|
|
|
|
|
|
|
|
|
|
|
let path;
|
|
|
|
|
|
if (uploadType == 'image') {
|
|
|
|
|
|
path = file.path
|
|
|
|
|
|
// 获取文件扩展名并设置 Content-Type
|
2026-04-09 16:10:17 +08:00
|
|
|
|
console.log('111111');
|
2026-03-24 11:45:13 +08:00
|
|
|
|
const ext = file.path.split('.').pop().toLowerCase()
|
2026-04-09 16:10:17 +08:00
|
|
|
|
console.log('33333333');
|
2026-03-24 11:45:13 +08:00
|
|
|
|
const mimeTypes = {
|
|
|
|
|
|
jpg: 'image/jpeg',
|
|
|
|
|
|
jpeg: 'image/jpeg',
|
|
|
|
|
|
png: 'image/png',
|
|
|
|
|
|
gif: 'image/gif'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mimeTypes[ext]) {
|
|
|
|
|
|
formData['Content-Type'] = mimeTypes[ext],
|
|
|
|
|
|
formData['x-oss-meta-content-type'] = mimeTypes[ext]
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('formData', formData)
|
|
|
|
|
|
} else if (uploadType == 'video') {
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
path = file.name
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef APP-PLUS || MP-WEIXIN
|
|
|
|
|
|
path = file.tempFilePath
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
}
|
2026-04-29 18:17:50 +08:00
|
|
|
|
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: '上传中'
|
|
|
|
|
|
})
|
2026-03-24 11:45:13 +08:00
|
|
|
|
|
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
|
url: ossConfig.host,
|
|
|
|
|
|
filePath: path, // 手机端文件路径
|
|
|
|
|
|
name: 'file',
|
|
|
|
|
|
formData: {
|
|
|
|
|
|
key: ossConfig.dir + name, // 文件名
|
|
|
|
|
|
policy: ossConfig.policy, // 后台获取超时时间
|
|
|
|
|
|
OSSAccessKeyId: ossConfig.ossAccessKeyId, // 后台获取临时ID
|
|
|
|
|
|
success_action_status: 200, // 让服务端返回200,不然,默认会返回204
|
|
|
|
|
|
signature: ossConfig.signature // 后台获取签名
|
|
|
|
|
|
},
|
|
|
|
|
|
success(res) {
|
|
|
|
|
|
resolve(`${ossConfig.host}/${ossConfig.dir}${name}`)
|
2026-04-29 18:17:50 +08:00
|
|
|
|
uni.hideLoading();
|
2026-03-24 11:45:13 +08:00
|
|
|
|
},
|
|
|
|
|
|
complete(err) {
|
|
|
|
|
|
console.log('err', err)
|
2026-04-29 18:17:50 +08:00
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
|
2026-03-24 11:45:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
export default {
|
|
|
|
|
|
handleUpload
|
|
|
|
|
|
}
|