5 changed files with 468 additions and 2 deletions
			
			
		@ -0,0 +1,370 @@
					 | 
				
			||||
<template> | 
				
			||||
  <div ref="containerRef" :class="`${prefixCls}-container`"> | 
				
			||||
    <Upload | 
				
			||||
      :headers="headers" | 
				
			||||
      :multiple="multiple" | 
				
			||||
      :action="(uploadUrl as any)" | 
				
			||||
      :fileList="fileList" | 
				
			||||
      :disabled="disabled" | 
				
			||||
      v-bind="bindProps" | 
				
			||||
      @remove="onRemove" | 
				
			||||
      @change="onFileChange" | 
				
			||||
      @preview="onFilePreview" | 
				
			||||
    > | 
				
			||||
      <template v-if="isImageMode"> | 
				
			||||
        <div v-if="!isMaxCount"> | 
				
			||||
          <Icon icon="ant-design:plus-outlined" /> | 
				
			||||
          <div class="ant-upload-text">{{ text }}</div> | 
				
			||||
        </div> | 
				
			||||
      </template> | 
				
			||||
      <a-button v-else-if="buttonVisible" :disabled="isMaxCount || disabled"> | 
				
			||||
        <Icon icon="ant-design:upload-outlined" /> | 
				
			||||
        <span>{{ text }}</span> | 
				
			||||
      </a-button> | 
				
			||||
    </Upload> | 
				
			||||
  </div> | 
				
			||||
</template> | 
				
			||||
 | 
				
			||||
<script lang="ts" setup name="FileUpload"> | 
				
			||||
import { Upload } from 'ant-design-vue' | 
				
			||||
import { ref, reactive, computed, watch, unref } from 'vue' | 
				
			||||
import { Icon } from '@/components/Icon' | 
				
			||||
import { getAccessToken, getTenantId } from '@/utils/auth' | 
				
			||||
import { propTypes } from '@/utils/propTypes' | 
				
			||||
import { useMessage } from '@/hooks/web/useMessage' | 
				
			||||
import { createImgPreview } from '@/components/Preview/index' | 
				
			||||
import { useAttrs } from '@/hooks/core/useAttrs' | 
				
			||||
import { useDesign } from '@/hooks/web/useDesign' | 
				
			||||
import { useGlobSetting } from '@/hooks/setting' | 
				
			||||
 | 
				
			||||
const { createMessage, createConfirm } = useMessage() | 
				
			||||
const { prefixCls } = useDesign('upload') | 
				
			||||
const attrs = useAttrs() | 
				
			||||
const emit = defineEmits(['change', 'update:value']) | 
				
			||||
const props = defineProps({ | 
				
			||||
  value: propTypes.oneOfType([propTypes.string, propTypes.array]), | 
				
			||||
  text: propTypes.string.def('上传'), | 
				
			||||
  fileType: propTypes.oneOf(['all', 'image', 'file']).def('all'), | 
				
			||||
  // eslint-disable-next-line vue/valid-define-props | 
				
			||||
  uploadUrl: propTypes.string.def(useGlobSetting().uploadUrl), | 
				
			||||
  /*这个属性用于控制文件上传的业务路径*/ | 
				
			||||
  bizPath: propTypes.string.def('temp'), | 
				
			||||
  /** | 
				
			||||
   * 是否返回url, | 
				
			||||
   * true:仅返回url | 
				
			||||
   * false:返回fileName filePath fileSize | 
				
			||||
   */ | 
				
			||||
  returnUrl: propTypes.bool.def(true), | 
				
			||||
  // 最大上传数量 | 
				
			||||
  maxCount: propTypes.number.def(0), | 
				
			||||
  buttonVisible: propTypes.bool.def(true), | 
				
			||||
  multiple: propTypes.bool.def(true), | 
				
			||||
  // 是否显示左右移动按钮 | 
				
			||||
  mover: propTypes.bool.def(true), | 
				
			||||
  // 是否显示下载按钮 | 
				
			||||
  download: propTypes.bool.def(true), | 
				
			||||
  // 删除时是否显示确认框 | 
				
			||||
  removeConfirm: propTypes.bool.def(false), | 
				
			||||
  beforeUpload: propTypes.func, | 
				
			||||
  disabled: propTypes.bool.def(false) | 
				
			||||
}) | 
				
			||||
 | 
				
			||||
