当前位置: 首页 > article >正文

UniApp基于xe-upload实现文件上传组件

xe-upload地址:文件选择、文件上传组件(图片,视频,文件等) - DCloud 插件市场

致敬开发者!!!

感觉好用的话,给xe-upload的作者一个好评

背景:开发中经常会有上传附件的情况,但是找了一圈没有满意的组件,无意间看到xe-upload,满足我需要上传图片和视频的需求,直接使用示例项目开始二次开发my-file-upload。

修改内容:

1.文件的点击事件

2.对video类型文件的处理

3.根据文件名称创建file文件内容

<template>
	<view>
		<view class="upload-wrap">
			<view class="cu-form-group1">
				<view class="label">{{label}}:</view>
				<view class="btn-click mgb-16 upload-btn" @click="handleUploadClick" v-show="!disabled">
					<image :src="icons.upload" mode="aspectFill" class="upload-icon" />
					<text class="upload-text">上传{{ label }}</text>
				</view>
			</view>
			<view class="mgb-16 file-wrap" v-for="(item, index) in fileList" :key="index">
				<view class="btn-click file-line" @click="handlePreview(item)">
					<!-- <view class="btn-click file-line" @click="handleUploadFile(item)"> -->
					<view class="file-info">
						<image :src="icons[item.fileType || 'file']" mode="aspectFill" class="file-icon" />
						<text class="file-name">{{ item.name || title[type] }}</text>
					</view>
					<image :src="icons.close" mode="aspectFill" class="file-icon"
						@click.stop="handleDeleteFile(index)" />
				</view>
			</view>
			<view class="mgb-16 file-wrap" v-if="fileList.length === 0 && disabled">
				<view class="file-line">
					<text class="file-empty">暂无数据</text>
				</view>
			</view>
			<view>
				<video id="myVideo" :src="videoUrl" autoplay loop muted objectFit="cover" v-if="showVideo"></video>
			</view>
		</view>
		<xe-upload ref="XeUpload" :options="uploadOptions" @callback="handleUploadCallback"></xe-upload>
	</view>
</template>

