|
@@ -0,0 +1,325 @@
|
|
|
|
+<template>
|
|
|
|
+ <div v-loading="isLoading" class="component-upload-image">
|
|
|
|
+ <el-upload
|
|
|
|
+ multiple
|
|
|
|
+ :action="uploadUrl"
|
|
|
|
+ :http-request="httpRequest"
|
|
|
|
+ :list-type="listType"
|
|
|
|
+ :disabled="disabledUpload"
|
|
|
|
+ :before-upload="handleBeforeUpload"
|
|
|
|
+ :limit="limit"
|
|
|
|
+ :on-exceed="handleExceed"
|
|
|
|
+ name="file"
|
|
|
|
+ :on-remove="handleRemove"
|
|
|
|
+ :show-file-list="true"
|
|
|
|
+ :headers="headers"
|
|
|
|
+ :file-list="fileList"
|
|
|
|
+ :on-success="handleUploadSuccess"
|
|
|
|
+ :auto-upload="true"
|
|
|
|
+ :accept="accept.join(',')"
|
|
|
|
+ >
|
|
|
|
+ <el-button v-show="disabledUpload === false" plain icon="Upload"></el-button>
|
|
|
|
+ <template v-if="disabledUpload === false" #tip>
|
|
|
|
+ <!-- 上传提示 -->
|
|
|
|
+ <el-tooltip v-if="showTip" placement="bottom">
|
|
|
|
+ <div class="el-upload__tip">
|
|
|
|
+ <el-icon><QuestionFilled /></el-icon>
|
|
|
|
+ </div>
|
|
|
|
+ <template #content>
|
|
|
|
+ 请上传
|
|
|
|
+ <template v-if="fileSize">
|
|
|
|
+ 大小不超过
|
|
|
|
+ <b style="color: #f56c6c">{{ fileSize }}MB</b>
|
|
|
|
+ </template>
|
|
|
|
+ <template v-if="accept">
|
|
|
|
+ 格式为
|
|
|
|
+ <b style="color: #f56c6c">{{ accept.join('/') }}</b>
|
|
|
|
+ </template>
|
|
|
|
+ 的文件
|
|
|
|
+ </template>
|
|
|
|
+ </el-tooltip>
|
|
|
|
+ </template>
|
|
|
|
+
|
|
|
|
+ <template #file="{ file }">
|
|
|
|
+ <div>
|
|
|
|
+ <el-link type="success" @click="previewFile(file)">{{ file.name }}</el-link>
|
|
|
|
+ <el-button v-if="disabledUpload === false" text icon="Delete" @click="handleRemove(file)"></el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ </el-upload>
|
|
|
|
+
|
|
|
|
+ <el-dialog v-model="dialogVisible" :title="'预览 (' + dialogTitle + ')'" width="800px" append-to-body destroy-on-close>
|
|
|
|
+ <iframe :src="dialogUrl" frameborder="0" width="100%" height="500" allowfullscreen></iframe>
|
|
|
|
+ </el-dialog>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup>
|
|
|
|
+import files from '@/api/common/files'
|
|
|
|
+// import { getToken } from '@/utils/auth' // 需要获取到token
|
|
|
|
+import { debounce } from 'lodash-es'
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
+import request from '@/utils/request'
|
|
|
|
+import { computed, ref, watch } from 'vue'
|
|
|
|
+import { QuestionFilled } from '@element-plus/icons-vue'
|
|
|
|
+
|
|
|
|
+const props = defineProps({
|
|
|
|
+ modelValue: {
|
|
|
|
+ type: [String, Object, Array],
|
|
|
|
+ default: undefined
|
|
|
|
+ },
|
|
|
|
+ // 数量限制
|
|
|
|
+ limit: {
|
|
|
|
+ type: Number,
|
|
|
|
+ default: 5
|
|
|
|
+ },
|
|
|
|
+ // 文件所属模块
|
|
|
|
+ source: {
|
|
|
|
+ type: String,
|
|
|
|
+ default: ''
|
|
|
|
+ },
|
|
|
|
+ // 大小限制(MB)
|
|
|
|
+ fileSize: {
|
|
|
|
+ type: Number,
|
|
|
|
+ default: 5
|
|
|
|
+ },
|
|
|
|
+ // 是否显示提示
|
|
|
|
+ isShowTip: {
|
|
|
|
+ type: Boolean,
|
|
|
|
+ default: true
|
|
|
|
+ },
|
|
|
|
+ // 文件列表类型
|
|
|
|
+ listType: {
|
|
|
|
+ type: String,
|
|
|
|
+ default: 'picture'
|
|
|
|
+ },
|
|
|
|
+ accept: {
|
|
|
|
+ type: Array,
|
|
|
|
+ default: () => {
|
|
|
|
+ return ['.docx', '.pptx', '.xlsx', '.zip', '.csv', '.pdf']
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ preview: {
|
|
|
|
+ type: Boolean,
|
|
|
|
+ default: true
|
|
|
|
+ },
|
|
|
|
+ disabledUpload: {
|
|
|
|
+ type: Boolean,
|
|
|
|
+ default: false
|
|
|
|
+ },
|
|
|
|
+ return: {
|
|
|
|
+ type: String,
|
|
|
|
+ default: 'array',
|
|
|
|
+ validator(value) {
|
|
|
|
+ // 这个值必须与下列字符串中的其中一个相匹配
|
|
|
|
+ return ['array', 'string'].includes(value)
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ loading: {
|
|
|
|
+ type: Boolean,
|
|
|
|
+ default: false
|
|
|
|
+ }
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const isLoading = computed({
|
|
|
|
+ get() {
|
|
|
|
+ return props.loading
|
|
|
|
+ },
|
|
|
|
+ set(val) {
|
|
|
|
+ console.log('set loading', val)
|
|
|
|
+ emit('update:loading', val)
|
|
|
|
+ }
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const emit = defineEmits(['success', 'update:modelValue', 'update:loading'])
|
|
|
|
+const number = ref(0)
|
|
|
|
+const dialogUrl = ref('')
|
|
|
|
+const dialogTitle = ref('')
|
|
|
|
+const dialogVisible = ref(false)
|
|
|
|
+// const baseUrl = import.meta.env.VITE_APP_BASE_INTERFACE
|
|
|
|
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_INTERFACE + '/file/upload') // 上传的服务器地址
|
|
|
|
+// const headers = ref({ Authorization: 'Bearer ' + getToken() })
|
|
|
|
+const headers = ref({ Authorization: 'Bearer =====' })
|
|
|
|
+const fileList = ref([]) // 已上传的
|
|
|
|
+const showTip = computed(() => props.isShowTip && (props.accept || props.fileSize))
|
|
|
|
+let multipleCount = 0 //并发上传的数量
|
|
|
|
+
|
|
|
|
+watch(
|
|
|
|
+ () => props.modelValue,
|
|
|
|
+ async val => {
|
|
|
|
+ if (val) {
|
|
|
|
+ // 首先将值转为数组
|
|
|
|
+ const fileIdList = Array.isArray(val) ? val : props.modelValue.split(',')
|
|
|
|
+ if (fileIdList.length > 0) {
|
|
|
|
+ await files.batchGetUrlApi({ fileIdList }).then(res => {
|
|
|
|
+ fileList.value = res.data
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ fileList.value = []
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ { deep: true, immediate: true }
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+function filterResponse(list) {
|
|
|
|
+ return list.map(item => {
|
|
|
|
+ return item.response ? item.response.data : item
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function getFileIds(list) {
|
|
|
|
+ const ids = list.map(item => item.id)
|
|
|
|
+ if (props.return === 'string') {
|
|
|
|
+ return ids.join(',')
|
|
|
|
+ } else {
|
|
|
|
+ return ids
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const updateModelValue = debounce(() => {
|
|
|
|
+ emit('update:modelValue', getFileIds(filterResponse(fileList.value)))
|
|
|
|
+ emit('success', getFileIds(filterResponse(fileList.value)))
|
|
|
|
+ isLoading.value = false
|
|
|
|
+}, 300)
|
|
|
|
+
|
|
|
|
+async function handleRemove(file) {
|
|
|
|
+ isLoading.value = true
|
|
|
|
+ try {
|
|
|
|
+ await files.deleteUrlApi({
|
|
|
|
+ fileIds: [file.id]
|
|
|
|
+ })
|
|
|
|
+ number.value--
|
|
|
|
+ fileList.value = fileList.value.filter(item => item.id !== file.id)
|
|
|
|
+ updateModelValue()
|
|
|
|
+ emit('success', {})
|
|
|
|
+ isLoading.value = false
|
|
|
|
+ } catch (e) {
|
|
|
|
+ isLoading.value = false
|
|
|
|
+ console.log(e);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function handleUploadSuccess(res) {
|
|
|
|
+ console.log('handleUploadSuccess', res)
|
|
|
|
+ isLoading.value = false
|
|
|
|
+ fileList.value.push(res.data)
|
|
|
|
+ number.value++
|
|
|
|
+ multipleCount--
|
|
|
|
+ if (multipleCount === 0) {
|
|
|
|
+ updateModelValue()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function handleBeforeUpload(file) {
|
|
|
|
+ console.log('handleBeforeUpload', file)
|
|
|
|
+ isLoading.value = true
|
|
|
|
+ let isImg = false
|
|
|
|
+ if (props.accept.length) {
|
|
|
|
+ let fileExtension = ''
|
|
|
|
+ if (file.name.toLowerCase().lastIndexOf('.') > -1) {
|
|
|
|
+ fileExtension = file.name.toLowerCase().slice(file.name.toLowerCase().lastIndexOf('.'))
|
|
|
|
+ }
|
|
|
|
+ isImg = props.accept.some(type => {
|
|
|
|
+ if (file.type.indexOf(type) > -1) return true
|
|
|
|
+ if (fileExtension && fileExtension.indexOf(type) > -1) return true
|
|
|
|
+ return false
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ isImg = file.type.indexOf('image') > -1
|
|
|
|
+ }
|
|
|
|
+ if (!isImg) {
|
|
|
|
+ ElMessage.error(`文件格式不正确, 请上传${props.accept.join('/')}格式文件!`)
|
|
|
|
+ isLoading.value = false
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ if (props.fileSize) {
|
|
|
|
+ const isLt = file.size / 1024 / 1024 < props.fileSize
|
|
|
|
+ if (!isLt) {
|
|
|
|
+ ElMessage.error(`上传头文件大小不能超过 ${props.fileSize} MB!`)
|
|
|
|
+ isLoading.value = false
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ multipleCount++
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 文件个数超出
|
|
|
|
+function handleExceed() {
|
|
|
|
+ ElMessage.error(`上传文件数量不能超过 ${props.limit} 个!`)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 上传失败
|
|
|
|
+// function handleUploadError() {
|
|
|
|
+// ElMessage.error(`上传失败`)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// 预览
|
|
|
|
+function previewFile(file) {
|
|
|
|
+ if (!props.preview) return
|
|
|
|
+
|
|
|
|
+ const officeFiles = ['.docx', '.pptx', '.xlsx', '.doc', '.ppt', '.xls']
|
|
|
|
+ const pdfFiles = ['.pdf']
|
|
|
|
+
|
|
|
|
+ // 根据file.url确定是否是office文件
|
|
|
|
+ const isOffice = officeFiles.some(item => file.name.indexOf(item) > -1)
|
|
|
|
+ const isPdf = pdfFiles.some(item => file.name.indexOf(item) > -1)
|
|
|
|
+ const isBlob = file.url.startsWith('blob:')
|
|
|
|
+
|
|
|
|
+ if (isOffice) {
|
|
|
|
+ let fileUrl = file.url
|
|
|
|
+ if (isBlob) {
|
|
|
|
+ fileUrl = file.response.data.url
|
|
|
|
+ }
|
|
|
|
+ dialogUrl.value = 'https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(fileUrl)
|
|
|
|
+ } else if (isPdf) {
|
|
|
|
+ dialogUrl.value = isBlob ? file.response.data.url : file.url
|
|
|
|
+ } else {
|
|
|
|
+ // window.location.href = isBlob ? file.response.data.url : file.url
|
|
|
|
+ window.open(isBlob ? file.response.data.url : file.url)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dialogTitle.value = file.name
|
|
|
|
+ dialogVisible.value = true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function httpRequest(options) {
|
|
|
|
+ const data = new FormData()
|
|
|
|
+ const params = { file: options.file, source: props.source }
|
|
|
|
+ for (const key in params) {
|
|
|
|
+ data.append(key, params[key])
|
|
|
|
+ }
|
|
|
|
+ return request({
|
|
|
|
+ url: '/service-file/upload',
|
|
|
|
+ method: 'post',
|
|
|
|
+ data
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
+.upload-file-uploader {
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
+}
|
|
|
|
+.upload-file-list .el-upload-list__item {
|
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
|
+ line-height: 2;
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+ position: relative;
|
|
|
|
+}
|
|
|
|
+.upload-file-list .ele-upload-list__item-content {
|
|
|
|
+ display: flex;
|
|
|
|
+ justify-content: space-between;
|
|
|
|
+ align-items: center;
|
|
|
|
+ color: inherit;
|
|
|
|
+}
|
|
|
|
+.ele-upload-list__item-content-action .el-link {
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+}
|
|
|
|
+.el-upload__tip {
|
|
|
|
+ display: inline-block;
|
|
|
|
+ margin-left: 10px;
|
|
|
|
+ line-height: 1.5;
|
|
|
|
+}
|
|
|
|
+</style>
|