const headers = reactive({ | 
				
			||||
  Authorization: 'Bearer ' + getAccessToken(), | 
				
			||||
  'tenant-id': getTenantId() | 
				
			||||
}) | 
				
			||||
const fileList = ref<any[]>([]) | 
				
			||||
const uploadGoOn = ref<boolean>(true) | 
				
			||||
// refs | 
				
			||||
const containerRef = ref() | 
				
			||||
// 是否达到了最大上传数量 | 
				
			||||
const isMaxCount = computed(() => props.maxCount > 0 && fileList.value.length >= props.maxCount) | 
				
			||||
// 当前是否是上传图片模式 | 
				
			||||
const isImageMode = computed(() => props.fileType === 'image') | 
				
			||||
// 合并 props 和 attrs | 
				
			||||
const bindProps = computed(() => { | 
				
			||||
  //update-begin-author:liusq date:20220411 for: [issue/455]上传组件传入accept限制上传文件类型无效 | 
				
			||||
  const bind: any = Object.assign({}, props, unref(attrs)) | 
				
			||||
  //update-end-author:liusq date:20220411 for: [issue/455]上传组件传入accept限制上传文件类型无效 | 
				
			||||
 | 
				
			||||
  bind.name = 'file' | 
				
			||||
  bind.listType = isImageMode.value ? 'picture-card' : 'text' | 
				
			||||
  bind.class = [bind.class, { 'upload-disabled': props.disabled }] | 
				
			||||
  bind.data = { biz: props.bizPath, ...bind.data } | 
				
			||||
  //update-begin-author:taoyan date:20220407 for: 自定义beforeUpload return false,并不能中断上传过程 | 
				
			||||
  if (!bind.beforeUpload) { | 
				
			||||
    bind.beforeUpload = onBeforeUpload | 
				
			||||
  } | 
				
			||||
  //update-end-author:taoyan date:20220407 for: 自定义beforeUpload return false,并不能中断上传过程 | 
				
			||||
  // 如果当前是图片上传模式,就只能上传图片 | 
				
			||||
  if (isImageMode.value && !bind.accept) { | 
				
			||||
    bind.accept = 'image/*' | 
				
			||||
  } | 
				
			||||
  return bind | 
				
			||||
}) | 
				
			||||
 | 
				
			||||
watch( | 
				
			||||
  () => props.value, | 
				
			||||
  (val) => { | 
				
			||||
    if (Array.isArray(val)) { | 
				
			||||
      if (props.returnUrl) { | 
				
			||||
        parsePathsValue(val.join(',')) | 
				
			||||
      } else { | 
				
			||||
        parseArrayValue(val) | 
				
			||||
      } | 
				
			||||
    } else { | 
				
			||||
      parsePathsValue(val) | 
				
			||||
    } | 
				
			||||
  }, | 
				
			||||
  { immediate: true } | 
				
			||||
) | 
				
			||||
 | 
				
			||||
// 解析数据库存储的逗号分割 | 
				
			||||
