Files
dvcp_v2_webapp/ui/packages/basic/AiUploader.vue
2022-12-01 09:35:20 +08:00

515 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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支持jpgjpegpng格式</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;
:deep(.el-upload ){
width: 100%;
text-align: start;
}
:deep(.validError ){
.el-button {
border-color: #f46;
color: #f46;
}
}
:deep(.el-upload--picture-card ){
border: none;
}
:deep( .el-list-leave-active), :deep( .el-upload-list__item ){
transition: all 0s !important;
}
:deep(.el-upload-list--picture-card .el-upload-list__item ){
width: 120px;
height: 120px;
border-radius: 4px;
}
:deep(.el-upload--picture-card ){
width: auto;
height: auto;
}
.el-upload__tip p {
color: #999;
line-height: 16px;
}
: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>