1.合并前段时间写的页面

This commit is contained in:
2025-07-24 16:00:29 +08:00
parent 33ef7f5cdb
commit 658d21399d
89 changed files with 7649 additions and 243 deletions

View File

@@ -0,0 +1,144 @@
# 媒体选择器工具类 (MediaSelector)
这是一个用于在uni-app项目中选择本地图片和视频的工具类提供了统一的接口和灵活的配置选项。
## 功能特点
- 支持选择图片、视频或让用户选择图片/视频
- 支持设置选择数量
- 支持设置是否压缩
- 支持图片裁剪
- 支持设置视频最大时长
- 支持设置使用的摄像头(前置/后置)
- 支持设置允许的文件扩展名
- 提供媒体预览功能
## 安装和使用
### 1. 导入工具类
```javascript
import MediaSelector, { MediaType } from '@/utils/mediaSelector';
```
### 2. 选择图片
```javascript
// 选择最多9张图片
const images = await MediaSelector.choose({
type: MediaType.IMAGE,
count: 9,
compressed: true,
crop: false
});
console.log('选择的图片:', images);
```
### 3. 选择视频
```javascript
// 选择最多1个视频
const videos = await MediaSelector.choose({
type: MediaType.VIDEO,
count: 1,
compressed: true,
videoMaxDuration: 60 // 最长60秒
});
console.log('选择的视频:', videos);
```
### 4. 选择图片或视频(用户可选择)
```javascript
// 让用户选择图片或视频
const media = await MediaSelector.choose({
type: MediaType.BOTH,
count: 5
});
console.log('选择的媒体:', media);
```
### 5. 预览媒体
```javascript
// 预览媒体(自动判断类型)
if (media.length > 0) {
MediaSelector.preview(media[0].path, media[0].type);
}
```
## API 文档
### MediaType 枚举
```javascript
export const MediaType = {
IMAGE: 'image', // 图片
VIDEO: 'video', // 视频
BOTH: 'both' // 图片和视频
};
```
### MediaSelector.choose(options)
选择媒体文件(图片或视频)。
#### 参数
| 参数名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| options.type | string | 'image' | 媒体类型,可选值:'image'、'video'、'both' |
| options.count | number | 9 | 最大选择数量 |
| options.imageExtensions | Array<string> | ['png', 'jpg', 'jpeg', 'gif', 'webp'] | 图片扩展名 |
| options.videoExtensions | Array<string> | ['mp4', 'mov', '3gp', 'avi', 'rmvb', 'rm', 'flv', 'mkv'] | 视频扩展名 |
| options.compressed | boolean | true | 是否压缩所选文件 |
| options.crop | boolean | false | 是否裁剪(仅对图片有效) |
| options.videoMaxDuration | number | 60 | 拍摄视频最长拍摄时间,单位秒 |
| options.camera | string | 'back' | 使用的摄像头,可选值:'back'、'front' |
#### 返回值
返回一个Promise解析为选择的媒体文件数组。每个媒体文件对象包含以下属性
- **图片对象属性**
- path: 文件路径
- size: 文件大小(字节)
- name: 文件名
- type: 媒体类型('image'
- extension: 文件扩展名
- createTime: 创建时间戳
- **视频对象属性**
- path: 文件路径
- size: 文件大小(字节)
- duration: 视频时长(秒)
- width: 视频宽度
- height: 视频高度
- name: 文件名
- type: 媒体类型('video'
- extension: 文件扩展名
- createTime: 创建时间戳
### MediaSelector.preview(path, type)
预览媒体文件。
#### 参数
| 参数名 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| path | string | - | 文件路径 |
| type | string | 自动判断 | 媒体类型,可选值:'image'、'video',默认根据文件扩展名自动判断 |
## 注意事项
1. 由于uni.chooseVideo一次只能选择一个视频当设置count>1时工具类会引导用户多次选择。
2. 视频预览功能需要一个视频播放页面,默认路径为`/pages/common/video-player`,如果您的项目中没有此页面,请自行创建或修改预览方法。
3. 在某些平台上,部分功能可能受到限制,请根据实际情况调整使用方式。
## 示例代码
完整的使用示例请参考 `mediaSelector.example.js` 文件。

View File

@@ -0,0 +1,136 @@
/**
* 媒体选择器使用示例
*/
// 导入媒体选择器工具类
import MediaSelector, { MediaType } from './mediaSelector';
/**
* 选择图片示例
*/
export async function chooseImagesExample() {
try {
// 选择最多9张图片
const images = await MediaSelector.choose({
type: MediaType.IMAGE,
count: 9,
compressed: true,
crop: false
});
console.log('选择的图片:', images);
return images;
} catch (error) {
console.error('选择图片失败:', error);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
return [];
}
}
/**
* 选择视频示例
*/
export async function chooseVideosExample() {
try {
// 选择最多1个视频
const videos = await MediaSelector.choose({
type: MediaType.VIDEO,
count: 1,
compressed: true,
videoMaxDuration: 60 // 最长60秒
});
console.log('选择的视频:', videos);
return videos;
} catch (error) {
console.error('选择视频失败:', error);
uni.showToast({
title: '选择视频失败',
icon: 'none'
});
return [];
}
}
/**
* 选择图片或视频示例(用户可选择)
*/
export async function chooseBothExample() {
try {
// 让用户选择图片或视频
const media = await MediaSelector.choose({
type: MediaType.BOTH,
count: 5
});
console.log('选择的媒体:', media);
return media;
} catch (error) {
console.error('选择媒体失败:', error);
uni.showToast({
title: '选择媒体失败',
icon: 'none'
});
return [];
}
}
/**
* 预览媒体示例
* @param {Object} mediaItem 媒体项
*/
export function previewMediaExample(mediaItem) {
if (!mediaItem || !mediaItem.path) {
uni.showToast({
title: '无效的媒体文件',
icon: 'none'
});
return;
}
// 预览媒体(自动判断类型)
MediaSelector.preview(mediaItem.path, mediaItem.type);
}
/**
* 在页面中使用的完整示例
*/
export default {
data() {
return {
mediaList: [] // 存储选择的媒体列表
};
},
methods: {
// 选择图片
async chooseImages() {
const images = await chooseImagesExample();
this.mediaList = [...this.mediaList, ...images];
},
// 选择视频
async chooseVideos() {
const videos = await chooseVideosExample();
this.mediaList = [...this.mediaList, ...videos];
},
// 选择图片或视频
async chooseBoth() {
const media = await chooseBothExample();
this.mediaList = [...this.mediaList, ...media];
},
// 预览媒体
previewMedia(item) {
previewMediaExample(item);
},
// 删除媒体
deleteMedia(index) {
this.mediaList.splice(index, 1);
}
}
};

328
utils/mediaSelector.js Normal file
View File

@@ -0,0 +1,328 @@
/**
* 媒体选择器工具类
* 用于选择本地图片和视频
* 支持设置选择类型(图片、视频或两者)和选择数量
*/
// 媒体类型枚举
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);
* }
*/