123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- <template>
- <div class="input file" :class="{ suffix: $slots.icon, disabled: disabled, valuable }">
- <template v-if="valuable">
- <slot :key="modelValue" name="valuable" />
- </template>
- <input v-if="!maxLen || maxLen > modelValue.length" ref="inputRef" title="" class="ui-text" type="file" :accept="accept" :multiple="multiple" @change="selectFileHandler" />
- <template v-if="!$slots.replace">
- <span class="replace">
- <div v-if="!valuable" class="placeholder">
- <p><ui-icon type="add" /></p>
- <p>{{ placeholder }}</p>
- <p class="bottom">
- <template v-if="!othPlaceholder">
- <template v-if="accept">支持 {{ accept }} 等格式,</template>
- <template v-if="normalizeScale">宽*高比例 {{ scale }},</template>
- <template v-if="maxSize">大小不超过 {{ sizeStr }}{{ maxLen ? ',' : '' }}</template>
- <template v-if="maxLen">个数不超过 {{ maxLen }}个</template>
- </template>
- <template v-else>
- {{ othPlaceholder }}
- </template>
- </p>
- </div>
- <span v-else-if="!maxLen || maxLen > modelValue.length">
- {{ multiple ? '继续添加' : '替换' }}
- </span>
- <span v-if="maxLen && modelValue.length" class="tj">
- <span>{{ modelValue.length || 0 }}</span> / {{ maxLen }}
- </span>
- </span>
- </template>
- <div v-else class="use-replace">
- <slot name="replace" />
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, defineEmits, defineExpose, defineProps, ref } from 'vue'
- import { toRawType } from '@kankan/utils'
- import Dialog from '@kankan/components/basic/dialog'
- import { fileProps } from './file'
- // import Message from '../message';
- // import { useI18n } from '@/i18n'
- // const { t } = useI18n({ useScope: 'global' })
- const props = defineProps(fileProps)
- const emit = defineEmits(['update:modelValue'])
- const inputRef = ref(null)
- const normalizeScale = computed(() => {
- if (props.scale) {
- const [w, h] = props.scale.split(':')
- if (Number(w) && Number(h)) {
- return [Number(w), Number(h)]
- }
- }
- return []
- })
- const valuable = computed(() => (Array.isArray(props.modelValue) ? props.modelValue.length : !!props.modelValue))
- const sizeStr = computed(() => props.maxSize && `${props.maxSize / 1024 / 1024}MB`)
- const supports = {
- image: {
- types: ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'],
- preview(file, url) {
- return new Promise((resolve, reject) => {
- const img = new Image()
- img.onload = () => resolve([img.width, img.height, file])
- img.onerror = reject
- img.src = url
- })
- },
- },
- video: {
- types: ['video/mp4'],
- preview(file, url) {
- return new Promise((resolve, reject) => {
- const video = document.createElement('video')
- video.preload = 'metadata'
- video.onloadedmetadata = () => resolve([video.videoWidth, video.videoHeight, file])
- video.onerror = reject
- video.src = url
- })
- },
- },
- }
- const producePreviews = files =>
- Promise.all(
- files.map(
- file =>
- new Promise((resolve, reject) => {
- const fr = new FileReader()
- fr.onloadend = e => resolve(e.target.result)
- fr.onerror = e => loaderror(file, reject(e))
- fr.readAsDataURL(file)
- })
- )
- )
- const calcScale = (w, h) => Number.parseInt((w / h) * 1000)
- const selectFileHandler = async ev => {
- const fileEl = ev.target
- const files = Array.from(fileEl.files)
- const previewError = (e, msg = `预览加载失败!`) => {
- console.error(e)
- // Message.error(msg)
- Dialog.toast({
- content: msg,
- type: 'error',
- })
- fileEl.value = ''
- }
- if (props.accept) {
- for (const file of files) {
- const accepts = props.accept.split(',').map(atom => {
- return atom.trim()
- })
- const hname = file.name.slice(file.name.lastIndexOf('.')).toLowerCase()
- if (!accepts.includes(hname)) {
- // return previewError('格式错误', `仅支持${props.accept}格式文件`)
- const accept = props.accept.split('.').join('').replace(',', '/').toLowerCase()
- return previewError('格式错误', `${t('limit.formFile', { form: accept })}`)
- }
- }
- }
- let previews
- if (props.preview || normalizeScale.value) {
- try {
- previews = await producePreviews(files)
- } catch (e) {
- return previewError(e)
- }
- }
- if (normalizeScale.value) {
- const sizesConfirm = []
- for (const [i, file] of files.entries()) {
- const support = Object.values(supports).find(support => support.types.includes(file.type))
- if (support) {
- sizesConfirm.push(support.preview(file, previews[i]))
- }
- }
- let sizes
- try {
- sizes = await Promise.all(sizesConfirm)
- } catch (e) {
- return previewError(e)
- }
- for (const [w, h, file] of sizes) {
- const scaleDiff = calcScale(...normalizeScale.value) - calcScale(w, h)
- if (Math.abs(scaleDiff) > 300) {
- // return previewError('error scale', `${file.name}的比例部位不为${props.scale}`)
- return previewError('error scale', `${t('limit.scaleFile', { fileName: file.name, scale: props.scale })}`)
- }
- }
- }
- if (props.maxSize) {
- for (const file of files) {
- if (file.size > props.maxSize) {
- // return previewError('error size', `${file.name}的大小超过${sizeStr.value}`)
- // return previewError('error size', `${file.name}的大小超过${sizeStr.value}`)
- const accept = props.accept.split('.').join('').replace(',', '/').toLowerCase()
- return previewError('格式错误', `${t('limit.formSize', { size: sizeStr.value, form: accept })}`)
- }
- }
- }
- const value = props.modelValue ? (props.multiple ? (toRawType(props.modelValue) === 'Array' ? props.modelValue : [props.modelValue]) : null) : props.multiple ? [] : null
- const emitData = props.multiple
- ? props.preview
- ? [...value, ...files.map((file, i) => ({ file, preview: previews[i] }))]
- : [...value, files]
- : props.preview
- ? { file: files[0], preview: previews[0] }
- : files[0]
- if (Array.isArray(emitData) && props.maxLen && emitData.length > props.maxLen) {
- // return previewError('err len', `最多仅支持${props.maxLen}个文件!`)
- return previewError('err len', `${t('limit.maxLengthFile', { length: props.maxLen })}`)
- }
- emit('update:modelValue', emitData)
- fileEl.value = ''
- }
- defineExpose({
- input: inputRef,
- })
- </script>
|