<script>
	export default {
		name: 'MyFileUpload',
		components: {},
		props: {
			type: {
				default: 'image', // image, video, file
				type: String,
			},
			list: {
				default: () => ([]),
				type: Array,
			},
			// disabled: {
			// 	default: false,
			// 	type: Boolean,
			// },
			value: {
				type: String, // 或者是 File[],取决于你的需求
				default: null
			},
			maxFile: {
				type: Number, //最大上传数量
				default: 1
			},
			label: {
				type: String, // 或者是 File[],取决于你的需求
				default: '附件'
			},
		},
		data() {
			return {
				// uploadOptions 参数跟uni.uploadFile的参数是一样的(除了类型为Function的属性)
				uploadOptions: {
					// url: 'http://192.168.31.185:3000/api/upload', // 不传入上传地址则返回本地链接
				},
				uploadUrl: "/sys/common/upload",
				staticUrl: "/sys/common/static/",
				fileList: [],
				title: {
					image: '图片',
					video: '视频',
					file: '文件',
				},
				icons: {
					upload: '/static/xeUpload/icon_upload.png',
					close: '/static/xeUpload/icon_close.png',
					image: '/static/xeUpload/icon_image.png',
					video: '/static/xeUpload/icon_video.png',
					file: '/static/xeUpload/icon_file.png',
				},
				disabled: false,
				showVideo: false,
				videoUrl: ''
			};
		},
		watch: {
			value: {
				async handler(val) {
					if (val && val !== null && val !== undefined) {
						const url = this.$config.apiUrl + this.staticUrl + val;
						const file = this.urlToFile(url);
						this.fileList = [file];
						if (this.fileList.length === this.maxFile) {
							this.disabled = true;
						}
					}
				},
				immediate: true,
				deep: true,
			},
		},
		onLoad: function() {
			this.videoContext = uni.createVideoContext('myVideo', this)
		},
		methods: {
			handleUploadClick() {
				// 使用默认配置则不需要传入第二个参数
				// App、H5 文件拓展名过滤 { extension: ['.doc', '.docx'] } 或者 { extension: '.doc, .docx' }
				this.$refs.XeUpload.upload(this.type);
				// 可以根据当前的平台,传入选择文件的参数,例如
				// 注意 当chooseMedia可用时,会优先使用chooseMedia
				// // uni.chooseImage
				// this.$refs.XeUpload.upload(type, {
				// 	count: 6,
				// 	sizeType: ['original', 'compressed'],
				// 	sourceType: ['album'],
				// });
				// // uni.chooseVideo
				// this.$refs.XeUpload.upload(type, {
				// 	sourceType: ['camera', 'album'],
				// });
				// // uni.chooseMedia (微信小程序2.10.0+;抖音小程序、飞书小程序;京东小程序支持)
				// this.$refs.XeUpload.upload(type, {
				// 	count: 9,
				// 	sourceType: ['album', 'camera'],
				// });
			},
			handleUploadCallback(e) {
				console.log('UploadCallback', e);
				if (['choose', 'success'].includes(e.type)) {
					// 根据接口返回修改对应的response相关的逻辑
					const tmpFiles = (e.data || []).map(({
						response,
						tempFilePath,
						name,
						fileType
					}) => {
						// 当前测试服务返回的数据结构如下
						// {
						//   "result": {
						//       "fileName": "fileName",
						//       "filePath": `http://192.168.1.121:3000/static/xxxxx.png`,
						//   },
						//   "success": true,
						// }
						const res = response?.result || {};
						const tmpUrl = res.filePath ?? tempFilePath;
						const tmpName = res.fileName ?? name;
						return {
							...res,
							url: tmpUrl,
							name: tmpName,
							fileType,
						};
					});
					this.fileList.push(...tmpFiles);
					this.handleUploadFile(e.data[0].tempFilePath);
				}
			},
			// 自定义上传
			handleUploadFile(url) {
				var that = this;
				console.log('UploadFile', url);
				if (url != undefined) {
					that.$http.upload(that.$config.apiUrl + that.uploadUrl + '?biz=temp', {
						filePath: url,
						name: 'file',
					}).then(res => {
						console.log('handleUpload success', res);
						//回传至表单
						that.$emit('input', res.data.message);
						const tmpData = JSON.parse(res.data);
						uni.showToast({
							title: tmpData.success ? '上传成功' : '上传失败',
							icon: 'none'
						});
					})
				}
			},
			// 预览
			handlePreview(item) {
				console.log('PreviewFile', item);
				const fileType = this.getFileType(item.name);
				const url = item.url;
				if (fileType === 'image') {
					return uni.previewImage({
						current: url,
						urls: [url],
					});
				}else if (fileType === 'video') {
					this.videoUrl = url;
					this.showVideo = !this.showVideo;
					//全屏显示
					// this.videoContext.requestFullScreen();
				}
				// #ifndef H5
				else if (fileType === 'office') {
					return uni.openDocument({
						filePath: url,
						fail: (err) => {
							console.log(err);
							uni.showToast({
								icon: 'none',
								title: '文件预览失败'
							});
						},
					});
				}
				// #endif
				else{
					uni.showModal({
						title: '提示',
						content: url,
						showCancel: false,
					});
				}
			},
			handleDeleteFile(index) {
				this.fileList.splice(index, 1);
				if (this.fileList.length < this.maxFile) {
					this.disabled = false;
				}
			},
			urlToFile(url) {
				// 获取URL的最后一部分
				const lastPart = url.split('/').pop();
				// 获取文件名(不包括扩展名)
				const filenameWithoutExtension = lastPart.split('_').slice(0, -1).join('_');
				// 获取文件扩展名
				const extension = lastPart.split('.').pop();
				// 组合文件名和扩展名
				const filename = filenameWithoutExtension + '.' + extension;
				const fileType = this.getFileType(url);
				const file = {
					"fileName": filename,
					"fileKey": lastPart,
					"filePath": url,
					"url": url,
					"name": filename,
					"fileType": fileType
				}
				return file;
			},
			/**
			 * 获取文件类型
			 * @param {String} fileName 文件链接
			 * @returns {String} fileType => '', image, video, audio, office, unknown
			 */
			getFileType(fileName = '') {
				const fileType = fileName.split('.').pop();
				// let suffix = flieArr[flieArr.length - 1];
				// if (!suffix) return '';
				// suffix = suffix.toLocaleLowerCase();
				const image = ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'];
				if (image.includes(fileType)) return 'image';
				const video = ['mp4', 'm4v'];
				if (video.includes(fileType)) return 'video';
				const audio = ['mp3', 'm4a', 'wav', 'aac'];
				if (audio.includes(fileType)) return 'audio';
				const office = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'plain'];
				if (office.includes(fileType)) return 'office';
				return 'unknown';
			},
		},
	};
</script>

