uniapp uni-file-picker 组件删除时增加二次确认弹框

全文放在下面,先写一下思路

1.思路

首先看了一下api文档,只有删除的事件,然后发现抛出的删除事件是已经删除了图片之后的事件,然后去源码里看在哪里抛出的删除
在这里插入图片描述
发现是在点击按钮的事件里做的删除,所以我们需要在这之前加一步确认操作,为了不影响之前的功能,我给增加了一个配置项控制是否需要二次确认,如果需要的话弹弹框+进行删除操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后发现确认的时候事件是没有当前索引的,所以从点击事件的时候先把当前选中的索引存到全局,然后确认删除的时候就可以直接拿到索引了

2.效果

下面是效果

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

3.源码,直接替换即可

在这里插入图片描述

<template>
	<view class="uni-file-picker">
		<view v-if="title" class="uni-file-picker__header">
			<text class="file-title">{{ title }}</text>
			<text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
		</view>
		<upload-image v-if="fileMediatype === 'image' && showType === 'grid'" :readonly="readonly"
			:image-styles="imageStyles" :files-list="filesList" :limit="limitLength" :disablePreview="disablePreview"
			:delIcon="delIcon" @uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
			<slot>
				<view class="is-add">
					<view class="icon-add"></view>
					<view class="icon-add rotate"></view>
				</view>
			</slot>
		</upload-image>
		<upload-file v-if="fileMediatype !== 'image' || showType !== 'grid'" :readonly="readonly"
			:list-styles="listStyles" :files-list="filesList" :showType="showType" :delIcon="delIcon"
			@uploadFiles="uploadFiles" @choose="choose" @delFile="delFile">
			<slot><button type="primary" size="mini">选择文件</button></slot>
		</upload-file>
		
		<uni-popup ref="popup" type="dialog">
			<uni-popup-dialog title="是否确认删除" :duration="2000" :before-close="true" @close="closeConfirm" @confirm="handleDeleteConfirm"></uni-popup-dialog>
		</uni-popup>
	</view>
</template>

