Files
boursecu_admin/src/pages/menuPages/banners/components/BannerImageUpload.vue
T

245 lines
5.8 KiB
Vue
Raw Normal View History

2026-07-06 19:06:57 +03:30
<template>
<div class="banner-image-upload">
<a-upload
v-if="!imageUrl"
class="banner-image-upload__zone"
name="file"
accept="image/*"
:show-upload-list="false"
:action="uploadURL"
:headers="fileHeader"
2026-07-16 17:30:06 +03:30
:before-upload="beforeUpload"
2026-07-16 17:55:08 +03:30
:custom-request="customRequest"
2026-07-06 19:06:57 +03:30
@change="onChange"
>
<div class="banner-image-upload__empty">
<div v-if="loading" class="banner-image-upload__loading">
<LoadingOutlined />
<span>در حال آپلود...</span>
</div>
<template v-else>
<BaseIcon icon="vuesax-linear:gallery-add" size="40" class="banner-image-upload__icon" />
<span class="banner-image-upload__cta">افزودن تصویر بنر</span>
2026-07-16 17:30:06 +03:30
<span class="banner-image-upload__hint">حداکثر ۱ مگابایت — تبدیل خودکار به WebP — نسبت ۱۶:۹</span>
2026-07-06 19:06:57 +03:30
</template>
</div>
</a-upload>
<div v-else class="banner-image-upload__filled">
<img :src="imageUrl" alt="" class="banner-image-upload__img" />
<div class="banner-image-upload__bar">
<a-upload
name="file"
accept="image/*"
:show-upload-list="false"
:action="uploadURL"
:headers="fileHeader"
2026-07-16 17:30:06 +03:30
:before-upload="beforeUpload"
2026-07-16 17:55:08 +03:30
:custom-request="customRequest"
2026-07-06 19:06:57 +03:30
@change="onChange"
>
<button type="button" class="banner-image-upload__btn banner-image-upload__btn--primary">
تغییر تصویر
</button>
</a-upload>
<button
type="button"
class="banner-image-upload__btn banner-image-upload__btn--danger"
@click="emit('remove')"
>
حذف تصویر
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { LoadingOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { uploadURL } from '@root/config'
import { getToken } from '@/utils'
import { normalizeUploadFileResponse } from '@/utils/file'
2026-07-16 17:55:08 +03:30
import {
createImageBeforeUpload,
createImageCustomRequest
} from '@/utils/prepareImageForUpload'
2026-07-16 17:30:06 +03:30
import type { UploadProps } from 'ant-design-vue'
2026-07-06 19:06:57 +03:30
defineProps<{
imageUrl?: string | null
}>()
const emit = defineEmits<{
upload: [url: string]
remove: []
}>()
const loading = ref(false)
const fileHeader = computed(() => ({
Authorization: `Bearer ${getToken()}`,
}))
2026-07-16 17:55:08 +03:30
const beforeUpload: UploadProps['beforeUpload'] = createImageBeforeUpload((msg) =>
2026-07-16 17:30:06 +03:30
message.error(msg)
)
2026-07-16 17:55:08 +03:30
const customRequest: UploadProps['customRequest'] = createImageCustomRequest({
action: () => uploadURL,
getHeaders: () => fileHeader.value,
messageError: (msg) => message.error(msg),
onStarted: () => {
2026-07-06 19:06:57 +03:30
loading.value = true
2026-07-16 17:55:08 +03:30
},
onUploaded: (body) => {
2026-07-06 19:06:57 +03:30
loading.value = false
2026-07-16 17:55:08 +03:30
const fileResource = normalizeUploadFileResponse(body)
2026-07-06 19:06:57 +03:30
if (!fileResource?.full_src) {
message.error('خطا در ساخت آدرس تصویر')
return
}
emit('upload', fileResource.full_src)
message.success('تصویر با موفقیت آپلود شد')
2026-07-16 17:55:08 +03:30
},
onFailed: () => {
loading.value = false
}
})
function onChange({ file }: { file: { status?: string } }) {
if (file.status === 'uploading') {
loading.value = true
2026-07-06 19:06:57 +03:30
}
}
</script>
<style scoped lang="less">
.banner-image-upload {
width: 100%;
height: 100%;
}
.banner-image-upload__zone {
display: block;
width: 100%;
height: 100%;
:deep(.ant-upload) {
display: block;
width: 100%;
height: 100%;
}
:deep(.ant-upload-select) {
display: block !important;
width: 100% !important;
height: 100% !important;
margin: 0 !important;
border: none !important;
background: transparent !important;
}
}
.banner-image-upload__empty {
display: flex;
width: 100%;
height: 100%;
min-height: 10rem;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1.25rem;
border: 2px dashed color-mix(in srgb, var(--admin-primary, #f5b800) 45%, var(--admin-border));
border-radius: 12px;
background: color-mix(in srgb, var(--admin-bg-muted) 85%, transparent);
color: var(--admin-text-secondary);
text-align: center;
cursor: pointer;
transition: border-color 0.15s ease, background 0.15s ease;
&:hover {
border-color: var(--admin-primary, #f5b800);
background: color-mix(in srgb, var(--admin-primary, #f5b800) 8%, var(--admin-bg-muted));
}
}
.banner-image-upload__icon {
color: var(--admin-primary, #f5b800);
opacity: 0.9;
}
.banner-image-upload__cta {
font-size: 0.9375rem;
font-weight: 600;
color: var(--admin-text);
}
.banner-image-upload__hint {
font-size: 0.75rem;
line-height: 1.5;
}
.banner-image-upload__loading {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
font-size: 0.8125rem;
}
.banner-image-upload__filled {
position: relative;
width: 100%;
height: 100%;
min-height: 10rem;
overflow: hidden;
border-radius: 12px;
border: 1px solid var(--admin-border);
background: #000;
}
.banner-image-upload__img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.banner-image-upload__bar {
position: absolute;
inset: auto 0 0 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem;
background: linear-gradient(to top, rgba(0, 0, 0, 0.78), transparent);
}
.banner-image-upload__btn {
padding: 0.4rem 0.85rem;
border: 1px solid rgba(255, 255, 255, 0.35);
border-radius: 8px;
background: rgba(0, 0, 0, 0.45);
color: #fff;
font-size: 0.8125rem;
cursor: pointer;
transition: background 0.15s ease;
&--primary:hover {
background: rgba(0, 0, 0, 0.65);
}
&--danger {
border-color: rgba(255, 77, 79, 0.55);
color: #ffb3b3;
}
&--danger:hover {
background: rgba(255, 77, 79, 0.35);
}
}
</style>