123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- <template>
- <div
- class="czr-form-column-upload-com"
- :class="{
- 'czr-form-column-upload-com_view': isValue($attrs.disabled)
- ? $attrs.disabled
- : isLimitCpt || isViewCpt,
- 'czr-form-column-upload-com_list': uploadLayout === 'list',
- 'czr-form-column-upload-com_card': uploadLayout === 'card',
- 'czr-form-column-upload-com_limit-no-upload':
- isLimitCpt && limitNoUpload !== false,
- }"
- v-loading="state.loading"
- :element-loading-background="state.elementLoadingBackground"
- >
- <el-upload
- class="el-upload-com"
- ref="ref_upload"
- v-bind="$attrs"
- :file-list="state.paramVal"
- action="#"
- :list-type="uploadLayout === 'card' ? 'picture-card' : 'text'"
- :http-request="handleRequest"
- :before-upload="handleBeforeUpload"
- :accept="acceptTypeCpt"
- :limit="limit > 0 ? limit : null"
- :on-remove="handleRemove"
- :disabled="
- isValue($attrs.disabled) ? $attrs.disabled : isLimitCpt || isViewCpt
- "
- >
- <el-tooltip
- :content="acceptTooltipCpt"
- placement="top"
- popper-class="upload-tooltip-popper"
- >
- <template v-if="uploadLayout === 'card'">
- <div
- class="upload-layout-card __hover"
- :class="{
- 'limit-disabled':
- isLimitCpt || (isViewCpt && state.paramVal.length === 0),
- }"
- >
- <SvgIcon name="add" color="#0062E9" size="24" />
- <template v-if="title">
- <div>{{ title }}</div>
- </template>
- <template v-else>
- <div>选择{{ acceptTypeFormatCpt }}</div>
- <div v-if="limit > 0">(最多{{ limit }}个)</div>
- </template>
- </div>
- </template>
- <template v-else>
- <div
- class="upload-layout-list_button"
- :class="{
- 'limit-disabled':
- isLimitCpt || (isViewCpt && state.paramVal.length === 0),
- }"
- >
- <SvgIcon name="add" color="#ffffff" size="14" />
- <template v-if="title">
- {{ title }}
- </template>
- <template v-else>
- 选择{{ acceptTypeFormatCpt }}
- <template v-if="limit > 0">(最多{{ limit }}个)</template>
- </template>
- </div>
- </template>
- </el-tooltip>
- <template #file="{ file }">
- <template v-if="uploadLayout === 'card'">
- <el-tooltip
- placement="top"
- :content="file[nameKey] ?? file[urlKey]"
- :disabled="true"
- >
- <div
- class="upload-layout-card_item"
- :class="{ 'item-view': isViewCpt || !delRule(file) }"
- @mouseenter="file.hover = true"
- @mouseleave="file.hover = false"
- >
- <template v-if="validImgByUrl(file[urlKey])">
- <img
- class="img __hover"
- :src="file[urlKey]"
- @click="viewImg(file[urlKey])"
- />
- </template>
- <template v-else-if="validVideoByUrl(file[urlKey])">
- <video class="video __hover" controls :src="file[urlKey]" />
- </template>
- <template v-else>
- <img
- class="file __hover"
- :src="getFileImgByUrl(file[urlKey])"
- @click="downloadFileByUrl(file[urlKey], file[nameKey])"
- />
- </template>
- <img
- class="del __hover"
- src="../imgs/file-del.png"
- v-if="file.hover && !isViewCpt && delRule(file)"
- @click.stop="ref_upload.handleRemove(file)"
- />
- </div>
- </el-tooltip>
- </template>
- <template v-else>
- <div
- class="upload-layout-list_item __hover"
- :class="{ 'item-view': isViewCpt || !delRule(file) }"
- @click="
- validImgByUrl(file[urlKey])
- ? viewImg(file[urlKey])
- : downloadFileByUrl(file[urlKey], file[nameKey])
- "
- >
- <img class="file-type-img" :src="getFileImgByUrl(file[urlKey])" />
- <CzrEllipsis
- v-if="file[nameKey] ?? file[urlKey]"
- class="label"
- :value="file[nameKey] ?? file[urlKey]"
- />
- <SvgIcon
- v-if="!isViewCpt && delRule(file)"
- class="close"
- name="close_3"
- size="12"
- @click.stop="ref_upload.handleRemove(file)"
- />
- </div>
- </template>
- </template>
- </el-upload>
- <el-image-viewer
- v-if="state.currentImg.show"
- :zoom-rate="2"
- :max-scale="10"
- :min-scale="0.2"
- :hide-on-click-modal="true"
- :url-list="[state.currentImg.url]"
- :initial-index="0"
- @close="imageClose"
- :teleported="true"
- :crossorigin="null"
- />
- </div>
- </template>
- <script setup lang="ts">
- import {
- defineComponent,
- computed,
- onMounted,
- ref,
- reactive,
- watch,
- getCurrentInstance,
- ComponentInternalInstance,
- toRefs,
- nextTick,
- onBeforeMount,
- inject,
- } from 'vue'
- import { useRouter, useRoute } from 'vue-router'
- import { ElMessage } from 'element-plus'
- import FileDefaultImg from '../imgs/file-type/file-type_default.png'
- import FilePDFImg from '../imgs/file-type/file-type_pdf.png'
- import FilePPTImg from '../imgs/file-type/file-type_ppt.png'
- import FileRARImg from '../imgs/file-type/file-type_rar.png'
- import FileTXTImg from '../imgs/file-type/file-type_txt.png'
- import FileWORDImg from '../imgs/file-type/file-type_word.png'
- import FileEXCELImg from '../imgs/file-type/file-type_excel.png'
- import { isValue } from '@/utils/czr-util'
- import { fileUploadFile } from '@/api/modules/global/upload'
- // import {frontUploadFile} from "@/api/modules/manage/global";
- const emit = defineEmits(['emitParam'])
- const props = defineProps({
- param: {},
- label: {},
- limit: {
- default: 0,
- },
- type: {
- default: 'all',
- validator(val: string) {
- return ['all', 'img', 'file'].includes(val)
- },
- },
- uploadLayout: {
- default: 'list',
- validator(val: string) {
- return ['list', 'card'].includes(val)
- },
- },
- acceptType: { default: '', type: String },
- acceptMax: { default: 0, type: Number },
- acceptFunc: { default: () => (options) => {} },
- view: { default: null },
- delRule: { default: () => () => false },
- cardWidth: { default: '8rem' },
- cardHeight: { default: '8rem' },
- urlKey: { default: 'url' },
- nameKey: { default: 'name' },
- limitNoUpload: { default: false },
- title: {},
- })
- const state = reactive({
- paramVal: <any>props.param,
- loading: false,
- elementLoadingBackground: inject('element-loading-background', null),
- currentImg: {
- show: false,
- url: '',
- },
- formView: inject('form-view', false),
- config: {
- fileMax: 50,
- fileType: '.doc,.docx,.ppt,.pptx,.pdf,.xls,.xlsx,.txt,.rar,.zip',
- imgMax: 50,
- imgType: '.jpg,.jpeg,.png,.gif,.svg,.bmp,.webp',
- },
- })
- watch(
- () => state.paramVal,
- (n) => {
- emit('emitParam', n)
- },
- )
- watch(
- () => props.param,
- (n) => {
- state.paramVal = n
- },
- )
- const isViewCpt = computed(() => {
- return isValue(props.view) ? props.view : state.formView
- })
- const ref_upload = ref()
- const validImgByUrl = (url) => {
- if (url) {
- const imgList = [
- 'bmp',
- 'jpg',
- 'jpeg',
- 'png',
- 'tif',
- 'gif',
- 'pcx',
- 'tga',
- 'exif',
- 'fpx',
- 'svg',
- 'psd',
- 'cdr',
- 'pcd',
- 'dxf',
- 'ufo',
- 'eps',
- 'ai',
- 'raw',
- 'WMF',
- 'webp',
- 'avif',
- 'apng',
- 'jfif',
- ]
- //进行图片匹配
- let result =
- [
- ...imgList.map((v) => v.toLowerCase()),
- ...imgList.map((v) => v.toUpperCase()),
- ].some((t) => url.includes('.' + t)) ||
- url.includes('/ngx/proxy') ||
- url.includes('/pic?')
- return result
- }
- return false
- }
- const validVideoByUrl = (url) => {
- if (url) {
- const videoList = ['mp4']
- //进行图片匹配
- let result = [
- ...videoList.map((v) => v.toLowerCase()),
- ...videoList.map((v) => v.toUpperCase()),
- ].some((t) => url.includes('.' + t))
- return result
- }
- return false
- }
- const acceptTypeCpt = computed(() => {
- let str = ''
- if (isValue(props.acceptType)) {
- str += props.acceptType
- } else {
- if (props.type === 'all') {
- str += state.config.imgType
- str += ',' + state.config.fileType
- } else if (props.type === 'img') {
- str += state.config.imgType
- } else if (props.type === 'file') {
- str += state.config.fileType
- }
- }
- return str
- })
- const acceptTypeFormatCpt = computed(() => {
- let str = ''
- if (isValue(props.acceptType)) {
- str += `自定义文件`
- } else {
- if (props.type === 'all') {
- str += `图片、文件`
- } else if (props.type === 'img') {
- str += `图片`
- } else if (props.type === 'file') {
- str += `文件`
- }
- }
- return str
- })
- const acceptTooltipCpt = computed(() => {
- let str = ''
- if (isValue(props.acceptType)) {
- str += `只支持大小在0~${props.acceptMax}M内,格式为${props.acceptType}的自定义文件`
- } else {
- if (props.type === 'all') {
- str += `只支持大小在0~${state.config.imgMax}M内,格式为${state.config.imgType}的图片,`
- str += `或大小在0~${state.config.fileMax}M内,格式为${state.config.fileType}的文件`
- } else if (props.type === 'img') {
- str += `只支持大小在0~${state.config.imgMax}M内,格式为${state.config.imgType}的图片`
- } else if (props.type === 'file') {
- str += `只支持大小在0~${state.config.fileMax}M内,格式为${state.config.fileType}的文件`
- }
- }
- if (isLimitCpt.value) {
- str = `最多上传${props.limit}个${acceptTypeFormatCpt.value}`
- }
- return str
- })
- const isLimitCpt = computed(() => {
- return props.limit > 0 && props.limit <= state.paramVal.length
- })
- const handleBeforeUpload = (file) => {
- if (isValue(props.acceptType)) {
- if (file.size / (1024 * 1024) > props.acceptMax) {
- ElMessage.warning(`自定义文件大小不可超过${props.acceptMax}M`)
- return false
- } else if (
- !props.acceptType.split(',').some((v) => file.name.includes(v))
- ) {
- ElMessage.warning(`自定义文件类型仅支持${props.acceptType}`)
- return false
- }
- } else {
- if (props.type === 'img') {
- if (file.size / (1024 * 1024) > state.config.imgMax) {
- ElMessage.warning(`图片大小不可超过${state.config.imgMax}M`)
- return false
- } else if (
- !state.config.imgType.split(',').some((v) => file.name.includes(v))
- ) {
- ElMessage.warning(`图片类型仅支持${state.config.imgType}`)
- return false
- }
- } else if (props.type === 'file') {
- if (file.size / (1024 * 1024) > state.config.fileMax) {
- ElMessage.warning(`文件大小不可超过${state.config.fileMax}M`)
- return false
- } else if (
- !state.config.fileType.split(',').some((v) => file.name.includes(v))
- ) {
- ElMessage.warning(`文件类型仅支持${state.config.fileType}`)
- return false
- }
- } else if (props.type === 'all') {
- if (!acceptTypeCpt.value.split(',').some((v) => file.name.includes(v))) {
- ElMessage.warning(`文件或图片类型仅支持${acceptTypeCpt.value}`)
- return false
- } else {
- if (
- state.config.imgType.split(',').some((v) => file.name.includes(v))
- ) {
- if (file.size / (1024 * 1024) > state.config.imgMax) {
- ElMessage.warning(`图片大小不可超过${state.config.imgMax}M`)
- return false
- }
- } else if (
- state.config.fileType.split(',').some((v) => file.name.includes(v))
- ) {
- if (file.size / (1024 * 1024) > state.config.fileMax) {
- ElMessage.warning(`文件大小不可超过${state.config.fileMax}M`)
- return false
- }
- }
- }
- }
- }
- return file
- }
- const handleRequest = async (options) => {
- state.loading = true
- if (isValue(props.acceptType)) {
- const result: any = await props.acceptFunc(options)
- if (result?.[props.urlKey] && result?.[props.nameKey]) {
- state.paramVal = [...state.paramVal, result]
- } else {
- state.paramVal = [...state.paramVal]
- }
- state.loading = false
- } else if (
- [
- ...state.config.imgType.split(','),
- ...state.config.fileType.split(','),
- ].some((v) => options.file.name.includes(v))
- ) {
- const formData = new FormData()
- formData.append('file', options.file)
- fileUploadFile(formData)
- .then(({ data }: any) => {
- state.paramVal = [
- ...state.paramVal,
- {
- [props.urlKey]: data.path,
- [props.nameKey]: data.filename.replace(/,/g, '_'),
- },
- ]
- })
- .catch(() => {})
- .finally(() => {
- state.loading = false
- })
- }
- }
- const fileTypeMapper = new Map([
- ['doc', FileWORDImg],
- ['docx', FileWORDImg],
- ['ppt', FilePPTImg],
- ['pptx', FilePPTImg],
- ['pdf', FilePDFImg],
- ['xls', FileEXCELImg],
- ['xlsx', FileEXCELImg],
- ['txt', FileTXTImg],
- ['rar', FileRARImg],
- ['zip', FileRARImg],
- ])
- const getFileImgByUrl = (url) => {
- if (url) {
- const regex = /\.([^.]*)$/
- const match = url.match(regex)
- const t = match ? match[1] : ''
- if (t && fileTypeMapper.has(t)) {
- return fileTypeMapper.get(t)
- }
- }
- return FileDefaultImg
- }
- const handleRemove = (file, files) => {
- state.paramVal = files
- }
- const downloadFileByUrl = (url, name) => {
- ElMessage.info('开始下载!')
- const xhr = new XMLHttpRequest()
- xhr.open('GET', url, true)
- xhr.responseType = 'blob'
- xhr.onload = function () {
- if (xhr.status === 200) {
- const blob = xhr.response
- const url = window.URL.createObjectURL(blob)
- const a = document.createElement('a')
- a.href = url
- a.download = name || 'default'
- a.click()
- ElMessage.success('下载成功!')
- window.URL.revokeObjectURL(url)
- }
- }
- xhr.send()
- }
- const viewImg = (url) => {
- state.currentImg.url = url
- state.currentImg.show = true
- }
- const imageClose = () => {
- state.currentImg.show = false
- state.currentImg.url = ''
- }
- </script>
- <style scoped lang="scss">
- .czr-form-column-upload-com {
- $uploadWidth: v-bind(cardWidth);
- $uploadHeight: v-bind(cardHeight);
- width: 100%;
- position: relative;
- .el-upload-com {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- :deep(.el-upload) {
- .upload-layout-list_button {
- height: 32px;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0 12px;
- background-color: var(--czr-main-color);
- border-radius: 4px;
- font-size: 14px;
- font-family: Microsoft YaHei;
- font-weight: 400;
- color: #ffffff;
- .svg-icon {
- margin-right: 7px;
- }
- &.limit-disabled {
- cursor: no-drop;
- opacity: 0.5;
- }
- }
- .upload-layout-card {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- line-height: 1;
- align-items: center;
- justify-content: center;
- background-color: rgba(46, 129, 255, 0.04);
- .svg-icon {
- margin-bottom: 10px;
- opacity: 0.5;
- }
- > div {
- font-size: 14px;
- font-family:
- PingFang SC-Regular,
- PingFang SC;
- font-weight: 400;
- color: rgba(0, 98, 233, 0.5);
- }
- &.limit-disabled {
- cursor: no-drop;
- opacity: 0.5;
- }
- }
- }
- :deep(.el-upload-list) {
- margin: 0;
- .el-upload-list__item {
- transition: none;
- .upload-layout-list_item {
- display: flex;
- align-items: center;
- padding: 0 6px;
- .file-type-img {
- width: 14px;
- margin: 0 6px 0 6px;
- }
- .label {
- width: calc(100% - 6px - 6px - 14px - 30px);
- }
- .close {
- margin-left: auto;
- }
- &.item-view {
- .label {
- width: calc(100% - 6px - 6px - 14px);
- }
- }
- }
- .upload-layout-card_item {
- width: 100%;
- height: 100%;
- position: relative;
- .img {
- width: 100%;
- height: 100%;
- }
- .video {
- width: 100%;
- height: 100%;
- object-fit: fill;
- }
- .file {
- width: 100%;
- height: 100%;
- }
- .del {
- position: absolute;
- top: 0;
- right: 0;
- z-index: 2;
- width: 20px;
- height: 20px;
- }
- }
- }
- }
- }
- &.czr-form-column-upload-com_view,
- &.czr-form-column-upload-com_limit-no-upload {
- .el-upload-com {
- :deep(.el-upload) {
- display: none;
- }
- }
- }
- &.czr-form-column-upload-com_card {
- .el-upload-com {
- :deep(.el-upload) {
- width: $uploadWidth;
- height: $uploadHeight;
- }
- :deep(.el-upload-list) {
- .el-upload-list__item {
- width: $uploadWidth;
- height: $uploadHeight;
- }
- }
- }
- }
- }
- </style>
|