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, filetype: 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 H5else if (fileType === 'office') {return uni.openDocument({filePath: url,fail: (err) => {console.log(err);uni.showToast({icon: 'none',title: '文件预览失败'});},});}// #endifelse{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:{}}