// utils/mediaSelector.js
/**
* 媒体选择统一服务(支持 UniApp + 企业微信小程序)—— 支持半成功返回
*/
export class MediaSelector {
/**
* 选择手机相册中的图片或视频(可多选)
*/
static async selectFromAlbum(options = {}) {
return new Promise(resolve => {
// 注意:这里不再轻易 reject,而是 resolve({ successList, failedList })
uni.chooseMedia({
count: options.count || 1,
mediaType: options.mediaType || ['image', 'video'],
sourceType: ['album'],
maxDuration: options.maxDuration,
...options,
success: res => {
uni.showLoading({ title: '加载中' })
const { tempFiles } = res
const successList = []
const failedList = []
tempFiles.forEach((file, index) => {
try {
if (!file.tempFilePath) {
throw new Error('empty_path')
}
const item = {
path: file.tempFilePath,
type: file.width ? 'local-image' : 'local-video',
size: file.size,
duration: file.duration || 0,
index
}
successList.push(item)
} catch (e) {
failedList.push({
original: file,
index,
error: e.message,
reason: 'invalid_file_path_or_metadata'
})
}
})
resolve({ successList, failedList })
},
fail: err => {
console.error('相册选择失败:', err)
// 完全失败的情况
resolve({
successList: [],
failedList: [{ error: 'select_from_album_failed', detail: err }]
})
}
})
})
}
/**
* 直接调用相机拍照
*/
static async takePhoto(options = {}) {
return new Promise((resolve, reject) => {
uni.chooseImage({
count: 1,
sourceType: ['camera'],
...options,
success: res => {
uni.showLoading({ title: '加载中' })
const paths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePath]
const files = paths.map(path => ({
path,
type: 'local-image'
}))
resolve({ successList: files, failedList: [] })
},
fail: err => {
console.log('拍照取消或出错', err)
resolve({
successList: [],
failedList: [{ error: 'take_photo_failed', detail: err }]
})
}
})
})
}
/**
* 直接录制视频
*/
static async recordVideo(options = {}) {
return new Promise(resolve => {
uni.chooseVideo({
sourceType: ['camera'],
compressed: options.compressed ?? true,
maxDuration: options.maxDuration,
camera: options.camera || 'back',
...options,
success: res => {
uni.showLoading({ title: '加载中' })
const file = {
path: res.tempFilePath,
size: res.size,
duration: res.duration,
type: 'local-video'
}
if (!res.tempFilePath) {
resolve({
successList: [],
failedList: [{ error: 'recorded_video_path_empty', detail: res }]
})
} else {
resolve({ successList: [file], failedList: [] })
}
},
fail: err => {
console.log('录制失败', err)
resolve({
successList: [],
failedList: [{ error: 'record_video_failed', detail: err }]
})
}
})
})
}
/**
* 获取企业微信用户 session_key(用于后续 API 调用)
*/
static async getQyWxSession() {
return new Promise((resolve, reject) => {
wx.qy.login({
success: loginRes => {
if (!loginRes.code) {
reject(new Error('login_failed'))
return
}
wx.request({
url: 'https://qyapi.weixin.qq.com/cgi-bin/gettoken',
data: {
corpid: 'ww89375d6c15dd0e96',
corpsecret: 'QIBK6NaGBFFWpOSY1qBTXDpe7MvtgCUTLbr0vEIOQzI'
},
success: tokenRes => {
if (!tokenRes.data.access_token) {
reject(new Error('get_access_token_failed'))
return
}
wx.request({
url: 'https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session',
data: {
access_token: tokenRes.data.access_token,
js_code: loginRes.code,
grant_type: 'authorization_code'
},
success: sessionRes => {
if (sessionRes.data.errcode) {
reject(new Error(`session_error: ${sessionRes.data.errmsg}`))
} else {
resolve(sessionRes.data)
}
},
fail: () => reject(new Error('jscode2session_request_failed'))
})
},
fail: () => reject(new Error('get_token_request_failed'))
})
},
fail: () => reject(new Error('wx_qy_login_failed'))
})
})
}
/**
* 将 chooseMessageFile 包装为 Promise 并支持部分失败
*/
static async chooseMessageFile(options) {
const res = await this.getQyWxSession()
// console.log('🚀 ~ MediaSelector ~ chooseMessageFile ~ res:', res)
return new Promise(resolve => {
if (!wx || !wx.qy || !wx.qy.chooseMessageFile) {
return resolve({
successList: [],
failedList: [{ error: 'sdk_not_ready', detail: 'wx.qy.chooseMessageFile is not available' }]
})
}
wx.qy.chooseMessageFile({
count: options.count || 1,
type: ['image', 'video'].includes(options.type) ? options.type : 'file',
extension: options.extension || [
'.jpg',
'.jpeg',
'.png',
'.gif',
'.bmp',
'.mp4',
'.avi',
'.mov',
'.wmv',
'.pdf',
'.doc',
'.docx',
'.xls',
'.xlsx',
'.ppt',
'.pptx',
'.txt',
'.zip',
'.rar'
],
success: res => {
uni.showLoading({ title: '加载中' })
const { tempFiles } = res
const successList = []
const failedList = []
tempFiles.forEach((file, index) => {
try {
if (!file.path) {
throw new Error('file_path_missing')
}
const item = {
path: file.path,
name: file.name,
size: file.size,
type: file.type,
index
}
successList.push(item)
} catch (e) {
failedList.push({
original: file,
index,
error: e.message,
reason: 'parse_failed'
})
}
})
resolve({ successList, failedList })
},
fail: err => {
console.error('选择失败', err)
resolve({
successList: [],
failedList: [{ error: 'choose_message_file_failed', detail: err.errMsg }]
})
}
})
})
}
/**
* 映射企微文件类型到本地类型
*/
static getMimeType(qyType, fileName) {
console.log('🚀 ~ MediaSelector ~ getMimeType ~ qyType:', qyType)
switch (qyType) {
case 'image':
return 'local-image'
case 'video':
return 'local-video'
default:
return 'local-file'
}
}
/**
* 主入口:根据 type 执行对应操作(返回结构化结果)
*/
static async select(type, options = {}) {
try {
let result = { successList: [], failedList: [] }
switch (type) {
case 'mobileImage':
case 'mobileVideo':
// case 'mobileMix':
result = await this.selectFromAlbum({
mediaType: type === 'mobileImage' ? ['image'] : type === 'mobileVideo' ? ['video'] : ['image', 'video'],
...options
})
break
case 'cameraImage':
result = await this.takePhoto(options)
break
case 'cameraVideo':
result = await this.recordVideo(options)
break
case 'messageImage':
case 'messageVideo':
// case 'messageMix':
case 'messageFile':
result = await this.chooseMessageFile({
type:
type === 'messageImage'
? 'image'
: type === 'messageVideo'
? 'video'
: type === 'messageFile'
? 'file'
: 'all',
...options
})
break
default:
return {
success: false,
error: 'unsupported_media_type',
data: null
}
}
// 统一返回格式
uni.hideLoading()
return {
success: result.failedList.length === 0,
partial: result.successList.length > 0 && result.failedList.length > 0,
data: {
successList: result.successList,
failedList: result.failedList,
successCount: result.successList.length,
failCount: result.failedList.length
}
}
} catch (err) {
console.error('[MediaSelector] 操作失败:', err.message)
uni.hideLoading()
return {
success: false,
partial: false,
error: err.message,
data: {
successList: [],
failedList: [{ error: 'unexpected_catch_error', message: err.message }]
}
}
}
}
}
加的 uni.showLoading({ title: '加载中' })
但是有时候不展示,有时候就展示一下
最新发布