ui库和web端产品库合并版本(还需修复细节)
This commit is contained in:
		
							
								
								
									
										514
									
								
								ui/packages/basic/AiUploader.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										514
									
								
								ui/packages/basic/AiUploader.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,514 @@ | ||||
| <template> | ||||
|   <section class="uploader"> | ||||
|     <el-upload | ||||
|         action | ||||
|         multiple | ||||
|         ref="upload" | ||||
|         :class="{validError:!validateState}" | ||||
|         :http-request="submitUpload" | ||||
|         :on-remove="handleRemove" | ||||
|         :on-change="handleChange" | ||||
|         :before-upload="onBeforeUpload" | ||||
|         :file-list="fileList" | ||||
|         :limit="limit" | ||||
|         :disabled="disabled" | ||||
|         :list-type="isImg ? 'picture-card' : 'text'" | ||||
|         :accept="accept" | ||||
|         :show-file-list="!isSingle" | ||||
|         :on-preview="handlePictureCardPreview" | ||||
|         :auto-upload="isAutoUpload" | ||||
|         :on-exceed="handleExceed"> | ||||
|       <template v-if="!disabled"> | ||||
|         <template v-if="hasUploaded&&isSingle"> | ||||
|           <div class="fileItem"> | ||||
|             <div class="uploadFile" @click.stop> | ||||
|               <ai-icon type="svg" :icon="uploadFile.icon"/> | ||||
|               <div class="info"> | ||||
|                 <span v-text="uploadFile.name"/> | ||||
|                 <span class="size" v-text="uploadFile.size"/> | ||||
|               </div> | ||||
|             </div> | ||||
|             <el-button>重新选择</el-button> | ||||
|             <el-button v-if="clearable" plain type="danger" @click.stop="handleClear">删除</el-button> | ||||
|           </div> | ||||
|         </template> | ||||
|         <template v-else-if="limit > fileList.length"> | ||||
|           <slot v-if="hasTriggerSlot" name="trigger"/> | ||||
|           <div v-else class="uploaderBox"> | ||||
|             <span class="iconfont" :class="isImg ? 'iconPhoto' : 'iconAdd'"/> | ||||
|             <p>上传{{ isImg ? '图片' : '附件' }}</p> | ||||
|           </div> | ||||
|         </template> | ||||
|         <div slot="tip" class="el-upload__tip" v-if="showTips"> | ||||
|           <p v-if="fileType === 'img' && !acceptType && !hasTipsSlot">最多上传{{ | ||||
|               limit | ||||
|             }}张图片,单个文件最大10MB,支持jpg、jpeg、png格式</p> | ||||
|           <p v-if="fileType === 'file' && !acceptType && !hasTipsSlot">最多上传{{ limit }}个附件,单个文件最大10MB</p> | ||||
|           <p v-if="fileType === 'file' && !acceptType && !hasTipsSlot"> | ||||
|             支持.zip、.rar、.doc、.docx、.xls、.xlsx、.ppt、.pptx、.pdf、.txt、.jpg、.png格式</p> | ||||
|           <p> | ||||
|             <slot name="tips" v-if="hasTipsSlot"></slot> | ||||
|           </p> | ||||
|         </div> | ||||
|       </template> | ||||
|     </el-upload> | ||||
|     <el-dialog :visible.sync="dialog" title="图片预览编辑器" :modal="false" :show-close="false" append-to-body> | ||||
|       <vue-cropper | ||||
|           ref="cropper" | ||||
|           style="height: 400px;" | ||||
|           :img="fileList.length ? fileList[0].url : ''" v-bind="crop"/> | ||||
|       <div style="text-align: center;margin-top: 10px;"> | ||||
|         <el-radio-group v-if="crop.fixed" size="small" v-model="currFixedIndex" @change="onFixedChange" | ||||
|                         style="margin-right: 8px;"> | ||||
|           <el-radio-button | ||||
|               :label="0" | ||||
|               size="small"> | ||||
|             1.6:1 | ||||
|           </el-radio-button> | ||||
|           <el-radio-button | ||||
|               :label="1" | ||||
|               size="small"> | ||||
|             4:3 | ||||
|           </el-radio-button> | ||||
|         </el-radio-group> | ||||
|         <el-button size="small" circle icon="el-icon-refresh-right" @click="$refs.cropper.rotateRight()"></el-button> | ||||
|         <el-button size="small" circle icon="el-icon-refresh-left" @click="$refs.cropper.rotateLeft()"></el-button> | ||||
|       </div> | ||||
|       <div slot="footer"> | ||||
|         <el-popconfirm title="是否关闭图片预览器,并上传图片?" @confirm="previewCrop"> | ||||
|           <el-button slot="reference" type="primary">保存</el-button> | ||||
|         </el-popconfirm> | ||||
|         <el-button @click="onClose">关闭</el-button> | ||||
|       </div> | ||||
|     </el-dialog> | ||||
|     <div class="images" v-viewer="{movable: true}" v-show="false"> | ||||
|       <img v-for="(item, index) in imgList" :src="item" :key="index" alt=""> | ||||
|     </div> | ||||
|   </section> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {VueCropper} from 'vue-cropper' | ||||
| import 'viewerjs/dist/viewer.css' | ||||
| import Viewer from 'v-viewer' | ||||
| import Vue from "vue"; | ||||
|  | ||||
| Viewer.setDefaults({ | ||||
|   zIndex: 20170 | ||||
| }) | ||||
| Vue.use(Viewer) | ||||
| export default { | ||||
|   name: 'AiUploader', | ||||
|   components: {VueCropper}, | ||||
|   inject: { | ||||
|     elFormItem: {default: ""}, | ||||
|     elForm: {default: ''}, | ||||
|   }, | ||||
|   model: { | ||||
|     prop: 'value', | ||||
|     event: 'change' | ||||
|   }, | ||||
|   props: { | ||||
|     value: {default: () => []}, | ||||
|     url: { | ||||
|       type: String, | ||||
|       default: '/admin/file/add' | ||||
|     }, | ||||
|     isShowTip: { | ||||
|       type: Boolean, | ||||
|       default: false | ||||
|     }, | ||||
|     isWechat: { | ||||
|       type: Boolean, | ||||
|       default: false | ||||
|     }, | ||||
|     maxSize: { | ||||
|       type: Number, | ||||
|       default: 10 | ||||
|     }, | ||||
|     instance: Function, | ||||
|     acceptType: {type: String}, | ||||
|     fileType: {type: String, default: 'img'}, | ||||
|     limit: {type: Number, default: 9}, | ||||
|     disabled: {type: Boolean, default: false}, | ||||
|     isCrop: {type: Boolean, default: false}, | ||||
|     cropOps: Object, | ||||
|     isImport: { | ||||
|       type: Boolean, | ||||
|       default: false | ||||
|     }, | ||||
|     clearable: {default: true}, | ||||
|     valueIsUrl: Boolean | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       fileList: [], | ||||
|       dialog: false, | ||||
|       currFixedIndex: 0, | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     value: { | ||||
|       handler(v) { | ||||
|         this.dispatch('ElFormItem', 'el.form.change', [v]); | ||||
|         if (v?.length > 0) { | ||||
|           this.fileList = this.valueIsUrl ? v?.split(",")?.map(url => ({url})) : [...v] | ||||
|         } | ||||
|       }, | ||||
|       immediate: true, | ||||
|       deep: true | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     isImg() { | ||||
|       return this.fileType === 'img' | ||||
|     }, | ||||
|     validateState() { | ||||
|       return ['', 'success'].includes(this.elFormItem?.validateState) | ||||
|     }, | ||||
|     isAutoUpload() { | ||||
|       return !(this.isCrop || this.isImport); | ||||
|     }, | ||||
|     hasTipsSlot() { | ||||
|       return this.$slots.tips | ||||
|     }, | ||||
|     hasTriggerSlot() { | ||||
|       return this.$slots.trigger | ||||
|     }, | ||||
|     accept() { | ||||
|       if (this.acceptType) { | ||||
|         return this.acceptType | ||||
|       } | ||||
|  | ||||
|       return this.isImg ? '.jpg,.png,.jpeg' : '.zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.jpg,.png,.mp4' | ||||
|     }, | ||||
|     crop() { | ||||
|       return { | ||||
|         autoCrop: true, | ||||
|         outputType: 'png', | ||||
|         fixedBox: false, | ||||
|         fixed: true, | ||||
|         fixedNumber: [1.6, 1], | ||||
|         width: 0, | ||||
|         height: 0, | ||||
|         ...this.cropOps | ||||
|       } | ||||
|     }, | ||||
|     imgList() { | ||||
|       return this.fileList.map(v => v.url) | ||||
|     }, | ||||
|     isSingle() { | ||||
|       return this.limit == 1 && this.isImport | ||||
|     }, | ||||
|     showTips() { | ||||
|       return this.isShowTip || this.$slots.tips | ||||
|     }, | ||||
|     hasUploaded() { | ||||
|       return this.fileList?.length > 0 | ||||
|     }, | ||||
|     uploadFile() { | ||||
|       let file = this.fileList?.[0], | ||||
|           size = Number(file.size), | ||||
|           icon = "iconTxt" | ||||
|       //显示大小 | ||||
|       if (size > Math.pow(1024, 2)) { | ||||
|         size = (size / Math.pow(1024, 2)).toFixed(1) + 'MB' | ||||
|       } else { | ||||
|         size = (size / 1024).toFixed(1) + 'KB' | ||||
|       } | ||||
|       //显示图标 | ||||
|       if (/\.(xls|xlsx)$/.test(file.name)) { | ||||
|         icon = "iconExcel" | ||||
|       } else if (/\.(zip)$/.test(file.name)) { | ||||
|         icon = "iconZip" | ||||
|       } else if (/\.(rar)$/.test(file.name)) { | ||||
|         icon = "iconRar" | ||||
|       } else if (/\.(png)$/.test(file.name)) { | ||||
|         icon = "iconPng" | ||||
|       } else if (/\.(pptx|ppt)$/.test(file.name)) { | ||||
|         icon = "iconPPT" | ||||
|       } else if (/\.(doc|docx)$/.test(file.name)) { | ||||
|         icon = "iconWord" | ||||
|       } | ||||
|  | ||||
|       return {...file, size, icon} | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     onFixedChange(e) { | ||||
|       this.fixedNumber = e === 0 ? [1.6, 1] : [4, 3] | ||||
|  | ||||
|       this.$nextTick(() => { | ||||
|         this.$refs.cropper.goAutoCrop() | ||||
|       }) | ||||
|     }, | ||||
|     handleChange(file, fileList) { | ||||
|       if (this.isImport) { | ||||
|         if (!this.onOverSize(file)) { | ||||
|           this.fileList = [] | ||||
|           return false | ||||
|         } | ||||
|         this.fileList = fileList | ||||
|         this.emitChange(fileList) | ||||
|         return false | ||||
|       } | ||||
|       if (this.isCrop) { | ||||
|         if (file.raw.type === 'image/gif') { | ||||
|           this.$message.error(`不支持gif格式的图片`) | ||||
|           this.fileList = [] | ||||
|  | ||||
|           return false | ||||
|         } | ||||
|  | ||||
|         if (!this.onOverSize(file)) { | ||||
|           this.fileList = [] | ||||
|  | ||||
|           return false | ||||
|         } | ||||
|         this.dialog = true | ||||
|         this.fileList = fileList | ||||
|       } else { | ||||
|         this.fileList = fileList | ||||
|       } | ||||
|     }, | ||||
|     handleExceed(files) { | ||||
|       if (this.isSingle && files[0]) { | ||||
|         this.$refs.upload?.clearFiles() | ||||
|         this.$refs.upload?.handleStart(files[0]) | ||||
|       } else this.$message.warning(`最多上传${this.limit}个${this.isImg ? '图片' : '文件'}`) | ||||
|     }, | ||||
|     handlePictureCardPreview(file) { | ||||
|       if (this.fileType !== 'img') return | ||||
|  | ||||
|       const index = this.imgList.indexOf(file.url) | ||||
|       const viewer = this.$el.querySelector('.images').$viewer | ||||
|       viewer.view(index) | ||||
|     }, | ||||
|  | ||||
|     handleRemove(file, fileList) { | ||||
|       this.fileList = fileList | ||||
|       this.emitChange(fileList) | ||||
|     }, | ||||
|     emitChange(files) { | ||||
|       this.$emit('change', this.valueIsUrl ? files?.map(e => e.url)?.toString() : files) | ||||
|     }, | ||||
|     handleClear() { | ||||
|       this.fileList = [] | ||||
|     }, | ||||
|     getExtension(name) { | ||||
|       return name.substring(name.lastIndexOf('.')) | ||||
|     }, | ||||
|  | ||||
|     onOverSize(e) { | ||||
|       const isLt10M = e.size / 1024 / 1024 < this.maxSize | ||||
|       const suffixName = this.getExtension(e.name) | ||||
|       const suffixNameList = this.accept.split(',') | ||||
|  | ||||
|       if (suffixNameList.indexOf(`${suffixName.toLowerCase()}`) === -1) { | ||||
|         this.$message.error(`不支持该格式`) | ||||
|         return false | ||||
|       } | ||||
|  | ||||
|       if (!isLt10M) { | ||||
|         this.$message.error(`${this.isImg ? '图片' : '文件'}大小不超过${this.maxSize}MB!`) | ||||
|         return false | ||||
|       } | ||||
|  | ||||
|       return true | ||||
|     }, | ||||
|  | ||||
|     onBeforeUpload(event) { | ||||
|       return this.onOverSize(event) | ||||
|     }, | ||||
|  | ||||
|     onClose() { | ||||
|       this.fileList = [] | ||||
|       this.dialog = false | ||||
|     }, | ||||
|  | ||||
|     submitUpload(file) { | ||||
|       let formData = new FormData() | ||||
|       formData.append('file', file.file) | ||||
|       this.instance.post(this.url, formData, { | ||||
|         withCredentials: false | ||||
|       }).then(res => { | ||||
|         if (res?.code == 0) { | ||||
|           if (this.isWechat) { | ||||
|             this.emitChange([{ | ||||
|               ...res.data.file, | ||||
|               media: res.data.media | ||||
|             }]) | ||||
|             this.fileList.forEach(item => { | ||||
|               if (item.uid === file.file.uid) { | ||||
|                 item.id = res.data.file.id | ||||
|                 item.path = res.data.file.url | ||||
|                 item.url = res.data.file.url, | ||||
|                     item.media = res.data.media | ||||
|               } | ||||
|             }) | ||||
|             this.emitChange(this.fileList) | ||||
|             this.$message.success('上传成功') | ||||
|             return false | ||||
|           } | ||||
|           let data = res.data[0].split(';') | ||||
|           this.fileList.forEach(item => { | ||||
|             if (item.uid === file.file.uid) { | ||||
|               item.id = data[1] | ||||
|               item.path = data[0] | ||||
|               item.url = data[0] | ||||
|             } | ||||
|           }) | ||||
|           this.emitChange(this.fileList) | ||||
|           this.$message.success('上传成功') | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     previewCrop() { | ||||
|       this.$refs.cropper.getCropBlob(data => { | ||||
|         data.name = this.fileList[0].name; | ||||
|         this.fileList[0].file = new window.File([data], data.name, {type: data.type}) | ||||
|         this.fileList[0].file.uid = this.fileList[0].uid | ||||
|         this.submitUpload(this.fileList[0]) | ||||
|         this.dialog = false | ||||
|       }) | ||||
|     }, | ||||
|     /** | ||||
|      * 表单验证 | ||||
|      * @param componentName | ||||
|      * @param eventName | ||||
|      * @param params | ||||
|      */ | ||||
|     dispatch(componentName, eventName, params) { | ||||
|       let parent = this.$parent || this.$root; | ||||
|       let name = parent.$options.componentName; | ||||
|  | ||||
|       while (parent && (!name || name !== componentName)) { | ||||
|         parent = parent.$parent; | ||||
|  | ||||
|         if (parent) { | ||||
|           name = parent.$options.componentName; | ||||
|         } | ||||
|       } | ||||
|       if (parent) { | ||||
|         parent.$emit.apply(parent, [eventName].concat(params)); | ||||
|       } | ||||
|     }, | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .uploader { | ||||
|   line-height: 1; | ||||
|  | ||||
|   ::v-deep.el-upload { | ||||
|     width: 100%; | ||||
|     text-align: start; | ||||
|   } | ||||
|  | ||||
|   ::v-deep.validError { | ||||
|     .el-button { | ||||
|       border-color: #f46; | ||||
|       color: #f46; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   ::v-deep.el-upload--picture-card { | ||||
|     border: none; | ||||
|   } | ||||
|  | ||||
|   ::v-deep .el-list-leave-active, ::v-deep .el-upload-list__item { | ||||
|     transition: all 0s !important; | ||||
|   } | ||||
|  | ||||
|   ::v-deep.el-upload-list--picture-card .el-upload-list__item { | ||||
|     width: 120px; | ||||
|     height: 120px; | ||||
|     border-radius: 4px; | ||||
|   } | ||||
|  | ||||
|   ::v-deep.el-upload--picture-card { | ||||
|     width: auto; | ||||
|     height: auto; | ||||
|   } | ||||
|  | ||||
|   .el-upload__tip p { | ||||
|     color: #999; | ||||
|     line-height: 16px; | ||||
|   } | ||||
|  | ||||
|   ::v-deep.fileItem { | ||||
|     width: 100%; | ||||
|     height: 60px; | ||||
|     background: #FFFFFF; | ||||
|     border-radius: 2px; | ||||
|     border: 1px solid #D0D4DC; | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|     padding: 0 16px; | ||||
|     cursor: default; | ||||
|  | ||||
|     .uploadFile { | ||||
|       text-align: start; | ||||
|       flex: 1; | ||||
|       min-width: 0; | ||||
|       display: flex; | ||||
|       color: #222; | ||||
|  | ||||
|       .AiIcon { | ||||
|         width: 40px; | ||||
|         height: 40px; | ||||
|       } | ||||
|  | ||||
|       .info { | ||||
|         flex: 1; | ||||
|         min-width: 0; | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         margin-left: 8px; | ||||
|         margin-right: 40px; | ||||
|         line-height: 22px; | ||||
|       } | ||||
|  | ||||
|       .size { | ||||
|         color: #888; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .uploaderBox { | ||||
|     width: 120px; | ||||
|     height: 120px; | ||||
|     line-height: 1; | ||||
|     background: #F3F4F7; | ||||
|     border-radius: 2px; | ||||
|     border: 1px solid #D0D4DC; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
|  | ||||
|     &:hover { | ||||
|       opacity: 0.6; | ||||
|     } | ||||
|  | ||||
|     span { | ||||
|       font-size: 32px; | ||||
|       color: #8899bb; | ||||
|  | ||||
|       &:hover { | ||||
|         color: #8899bb; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     p { | ||||
|       margin: 0; | ||||
|       padding-top: 4px; | ||||
|       color: #555; | ||||
|       font-size: 12px; | ||||
|       text-align: center; | ||||
|       line-height: 1; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user