328 lines
11 KiB
JavaScript
328 lines
11 KiB
JavaScript
|
/**
|
|||
|
* 媒体选择器工具类
|
|||
|
* 用于选择本地图片和视频
|
|||
|
* 支持设置选择类型(图片、视频或两者)和选择数量
|
|||
|
*/
|
|||
|
|
|||
|
// 媒体类型枚举
|
|||
|
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<string>} options.imageExtensions 图片扩展名,默认为['png', 'jpg', 'jpeg', 'gif', 'webp']
|
|||
|
* @param {Array<string>} 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<Array>} 返回选择的媒体文件数组
|
|||
|
*/
|
|||
|
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<Array>} 返回选择的图片数组
|
|||
|
*/
|
|||
|
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<Array>} 返回选择的视频数组
|
|||
|
*/
|
|||
|
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<Array>} 返回选择的视频数组
|
|||
|
*/
|
|||
|
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);
|
|||
|
* }
|
|||
|
*/
|