2025-11-06 16:16:58 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 七牛云上传工具封装
|
|
|
|
|
|
* 提供图片和文件上传到七牛云的功能
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2025-11-12 15:33:53 +08:00
|
|
|
|
import { getQiniuUploadToken } from '@/api'
|
2025-11-06 16:16:58 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传文件到七牛云
|
|
|
|
|
|
* @param {string} filePath - 文件路径(临时路径)
|
|
|
|
|
|
* @param {string} token - 七牛云上传token
|
|
|
|
|
|
* @param {string} key - 文件key(可选,不传则自动生成)
|
|
|
|
|
|
* @returns {Promise<object>} 返回上传结果,包含key等信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function uploadToQiniu(filePath, token, key = '') {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
|
url: 'https://up-z2.qiniup.com',
|
|
|
|
|
|
filePath: filePath,
|
|
|
|
|
|
name: 'file',
|
|
|
|
|
|
formData: {
|
|
|
|
|
|
token: token,
|
|
|
|
|
|
key: key,
|
|
|
|
|
|
},
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 七牛云上传成功返回格式: { hash: "...", key: "uploads/xxx.jpg" }
|
|
|
|
|
|
const data = JSON.parse(res.data)
|
|
|
|
|
|
|
|
|
|
|
|
// 验证返回数据格式
|
|
|
|
|
|
if (!data || typeof data !== 'object') {
|
|
|
|
|
|
reject(new Error('上传响应数据格式错误'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有错误信息(七牛云可能返回错误)
|
|
|
|
|
|
if (data.error) {
|
|
|
|
|
|
reject(new Error(data.error || '上传失败'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
resolve(data)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('解析上传响应失败:', res.data, error)
|
|
|
|
|
|
reject(new Error('响应数据解析失败: ' + error.message))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (error) => {
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 七牛云图片上传服务
|
|
|
|
|
|
* 将临时文件路径转换为七牛云URL
|
|
|
|
|
|
* @param {string} tempFilePath - 临时文件路径
|
|
|
|
|
|
* @param {object} [options] - 配置选项
|
|
|
|
|
|
* @param {number} [options.maxSize=10] - 最大文件大小(MB)
|
|
|
|
|
|
* @param {string} [options.prefix='uploads/'] - 文件前缀路径
|
|
|
|
|
|
* @param {boolean} [options.showToast=true] - 是否显示提示
|
|
|
|
|
|
* @param {string} [options.domain='https://api.ccttiot.com'] - 七牛云域名
|
|
|
|
|
|
* @returns {Promise<string>} 七牛云文件URL
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const tempUrlToRealUrl = async (tempFilePath, options = {}) => {
|
|
|
|
|
|
const {
|
|
|
|
|
|
maxSize = 10,
|
|
|
|
|
|
prefix = 'uploads/',
|
|
|
|
|
|
showToast = true,
|
|
|
|
|
|
domain = 'https://api.ccttiot.com',
|
|
|
|
|
|
} = options
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 检查文件大小
|
|
|
|
|
|
const fileInfo = await getFileInfo(tempFilePath)
|
|
|
|
|
|
const sizeInMB = fileInfo.size / (1024 * 1024)
|
|
|
|
|
|
|
|
|
|
|
|
if (sizeInMB > maxSize) {
|
|
|
|
|
|
throw new Error(`文件大小不能超过 ${maxSize}MB`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取上传凭证
|
|
|
|
|
|
const token = await getQiniuToken()
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
throw new Error('获取上传凭证失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 生成唯一文件名
|
|
|
|
|
|
const fileExt = getFileExtension(tempFilePath)
|
|
|
|
|
|
const key = generateUniqueKey(prefix, fileExt)
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 上传到七牛云
|
|
|
|
|
|
const uploadResult = await uploadToQiniu(tempFilePath, token, key)
|
|
|
|
|
|
|
|
|
|
|
|
// 验证上传结果(七牛云返回格式: { hash: "...", key: "uploads/xxx.jpg" })
|
|
|
|
|
|
if (!uploadResult || typeof uploadResult !== 'object') {
|
|
|
|
|
|
throw new Error('上传失败:返回数据格式错误')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!uploadResult.key) {
|
|
|
|
|
|
console.error('上传响应缺少key字段:', uploadResult)
|
|
|
|
|
|
throw new Error('上传失败:未返回文件key')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 构建完整URL
|
|
|
|
|
|
const qiniuUrl = `${domain}/${uploadResult.key}`
|
|
|
|
|
|
console.log('上传成功:', { key: uploadResult.key, hash: uploadResult.hash, url: qiniuUrl })
|
|
|
|
|
|
|
|
|
|
|
|
if (showToast) {
|
|
|
|
|
|
// 上传成功不显示提示,避免打扰用户
|
|
|
|
|
|
// uni.showToast({ title: '上传成功', icon: 'success' })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return qiniuUrl
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('七牛云上传失败:', error)
|
|
|
|
|
|
if (showToast) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: error.message || '上传失败',
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 批量上传多张图片
|
|
|
|
|
|
* @param {string[]} tempFilePaths - 临时文件路径数组
|
|
|
|
|
|
* @param {object} [options] - 配置选项
|
|
|
|
|
|
* @returns {Promise<string[]>} 七牛云URL数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const batchUploadToQiniu = async (tempFilePaths, options = {}) => {
|
|
|
|
|
|
return Promise.all(
|
|
|
|
|
|
tempFilePaths.map(filePath => tempUrlToRealUrl(filePath, { ...options, showToast: false }))
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 选择图片并自动上传到七牛云
|
|
|
|
|
|
* @param {object} [options] - 配置选项
|
|
|
|
|
|
* @param {number} [options.count=9] - 最多选择图片数量
|
|
|
|
|
|
* @param {string[]} [options.sizeType=['original', 'compressed']] - 图片尺寸类型
|
|
|
|
|
|
* @param {string[]} [options.sourceType=['album', 'camera']] - 图片来源
|
|
|
|
|
|
* @param {object} [options.uploadOptions] - 上传配置选项(传递给tempUrlToRealUrl)
|
|
|
|
|
|
* @returns {Promise<string[]>} 七牛云URL数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const chooseAndUploadImages = (options = {}) => {
|
|
|
|
|
|
const {
|
|
|
|
|
|
count = 9,
|
|
|
|
|
|
sizeType = ['original', 'compressed'],
|
|
|
|
|
|
sourceType = ['album', 'camera'],
|
|
|
|
|
|
uploadOptions = {}
|
|
|
|
|
|
} = options
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
uni.chooseImage({
|
|
|
|
|
|
count: count,
|
|
|
|
|
|
sizeType: sizeType,
|
|
|
|
|
|
sourceType: sourceType,
|
|
|
|
|
|
success: async (res) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 显示上传中提示
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: '上传中...',
|
|
|
|
|
|
mask: true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 批量上传图片
|
|
|
|
|
|
const urls = await batchUploadToQiniu(res.tempFilePaths, uploadOptions)
|
|
|
|
|
|
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
resolve(urls)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
reject(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 18:01:41 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 上传单个文件到七牛云(支持图片和文档)
|
|
|
|
|
|
* @param {string} filePath - 文件临时路径或URI
|
|
|
|
|
|
* @param {string} fileName - 文件名(可选,用于文档文件)
|
|
|
|
|
|
* @param {object} [options] - 配置选项
|
|
|
|
|
|
* @param {number} [options.maxSize=50] - 最大文件大小(MB),文档文件默认50MB
|
|
|
|
|
|
* @param {string} [options.prefix='uploads/'] - 文件前缀路径
|
|
|
|
|
|
* @param {boolean} [options.showToast=true] - 是否显示提示
|
|
|
|
|
|
* @param {string} [options.domain='https://api.ccttiot.com'] - 七牛云域名
|
|
|
|
|
|
* @returns {Promise<object>} 返回 { url: string, name: string, size: number }
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const uploadFileToQiniu = async (filePath, fileName = '', options = {}) => {
|
|
|
|
|
|
const {
|
|
|
|
|
|
maxSize = 50,
|
|
|
|
|
|
prefix = 'uploads/',
|
|
|
|
|
|
showToast = true,
|
|
|
|
|
|
domain = 'https://api.ccttiot.com',
|
|
|
|
|
|
} = options
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 如果是URI格式(content://),需要先转换为临时路径
|
|
|
|
|
|
let tempFilePath = filePath
|
|
|
|
|
|
if (filePath.startsWith('content://')) {
|
|
|
|
|
|
// 在uni-app中,content:// URI需要特殊处理
|
|
|
|
|
|
// 对于文档文件,uni.uploadFile可以直接使用URI
|
|
|
|
|
|
tempFilePath = filePath
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取文件信息(如果不是URI格式)
|
|
|
|
|
|
let fileInfo = { size: 0 }
|
|
|
|
|
|
if (!filePath.startsWith('content://')) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
fileInfo = await getFileInfo(tempFilePath)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('获取文件信息失败:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const sizeInMB = fileInfo.size / (1024 * 1024)
|
|
|
|
|
|
if (sizeInMB > maxSize) {
|
|
|
|
|
|
throw new Error(`文件大小不能超过 ${maxSize}MB`)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 获取上传凭证
|
|
|
|
|
|
const token = await getQiniuToken()
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
throw new Error('获取上传凭证失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 生成唯一文件名
|
|
|
|
|
|
let fileExt = 'file'
|
|
|
|
|
|
if (fileName) {
|
|
|
|
|
|
const parts = fileName.split('.')
|
|
|
|
|
|
if (parts.length > 1) {
|
|
|
|
|
|
fileExt = parts.pop().toLowerCase()
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fileExt = getFileExtension(tempFilePath)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const key = generateUniqueKey(prefix, fileExt)
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 上传到七牛云
|
|
|
|
|
|
const uploadResult = await uploadToQiniu(tempFilePath, token, key)
|
|
|
|
|
|
|
|
|
|
|
|
// 验证上传结果
|
|
|
|
|
|
if (!uploadResult || typeof uploadResult !== 'object') {
|
|
|
|
|
|
throw new Error('上传失败:返回数据格式错误')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!uploadResult.key) {
|
|
|
|
|
|
console.error('上传响应缺少key字段:', uploadResult)
|
|
|
|
|
|
throw new Error('上传失败:未返回文件key')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 构建完整URL
|
|
|
|
|
|
const qiniuUrl = `${domain}/${uploadResult.key}`
|
|
|
|
|
|
console.log('文件上传成功:', { key: uploadResult.key, url: qiniuUrl })
|
|
|
|
|
|
|
|
|
|
|
|
// 7. 生成文件名(如果没有提供)
|
|
|
|
|
|
const finalFileName = fileName || `文件.${fileExt}`
|
|
|
|
|
|
|
|
|
|
|
|
if (showToast) {
|
|
|
|
|
|
// 上传成功不显示提示,避免打扰用户
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
url: qiniuUrl,
|
|
|
|
|
|
name: finalFileName,
|
|
|
|
|
|
size: fileInfo.size || 0
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('文件上传失败:', error)
|
|
|
|
|
|
if (showToast) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: error.message || '上传失败',
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 批量上传文件到七牛云(支持图片和文档)
|
|
|
|
|
|
* @param {Array<{path: string, name?: string}>} files - 文件数组,每个文件包含path和可选的name
|
|
|
|
|
|
* @param {object} [options] - 配置选项
|
|
|
|
|
|
* @returns {Promise<Array<{url: string, name: string, size: number}>>} 上传结果数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const batchUploadFilesToQiniu = async (files, options = {}) => {
|
|
|
|
|
|
return Promise.all(
|
|
|
|
|
|
files.map(file => uploadFileToQiniu(file.path, file.name || '', { ...options, showToast: false }))
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 16:16:58 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取文件信息
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getFileInfo = (filePath) => {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
uni.getFileInfo({
|
|
|
|
|
|
filePath,
|
|
|
|
|
|
success: resolve,
|
|
|
|
|
|
fail: reject,
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取七牛云上传token
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getQiniuToken = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getQiniuUploadToken()
|
|
|
|
|
|
|
|
|
|
|
|
// 根据实际API返回格式处理
|
|
|
|
|
|
// 如果API返回的是 { code: 200, data: { token: 'xxx' } } 格式
|
|
|
|
|
|
if (res && typeof res === 'object') {
|
|
|
|
|
|
// 如果响应拦截器已经处理过,直接返回token
|
|
|
|
|
|
if (res.token) {
|
|
|
|
|
|
return res.token
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果返回的是 { data: { token: 'xxx' } } 格式
|
|
|
|
|
|
if (res.data && res.data.token) {
|
|
|
|
|
|
return res.data.token
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果返回的是 { token: 'xxx' } 格式
|
|
|
|
|
|
if (res.token) {
|
|
|
|
|
|
return res.token
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果返回的是字符串token
|
|
|
|
|
|
if (typeof res === 'string') {
|
|
|
|
|
|
return res
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error('获取token失败:返回格式不正确')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取七牛云token失败:', error)
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取文件扩展名
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getFileExtension = (filePath) => {
|
|
|
|
|
|
const parts = filePath.split('.')
|
|
|
|
|
|
return parts.length > 1 ? parts.pop().toLowerCase() : 'jpg'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成唯一文件名
|
|
|
|
|
|
* @private
|
|
|
|
|
|
*/
|
|
|
|
|
|
const generateUniqueKey = (prefix, extension) => {
|
|
|
|
|
|
const timestamp = Date.now()
|
|
|
|
|
|
const randomStr = Math.random().toString(36).slice(2, 10)
|
|
|
|
|
|
return `${prefix}${timestamp}_${randomStr}.${extension}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 工具函数:检查是否为临时路径
|
|
|
|
|
|
* @param {string} url - 要检查的URL
|
|
|
|
|
|
* @returns {boolean} 是否为临时路径
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const isTempFilePath = (url) => {
|
|
|
|
|
|
return (
|
|
|
|
|
|
url &&
|
|
|
|
|
|
(url.startsWith('http://temp/') ||
|
|
|
|
|
|
url.startsWith('wxfile://') ||
|
|
|
|
|
|
url.includes('tmp/') ||
|
|
|
|
|
|
!url.startsWith('http'))
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 工具函数:从七牛云URL提取文件key
|
|
|
|
|
|
* @param {string} qiniuUrl - 七牛云完整URL
|
|
|
|
|
|
* @returns {string|null} 文件key
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const extractQiniuKey = (qiniuUrl) => {
|
|
|
|
|
|
if (!qiniuUrl) return null
|
|
|
|
|
|
try {
|
|
|
|
|
|
const urlObj = new URL(qiniuUrl)
|
|
|
|
|
|
return urlObj.pathname.substring(1) // 去除开头的/
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 如果不是标准URL,尝试直接提取路径
|
|
|
|
|
|
const match = qiniuUrl.match(/https?:\/\/[^\/]+\/(.+)/)
|
|
|
|
|
|
return match ? match[1] : null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|