Files
dvcp_v2_webapp/ui/packages/basic/AiUploader.vue
aixianling 437ae1425c feat(xumu): 新增理赔申请和贷款申请功能
- 添加理赔申请和贷款申请的路由、页面组件和相关逻辑
- 实现理赔申请和贷款申请的数据获取、表单提交和审核流程
- 优化耳标选择器组件,支持在不同场景下的使用
- 调整图片上传组件,增加只读模式和预览功能
2025-01-02 15:30:44 +08:00

531 lines
14 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">
<template v-if="!readonly">
<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>
</template>
<el-image v-else v-for="(url,i) in imgList" :key="i" :src="url" :preview-src-list="[url]"/>
</section>
</template>
<script>
import {VueCropper} from 'vue-cropper'
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,
showLoading: Boolean,
readonly: 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)
let loading
if (this.showLoading) {
loading = this.$loading({lock: true, text: '上传中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)', customClass: 'uploader-loading'})
}
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
}
if (Object.prototype.toString.call(res.data) === '[object Object]') {
this.fileList.forEach(item => {
if (item.uid === file.file.uid) {
item.id = res.data.id
item.path = res.data.url
item.url = res.data.url
item.url = res.data.url
item.fileSizeStr = res.data.fileSizeStr
item.fileSize = res.data.size
item.postfix = res.data.postfix
}
})
} else {
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('上传成功')
}
}).finally(() => loading?.close())
},
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));
}
},
checkUpload() {
return this.limit > this.fileList.length
}
},
}
</script>
<style lang="scss">
.uploader-loading {
z-index: 202408221749;
}
</style>
<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>