function parsePathsValue(paths) { | 
				
			||||
  if (!paths || paths.length == 0) { | 
				
			||||
    fileList.value = [] | 
				
			||||
    return | 
				
			||||
  } | 
				
			||||
  let list: any[] = [] | 
				
			||||
  for (const item of paths.split(',')) { | 
				
			||||
    list.push({ | 
				
			||||
      uid: uidGenerator(), | 
				
			||||
      name: getFileName(item), | 
				
			||||
      status: 'done', | 
				
			||||
      url: item, | 
				
			||||
      response: { status: 'history', message: item } | 
				
			||||
    }) | 
				
			||||
  } | 
				
			||||
  fileList.value = list | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// 解析数组值 | 
				
			||||
function parseArrayValue(array) { | 
				
			||||
  if (!array || array.length == 0) { | 
				
			||||
    fileList.value = [] | 
				
			||||
    return | 
				
			||||
  } | 
				
			||||
  let list: any[] = [] | 
				
			||||
  for (const item of array) { | 
				
			||||
    list.push({ | 
				
			||||
      uid: uidGenerator(), | 
				
			||||
      name: item.fileName, | 
				
			||||
      url: item.filePath, | 
				
			||||
      status: 'done', | 
				
			||||
      response: { status: 'history', message: item.filePath } | 
				
			||||
    }) | 
				
			||||
  } | 
				
			||||
  fileList.value = list | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// 文件上传之前的操作 | 
				
			||||
function onBeforeUpload(file) { | 
				
			||||
  uploadGoOn.value = true | 
				
			||||
  if (isImageMode.value) { | 
				
			||||
    if (file.type.indexOf('image') < 0) { | 
				
			||||
      createMessage.warning('请上传图片') | 
				
			||||
      uploadGoOn.value = false | 
				
			||||
      return false | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  // 扩展 beforeUpload 验证 | 
				
			||||
  if (typeof props.beforeUpload === 'function') { | 
				
			||||
    return props.beforeUpload(file) | 
				
			||||
  } | 
				
			||||
  return true | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// 删除处理事件 | 
				
			||||
function onRemove() { | 
				
			||||
  if (props.removeConfirm) { | 
				
			||||
    return new Promise((resolve) => { | 
				
			||||
      createConfirm({ | 
				
			||||
        title: '删除', | 
				
			||||
        content: `确定要删除这${isImageMode.value ? '张图片' : '个文件'}吗?`, | 
				
			||||
        iconType: 'warning', | 
				
			||||
        onOk: () => resolve(true), | 
				
			||||
        onCancel: () => resolve(false) | 
				
			||||
      }) | 
				
			||||
    }) | 
				
			||||
  } | 
				
			||||
  return true | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// upload组件change事件 | 
				
			||||
function onFileChange(info) { | 
				
			||||
  if (!info.file.status && uploadGoOn.value === false) { | 
				
			||||
    info.fileList.pop() | 
				
			||||
  } | 
				
			||||
  let fileListTemp = info.fileList | 
				
			||||
  // 限制最大上传数 | 
				
			||||
  if (props.maxCount > 0) { | 
				
			||||
    let count = fileListTemp.length | 
				
			||||
    if (count >= props.maxCount) { | 
				
			||||
      let diffNum = props.maxCount - fileListTemp.length | 
				
			||||
      if (diffNum >= 0) { | 
				
			||||
        fileListTemp = fileListTemp.slice(-props.maxCount) | 
				
			||||
      } else { | 
				
			||||
        return | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  if (info.file.status === 'done') { | 
				
			||||
    if (info.file.response.success) { | 
				
			||||
      fileListTemp = fileListTemp.map((file) => { | 
				
			||||
        if (file.response) { | 
				
			||||
          let reUrl = file.response.message | 
				
			||||
          file.url = reUrl | 
				
			||||
        } | 
				
			||||
        return file | 
				
			||||
      }) | 
				
			||||
    } | 
				
			||||
  } else if (info.file.status === 'error') { | 
				
			||||
    createMessage.error(`${info.file.name} 上传失败.`) | 
				
			||||
  } | 
				
			||||
  fileList.value = fileListTemp | 
				
			||||
  if (info.file.status === 'done' || info.file.status === 'removed') { | 
				
			||||
    //returnUrl为true时仅返回文件路径 | 
				
			||||
    if (props.returnUrl) { | 
				
			||||
      handlePathChange() | 
				
			||||
    } else { | 
				
			||||
      //returnUrl为false时返回文件名称、文件路径及文件大小 | 
				
			||||
      let newFileList: any[] = [] | 
				
			||||
      for (const item of fileListTemp) { | 
				
			||||
        if (item.status === 'done') { | 
				
			||||
          let fileJson = { | 
				
			||||
            fileName: item.name, | 
				
			||||
            filePath: item.response.message, | 
				
			||||
            fileSize: item.size | 
				
			||||
          } | 
				
			||||
          newFileList.push(fileJson) | 
				
			||||
        } else { | 
				
			||||
          return | 
				
			||||
        } | 
				
			||||
      } | 
				
			||||
      emitValue(newFileList) | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function handlePathChange() { | 
				
			||||
  let uploadFiles = fileList.value | 
				
			||||
  let path = '' | 
				
			||||
  if (!uploadFiles || uploadFiles.length == 0) { | 
				
			||||
    path = '' | 
				
			||||
  } | 
				
			||||
  let pathList: string[] = [] | 
				
			||||
  for (const item of uploadFiles) { | 
				
			||||
    if (item.status === 'done') { | 
				
			||||
      pathList.push(item.response.data) | 
				
			||||
    } else { | 
				
			||||
      return | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  if (pathList.length > 0) { | 
				
			||||
    path = pathList.join(',') | 
				
			||||
  } | 
				
			||||
  emitValue(path) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// 预览文件、图片 | 
				
			||||
function onFilePreview(file) { | 
				
			||||
  if (isImageMode.value) { | 
				
			||||
    createImgPreview({ imageList: [file.url], maskClosable: true }) | 
				
			||||
  } else { | 
				
			||||
    window.open(file.url) | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function emitValue(value) { | 
				
			||||
  emit('change', value) | 
				
			||||
  emit('update:value', value) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function uidGenerator() { | 
				
			||||
  return '-' + parseInt(Math.random() * 10000 + 1, 10) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function getFileName(path) { | 
				
			||||
  if (path.lastIndexOf('\\') >= 0) { | 
				
			||||
    let reg = new RegExp('\\\\', 'g') | 
				
			||||
    path = path.replace(reg, '/') | 
				
			||||
  } | 
				
			||||
  return path.substring(path.lastIndexOf('/') + 1) | 
				
			||||
} | 
				
			||||
</script> | 
				
			||||
 | 
				
			||||
<style lang="less"> | 
				
			||||
//noinspection LessUnresolvedVariable | 
				
			||||
@prefix-cls: ~'@{namespace}-upload'; | 
				
			||||
 | 
				
			||||
.@{prefix-cls} { | 
				
			||||
  &-container { | 
				
			||||
    position: relative; | 
				
			||||
 | 
				
			||||
    .upload-disabled { | 
				
			||||
      .ant-upload-list-item { | 
				
			||||
        .anticon-close { | 
				
			||||
          display: none; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        .anticon-delete { | 
				
			||||
          display: none; | 
				
			||||
        } | 
				
			||||
      } | 
				
			||||
 | 
				
			||||
      /* update-begin-author:taoyan date:2022-5-24 for:VUEN-1093详情界面 图片下载按钮显示不全 */ | 
				
			||||
      .upload-download-handler { | 
				
			||||
        right: 6px !important; | 
				
			||||
      } | 
				
			||||
 | 
				
			||||
      /* update-end-author:taoyan date:2022-5-24 for:VUEN-1093详情界面 图片下载按钮显示不全 */ | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    .ant-upload-list-item { | 
				
			||||
      .upload-actions-container { | 
				
			||||
        position: absolute; | 
				
			||||
        top: -31px; | 
				
			||||
        left: -18px; | 
				
			||||
        z-index: 11; | 
				
			||||
        width: 84px; | 
				
			||||
        height: 84px; | 
				
			||||
        line-height: 28px; | 
				
			||||
        text-align: center; | 
				
			||||
        pointer-events: none; | 
				
			||||
 | 
				
			||||
        a { | 
				
			||||
          opacity: 0.9; | 
				
			||||
          margin: 0 5px; | 
				
			||||
          cursor: pointer; | 
				
			||||
          transition: opacity 0.3s; | 
				
			||||
 | 
				
			||||
          .anticon { | 
				
			||||
            color: #fff; | 
				
			||||
            font-size: 16px; | 
				
			||||
          } | 
				
			||||
 | 
				
			||||
          &:hover { | 
				
			||||
            opacity: 1; | 
				
			||||
          } | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        .upload-mover-handler, | 
				
			||||
        .upload-download-handler { | 
				
			||||
          position: absolute; | 
				
			||||
          pointer-events: auto; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        .upload-mover-handler { | 
				
			||||
          width: 100%; | 
				
			||||
          bottom: 0; | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        .upload-download-handler { | 
				
			||||
          top: -4px; | 
				
			||||
          right: -4px; | 
				
			||||
        } | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
</style> | 
				
			||||
@ -0,0 +1,90 @@
					 | 
				
			||||
<template> | 
				
			||||
  <div v-show="download" class="upload-download-handler"> | 
				
			||||
    <a class="download" title="下载" @click="onDownload"> | 
				
			||||
      <Icon icon="ant-design:download" /> | 
				
			||||
    </a> | 
				
			||||
  </div> | 
				
			||||
  <div v-show="mover && list.length > 1" class="upload-mover-handler"> | 
				
			||||
    <a title="向前移动" @click="onMoveForward"> | 
				
			||||
      <Icon icon="ant-design:arrow-left" /> | 
				
			||||
    </a> | 
				
			||||
    <a title="向后移动" @click="onMoveBack"> | 
				
			||||
      <Icon icon="ant-design:arrow-right" /> | 
				
			||||
    </a> | 
				
			||||
  </div> | 
				
			||||
</template> | 
				
			||||
 | 
				
			||||
<script lang="ts" setup> | 
				
			||||
import { unref, computed } from 'vue' | 
				
			||||
import { Icon } from '@/components/Icon' | 
				
			||||
import { useMessage } from '@/hooks/web/useMessage' | 
				
			||||
 | 
				
			||||
const { createMessage } = useMessage() | 
				
			||||
 | 
				
			||||
const props = defineProps({ | 
				
			||||
  element: { type: HTMLElement, required: true }, | 
				
			||||
  fileList: { type: Object, required: true }, | 
				
			||||
  mover: { type: Boolean, required: true }, | 
				
			||||
  download: { type: Boolean, required: true }, | 
				
			||||
  emitValue: { type: Function, required: true } | 
				
			||||
}) | 
				
			||||
const list = computed(() => unref(props.fileList)) | 
				
			||||
 | 
				
			||||
// 向前移动图片 | 
				
			||||
function onMoveForward() { | 
				
			||||
  let index = getIndexByUrl() | 
				
			||||
  if (index === -1) { | 
				
			||||
    createMessage.warn('移动失败:' + index) | 
				
			||||
    return | 
				
			||||
  } | 
				
			||||
  if (index === 0) { | 
				
			||||
    doSwap(index, unref(list).length - 1) | 
				
			||||
    return | 
				
			||||
  } | 
				
			||||
  doSwap(index, index - 1) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
// 向后移动图片 | 
				
			||||
function onMoveBack() { | 
				
			||||
  let index = getIndexByUrl() | 
				
			||||
  if (index === -1) { | 
				
			||||
    createMessage.warn('移动失败:' + index) | 
				
			||||
    return | 
				
			||||
  } | 
				
			||||
  if (index == unref(list).length - 1) { | 
				
			||||
    doSwap(index, 0) | 
				
			||||
    return | 
				
			||||
  } | 
				
			||||
  doSwap(index, index + 1) | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function doSwap(oldIndex, newIndex) { | 
				
			||||
  if (oldIndex !== newIndex) { | 
				
			||||
    let array: any[] = [...(unref(list) as Array<any>)] | 
				
			||||
    let temp = array[oldIndex] | 
				
			||||
    array[oldIndex] = array[newIndex] | 
				
			||||
    array[newIndex] = temp | 
				
			||||
    props.emitValue(array.map((i) => i.url).join(',')) | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function getIndexByUrl() { | 
				
			||||
  const url = props.element?.getElementsByTagName('img')[0]?.src | 
				
			||||
  if (url) { | 
				
			||||
    const fileList: any = unref(list) | 
				
			||||
    for (let i = 0; i < fileList.length; i++) { | 
				
			||||
      let current = fileList[i].url | 
				
			||||
      const replace = url.replace(window.location.origin, '') | 
				
			||||
      if (current === replace || encodeURI(current) === replace) { | 
				
			||||
        return i | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  return -1 | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onDownload() { | 
				
			||||
  const url = props.element?.getElementsByTagName('img')[0]?.src | 
				
			||||
  window.open(url) | 
				
			||||
} | 
				
			||||
</script> | 
				
			||||
		Reference in new issue