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

news2024/12/29 9:50:08

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.coloradmin.cn/o/2169217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dubbo快速入门(一):分布式与微服务、Dubbo基本概念

文章目录 一、分布式与微服务概念1.大型互联网架构目标2.集群和分布式&#xff08;1&#xff09;集群 (Cluster)&#xff08;2&#xff09;分布式计算 (Distributed Computing)&#xff08;3&#xff09;集群与分布式的关系&#xff08;4&#xff09;实践中的应用案例 3.架构演…

【AI大模型】向量及向量知识库

一、词向量与向量 什么是词向量 在机器学习和自然语言处理&#xff08;NLP&#xff09;中&#xff0c;词向量&#xff08;word embedding&#xff09;是一种以单词为单位将每个单词转化为实数向量的技术。这些实数向量可以被计算机更好地理解和处理。 词向量背后的主要想法是…

.NET 6 中,使用 ActionFilterAttribute 实现 AOP(面向切面编程)

AOP概述&#xff1a;AOP&#xff08;面向切面编程&#xff09;是一种编程规范的风格&#xff0c;通过横切的思想&#xff0c;将系统功能和业务功能分离开&#xff0c;以提高代码的可维护性和清晰度。 系统功能模块&#xff1a; 1、缓存模块&#xff1a; 作用&#xff1a;提高…

OpenHarmony(鸿蒙南向)——平台驱动指南【MIPI CSI】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 CSI&#xff08;Camera Serial Interface&#xf…

wx小程序中,商城订单详情显示还有多少分钟关闭

问题&#xff1a;wx小程序中&#xff0c;商城订单详情需要显示还有多少分钟关闭 思路&#xff1a;创建订单时间戳和当前时间戳相减&#xff0c;得到时间差&#xff0c;再除1000&#xff0c;得到相差秒数&#xff0c;然后除60&#xff0c;向下取整&#xff0c;得到分钟。 代码如…

物联网行业中模组的AT指令详解以及使用

01 概述 AT 命令&#xff08;AT Commands&#xff09;最早是由发明拨号调制解调器&#xff08;MODEM&#xff09;的贺氏公司&#xff08;Hayes&#xff09;为了控制 MODEM 而发明的控制协议。后来随着网络带宽的升级&#xff0c;速度很低的拨号 MODEM 基本退出一般使用市场&am…

凌晨1点开播!Meta Connect 2024开发者大会,聚焦Llama新场景和AR眼镜

作者&#xff1a;十九 编辑&#xff1a;李宝珠 北京时间 9 月 26 日凌晨 1 点&#xff0c;Meta Connect 2024 开发者大会即将举行&#xff0c;马克扎克伯格将聚焦 AI 和元宇宙&#xff0c;向大家分享 Llama 模型的更多潜在应用&#xff0c;并介绍 Meta 最新产品 AR 眼镜和 Meta…

java基础 之 实现一个链表

文章目录 引言链表节点单向链表双向链表链表的优缺点 java封装的链表自己实现一下链表LinkNode节点类LinkedList类实现示例图代码 引言 1、新建的节点需要两个值&#xff1a;value 和 节点 next&#xff1b; 2、新建的节点链表需要有一个head&#xff1b; 3、根据位置对链表进行…

The Open Group 2024生态系统架构·可持续发展年度大会全面解读

在全球数字化转型加速的时代背景下&#xff0c;人工智能技术正以前所未有的速度重塑各行各业的生态系统。尤其是随着ChatGPT、Sora等技术的爆发&#xff0c;AIGC&#xff08;人工智能生成内容&#xff09;技术在多个领域展现出超越人类的能力&#xff0c;AGI&#xff08;通用人…

Llama 3.2:利用开放、可定制的模型实现边缘人工智能和视觉革命

在我们发布 Llama 3.1 模型群后的两个月内&#xff0c;包括 405B - 第一个开放的前沿级人工智能模型在内&#xff0c;它们所产生的影响令我们兴奋不已。 虽然这些模型非常强大&#xff0c;但我们也认识到&#xff0c;使用它们进行构建需要大量的计算资源和专业知识。 我们也听到…

成都网安周暨CCS2024 | 大模型安全与产业应用创新研讨活动成功举办

9月11日-12日&#xff0c;作为2024年国家网络安全宣传周成都系列活动的重磅活动之一&#xff0c;CCS 2024成都网络安全系列活动在成都举行。“大模型安全与产业应用创新研讨活动”同期举办&#xff0c;本场活动由百度安全、成都无糖信息联合承办&#xff0c;特邀云安全联盟CSA大…

MYSQL求月份同比数据和环比数据

1.需求题目如下 1.首先求出每月每个account_id 对应的amount金额 2.利用表自关联&#xff0c;获取上月&#xff0c;上年对应月份及金额&#xff0c; 关联条件利用 主表月份-1个月上月月份 和 主表月份-1年上年月份 3.最后求同比和环比 附代码及测试数据 CREATE TABLE transa…

HTML·第3章 表格布局与表单交互

3.1 表格概述 3.1.1 表格的结构 表格是由行和列组成的二维表&#xff0c;而每行又由一个或多个单元格组成&#xff0c;用于放置数据或其他内容。表格中的单元格是行与列的交叉部分&#xff0c;是组成表格的最基本单元。单元格的内容是数据&#xff0c;也称数据单元格。数据单元…

如何在 macOS(MacBook Pro、Air 和 iMac)上恢复未保存的 Word 文档

Microsoft Word 在许多用户中很受欢迎&#xff0c;并且有多种用途。无论是为学校写论文、在办公室写报告还是其他许多事情。但是不保存文档并丢失数据可能是您可能面临的最可怕的噩梦。但是&#xff0c;也有几种方法可以在 macOS 上恢复未保存的 Word 文档。 用户在 Windows P…

【C++笔试强训】如何成为算法糕手Day5

学习编程就得循环渐进&#xff0c;扎实基础&#xff0c;勿在浮沙筑高台 循环渐进Forward-CSDN博客 目录 循环渐进Forward-CSDN博客 第一题&#xff1a;游游的you 思路&#xff1a; 第二题&#xff1a;腐烂的苹果 思路&#xff1a; 第三题&#xff1a;孩子们的游戏 思路&…

RabbitMQ下载安装运行环境搭建

RabbitMQ运行环境搭建 1、Erlang及RabbitMQ安装版本的选择2、下载安装Erlang2.1、下载Erlang2.2、安装Erlang2.2.1、安装Erlang前先安装Linux依赖库2.2.2、解压Erlang压缩包文件2.2.3、配置2.2.4、编译2.2.5、安装2.2.6、验证erlang是否安装成功 3、RabbitMQ下载安装3.1、下载3…

基于SSM+小程序的医院核酸检测服务管理系统(医院2)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM小程序的医院核酸检测服务管理系统实现了管理员、用户管理、普通管理员、医护人员。 1、管理员实现了首页、用户管理、医护人员管理、普通管理员、通知公告管理、疫苗接种管理、核…

2023_Spark_实验九:编写WordCount程序(Scala版)

需求&#xff1a; 1、做某个文件的词频统计//某个单词在这个文件出现次数 步骤&#xff1a; 文件单词规律&#xff08;空格分开&#xff09;单词切分单词的统计&#xff08;k,v&#xff09;->(k:单词&#xff0c;V&#xff1a;数量&#xff09;打印 框架&#xff1a; 单…

基于RPA+BERT的文档辅助“悦读”系统 | OPENAIGC开发者大赛高校组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…

毕业设计选题:基于ssm+vue+uniapp的鲜花销售小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…