导入七牛云

This commit is contained in:
WindowBird 2025-11-06 16:16:58 +08:00
parent 881f8b990e
commit 74c2fda0ef
3 changed files with 325 additions and 13 deletions

View File

@ -85,3 +85,15 @@ export const getTaskDetail = (id) => {
});
};
/**
* 获取七牛云上传token
* @returns {Promise} 返回七牛云上传token
*/
export const getQiniuUploadToken = () => {
return uni.$uv.http.get('/common/qiniuToken', {
custom: {
auth: true // 启用 token 认证
}
});
};

View File

@ -121,6 +121,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { chooseAndUploadImages } from '@/utils/qiniu.js';
//
const formData = ref({
@ -228,23 +229,34 @@ const confirmProgress = () => {
showProgressPicker.value = false;
};
//
const chooseImages = () => {
uni.chooseImage({
count: 9 - formData.value.images.length,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
formData.value.images = [...formData.value.images, ...res.tempFilePaths];
},
fail: (err) => {
console.error('选择图片失败:', err);
//
const chooseImages = async () => {
try {
const remainingCount = 9 - formData.value.images.length;
if (remainingCount <= 0) {
uni.showToast({
title: '选择图片失败',
title: '最多只能添加9张图片',
icon: 'none'
});
return;
}
});
// 使
const urls = await chooseAndUploadImages({
count: remainingCount,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera']
});
// URL
formData.value.images = [...formData.value.images, ...urls];
} catch (err) {
console.error('选择或上传图片失败:', err);
uni.showToast({
title: err.message || '选择图片失败',
icon: 'none'
});
}
};
//

288
utils/qiniu.js Normal file
View File

@ -0,0 +1,288 @@
/**
* 七牛云上传工具封装
* 提供图片和文件上传到七牛云的功能
*/
import { getQiniuUploadToken } from '@/common/api.js'
/**
* 上传文件到七牛云
* @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)
}
})
})
}
/**
* 获取文件信息
* @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
}
}