167 lines
4.3 KiB
Vue
167 lines
4.3 KiB
Vue
<template>
|
|
<div class="component-upload-image">
|
|
<el-upload
|
|
v-if="!disabled"
|
|
v-model:file-list="fileList"
|
|
list-type="picture-card"
|
|
:limit="limit"
|
|
:accept="fileAccept"
|
|
:http-request="handleUploadRequest"
|
|
:before-upload="handleBeforeUpload"
|
|
:on-remove="handleRemove"
|
|
:on-preview="handlePreview"
|
|
:class="{ hide: displayItems.length >= limit }"
|
|
>
|
|
<el-icon><Plus /></el-icon>
|
|
</el-upload>
|
|
<div v-else-if="displayItems.length" class="image-list">
|
|
<ImagePreview
|
|
v-for="(item, index) in displayItems"
|
|
:key="`${item}-${index}`"
|
|
:src="item"
|
|
:width="100"
|
|
:height="100"
|
|
:is-oss-id="!isDirectUrl(item)"
|
|
/>
|
|
</div>
|
|
<div v-else-if="placeholder" class="upload-empty">{{ placeholder }}</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
import { Plus } from '@element-plus/icons-vue'
|
|
import type { UploadFile, UploadRequestOptions } from 'element-plus'
|
|
import { ElMessage } from 'element-plus'
|
|
import ImagePreview from '@/components/ImagePreview/index.vue'
|
|
import { fetchUploadAttachment } from '@/api/admin'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
modelValue?: string | string[]
|
|
limit?: number
|
|
fileType?: string[]
|
|
disabled?: boolean
|
|
placeholder?: string
|
|
folder?: string
|
|
}>(),
|
|
{
|
|
modelValue: '',
|
|
limit: 15,
|
|
fileType: () => ['png', 'jpg', 'jpeg'],
|
|
disabled: false,
|
|
placeholder: '',
|
|
folder: 'crm/customer'
|
|
}
|
|
)
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string]
|
|
}>()
|
|
|
|
const fileList = ref<UploadFile[]>([])
|
|
|
|
const isDirectUrl = (value: string) => /^https?:\/\//i.test(value)
|
|
|
|
const displayItems = computed(() => parseValue(props.modelValue))
|
|
|
|
const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(','))
|
|
|
|
function parseValue(raw: string | string[] | undefined): string[] {
|
|
if (!raw) return []
|
|
if (Array.isArray(raw)) {
|
|
return raw.map((item) => String(item).trim()).filter(Boolean)
|
|
}
|
|
return String(raw)
|
|
.split(',')
|
|
.map((item) => item.trim())
|
|
.filter(Boolean)
|
|
}
|
|
|
|
function listToString(items: string[]) {
|
|
return items.filter(Boolean).join(',')
|
|
}
|
|
|
|
function syncFileListFromModel() {
|
|
fileList.value = displayItems.value.map((url, index) => ({
|
|
name: url.split('/').pop() || `file-${index + 1}`,
|
|
url,
|
|
status: 'success'
|
|
}))
|
|
}
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
() => {
|
|
if (props.disabled) return
|
|
syncFileListFromModel()
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
const handleBeforeUpload = (file: File) => {
|
|
const ext = file.name.includes('.') ? file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase() : ''
|
|
const matched = props.fileType.some((type) => {
|
|
const normalized = type.toLowerCase()
|
|
return file.type.includes(normalized) || ext === normalized
|
|
})
|
|
if (!matched) {
|
|
ElMessage.error(`文件格式不正确,请上传 ${props.fileType.join('/')} 格式文件`)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
async function handleUploadRequest(options: UploadRequestOptions) {
|
|
const formData = new FormData()
|
|
formData.append('file', options.file)
|
|
if (props.folder) {
|
|
formData.append('folder', props.folder)
|
|
}
|
|
try {
|
|
const res = await fetchUploadAttachment(formData)
|
|
const fileUrl = res?.fileUrl
|
|
if (!fileUrl) {
|
|
throw new Error('上传失败')
|
|
}
|
|
const next = [...displayItems.value, fileUrl]
|
|
emit('update:modelValue', listToString(next))
|
|
options.onSuccess?.(res)
|
|
} catch (error: any) {
|
|
ElMessage.error(error?.message || '上传失败')
|
|
options.onError?.(error)
|
|
}
|
|
}
|
|
|
|
function handleRemove(uploadFile: UploadFile) {
|
|
const removedUrl = uploadFile.url || (uploadFile.response as { fileUrl?: string })?.fileUrl
|
|
if (!removedUrl) return
|
|
const next = displayItems.value.filter((url) => url !== removedUrl)
|
|
emit('update:modelValue', listToString(next))
|
|
}
|
|
|
|
function handlePreview(uploadFile: UploadFile) {
|
|
const url = uploadFile.url || (uploadFile.response as { fileUrl?: string })?.fileUrl
|
|
if (url && isDirectUrl(url)) {
|
|
window.open(url, '_blank')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.image-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
.upload-empty {
|
|
color: var(--el-text-color-secondary);
|
|
font-size: 13px;
|
|
}
|
|
|
|
:deep(.hide .el-upload--picture-card) {
|
|
display: none;
|
|
}
|
|
</style>
|