feat: add image ImageUpload
parent
ad91b8ac82
commit
be2ff70cf6
|
|
@ -13,5 +13,6 @@ export { default as ApiTree } from './src/components/ApiTree.vue'
|
|||
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'
|
||||
export { default as ApiCascader } from './src/components/ApiCascader.vue'
|
||||
export { default as ApiTransfer } from './src/components/ApiTransfer.vue'
|
||||
export { default as ImageUpload } from './src/components/ImageUpload.vue'
|
||||
|
||||
export { BasicForm }
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import type { ComponentType } from './types'
|
|||
/**
|
||||
* Component list, register here to setting it in the form
|
||||
*/
|
||||
|
||||
import ApiRadioGroup from './components/ApiRadioGroup.vue'
|
||||
import RadioButtonGroup from './components/RadioButtonGroup.vue'
|
||||
import ApiSelect from './components/ApiSelect.vue'
|
||||
|
|
@ -29,6 +28,7 @@ import ApiTreeSelect from './components/ApiTreeSelect.vue'
|
|||
import ApiCascader from './components/ApiCascader.vue'
|
||||
import ApiTransfer from './components/ApiTransfer.vue'
|
||||
import FileUpload from './components/FileUpload.vue'
|
||||
import ImageUpload from './components/ImageUpload.vue'
|
||||
import { BasicUpload } from '@/components/Upload'
|
||||
import { StrengthMeter } from '@/components/StrengthMeter'
|
||||
import { IconPicker } from '@/components/Icon'
|
||||
|
|
@ -44,7 +44,7 @@ componentMap.set('InputSearch', Input.Search)
|
|||
componentMap.set('InputTextArea', Input.TextArea)
|
||||
componentMap.set('InputNumber', InputNumber)
|
||||
componentMap.set('AutoComplete', AutoComplete)
|
||||
|
||||
componentMap.set('ImageUpload', ImageUpload)
|
||||
componentMap.set('Select', Select)
|
||||
componentMap.set('ApiSelect', ApiSelect)
|
||||
componentMap.set('ApiTree', ApiTree)
|
||||
|
|
@ -67,6 +67,7 @@ componentMap.set('MonthPicker', DatePicker.MonthPicker)
|
|||
componentMap.set('RangePicker', DatePicker.RangePicker)
|
||||
componentMap.set('WeekPicker', DatePicker.WeekPicker)
|
||||
componentMap.set('TimePicker', TimePicker)
|
||||
componentMap.set('TimeRangePicker', TimePicker.TimeRangePicker)
|
||||
componentMap.set('StrengthMeter', StrengthMeter)
|
||||
componentMap.set('IconPicker', IconPicker)
|
||||
componentMap.set('InputCountDown', CountdownInput)
|
||||
|
|
|
|||
|
|
@ -57,19 +57,15 @@ const isMaxCount = computed(() => props.maxCount > 0 && fileList.value.length >=
|
|||
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/*'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,223 @@
|
|||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import type { UploadProps } from 'ant-design-vue'
|
||||
import { Modal, Upload, message } from 'ant-design-vue'
|
||||
import type { UploadFile } from 'ant-design-vue/lib/upload/interface'
|
||||
import { join } from 'lodash-es'
|
||||
import { PlusOutlined } from '@ant-design/icons-vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { buildShortUUID } from '@/utils/uuid'
|
||||
import { isArray, isNotEmpty, isUrl } from '@/utils/is'
|
||||
import { useRuleFormItem } from '@/hooks/component/useFormItem'
|
||||
import { useAttrs } from '@/hooks/core/useAttrs'
|
||||
|
||||
type ImageUploadType = 'text' | 'picture' | 'picture-card'
|
||||
|
||||
defineOptions({ name: 'ImageUpload', inheritAttrs: false })
|
||||
|
||||
const props = defineProps({
|
||||
value: [Array, String],
|
||||
api: {
|
||||
type: Function as PropType<(file: UploadFile) => Promise<string>>,
|
||||
default: null,
|
||||
},
|
||||
listType: {
|
||||
type: String as PropType<ImageUploadType>,
|
||||
default: () => 'picture-card',
|
||||
},
|
||||
// 文件类型
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ['image/png', 'image/jpeg'],
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: () => false,
|
||||
},
|
||||
// 最大数量的文件
|
||||
maxCount: {
|
||||
type: Number,
|
||||
default: () => 1,
|
||||
},
|
||||
// 最小数量的文件
|
||||
minCount: {
|
||||
type: Number,
|
||||
default: () => 0,
|
||||
},
|
||||
// 文件最大多少MB
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: () => 2,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change', 'update:value'])
|
||||
|
||||
const attrs = useAttrs()
|
||||
const { t } = useI18n()
|
||||
const previewOpen = ref(false)
|
||||
const previewImage = ref('')
|
||||
const emitData = ref<any[] | any | undefined>()
|
||||
const fileList = ref<UploadFile[]>([])
|
||||
// Embedded in the form, just use the hook binding to perform form verification
|
||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData)
|
||||
const fileState = reactive<{
|
||||
newList: any[]
|
||||
newStr: string
|
||||
oldStr: string
|
||||
}>({
|
||||
newList: [],
|
||||
newStr: '',
|
||||
oldStr: '',
|
||||
})
|
||||
watch(
|
||||
() => fileList.value,
|
||||
(v) => {
|
||||
fileState.newList = v
|
||||
.filter((item: any) => {
|
||||
return item?.url && item.status === 'done' && isUrl(item?.url)
|
||||
})
|
||||
.map((item: any) => item?.url)
|
||||
fileState.newStr = join(fileState.newList)
|
||||
// 不相等代表数据变更
|
||||
if (fileState.newStr !== fileState.oldStr) {
|
||||
fileState.oldStr = fileState.newStr
|
||||
emitData.value = props.multiple ? fileState.newList : fileState.newStr
|
||||
state.value = props.multiple ? fileState.newList : fileState.newStr
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
},
|
||||
)
|
||||
watch(
|
||||
() => state.value,
|
||||
(v) => {
|
||||
changeFileValue(v)
|
||||
emit('update:value', v)
|
||||
},
|
||||
)
|
||||
function changeFileValue(value: any) {
|
||||
const stateStr = props.multiple ? join((value as string[]) || []) : value || ''
|
||||
if (stateStr !== fileState.oldStr) {
|
||||
fileState.oldStr = stateStr
|
||||
let list: string[] = []
|
||||
if (props.multiple) {
|
||||
if (isNotEmpty(value)) {
|
||||
if (isArray(value))
|
||||
list = value as string[]
|
||||
else
|
||||
list.push(value as string)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isNotEmpty(value))
|
||||
list.push(value as string)
|
||||
}
|
||||
fileList.value = list.map((item) => {
|
||||
const uuid = buildShortUUID()
|
||||
return {
|
||||
uid: uuid,
|
||||
name: uuid,
|
||||
status: 'done',
|
||||
url: item,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
/** 关闭查看 */
|
||||
function handleCancel() {
|
||||
previewOpen.value = false
|
||||
}
|
||||
/** 查看图片 */
|
||||
async function handlePreview(file: UploadProps['fileList'][number]) {
|
||||
if (!file.url && !file.preview)
|
||||
file.preview = (await getBase64(file.originFileObj)) as string
|
||||
|
||||
previewImage.value = file.url || file.preview
|
||||
previewOpen.value = true
|
||||
}
|
||||
/** 上传前校验 */
|
||||
const handleBeforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
if (fileList.value.length > props.maxCount) {
|
||||
fileList.value.splice(props.maxCount, fileList.value.length - props.maxCount)
|
||||
message.error(t('component.upload.maxNumber', [props.maxCount]))
|
||||
return Upload.LIST_IGNORE
|
||||
}
|
||||
const isPNG = props.fileType.includes(file.type)
|
||||
if (!isPNG)
|
||||
message.error(t('component.upload.acceptUpload', [props.fileType.toString()]))
|
||||
|
||||
const isLt2M = file.size / 1024 / 1024 < props.maxSize
|
||||
if (!isLt2M)
|
||||
message.error(t('component.upload.maxSizeMultiple', [props.maxSize]))
|
||||
|
||||
if (!(isPNG && isLt2M))
|
||||
fileList.value.pop()
|
||||
|
||||
return (isPNG && isLt2M) || Upload.LIST_IGNORE
|
||||
}
|
||||
/** 自定义上传 */
|
||||
async function handleCustomRequest(option: any) {
|
||||
const { file } = option
|
||||
await props
|
||||
.api(option)
|
||||
.then((url) => {
|
||||
file.url = url
|
||||
file.status = 'done'
|
||||
fileList.value.pop()
|
||||
fileList.value.push(file)
|
||||
})
|
||||
.catch(() => {
|
||||
fileList.value.pop()
|
||||
})
|
||||
}
|
||||
function getBase64(file: File) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => resolve(reader.result)
|
||||
reader.onerror = error => reject(error)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="clearfix">
|
||||
<Upload
|
||||
v-model:file-list="fileList"
|
||||
v-bind="attrs"
|
||||
v-model:value="state"
|
||||
:list-type="listType"
|
||||
:multiple="multiple"
|
||||
:max-count="maxCount"
|
||||
:custom-request="handleCustomRequest"
|
||||
:before-upload="handleBeforeUpload"
|
||||
@preview="handlePreview"
|
||||
>
|
||||
<div v-if="fileList.length < maxCount">
|
||||
<PlusOutlined />
|
||||
<div style="margin-top: 8px">
|
||||
{{ t('component.upload.upload') }}
|
||||
</div>
|
||||
</div>
|
||||
</Upload>
|
||||
<Modal :open="previewOpen" :footer="null" @cancel="handleCancel">
|
||||
<img alt="example" style="width: 100%" :src="previewImage">
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* you can make up upload button and sample style by using stylesheets */
|
||||
.ant-upload-select-picture-card i {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.ant-upload-select-picture-card .ant-upload-text {
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -106,9 +106,11 @@ export type ComponentType =
|
|||
| 'RangePicker'
|
||||
| 'WeekPicker'
|
||||
| 'TimePicker'
|
||||
| 'TimeRangePicker'
|
||||
| 'Switch'
|
||||
| 'StrengthMeter'
|
||||
| 'Upload'
|
||||
| 'ImageUpload'
|
||||
| 'IconPicker'
|
||||
| 'Render'
|
||||
| 'Slider'
|
||||
|
|
|
|||
Loading…
Reference in New Issue