From d8b41fff43a2cee6a8f714ffa807623b15803786 Mon Sep 17 00:00:00 2001 From: quanyawei <401863037@qq.com> Date: Fri, 20 Oct 2023 15:21:35 +0800 Subject: [PATCH] fix:立行立改Uniapp小程序新建项目 --- uni_modules/cl-upload/components/cl-upload/cl-upload.vue | 1031 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 1,031 insertions(+), 0 deletions(-) diff --git a/uni_modules/cl-upload/components/cl-upload/cl-upload.vue b/uni_modules/cl-upload/components/cl-upload/cl-upload.vue new file mode 100644 index 0000000..ee28b16 --- /dev/null +++ b/uni_modules/cl-upload/components/cl-upload/cl-upload.vue @@ -0,0 +1,1031 @@ +<template> + <view class="cl-updata"> + <view class="file-list" :style="[listRowStyle]"> + + <view v-for="(item, index) in previewList" @tap="clickSelectedFile(item, index)" class="file-list-row" + :style="[rowStyle]" :key="index"> + + <image + class="_image" + v-if="fileUrlType(item) === 'image'" + :src="item.path" + :style="[imgStyle]" + mode="aspectFill"> + </image> + + <view v-else class="_video" :style="[imgStyle]"> + + <!-- #ifdef MP-WEIXIN || MP-ALIPAY --> + <video + v-if="!autoUpload || cloudType === 'other'" + class="_video" + :style="[imgStyle]" + :src="item.path" + :show-center-play-btn="false" + :show-fullscreen-btn="false" + :show-play-btn="false" + :show-loading="false" + :enable-progress-gesture="false" + :controls="false"> + <view @tap="previewVideo(item, index)" class="play"> + <image style="width: 100%;" :src="playImg" mode="widthFix"></image> + </view> + </video> + + <!-- #endif --> + + <!-- #ifdef APP-PLUS --> + <video + v-if="cloudType === 'other'" + class="_video" + :style="[imgStyle]" + :src="item.path" + :poster="item.path" + :controls="false" + :show-center-play-btn="false" + :show-fullscreen-btn="false" + :show-play-btn="false" + :show-loading="false" + :enable-progress-gesture="false"> + <cover-image class="app_play" :src="playImg" @tap="previewVideo(item, index)"></cover-image> + <cover-view class="remove" v-if="remove" @tap="deleteSelectedFile(item, index)"> + <cover-image class="image" :src="deleteImg" mode="widthFix" @tap="deleteSelectedFile(item, index)"></cover-image> + </cover-view> + </video> + <!-- #endif --> + + <!-- #ifndef MP-WEIXIN || MP-ALIPAY || APP-PLUS --> + <video + v-if="cloudType === 'other'" + class="_video" + :autoplay="false" + :style="[imgStyle]" + :src="item.path" + :controls="false" + :show-center-play-btn="false" + :show-fullscreen-btn="false" + :show-play-btn="false" + :show-loading="false" + :enable-progress-gesture="false" > + <cover-view @tap="previewVideo(item, index)" class="play"> + <cover-image style="width: 100%;" :src="playImg" mode="widthFix"></cover-image> + </cover-view> + </video> + + <!-- #endif --> + + <template v-else> + <cl-image class="pay" :style="[imgStyle]" :cloudType="cloudType" + :src="(item.poster || item.path)"></cl-image> + + <view class="play" @tap="previewVideo(item, index)"> + <image class="play-img" :src="playImg" mode="widthFix"></image> + </view> + </template> + + </view> + + <view class="remove" v-if="remove" @tap.stop="deleteSelectedFile(item, index)"> + <image class="image" :src="deleteImg" mode="widthFix"></image> + </view> + </view> + + <view v-if="add && FileList.length < max" @tap="selectFileTypeOnAdd" :style="[rowStyle]" class="file-list-row"> + <slot name="addImg"> + <div class="add-image"> + <image class="_image" :src="addImg" mode="widthFix"></image> + </div> + </slot> + </view> + </view> + + + <view v-if="tempVideoUrl" class="mask"> + <image @tap="tempVideoUrl = ''" class="_root" :src="closeImg" mode="widthFix"></image> + + <view class="block" @tap.stop> + <video class="block_video" autoplay :src="tempVideoUrl"></video> + </view> + </view> + </view> +</template> + +<script> +import ClImage from '../cl-image/cl-image.vue' +export default { + name: "cl-upload", + components: { ClImage }, + props: { + //������������������ + // #ifdef VUE2 + value: { + type: Array, + default: () => [], + }, + // #endif + + // #ifdef VUE3 + modelValue: { + type: Array, + default: () => [], + }, + // #endif + + // ��������������� oss��������� vframe��������� process��������� other������ + cloudType: { + type: String, + default: 'oss' + }, + // ���������,������������������������ + fileName: { + type: String, + default: 'file' + }, + // ������������ 'image', 'video', 'all' + fileType: { + type: String, + default: 'all' + }, + // ������������������ + imageFormData: { + type: Object | null, + default: () => { } + }, + // ������������������ + videoFromData: { + type: Object, + default: () => { } + }, + + // ������������������������������ + action: { + type: String, + default: '' + }, + + // ������������, ���unicloud��������������� + // https://uniapp.dcloud.net.cn/uniCloud/storage.html#storage-dir + cloudPathAsRealPath: { + type: Boolean, + default: false + }, + + // ��������������������������� + headers: { + type: Object, + default: () => { } + }, + + // ������������������������������ + data: { + type: Object, + default: () => { } + }, + + // ������������������������ + isPreviewImage: { + type: Boolean, + default: true + }, + + // ������������������������������������"default" - ������������������������ "number" - ������������������������ "none" - ��������������������� + indicator: { + type: String, + default: 'none' + }, + // ������������������������������������������ + autoUpload: { + type: Boolean, + default: true + }, + // ������������������������ + remove: { + type: Boolean, + default: true + }, + // ������������������ + add: { + type: Boolean, + default: true + }, + // ������������������ + max: { + type: Number, + default: 9 + }, + // ������������������������ + maxVideo: { + type: Number, + default: 0 + }, + // ������������ + listStyle: { + type: Object, + default: () => { } + }, + // ������������������������ + deleteTitle: { + type: String, + default: '������' + }, + // ������������������������ + deleteText: { + type: String, + default: '������������������������' + }, + // ������������ + loadingText: { + type: String, + default: '���������������...' + }, + // ��������������������������� + useBeforeDelete: { + type: Boolean, + default: false + }, + // ��������������������������� + useBeforeUpload: { + type: Boolean, + default: false + }, + // ������������������ + addImg: { + type: String, + default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/bb1550b3-e0a8-4a90-a86f-00f8c6afa9fb.png' + }, + // ������������������ + playImg: { + type: String, + default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/ae40402f-aa53-4344-b553-2322799bebd6.png' + }, + // ������������������ + deleteImg: { + type: String, + default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/d20177a5-417e-4c5d-a266-1988361c543d.png' + }, + // ������������������������ + closeImg: { + type: String, + default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/cde4362d-7ec7-4cac-a692-12e1f576be1e.png' + }, + }, + data() { + return { + // ������������ + FileList: [], + + // ������������������ + tempVideoUrl: '', + + // ������������������ + tempFile_paths: [], + + }; + }, + watch: { + // #ifdef VUE2 + 'value': { + handler: function (newVal, oldVal) { + this.FileList = newVal; + }, + deep: true, + immediate: true + }, + // #endif + + // #ifdef VUE3 + 'modelValue': { + handler: function (newVal, oldVal) { + this.FileList = newVal; + }, + deep: true, + immediate: true + }, + // #endif + }, + computed: { + previewList() { + return this.FileList.map(item => { + return { + path: item.path || item, + poster: item.poster || '' + } + }) + }, + listRowStyle() { + const style = { + 'grid-template-columns': `repeat(${this.listStyle?.columns || 4}, 1fr)`, // ������������ + 'grid-column-gap': this.listStyle?.columnGap || '40rpx', // ��������� + 'grid-row-gap': this.listStyle?.rowGap || '40rpx', // ��������� + 'padding': this.listStyle?.padding || '0rpx' // ��������������� + } + + return style; + }, + rowStyle() { + const { height = '140rpx', ratio } = this.listStyle || {}; + const style = { + 'aspect-ratio': height ? '' : ratio || '1/1', // ������������ + 'height': height, + }; + + return style; + }, + + imgStyle() { + const style = { + 'border-radius': this.listStyle?.radius || '6rpx', // ������������ + } + return style; + } + }, + methods: { + /** + * ��������������������� + * @param {object} item ������������ + * @param {number} selectedFileIndex ������������ + * */ + deleteSelectedFile(item, selectedFileIndex) { + + const fileToDelete = this.FileList[selectedFileIndex]; + + // ��������������� + if (this.useBeforeDelete) { + this.$emit('beforeDelete', fileToDelete, selectedFileIndex, () => { + return deleteFileFromList() + }) + } + + if (!this.useBeforeDelete) { + uni.showModal({ + title: this.deleteTitle, + content: this.deleteText, + success: (res) => { + if (res.confirm) { + deleteFileFromList() + } + } + }); + } + + const deleteFileFromList = () => { + const tempFileIndex = this.tempFile_paths.indexOf(item || item.path); + + if (tempFileIndex > -1) { + this.tempFile_paths.splice(tempFileIndex, 1) + } + + this.FileList.splice(selectedFileIndex, 1) + + // #ifdef VUE2 + this.$emit('input', this.FileList) + // #endif + + // #ifdef VUE3 + this.$emit("update:modelValue", this.FileList); + // #endif + } + + }, + + /** + * ��������������������� + * @param {object} item ������������ + * @param {number} index ������������ + * */ + clickSelectedFile(item, index) { + this.previewImage(item?.path ?? item, index); + this.$emit('onImage', { + item, + index + }) + }, + + /** + * ������������������������ + * */ + selectFileTypeOnAdd() { + + switch (this.fileType) { + case 'image': + this.handleFileSelection(1); + break; + case 'video': + this.handleFileSelection(2); + break; + case 'all': + uni.showActionSheet({ + itemList: ['������', '������'], + success: (res) => { + const tapIndex = res.tapIndex; + if (tapIndex === 0) { + this.handleFileSelection(1); + } else { + this.handleFileSelection(2); + } + }, + fail: (res) => { + console.error(res.errMsg); + } + }); + break; + default: + this.handleFileSelection(1); + break; + } + }, + + + /** + * ������������������������ + * @param { number } updataType ������������ 1:������ 2������ + * */ + async handleFileSelection(updataType) { + const that = this; + if (updataType === 1) { + + const data = Object.assign({}, { + // ������������������������������������������9 + count: 9, + // ������ mediaType ��� image ������������������������������������ + // #ifndef MP-TOUTIAO + sizeType: ['original', 'compressed'], + // #endif + // album ������������������camera ������������������������������������ + sourceType: ['camera', 'album'], + + compress: false + }, this.imageFormData) + + data['count'] = this.max - this.FileList.length + + uni.chooseImage({ + ...data, + success: async (res) => { + let tempFiles = res.tempFiles + const compress = that.imageFormData?.compress || false; + + // ������������������������ + if (that.imageFormData?.size ?? false) { + const maxSize = that.imageFormData.size * 1024 * 1024 + + tempFiles.map((imgInfo, index) => { + if (imgInfo.size > maxSize) { + tempFiles.splice(index, 1) + that.$emit('onImageSize', imgInfo) + return uni.showToast({ + title: `������������������${that.imageFormData.size}MB`, + duration: 2000, + icon: 'none' + }); + } + }) + } + + // ������������������ + if (compress) { + const compressedImagePathList = tempFiles.map(imageItem => { + return that.compressImage(imageItem.path) + }) + + Promise.all(compressedImagePathList).then(result => { + upload(result); + }) + + } else { + upload(tempFiles); + } + + function upload(tempImages) { + if (that.autoUpload) { + tempImages.map(item => { + that.onBeforeUploadFile(item, 'image') + }) + } else { + that.FileList = [...that.FileList, ...tempImages] + tempImages.map(item => { + that.tempFile_paths.push(item) + }) + } + } + + }, + fail(err) { + console.error('������������������', err) + that.$emit('onError', err) + } + + }) + } + + if (updataType === 2) { + + // ������������������������������ + const VIDEO_REGEXP = /\.(mp4|flv|avi)/i + const videoList = await that.FileList.filter(item => { + const fileUrl = item?.url ?? item + return VIDEO_REGEXP.test(fileUrl) + }) + + if (that.maxVideo > 0 && videoList.length >= that.maxVideo) { + that.$emit('onVideoMax', that.maxVideo, videoList.length) + return uni.showToast({ + title: '���������������������', + duration: 2000, + icon: 'none' + }); + } + + const data = Object.assign({}, { + // ��������������������������������������������������������� 60 ������ + maxDuration: 60, + // #ifndef MP-TOUTIAO + // 'front'���'back'���������'back' + camera: "back", + // #endif + + // album ���������������������camera ������������������������������������������ + sourceType: ['camera', 'album'], + // ��������������������������������������������������� true������������������ + compressed: true, + // 'front'���'back'���������'back' + }, this.videoFromData) + + uni.chooseVideo({ + ...data, + success: (res) => { + let tempFilePath = { ...res } + tempFilePath['path'] = res.tempFilePath + + // ������������������������ + if (that.videoFromData?.size ?? false) { + const maxSize = that.videoFromData.size * 1024 * 1024 + + if (tempFilePath.size > maxSize) { + uni.showToast({ + title: `������������������${that.videoFromData.size}MB`, + duration: 2000, + icon: 'none' + }); + return false; + } + + } + if (that.autoUpload) { + that.onBeforeUploadFile(tempFilePath, 'video') + } else { + that.FileList.push(tempFilePath) + that.tempFile_paths.push(tempFilePath) + } + }, + fail(err) { + console.error('������������������', err) + } + + }) + } + }, + + /** + * ��������������� + * @param { tempFile } ������������ + * @return { Promise } + * */ + onBeforeUploadFile(tempFile) { + if (this.useBeforeUpload) { + return this.$emit('beforeUpload', tempFile, () => { + return this.updataFile(tempFile); + }) + } + return this.updataFile(tempFile); + }, + + /** + * ������������������������ + * @param { tempFile } ������������ + * @return { Promise } + * */ + updataFile(tempFile) { + const that = this; + const filePath = tempFile.path || tempFile; + const fileType = this.fileUrlType(filePath) == 'image' ? '.png' : '.mp4'; + const fileName = tempFile.name || Date.now() + fileType; + + uni.showLoading({ + title: this.loadingText, + icon: 'loading' + }) + + return new Promise((resolve, reject) => { + // uniCloud������ + if (that.action === 'uniCloud') { + + uniCloud.uploadFile({ + cloudPath: String(fileName), + filePath: filePath, + // #ifdef MP-ALIPAY + fileType: fileType, + // #endif + cloudPathAsRealPath: this.cloudPathAsRealPath, + + onUploadProgress: (progressEvent) => { + const percentCompleted = Math.round( + (progressEvent.loaded * 100) / progressEvent.total + ); + that.$emit('onProgress', percentCompleted) + }, + success(result) { + if (that.autoUpload) { + that.FileList.push(result.fileID) + } else { + that.FileList.map((item, index) => { + if (item === filePath || item.path === filePath) { + that.FileList.splice(index, 1, result.fileID) + } + }) + } + + // #ifdef VUE2 + that.$emit('input', that.FileList) + // #endif + // #ifdef VUE3 + that.$emit("update:modelValue", that.FileList); + // #endif + + resolve(result.fileID) + uni.hideLoading(); + that.$emit('onProgress', { + ...result + }) + }, + fail: (error) => { + uni.hideLoading(); + console.error('error', error); + that.$emit('onError', error) + reject(error) + } + }) + return false; + } + + // ������������������ + const uploadTask = uni.uploadFile({ + url: that.action, + filePath: filePath, + name: that.fileName, + formData: that.data, + header: that.headers, + // #ifdef MP-ALIPAY + fileType: filetype, + // #endif + success: (uploadFileRes) => { + const data = JSON.parse(uploadFileRes.data) + uni.hideLoading(); + that.success(data) + + if (!this.autoUpload) { + that.FileList.map((item, index) => { + if (item === filePath || item.path === filePath) { + that.FileList.splice(index, 1) + } + }) + } + + resolve(data) + }, + fail: (error) => { + uni.hideLoading(); + console.error('error', error); + that.$emit('onError', error) + reject(error) + } + }); + + uploadTask.onProgressUpdate((res) => { + that.$emit('onProgress', { + ...res, + ...tempFile + }) + }); + }) + }, + + /** + * ������������ + * */ + submit() { + + return new Promise((resolve, reject) => { + if (this.tempFile_paths.length <= 0) { + resolve([]) + } + + const uploadedFilePaths = this.tempFile_paths.map(item => { + return this.onBeforeUploadFile(item || item.path) + }) + + Promise.all(uploadedFilePaths).then(res => { + this.tempFile_paths = [] + resolve(res) + }).catch(err => { + reject(err) + }) + }) + + }, + + /** + * ������������ + * @param {array} data ������������������������ + * @return {array} ������������ + * */ + success(data) { + this.$emit('onSuccess', data); + + // ���������������������-��������������� + // const list = data.map(item=> { + // return JSON.parse(item).data.link; + // }) + // this.$emit('input', [...this.FileList, ...list]); + }, + /** + * ������������ + * @param {array} tempFilePaths ������������������ + * @return {array} ��������������������������� + * */ + async compressImage(tempFilePaths) { + const that = this; + + return new Promise((resolve, reject) => { + + if (typeof tempFilePaths !== 'string') { + console.error('������������������') + reject([]) + } + + uni.showLoading({ + title: '���������...', + icon: 'loading', + }) + + // #ifdef H5 + this.canvasDataURL(tempFilePaths, { + quality: that.imageFormData.quality / 100 + }, (base64Codes) => { + resolve(base64Codes); + uni.hideLoading(); + }) + // #endif + + // #ifndef H5 + uni.compressImage({ + src: tempFilePaths, + quality: that.imageFormData.quality || 80, + success: res => { + resolve(res.tempFilePath); + uni.hideLoading(); + }, + fail(err) { + reject(err); + uni.hideLoading(); + } + }) + // #endif + + }) + }, + + /** + * H5������������������ + * @param {string} path ������������ + * @param {object} obj ������������ + * @param {function} callback ������������ + * @return {string} base64 + * */ + canvasDataURL(path, obj, callback) { + var img = new Image(); + img.src = path; + img.onload = function () { + var that = this; + // ��������������������� + var w = that.width, + h = that.height, + scale = w / h; + w = obj.width || w; + h = obj.height || (w / scale); + var quality = 0.8; // ���������������������0.8 + //������canvas + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + // ������������������ + var anw = document.createAttribute("width"); + anw.nodeValue = w; + var anh = document.createAttribute("height"); + anh.nodeValue = h; + canvas.setAttributeNode(anw); + canvas.setAttributeNode(anh); + ctx.drawImage(that, 0, 0, w, h); + // ������������ + if (obj.quality && obj.quality <= 1 && obj.quality > 0) { + quality = obj.quality; + } + // quality������������������������������������������ + var base64 = canvas.toDataURL('image/jpeg', quality); + // ������������������base64������ + callback(base64); + } + }, + + /** + * ������������ + * @param {string, object} item ������������ + * */ + previewImage(item) { + if (this.fileUrlType(item) === 'video') return false; + if (!this.isPreviewImage) return false; + + const imgs = this.FileList.filter(item => { + return this.fileUrlType(item) !== 'video' + }).map(item => item?.path ?? item) + const current = imgs.indexOf(item || item.path); + + uni.previewImage({ + current: current, + urls: imgs, + success() { + }, + fail(err) { + console.log(err); + } + }) + }, + + /** + * ������������ + * @param {string, object} item ������������ + * @param {number} index ������ + * */ + previewVideo(item, index) { + this.$emit('onVideo', { + item, + index + }) + this.tempVideoUrl = item.path; + }, + + /** + * ������img������ + * @param {string, object} item ������������ + * @return {boolean} ������img������ + * */ + fileUrlType(file) { + const filePath = file.path || file; + + if (this.isBase64(filePath)) return 'image' + + const fileType = filePath.split('.').pop(); + + const IMAGE_REGEXP = /(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg|image)/i + if (IMAGE_REGEXP.test(fileType)) { + return 'image'; + } else { + return 'video'; + } + }, + // ���������������base64 + isBase64(str) { + if (str === '' || typeof str !== 'string') return console.error('������������������, base64', str); + return str.includes('blob:') || str.includes('data:image'); + } + } +} +</script> + +<style lang="scss" scoped> +.cl-updata { + + .file-list { + display: grid; + + &-row { + display: inline-flex; + align-items: center; + position: relative; + + .play-img { + width: 100%; + } + + ._image { + height: 100%; + width: 100%; + } + + ._video { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + } + + .video-fixed { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 100%; + height: 100%; + border-radius: 10rpx; + z-index: 96; + } + + .play { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 30%; + z-index: 95; + } + + .app_play { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 50rpx; + height: 50rpx; + } + + .remove { + position: absolute; + top: 0; + right: 0; + background-color: #373737; + height: 50rpx; + width: 50rpx; + border-bottom-left-radius: 200rpx; + z-index: 97; + + .image { + width: 20rpx; + height: 20rpx; + position: absolute; + right: 12rpx; + top: 12rpx; + } + } + } + + .add-image { + display: flex; + align-items: center; + justify-content: center; + border: 2rpx dashed #ccc; + width: 100%; + height: 100%; + border-radius: 5rpx; + + &:active { + opacity: 0.8; + } + + ._image { + width: 40%; + } + } + } + + .mask { + background-color: #000; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 99; + + .block { + padding: 0 30rpx; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; + + .block_video { + width: 100%; + height: 78vh; + } + } + + ._root { + width: 60rpx; + height: 60rpx; + position: absolute; + left: 40rpx; + top: 5vh + } + } +} +</style> -- Gitblit v1.8.0