New file |
| | |
| | | <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> |