<script>
	import {
		chooseAndUploadFile,
		uploadCloudFiles
	} from './choose-and-upload-file.js'
	import {
		get_file_ext,
		get_extname,
		get_files_and_is_max,
		get_file_info,
		get_file_data
	} from './utils.js'
	import uploadImage from './upload-image.vue'
	import uploadFile from './upload-file.vue'
	let fileInput = null
	/**
	 * FilePicker 文件选择上传
	 * @description 文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间
	 * @tutorial https://ext.dcloud.net.cn/plugin?id=4079
	 * @property {Object|Array}	value	组件数据,通常用来回显 ,类型由return-type属性决定
	 * @property {Boolean}	disabled = [true|false]	组件禁用
	 * 	@value true 	禁用
	 * 	@value false 	取消禁用
	 * @property {Boolean}	readonly = [true|false]	组件只读,不可选择,不显示进度,不显示删除按钮
	 * 	@value true 	只读
	 * 	@value false 	取消只读
	 * @property {String}	return-type = [array|object]	限制 value 格式,当为 object 时 ,组件只能单选,且会覆盖
	 * 	@value array	规定 value 属性的类型为数组
	 * 	@value object	规定 value 属性的类型为对象
	 * @property {Boolean}	disable-preview = [true|false]	禁用图片预览,仅 mode:grid 时生效
	 * 	@value true 	禁用图片预览
	 * 	@value false 	取消禁用图片预览
	 * @property {Boolean}	del-icon = [true|false]	是否显示删除按钮
	 * 	@value true 	显示删除按钮
	 * 	@value false 	不显示删除按钮
	 * @property {Boolean}	auto-upload = [true|false]	是否自动上传,值为true则只触发@select,可自行上传
	 * 	@value true 	自动上传
	 * 	@value false 	取消自动上传
	 * @property {Number|String}	limit	最大选择个数 ,h5 会自动忽略多选的部分
	 * @property {String}	title	组件标题,右侧显示上传计数
	 * @property {String}	mode = [list|grid]	选择文件后的文件列表样式
	 * 	@value list 	列表显示
	 * 	@value grid 	宫格显示
	 * @property {String}	file-mediatype = [image|video|all]	选择文件类型
	 * 	@value image	只选择图片
	 * 	@value video	只选择视频
	 * 	@value all		选择所有文件
	 * @property {Array}	file-extname	选择文件后缀,根据 file-mediatype 属性而不同
	 * @property {Object}	list-style	mode:list 时的样式
	 * @property {Object}	image-styles	选择文件后缀,根据 file-mediatype 属性而不同
	 * @event {Function} select 	选择文件后触发
	 * @event {Function} progress 文件上传时触发
	 * @event {Function} success 	上传成功触发
	 * @event {Function} fail 		上传失败触发
	 * @event {Function} delete 	文件从列表移除时触发
	 */
	export default {
		name: 'uniFilePicker',
		components: {
			uploadImage,
			uploadFile
		},
		emits: ['select', 'success', 'fail', 'progress', 'delete', 'update:modelValue', 'input'],
		props: {
			// #ifdef VUE3
			modelValue: {
				type: [Array, Object],
				default () {
					return []
				}
			},
			// #endif

			// #ifndef VUE3
			value: {
				type: [Array, Object],
				default () {
					return []
				}
			},
			// #endif

			disabled: {
				type: Boolean,
				default: false
			},
			disablePreview: {
				type: Boolean,
				default: false
			},
			delIcon: {
				type: Boolean,
				default: true
			},
			// 自动上传
			autoUpload: {
				type: Boolean,
				default: true
			},
			// 最大选择个数 ,h5只能限制单选或是多选
			limit: {
				type: [Number, String],
				default: 9
			},
			// 列表样式 grid | list | list-card
			mode: {
				type: String,
				default: 'grid'
			},
			// 选择文件类型  image/video/all
			fileMediatype: {
				type: String,
				default: 'image'
			},
			// 文件类型筛选
			fileExtname: {
				type: [Array, String],
				default () {
					return []
				}
			},
			title: {
				type: String,
				default: ''
			},
			listStyles: {
				type: Object,
				default () {
					return {
						// 是否显示边框
						border: true,
						// 是否显示分隔线
						dividline: true,
						// 线条样式
						borderStyle: {}
					}
				}
			},
			imageStyles: {
				type: Object,
				default () {
					return {
						width: 'auto',
						height: 'auto'
					}
				}
			},
			readonly: {
				type: Boolean,
				default: false
			},
			returnType: {
				type: String,
				default: 'array'
			},
			sizeType: {
				type: Array,
				default () {
					return ['original', 'compressed']
				}
			},
			deleteConfirm: {
				type: Boolean,
				default: false
			}
		},
		data() {
			return {
				files: [],
				localValue: [],
				activedIndex: null
			}
		},
		watch: {
			// #ifndef VUE3
			value: {
				handler(newVal, oldVal) {
					this.setValue(newVal, oldVal)
				},
				immediate: true
			},
			// #endif
			// #ifdef VUE3
			modelValue: {
				handler(newVal, oldVal) {
					this.setValue(newVal, oldVal)
				},
				immediate: true
			},
			// #endif
		},
		computed: {
			filesList() {
				let files = []
				this.files.forEach(v => {
					files.push(v)
				})
				return files
			},
			showType() {
				if (this.fileMediatype === 'image') {
					return this.mode
				}
				return 'list'
			},
			limitLength() {
				if (this.returnType === 'object') {
					return 1
				}
				if (!this.limit) {
					return 1
				}
				if (this.limit >= 99) {
					return 99
				}
				return this.limit
			}
		},
		created() {
			// TODO 兼容不开通服务空间的情况
			if (!(uniCloud.config && uniCloud.config.provider)) {
				this.noSpace = true
				uniCloud.chooseAndUploadFile = chooseAndUploadFile
			}
			this.form = this.getForm('uniForms')
			this.formItem = this.getForm('uniFormsItem')
			if (this.form && this.formItem) {
				if (this.formItem.name) {
					this.rename = this.formItem.name
					this.form.inputChildrens.push(this)
				}
			}
		},
		methods: {
			/**
			 * 公开用户使用,清空文件
			 * @param {Object} index
			 */
			clearFiles(index) {
				if (index !== 0 && !index) {
					this.files = []
					this.$nextTick(() => {
						this.setEmit()
					})
				} else {
					this.files.splice(index, 1)
				}
				this.$nextTick(() => {
					this.setEmit()
				})
			},
			/**
			 * 公开用户使用,继续上传
			 */
			upload() {
				let files = []
				this.files.forEach((v, index) => {
					if (v.status === 'ready' || v.status === 'error') {
						files.push(Object.assign({}, v))
					}
				})
				this.uploadFiles(files)
			},
			async setValue(newVal, oldVal) {
				const newData =  async (v) => {
					const reg = /cloud:\/\/([\w.]+\/?)\S*/
					let url = ''
					if(v.fileID){
						url = v.fileID
					}else{
						url = v.url
					}
					if (reg.test(url)) {
						v.fileID = url
						v.url = await this.getTempFileURL(url)
					}
					if(v.url) v.path = v.url
					return v
				}
				if (this.returnType === 'object') {
					if (newVal) {
						await newData(newVal)
					} else {
						newVal = {}
					}
				} else {
					if (!newVal) newVal = []
					for(let i =0 ;i < newVal.length ;i++){
						let v = newVal[i]
						await newData(v)
					}
				}
				this.localValue = newVal
				if (this.form && this.formItem &&!this.is_reset) {
					this.is_reset = false
					this.formItem.setValue(this.localValue)
				}
				let filesData = Object.keys(newVal).length > 0 ? newVal : [];
				this.files = [].concat(filesData)
			},

			/**
			 * 选择文件
			 */
			choose() {

				if (this.disabled) return
				if (this.files.length >= Number(this.limitLength) && this.showType !== 'grid' && this.returnType ===
					'array') {
					uni.showToast({
						title: `您最多选择 ${this.limitLength} 个文件`,
						icon: 'none'
					})
					return
				}
				this.chooseFiles()
			},

			/**
			 * 选择文件并上传
			 */
			chooseFiles() {
				const _extname = get_extname(this.fileExtname)
				// 获取后缀
				uniCloud
					.chooseAndUploadFile({
						type: this.fileMediatype,
						compressed: false,
						sizeType: this.sizeType,
						// TODO 如果为空,video 有问题
						extension: _extname.length > 0 ? _extname : undefined,
						count: this.limitLength - this.files.length, //默认9
						onChooseFile: this.chooseFileCallback,
						onUploadProgress: progressEvent => {
							this.setProgress(progressEvent, progressEvent.index)
						}
					})
					.then(result => {
						this.setSuccessAndError(result.tempFiles)
					})
					.catch(err => {
					})
			},

			/**
			 * 选择文件回调
			 * @param {Object} res
			 */
			async chooseFileCallback(res) {
				const _extname = get_extname(this.fileExtname)
				const is_one = (Number(this.limitLength) === 1 &&
						this.disablePreview &&
						!this.disabled) ||
					this.returnType === 'object'
				// 如果这有一个文件 ,需要清空本地缓存数据
				if (is_one) {
					this.files = []
				}

				let {
					filePaths,
					files
				} = get_files_and_is_max(res, _extname)
				if (!(_extname && _extname.length > 0)) {
					filePaths = res.tempFilePaths
					files = res.tempFiles
				}

				let currentData = []
				for (let i = 0; i < files.length; i++) {
					if (this.limitLength - this.files.length <= 0) break
					files[i].uuid = Date.now()
					let filedata = await get_file_data(files[i], this.fileMediatype)
					filedata.progress = 0
					filedata.status = 'ready'
					this.files.push(filedata)
					currentData.push({
						...filedata,
						file: files[i]
					})
				}
				this.$emit('select', {
					tempFiles: currentData,
					tempFilePaths: filePaths
				})
				res.tempFiles = files
				// 停止自动上传
				if (!this.autoUpload || this.noSpace) {
					res.tempFiles = []
				}
			},

			/**
			 * 批传
			 * @param {Object} e
			 */
			uploadFiles(files) {
				files = [].concat(files)
				uploadCloudFiles.call(this, files, 5, res => {
						this.setProgress(res, res.index, true)
					})
					.then(result => {
						this.setSuccessAndError(result)
					})
					.catch(err => {
					})
			},

			/**
			 * 成功或失败
			 */
			async setSuccessAndError(res, fn) {
				let successData = []
				let errorData = []
				let tempFilePath = []
				let errorTempFilePath = []
				for (let i = 0; i < res.length; i++) {
					const item = res[i]
					const index = item.uuid ? this.files.findIndex(p => p.uuid === item.uuid) : item.index

					if (index === -1 || !this.files) break
					if (item.errMsg === 'request:fail') {
						this.files[index].url = item.path
						this.files[index].status = 'error'
						this.files[index].errMsg = item.errMsg
						// this.files[index].progress = -1
						errorData.push(this.files[index])
						errorTempFilePath.push(this.files[index].url)
					} else {
						this.files[index].errMsg = ''
						this.files[index].fileID = item.url
						const reg = /cloud:\/\/([\w.]+\/?)\S*/
						if (reg.test(item.url)) {
							this.files[index].url = await this.getTempFileURL(item.url)
						}else{
							this.files[index].url = item.url
						}

						this.files[index].status = 'success'
						this.files[index].progress += 1
						successData.push(this.files[index])
						tempFilePath.push(this.files[index].fileID)
					}
				}

				if (successData.length > 0) {
					this.setEmit()
					// 状态改变返回
					this.$emit('success', {
						tempFiles: this.backObject(successData),
						tempFilePaths: tempFilePath
					})
				}

				if (errorData.length > 0) {
					this.$emit('fail', {
						tempFiles: this.backObject(errorData),
						tempFilePaths: errorTempFilePath
					})
				}
			},

			/**
			 * 获取进度
			 * @param {Object} progressEvent
			 * @param {Object} index
			 * @param {Object} type
			 */
			setProgress(progressEvent, index, type) {
				const fileLenth = this.files.length
				const percentNum = (index / fileLenth) * 100
				const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
				let idx = index
				if (!type) {
					idx = this.files.findIndex(p => p.uuid === progressEvent.tempFile.uuid)
				}
				if (idx === -1 || !this.files[idx]) return
				// fix by mehaotian 100 就会消失,-1 是为了让进度条消失
				this.files[idx].progress = percentCompleted - 1
				// 上传中
				this.$emit('progress', {
					index: idx,
					progress: parseInt(percentCompleted),
					tempFile: this.files[idx]
				})
			},

			/**
			 * 删除文件
			 * @param {Object} index
			 */
			delFile(index) {
				this.activedIndex = index;
				if (this.deleteConfirm) {
					this.$refs.popup.open()
					return;
				}
				this.$emit('delete', {
					tempFile: this.files[index],
					tempFilePath: this.files[index].url
				})
				this.files.splice(index, 1)
				this.$nextTick(() => {
					this.setEmit()
				})
			},

			/**
			 * 删除文件前二次确认
			 * @param {Object} index
			 */
			handleDeleteConfirm() {
				this.$emit('delete', {
					tempFile: this.files[this.activedIndex],
					tempFilePath: this.files[this.activedIndex].url
				})
				this.files.splice(this.activedIndex, 1)
				this.$nextTick(() => {
					this.setEmit()
				})
				this.$refs.popup.close()
			},
			closeConfirm() {
				this.$refs.popup.close()
			},

			/**
			 * 获取文件名和后缀
			 * @param {Object} name
			 */
			getFileExt(name) {
				const last_len = name.lastIndexOf('.')
				const len = name.length
				return {
					name: name.substring(0, last_len),
					ext: name.substring(last_len + 1, len)
				}
			},

			/**
			 * 处理返回事件
			 */
			setEmit() {
				let data = []
				if (this.returnType === 'object') {
					data = this.backObject(this.files)[0]
					this.localValue = data?data:null
				} else {
					data = this.backObject(this.files)
					if (!this.localValue) {
						this.localValue = []
					}
					this.localValue = [...data]
				}
				// #ifdef VUE3
				this.$emit('update:modelValue', this.localValue)
				// #endif
				// #ifndef VUE3
				this.$emit('input', this.localValue)
				// #endif
			},

			/**
			 * 处理返回参数
			 * @param {Object} files
			 */
			backObject(files) {
				let newFilesData = []
				files.forEach(v => {
					newFilesData.push({
						...v,
						extname: v.extname,
						fileType: v.fileType,
						image: v.image,
						name: v.name,
						path: v.path,
						size: v.size,
						fileID:v.fileID,
						url: v.url
					})
				})
				return newFilesData
			},
			async getTempFileURL(fileList) {
				fileList = {
					fileList: [].concat(fileList)
				}
				const urls = await uniCloud.getTempFileURL(fileList)
				return urls.fileList[0].tempFileURL || ''
			},
			/**
			 * 获取父元素实例
			 */
			getForm(name = 'uniForms') {
				let parent = this.$parent;
				let parentName = parent.$options.name;
				while (parentName !== name) {
					parent = parent.$parent;
					if (!parent) return false;
					parentName = parent.$options.name;
				}
				return parent;
			}
		}
	}
</script>

<style>
	.uni-file-picker {
		/* #ifndef APP-NVUE */
		box-sizing: border-box;
		overflow: hidden;
		/* #endif */
	}

	.uni-file-picker__header {
		padding-top: 5px;
		padding-bottom: 10px;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		justify-content: space-between;
	}

	.file-title {
		font-size: 14px;
		color: #333;
	}

	.file-count {
		font-size: 14px;
		color: #999;
	}

	.is-add {
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		align-items: center;
		justify-content: center;
	}

	.icon-add {
		width: 50px;
		height: 5px;
		background-color: #f1f1f1;
		border-radius: 2px;
	}

	.rotate {
		position: absolute;
		transform: rotate(90deg);
	}
</style>

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人间清醒小仙女

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值