Files
boursecu_user/src/views/base/wallet/components/WalletDepositGuideModal.vue
T

775 lines
20 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<BasicModal
v-model:visible="localVisible"
custom-footer
:closable="false"
wrap-class-name="app-premium-modal--no-header"
width="min(92vw, 420px)"
>
<div class="deposit-guide">
<div class="deposit-guide__icon-wrap" aria-hidden="true">
<BasicIcon icon="vuesax-linear:empty-wallet-add" size="34" color="#16a34a" />
</div>
<h2 class="deposit-guide__title">{{ title }}</h2>
<div class="deposit-guide__body">
<slot />
</div>
<div class="deposit-guide__accounts">
<div v-if="hasDepositCardValue" class="deposit-guide__account-row">
<span class="deposit-guide__account-label">{{ depositCardLabel }}</span>
<button
type="button"
class="deposit-guide__account-value"
:aria-label="`کپی ${depositCardLabel}`"
@click="copyDepositCardNumber"
>
<span class="deposit-guide__account-text" dir="ltr">{{ depositCardNumber }}</span>
<BasicIcon
icon="vuesax-linear:copy"
size="18"
color="#15803d"
class="deposit-guide__copy-icon"
/>
</button>
</div>
<div v-if="hasDepositAccountNumberValue" class="deposit-guide__account-row">
<span class="deposit-guide__account-label">{{ depositAccountNumberLabel }}</span>
<button
type="button"
class="deposit-guide__account-value"
:aria-label="`کپی ${depositAccountNumberLabel}`"
@click="copyDepositAccountNumber"
>
<span class="deposit-guide__account-text" dir="ltr">{{ depositAccountNumber }}</span>
<BasicIcon
icon="vuesax-linear:copy"
size="18"
color="#15803d"
class="deposit-guide__copy-icon"
/>
</button>
</div>
<div v-if="hasDepositShebaValue" class="deposit-guide__account-row">
<span class="deposit-guide__account-label">{{ depositShebaLabel }}</span>
<button
type="button"
class="deposit-guide__account-value"
:aria-label="`کپی ${depositShebaLabel}`"
@click="copyDepositShebaNumber"
>
<span class="deposit-guide__account-text" dir="ltr">{{ depositSheba }}</span>
<BasicIcon
icon="vuesax-linear:copy"
size="18"
color="#15803d"
class="deposit-guide__copy-icon"
/>
</button>
</div>
<!-- شناسه واریز اختصاصی (کد ملی شخص) -->
<div v-if="nationalCode" class="deposit-guide__account-row">
<span class="deposit-guide__account-label">شناسه واریز</span>
<button
type="button"
class="deposit-guide__account-value"
aria-label="کپی شناسه واریز"
@click="copyNationalCode"
>
<span class="deposit-guide__account-text" dir="ltr">{{ nationalCode }}</span>
<BasicIcon
icon="vuesax-linear:copy"
size="18"
color="#15803d"
class="deposit-guide__copy-icon"
/>
</button>
<div class="deposit-guide__tip-box">
<BasicIcon icon="vuesax-linear:info-circle" size="16" color="#16a34a" class="shrink-0" />
<span>شناسه واریز شما همان کد ملی شما می‌باشد؛ لطفاً هنگام انتقال وجه در همراه بانک یا اینترنت بانک، این شناسه را در بخش شناسه واریز وارد فرمایید.</span>
</div>
</div>
<a
v-if="hasMessengerLink"
ref="supportMessengerTriggerEl"
class="deposit-guide__support-link"
href="#"
@click.prevent="onDepositSupportClick"
>
<span class="deposit-guide__support-btn">
<BasicIcon icon="vuesax-linear:headphone" size="18" color="#16a34a" />
<span>پیام به پشتیبانی</span>
</span>
</a>
<a
v-if="smsLink"
class="deposit-guide__support-link"
:class="{ 'deposit-guide__support-link--spaced': hasMessengerLink }"
:href="smsLink"
>
<span class="deposit-guide__support-btn">
<BasicIcon icon="vuesax-linear:direct-inbox" size="18" color="#16a34a" />
<span>پیام به پشتیبانی (sms)</span>
</span>
</a>
<!-- بخش آپلود فیش واریزی و ثبت درخواست -->
<div class="deposit-guide__receipt-section">
<div class="deposit-guide__receipt-header">
<BasicIcon icon="vuesax-linear:receipt-text" size="20" color="#16a34a" />
<span>ثبت فیش و اطلاعات واریز</span>
</div>
<div class="deposit-guide__form-field">
<label class="deposit-guide__form-label">
مبلغ واریزی (ریال) <span class="text-red-500">*</span>
</label>
<input
v-model="depositAmountFormatted"
type="text"
dir="ltr"
class="deposit-guide__input"
placeholder="مثال: ۵,۰۰۰,۰۰۰"
@input="onAmountInput"
/>
<span v-if="depositAmountTomanText" class="deposit-guide__amount-hint">
{{ depositAmountTomanText }}
</span>
</div>
<div class="deposit-guide__form-field">
<label class="deposit-guide__form-label">شماره پیگیری / کد ارجاع فیش</label>
<input
v-model="trackedNumber"
type="text"
dir="ltr"
class="deposit-guide__input"
placeholder="کد پیگیری بانکی (اختیاری)"
/>
</div>
<div class="deposit-guide__form-field">
<label class="deposit-guide__form-label">
تصویر فیش واریزی <span class="text-red-500">*</span>
</label>
<input
ref="fileInputRef"
type="file"
accept="image/jpeg,image/png,image/webp,application/pdf"
class="hidden"
@change="onFileSelected"
/>
<div
v-if="!receiptFile"
class="deposit-guide__upload-dropzone"
@click="triggerFileInput"
>
<BasicIcon icon="vuesax-linear:document-upload" size="30" color="#16a34a" />
<span class="deposit-guide__upload-title">برای آپلود تصویر فیش کلیک کنید</span>
<span class="deposit-guide__upload-subtitle">JPG, PNG, WEBP, PDF (حداکثر ۱۰ مگابایت)</span>
</div>
<div v-else class="deposit-guide__receipt-preview">
<img
v-if="receiptPreviewUrl"
:src="receiptPreviewUrl"
alt="پیش‌نمایش فیش"
class="deposit-guide__receipt-img"
/>
<div v-else class="deposit-guide__receipt-file-badge">
<BasicIcon icon="vuesax-linear:receipt-text" size="26" color="#16a34a" />
<span class="deposit-guide__receipt-file-name">{{ receiptFile.name }}</span>
</div>
<button
type="button"
class="deposit-guide__receipt-remove"
aria-label="حذف فیش"
@click="removeReceipt"
>
<BasicIcon icon="vuesax-linear:trash" size="16" color="#dc2626" />
<span>حذف فایل</span>
</button>
</div>
</div>
<div class="deposit-guide__form-field">
<label class="deposit-guide__form-label">توضیحات (اختیاری)</label>
<textarea
v-model="extraNote"
rows="2"
class="deposit-guide__textarea"
placeholder="توضیحات اضافی در صورت نیاز..."
></textarea>
</div>
<BasicButton
type="primary"
class="app-premium-modal__btn-primary deposit-guide__submit-btn w-full"
:loading="submitting"
@click="handleSubmitDeposit"
>
ثبت درخواست واریز
</BasicButton>
</div>
</div>
</div>
<template #footer>
<BasicButton
type="default"
class="w-full"
@click="localVisible = false"
>
بستن
</BasicButton>
</template>
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from 'vue'
import { message } from 'ant-design-vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
import { useSupportMessenger } from '@/composables/useSupportMessenger'
import { loadSupportMessengerFromSettings } from '@/utils/supportMessenger'
import { useUserStore } from '@/store/userStore'
import { submitDepositRequest } from '@/api/wallet'
const props = withDefaults(
defineProps<{
visible: boolean
title?: string
supportLink?: string
smsLink?: string
initialAmount?: number
}>(),
{
title: 'راهنمای شارژ و ثبت واریز',
supportLink: '',
smsLink: '',
initialAmount: undefined,
},
)
const userStore = useUserStore()
const nationalCode = computed(() => {
return String(userStore.userInfo?.n_code ?? '').trim()
})
async function copyNationalCode() {
if (!nationalCode.value) return
try {
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(nationalCode.value)
} else {
const textarea = document.createElement('textarea')
textarea.value = nationalCode.value
textarea.setAttribute('readonly', 'true')
textarea.style.position = 'fixed'
textarea.style.left = '-9999px'
document.body.appendChild(textarea)
textarea.select()
document.execCommand('copy')
document.body.removeChild(textarea)
}
message.info('شناسه واریز کپی شد')
} catch {
message.error('خطا در کپی شناسه واریز')
}
}
const {
depositCardNumber,
depositAccountNumber,
depositSheba,
depositCardLabel,
depositAccountNumberLabel,
depositShebaLabel,
hasDepositCardValue,
hasDepositAccountNumberValue,
hasDepositShebaValue,
copyDepositCardNumber,
copyDepositAccountNumber,
copyDepositShebaNumber,
hasMessengerLink,
openDepositSupportMessenger,
} = useSupportMessenger()
const supportMessengerTriggerEl = ref<HTMLElement | null>(null)
function onDepositSupportClick() {
void openDepositSupportMessenger(supportMessengerTriggerEl.value)
}
const emit = defineEmits<{
'update:visible': [value: boolean]
'success': []
}>()
const localVisible = computed({
get: () => props.visible,
set: (value: boolean) => emit('update:visible', value),
})
// فیلدهای فرم فیش واریزی
const depositAmountRaw = ref<number | undefined>(props.initialAmount)
const depositAmountFormatted = ref<string>('')
const trackedNumber = ref<string>('')
const extraNote = ref<string>('')
const fileInputRef = ref<HTMLInputElement | null>(null)
const receiptFile = ref<File | null>(null)
const receiptPreviewUrl = ref<string>('')
const submitting = ref(false)
function formatNumber(num: number): string {
return num.toLocaleString('en-US')
}
function updateFormattedAmount(val?: number) {
if (val && val > 0) {
depositAmountFormatted.value = formatNumber(val)
} else {
depositAmountFormatted.value = ''
}
}
function onAmountInput(e: Event) {
const target = e.target as HTMLInputElement
const digits = target.value.replace(/\D/g, '')
if (digits) {
const num = parseInt(digits, 10)
depositAmountRaw.value = num
depositAmountFormatted.value = formatNumber(num)
} else {
depositAmountRaw.value = undefined
depositAmountFormatted.value = ''
}
}
const depositAmountTomanText = computed(() => {
const val = depositAmountRaw.value
if (!val || val <= 0) return ''
const toman = Math.floor(val / 10)
return `معادل ${formatNumber(toman)} تومان`
})
function triggerFileInput() {
fileInputRef.value?.click()
}
function onFileSelected(event: Event) {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
if (file.size > 10 * 1024 * 1024) {
message.error('حجم فایل نباید بیش از ۱۰ مگابایت باشد.')
return
}
receiptFile.value = file
if (file.type.startsWith('image/')) {
receiptPreviewUrl.value = URL.createObjectURL(file)
} else {
receiptPreviewUrl.value = ''
}
}
function removeReceipt() {
if (receiptPreviewUrl.value) {
URL.revokeObjectURL(receiptPreviewUrl.value)
}
receiptFile.value = null
receiptPreviewUrl.value = ''
if (fileInputRef.value) {
fileInputRef.value.value = ''
}
}
function resetForm() {
depositAmountRaw.value = undefined
depositAmountFormatted.value = ''
trackedNumber.value = ''
extraNote.value = ''
removeReceipt()
}
async function handleSubmitDeposit() {
const amount = depositAmountRaw.value
if (!amount || amount <= 0) {
message.error('لطفاً مبلغ واریزی را وارد فرمایید.')
return
}
if (!receiptFile.value) {
message.error('لطفاً تصویر فیش واریزی را بارگذاری فرمایید.')
return
}
try {
submitting.value = true
const formData = new FormData()
formData.append('cash', String(amount))
formData.append('receipt', receiptFile.value)
if (trackedNumber.value.trim()) {
formData.append('tracked_number', trackedNumber.value.trim())
}
if (extraNote.value.trim()) {
formData.append('extra_note', extraNote.value.trim())
}
const res = await submitDepositRequest(formData)
message.success(res?.message || 'درخواست واریز شما با موفقیت ثبت شد و پس از بررسی تایید خواهد شد.')
emit('success')
localVisible.value = false
resetForm()
} catch (e: any) {
console.error(e)
const errorMsg = e?.response?.data?.message || 'خطا در ثبت درخواست واریز'
message.error(errorMsg)
} finally {
submitting.value = false
}
}
watch(
() => props.visible,
(visible) => {
if (visible) {
void loadSupportMessengerFromSettings(true)
if (props.initialAmount && props.initialAmount > 0) {
depositAmountRaw.value = props.initialAmount
updateFormattedAmount(props.initialAmount)
}
}
},
)
watch(
() => props.initialAmount,
(newVal) => {
if (newVal && newVal > 0) {
depositAmountRaw.value = newVal
updateFormattedAmount(newVal)
}
},
)
</script>
<style scoped lang="less">
.deposit-guide {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 0.25rem 0.25rem 0.5rem;
}
.deposit-guide__icon-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 4rem;
height: 4rem;
border-radius: 9999px;
background: #ecfdf3;
margin-bottom: 0.75rem;
}
.deposit-guide__title {
margin: 0 0 0.5rem;
font-size: 1.05rem;
font-weight: 700;
color: #111827;
}
.deposit-guide__body {
width: 100%;
font-size: 0.9rem;
line-height: 1.8;
color: #4b5563;
}
.deposit-guide__accounts {
display: flex;
flex-direction: column;
gap: 0.75rem;
width: 100%;
margin-top: 0.85rem;
text-align: right;
}
.deposit-guide__account-row {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.deposit-guide__account-label {
font-size: 0.78rem;
font-weight: 700;
color: #6b7280;
}
.deposit-guide__account-value {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
width: 100%;
min-height: 2.75rem;
padding: 0.65rem 0.85rem;
border: 1px solid #bbf7d0;
border-radius: 0.75rem;
background: #f0fdf4;
color: #111827;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease;
text-align: left;
&:hover:not(.deposit-guide__account-value--empty) {
background: #dcfce7;
border-color: #86efac;
}
&--empty {
color: #9ca3af;
cursor: not-allowed;
}
}
.deposit-guide__account-text {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.deposit-guide__copy-icon {
flex-shrink: 0;
&--disabled {
opacity: 0.45;
}
}
.deposit-guide__tip-box {
display: flex;
align-items: flex-start;
gap: 0.4rem;
padding: 0.5rem 0.65rem;
border-radius: 0.5rem;
background: #f0fdf4;
border: 1px solid #dcfce7;
font-size: 0.75rem;
line-height: 1.6;
color: #15803d;
text-align: right;
}
.deposit-guide__support-link {
display: block;
width: 100%;
margin-top: 0.5rem;
text-decoration: none;
&--spaced {
margin-top: 0.5rem;
}
}
.deposit-guide__support-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
width: 100%;
min-height: 2.5rem;
padding: 0.5rem 1rem;
border: 1px solid #bbf7d0;
border-radius: 0.75rem;
background: #f0fdf4;
color: #15803d;
font-size: 0.85rem;
font-weight: 600;
}
/* بخش ثبت فیش */
.deposit-guide__receipt-section {
display: flex;
flex-direction: column;
gap: 0.65rem;
width: 100%;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px dashed #e2e8f0;
text-align: right;
}
.deposit-guide__receipt-header {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.92rem;
font-weight: 700;
color: #15803d;
margin-bottom: 0.25rem;
}
.deposit-guide__form-field {
display: flex;
flex-direction: column;
gap: 0.3rem;
width: 100%;
}
.deposit-guide__form-label {
font-size: 0.78rem;
font-weight: 600;
color: #374151;
}
.deposit-guide__input {
width: 100%;
height: 2.6rem;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.6rem;
font-size: 0.88rem;
color: #111827;
background: #fff;
outline: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
&:focus {
border-color: #16a34a;
box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.15);
}
}
.deposit-guide__textarea {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.6rem;
font-size: 0.85rem;
color: #111827;
background: #fff;
outline: none;
resize: vertical;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
&:focus {
border-color: #16a34a;
box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.15);
}
}
.deposit-guide__amount-hint {
font-size: 0.74rem;
color: #059669;
font-weight: 600;
}
.deposit-guide__upload-dropzone {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.35rem;
padding: 1rem;
border: 1.5px dashed #86efac;
border-radius: 0.75rem;
background: #f0fdf4;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease;
&:hover {
background: #dcfce7;
border-color: #4ade80;
}
}
.deposit-guide__upload-title {
font-size: 0.82rem;
font-weight: 700;
color: #15803d;
}
.deposit-guide__upload-subtitle {
font-size: 0.7rem;
color: #6b7280;
}
.deposit-guide__receipt-preview {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
border: 1px solid #bbf7d0;
border-radius: 0.75rem;
background: #f0fdf4;
}
.deposit-guide__receipt-img {
max-width: 100%;
max-height: 140px;
object-fit: contain;
border-radius: 0.5rem;
border: 1px solid #d1d5db;
}
.deposit-guide__receipt-file-badge {
display: flex;
align-items: center;
gap: 0.5rem;
}
.deposit-guide__receipt-file-name {
font-size: 0.8rem;
font-weight: 600;
color: #374151;
word-break: break-all;
}
.deposit-guide__receipt-remove {
display: inline-flex;
align-items: center;
gap: 0.35rem;
background: none;
border: none;
color: #dc2626;
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 0.35rem;
transition: background 0.15s ease;
&:hover {
background: #fee2e2;
}
}
.deposit-guide__submit-btn {
margin-top: 0.5rem;
min-height: 2.75rem;
font-size: 0.92rem;
font-weight: 700;
}
</style>