change prices &...

This commit is contained in:
2026-06-03 11:57:40 +03:30
parent f4b98576f5
commit b055cd4a5d
16 changed files with 305 additions and 25 deletions
+119
View File
@@ -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>