Files
workspace/code/link56/link56-frontend/src/components/ImageUploadCard.vue
T
2026-04-24 17:28:11 +08:00

148 lines
3.0 KiB
Vue

<script setup lang="ts">
import type { UploadImageResponse } from '@/utils/file'
import { Delete } from '@element-plus/icons-vue'
import { ref } from 'vue'
import { uploadImage, uploadToAliyun } from '@/utils/file'
import SvgIcon from './SvgIcon.vue'
const props = defineProps({
modelValue: {
type: String,
required: true,
},
autoUploadAliyun: {
type: Boolean,
required: false,
default: false,
},
})
const emit = defineEmits<{
(e: 'update:modelValue', val: string): void
(e: 'change', val: UploadImageResponse | null): void
}>()
const loading = ref(false)
async function handleClickUploadImage() {
if (loading.value)
return
try {
const res = await uploadImage()
loading.value = true
if (props.autoUploadAliyun) {
const aliyunRes = await uploadToAliyun(res.blob, res.name, res.size)
emit('update:modelValue', aliyunRes.url)
}
else {
emit('update:modelValue', res.url)
}
emit('change', res)
}
finally {
loading.value = false
}
}
function handleRemove(e: MouseEvent) {
e.stopPropagation()
emit('update:modelValue', '')
emit('change', null)
}
</script>
<template>
<div class="upload-card" :class="{ loading }" @click="handleClickUploadImage">
<div
v-if="props.modelValue && !loading"
class="remove-icon"
@click="handleRemove"
>
<el-icon><Delete /></el-icon>
</div>
<div v-if="!props.modelValue && !loading" class="upload-inner">
<SvgIcon icon="material-symbols:arrow-upload-ready-rounded" class="upload-icon" />
<div class="upload-text">
点击上传
</div>
</div>
<div v-else-if="loading" class="upload-inner">
<SvgIcon icon="line-md:loading-twotone-loop" class="upload-icon" />
<div class="upload-text">
上传中...
</div>
</div>
<img v-else :src="props.modelValue" alt="已上传图片" class="upload-image">
</div>
</template>
<style lang="scss" scoped>
.upload-card {
position: relative;
width: 120px;
height: 120px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
cursor: pointer;
transition: border-color 0.3s ease;
padding: 4px;
&:hover {
border-color: #409eff;
}
&.loading {
pointer-events: none;
opacity: 0.6;
}
.remove-icon {
position: absolute;
top: 4px;
right: 4px;
width: 25px;
height: 25px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.6);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
opacity: 1;
cursor: pointer;
z-index: 2;
&:hover {
background-color: red;
}
}
.upload-inner {
color: #999;
display: flex;
flex-direction: column;
align-items: center;
}
.upload-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
}
.upload-icon {
font-size: 24px;
}
}
</style>