<style lang="scss" scoped>
	view {
		box-sizing: border-box;
	}
	
	.label{
		text-align: justify;
		padding-right: 15px;
		white-space: nowrap;
		font-size: 15px;
		position: relative;
		height: 30px;
		line-height: 30px;
	}
	
	.cu-form-group1 {
	    background-color: #ffffff;
	    padding: 1px 15px 1px 0px;
	    display: flex;
	    align-items: center;
	    min-height: 50px;
	    // justify-content: space-between;
	}

	.btn-click {
		transition: all 0.3s;
		opacity: 1;
	}

	.btn-click:active {
		opacity: 0.5;
	}

	.mgb-16 {
		margin-bottom: 16rpx;

		&:last-child {
			margin-bottom: 0;
		}
	}

	.upload-wrap {
		width: 100%;
		border-radius: 16rpx;
		background: white;
		padding: 32rpx;

		.upload-btn {
			width: 60%;
			height: 120rpx;
			border: 2rpx dashed #AAAAAA;
			background: #FAFAFA;
			border-radius: 16rpx;
			display: flex;
			align-items: center;
			justify-content: center;
			flex-direction: column;

			.upload-icon {
				width: 48rpx;
				height: 48rpx;
				margin-bottom: 8rpx;
			}

			.upload-text {
				font-size: 26rpx;
				color: #9E9E9E;
				line-height: 40rpx;
			}
		}

		.file-wrap {
			.file-line {
				width: 100%;
				background: #F5F5F5;
				border-radius: 8rpx;
				padding: 16rpx;
				font-size: 26rpx;
				color: #1A1A1A;
				line-height: 40rpx;
				display: flex;
				align-items: center;
				justify-content: space-between;

				.file-info {
					width: 90%;
					display: flex;
					align-items: center;

					.file-name {
						max-width: 80%;
						padding-left: 16rpx;
						overflow: hidden;
						text-overflow: ellipsis;
						white-space: nowrap;
					}
				}

				.file-icon {
					width: 40rpx;
					height: 40rpx;
					flex-shrink: 0;
				}

				.file-empty {
					color: #999999;
				}
			}
		}
	}
</style>

父组件中引入使用

<my-file-upload type="file" v-model="model.attachment" label="附件"></my-file-upload>


import myFileUpload from '@/components/my-componets/my-file-upload.vue';

export default {
        name: "Test",
        components:{ myFileUpload },
        props:{
          formData:{
              type:Object,
              default:()=>{},
              required:false
          }
        },
        data(){
            return {
                model: {},
            }
        },
		onLoad: function (option) {

			}
		},
        created(){
        },
        methods:{

        }
    }


http://www.kler.cn/news/321848.html

相关文章:

  • electron的常用弹窗简单案例
  • 15年408-数据结构
  • 老人跌倒扶不扶?涪城三职工给出响亮答案
  • 【docker】在IDEA工具内,远程操作服务器上的docker
  • Rust Web开发常用库
  • Leetcode 706. 设计哈希映射
  • 大屏可视化px转rem方案实现
  • webservice cxf框架 jaxrs jaxws spring整合 接口测试方法 wsdl报文详解 springboot整合 拦截器 复杂参数类型
  • 作者分享|eDNA研究梯级水坝对浮游植物和浮游动物群落变化的影响
  • WPF入门教学十九 属性动画与时间线
  • 计算机网络nat 映射案列
  • 基于nodejs+vue的校园二手物品交易系统
  • Vue3+Vite中引用Swiper11自动轮播、左右切换不生效,已解决
  • Android常用C++特性之std::equal
  • 【python append函数的一些细节】
  • 音频转MP3格式困难?如何轻松实现wav转mp3?
  • Vue3中el-table组件实现分页,多选以及回显
  • 基于 STM32 的高精度 PID 温控系统设计与实现:采用 Pt1000 温度传感器与 PWM 控制技术
  • HT5169内置BOOST升压的11W I2S输入D类音频功放
  • 【游戏设计】游戏中需要管理的数据分类
  • MYSQL-查看表中字段属性语法(三)
  • 找质数的方式
  • MATLAB中的无线通信系统测试和验证方法有哪些
  • 代码随想录Day17 图论-1
  • 调和级数枚举+前缀和,CF 731F - Video Cards
  • flutter 设置字体大小,适应各种屏幕
  • 【LeetCode:2535. 数组元素和与数字和的绝对差 + 模拟】
  • 16.面试算法-树的层次遍历与相关面试题
  • ConfigurationManager类功能如何使用
  • 网络原理 - TCP/IP