/** * 媒体选择器工具类 * 用于选择本地图片和视频 * 支持设置选择类型(图片、视频或两者)和选择数量 */ // 媒体类型枚举 export const MediaType = { IMAGE: 'image', VIDEO: 'video', BOTH: 'both' }; /** * 媒体选择器类 */ export default class MediaSelector { /** * 选择媒体文件(图片或视频) * @param {Object} options 选择配置项 * @param {string} options.type 媒体类型,可选值:'image'、'video'、'both',默认为'image' * @param {number} options.count 最大选择数量,默认为9 * @param {Array} options.imageExtensions 图片扩展名,默认为['png', 'jpg', 'jpeg', 'gif', 'webp'] * @param {Array} options.videoExtensions 视频扩展名,默认为['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'] * @param {boolean} options.compressed 是否压缩所选文件,默认为true * @param {boolean} options.crop 是否裁剪(仅对图片有效),默认为false * @param {number} options.videoMaxDuration 拍摄视频最长拍摄时间,单位秒,默认为60 * @param {string} options.camera 使用的摄像头,可选值:'back'、'front',默认为'back' * @returns {Promise} 返回选择的媒体文件数组 */ static async choose(options = {}) { const { type = MediaType.IMAGE, count = 9, imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'], videoExtensions = ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'], compressed = true, crop = false, videoMaxDuration = 60, camera = 'back' } = options; // 根据类型选择不同的媒体 if (type === MediaType.IMAGE) { return this.chooseImages({ count, extensions: imageExtensions, compressed, crop, camera }); } else if (type === MediaType.VIDEO) { return this.chooseVideos({ count, extensions: videoExtensions, compressed, maxDuration: videoMaxDuration, camera }); } else if (type === MediaType.BOTH) { // 如果是两者都选,则先让用户选择类型 return new Promise((resolve, reject) => { uni.showActionSheet({ itemList: ['选择图片', '选择视频'], success: async (res) => { try { if (res.tapIndex === 0) { // 选择图片 const images = await this.chooseImages({ count, extensions: imageExtensions, compressed, crop, camera }); resolve(images); } else { // 选择视频 const videos = await this.chooseVideos({ count, extensions: videoExtensions, compressed, maxDuration: videoMaxDuration, camera }); resolve(videos); } } catch (error) { reject(error); } }, fail: (err) => { reject(err); } }); }); } else { throw new Error('不支持的媒体类型'); } } /** * 选择图片 * @param {Object} options 选择图片的配置项 * @returns {Promise} 返回选择的图片数组 */ static chooseImages(options) { const { count = 9, extensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'], compressed = true, crop = false, camera = 'back' } = options; return new Promise((resolve, reject) => { uni.chooseImage({ count, sizeType: compressed ? ['compressed'] : ['original'], sourceType: ['album', 'camera'], extension: extensions, crop: crop ? { quality: 100, width: 300, height: 300, resize: true } : false, camera, success: (res) => { // 处理返回的图片数据,统一格式 const images = res.tempFiles.map(file => ({ path: file.path, size: file.size, name: this.getFileName(file.path), type: 'image', extension: this.getFileExtension(file.path), createTime: new Date().getTime() })); resolve(images); }, fail: (err) => { // 用户取消选择不报错 if (err.errMsg.indexOf('cancel') !== -1) { resolve([]); } else { reject(err); } } }); }); } /** * 选择视频 * @param {Object} options 选择视频的配置项 * @returns {Promise} 返回选择的视频数组 */ static chooseVideos(options) { const { count = 1, extensions = ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'], compressed = true, maxDuration = 60, camera = 'back' } = options; // 由于uni.chooseVideo一次只能选择一个视频,如果count>1,需要多次选择 if (count <= 1) { return this.chooseSingleVideo({ extensions, compressed, maxDuration, camera }); } else { // 提示用户需要多次选择 return new Promise((resolve, reject) => { uni.showModal({ title: '提示', content: `您需要选择${count}个视频,将分${count}次选择`, confirmText: '开始选择', cancelText: '取消', success: async (res) => { if (res.confirm) { try { const videos = []; for (let i = 0; i < count; i++) { // 显示当前选择进度 uni.showLoading({ title: `正在选择第${i + 1}/${count}个视频`, mask: true }); // 选择单个视频 const videoResult = await this.chooseSingleVideo({ extensions, compressed, maxDuration, camera }); // 隐藏加载提示 uni.hideLoading(); // 如果用户取消了选择,则结束循环 if (videoResult.length === 0) { break; } // 添加到结果数组 videos.push(...videoResult); // 如果还没选完,询问是否继续 if (i < count - 1) { const continueRes = await new Promise((resolveDialog) => { uni.showModal({ title: '提示', content: `已选择${videos.length}个视频,是否继续选择?`, confirmText: '继续', cancelText: '完成', success: (modalRes) => { resolveDialog(modalRes.confirm); } }); }); // 如果用户选择不继续,则结束循环 if (!continueRes) { break; } } } resolve(videos); } catch (error) { uni.hideLoading(); reject(error); } } else { resolve([]); } }, fail: (err) => { reject(err); } }); }); } } /** * 选择单个视频 * @param {Object} options 选择视频的配置项 * @returns {Promise} 返回选择的视频数组 */ static chooseSingleVideo(options) { const { extensions = ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'], compressed = true, maxDuration = 60, camera = 'back' } = options; return new Promise((resolve, reject) => { uni.chooseVideo({ sourceType: ['album', 'camera'], compressed, maxDuration, camera, extension: extensions, success: (res) => { // 处理返回的视频数据,统一格式 const video = { path: res.tempFilePath, size: res.size, duration: res.duration, width: res.width, height: res.height, name: this.getFileName(res.tempFilePath), type: 'video', extension: this.getFileExtension(res.tempFilePath), createTime: new Date().getTime() }; resolve([video]); }, fail: (err) => { // 用户取消选择不报错 if (err.errMsg.indexOf('cancel') !== -1) { resolve([]); } else { reject(err); } } }); }); } /** * 获取文件名 * @param {string} path 文件路径 * @returns {string} 文件名 */ static getFileName(path) { if (!path) return ''; return path.substring(path.lastIndexOf('/') + 1); } /** * 获取文件扩展名 * @param {string} path 文件路径 * @returns {string} 文件扩展名 */ static getFileExtension(path) { if (!path) return ''; return path.substring(path.lastIndexOf('.') + 1).toLowerCase(); } /** * 预览媒体文件 * @param {string} path 文件路径 * @param {string} type 媒体类型,可选值:'image'、'video',默认根据文件扩展名自动判断 */ static preview(path, type) { if (!path) return; // 如果未指定类型,则根据文件扩展名判断 if (!type) { const extension = this.getFileExtension(path); if (['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(extension)) { type = MediaType.IMAGE; } else if (['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'].includes(extension)) { type = MediaType.VIDEO; } else { uni.showToast({ title: '不支持的文件类型', icon: 'none' }); return; } } // 根据类型预览 if (type === MediaType.IMAGE) { uni.previewImage({ urls: [path], current: path }); } else if (type === MediaType.VIDEO) { // 视频预览 uni.navigateTo({ url: `/pages/common/video-player?url=${encodeURIComponent(path)}` }); } } } /** * 使用示例: * * // 导入 * import MediaSelector, { MediaType } from '@/utils/mediaSelector'; * * // 选择图片 * const images = await MediaSelector.choose({ type: MediaType.IMAGE, count: 9 }); * console.log('选择的图片:', images); * * // 选择视频 * const videos = await MediaSelector.choose({ type: MediaType.VIDEO, count: 1 }); * console.log('选择的视频:', videos); * * // 选择图片或视频(用户可选择) * const media = await MediaSelector.choose({ type: MediaType.BOTH, count: 5 }); * console.log('选择的媒体:', media); * * // 预览媒体 * if (media.length > 0) { * MediaSelector.preview(media[0].path, media[0].type); * } */