change prices &...
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="relative">
|
||||
<div
|
||||
class="w-24 h-24 rounded-full overflow-hidden border-2 border-gray-200 bg-gray-100 flex items-center justify-center"
|
||||
>
|
||||
<img
|
||||
v-if="previewUrl"
|
||||
:src="previewUrl"
|
||||
alt="عکس پروفایل"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<BasicIcon v-else icon="vuesax-linear:user" size="40" color="#9CA3AF" />
|
||||
</div>
|
||||
<button
|
||||
v-if="previewUrl && !uploading"
|
||||
type="button"
|
||||
class="absolute bottom-0 left-0 z-10 p-1 rounded-full bg-white shadow"
|
||||
aria-label="حذف عکس"
|
||||
@click.stop="onRemove"
|
||||
>
|
||||
<BasicIcon icon="vuesax-linear:trash" class="text-red-600" size="18" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a-upload
|
||||
name="file"
|
||||
:show-upload-list="false"
|
||||
accept="image/jpeg,image/png,image/webp,image/gif"
|
||||
:disabled="uploading"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="customRequest"
|
||||
>
|
||||
<BasicButton size="small" type="default" :disabled="uploading">
|
||||
<a-spin v-if="uploading" size="small" class="ml-1" />
|
||||
{{ previewUrl ? 'تغییر عکس' : 'انتخاب عکس پروفایل' }}
|
||||
</BasicButton>
|
||||
</a-upload>
|
||||
<p class="text-xs text-gray-500 text-center">فرمت jpg، png یا webp — حداکثر ۲ مگابایت</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { message, Upload } from 'ant-design-vue'
|
||||
import type { UploadProps } from 'ant-design-vue'
|
||||
|
||||
type UploadRequestOption = Parameters<NonNullable<UploadProps['customRequest']>>[0]
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { uploadAvatar } from '@/api/avatar'
|
||||
import { getFileUrl } from '@/utils/file'
|
||||
|
||||
const props = defineProps<{
|
||||
imageUrl?: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'uploaded', payload: { id: number; url: string }): void
|
||||
(e: 'removed'): void
|
||||
}>()
|
||||
|
||||
const previewUrl = ref<string | null>(null)
|
||||
const uploading = ref(false)
|
||||
|
||||
watch(
|
||||
() => props.imageUrl,
|
||||
(url) => {
|
||||
previewUrl.value = url || null
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function beforeUpload(file: File) {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
message.error('فقط فایل تصویری مجاز است')
|
||||
return Upload.LIST_IGNORE
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 2) {
|
||||
message.error('حجم تصویر نباید بیشتر از ۲ مگابایت باشد')
|
||||
return Upload.LIST_IGNORE
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function customRequest(options: UploadRequestOption) {
|
||||
const file = options.file as File
|
||||
uploading.value = true
|
||||
const localPreview = URL.createObjectURL(file)
|
||||
previewUrl.value = localPreview
|
||||
|
||||
try {
|
||||
const res = await uploadAvatar(file)
|
||||
const url = res.full_src || getFileUrl(res) || localPreview
|
||||
previewUrl.value = url
|
||||
if (res.id != null) {
|
||||
emit('uploaded', { id: res.id, url })
|
||||
message.success('عکس پروفایل آپلود شد')
|
||||
options.onSuccess?.(res)
|
||||
} else {
|
||||
throw new Error('invalid response')
|
||||
}
|
||||
} catch {
|
||||
previewUrl.value = props.imageUrl || null
|
||||
message.error('آپلود عکس انجام نشد. (احتمالاً API سرور هنوز آماده نیست)')
|
||||
options.onError?.(new Error('upload failed'))
|
||||
} finally {
|
||||
uploading.value = false
|
||||
if (localPreview.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(localPreview)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onRemove() {
|
||||
previewUrl.value = null
|
||||
emit('removed')
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user