This commit is contained in:
2026-06-30 22:19:27 +03:30
parent 15c43a8ee9
commit bbec5b802d
11 changed files with 178 additions and 40 deletions
+14
View File
@@ -0,0 +1,14 @@
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
#RewriteBase /
RewriteRule ^ /index.html [L]
</IfModule>
+20 -11
View File
@@ -36,6 +36,7 @@
import {usePrefix} from "@/utils/usePrefix";
import {computed} from "vue";
import {usePrice} from "@/utils/usePrice";
import {persianToEnglishDigits, shouldNormalizeInputDigits} from '@/utils/persianDigits';
//Props
interface Prop {
@@ -59,9 +60,7 @@ const addonTextColor = computed(() => props.addonTextColor)
//emits
const localValue = computed({
set(value) {
props.type === 'price'
? emits('update:value', value?.toString().replaceAll(',', ''))
: emits('update:value', value)
emits('update:value', normalizeValue(value))
},
get() {
return props.type === 'price'
@@ -75,16 +74,26 @@ const props = withDefaults(defineProps<Prop>(), {
})
const emits = defineEmits(['update:value', 'onchange'])
const handlerChange = (e: any) => {
props.type === 'price'
? emits('update:value', e.target.value?.toString().replaceAll(',', ''))
: emits('update:value', e.target.value)
props.type === 'price'
? emits('onchange', e.target.value?.toString().replaceAll(',', ''))
: emits('onchange', e.target.value)
function normalizeValue(value: unknown) {
if (value == null || value === '') {
return value
}
let normalized = String(value)
if (shouldNormalizeInputDigits(props)) {
normalized = persianToEnglishDigits(normalized)
}
if (props.type === 'price') {
normalized = normalized.replaceAll(',', '')
}
return normalized
}
//css style
const handlerChange = (e: any) => {
const normalized = normalizeValue(e.target.value)
emits('update:value', normalized)
emits('onchange', normalized)
}
const {prefixCls} = usePrefix('input')
</script>
@@ -1,5 +1,5 @@
<template>
<BasicInput v-model:value="_val">
<BasicInput v-model:value="_val" type="price">
<template v-for="(_, slotKey) in $slots" #[slotKey]="data">
<slot :name="slotKey" v-bind="data || {}"></slot>
</template>
@@ -10,6 +10,7 @@
import {usePrice} from '@/utils/usePrice'
import {computed} from "vue";
import BasicInput from "../../BasicInput.vue";
import {persianToEnglishDigits} from '@/utils/persianDigits'
interface Props {
value?: string | number
@@ -23,7 +24,7 @@ const props = withDefaults(defineProps<Props>(), {})
const _val = computed({
set(val: string) {
emit('update:value', val.toString().replaceAll(',', ''))
emit('update:value', persianToEnglishDigits(val).replaceAll(',', ''))
},
get() {
return props.value ? usePrice(props.value as number, true, '').trim() : ''
+5
View File
@@ -78,6 +78,10 @@ export const useSettingsStore = defineStore('settings', () => {
const metalTypeLabel = computed(() => resolveMetalTypeLabel(metalTypeSettingValue.value))
const meltedGoldLabel = computed(() => resolveMeltedGoldLabel(metalTypeSettingValue.value))
const tradeWeightUnit = computed(() => resolveTradeWeightUnit(getSettingByKey('unit')))
const isReferralRequired = computed(() => {
const value = getSettingByKey('referral_id')
return String(value ?? '').trim().toLowerCase() === 'required'
})
return {
settings,
@@ -86,6 +90,7 @@ export const useSettingsStore = defineStore('settings', () => {
mergeSettings,
getSettingByKey,
isSettingTruthy,
isReferralRequired,
metalTypeSettingValue,
metalTypeLabel,
meltedGoldLabel,
+3 -2
View File
@@ -33,12 +33,13 @@ import type {Rule} from 'ant-design-vue/es/form'
import {useWalletStore} from "@/store/walletStore";
import { isCashLikeDealMethod, isMainMetalWalletDealMethod, getInsufficientCashWalletMessage, getInsufficientGoldWalletMessage } from '@/utils/productDealMethod'
import { getCashWalletBalance } from '@/utils/productAssets'
import { persianToEnglishDigits } from '@/utils/persianDigits'
export const mobileValidation = (title?: string): Rule => {
const label = title ?? 'شماره تلفن'
return {
validator: (_rule, value) => {
const normalized = String(value ?? '').trim()
const normalized = persianToEnglishDigits(String(value ?? '').trim())
if (!normalized) {
return Promise.resolve()
}
@@ -273,7 +274,7 @@ export const passwordRules = () => {
export const englishNumberRule = () => {
return {
validator: (_: any, value: string | number) => {
const str = value != null ? String(value) : ''
const str = persianToEnglishDigits(value)
if (!str) return Promise.resolve()
const isEnglishDigitsOnly = /^[0-9]+$/.test(str)
if (!isEnglishDigitsOnly) {
+29
View File
@@ -0,0 +1,29 @@
const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹'
const ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩'
/** تبدیل ارقام فارسی/عربی و جداکننده اعشار به فرمت انگلیسی */
export function persianToEnglishDigits(value: string | number | null | undefined): string {
if (value == null) {
return ''
}
return String(value)
.replace(/[۰-۹]/g, (d) => String(PERSIAN_DIGITS.indexOf(d)))
.replace(/[٠-٩]/g, (d) => String(ARABIC_DIGITS.indexOf(d)))
.replace(/٫/g, '.')
}
export function shouldNormalizeInputDigits(options: {
isPassword?: boolean
type?: string
inputmode?: string
}): boolean {
if (options.isPassword) {
return false
}
if (options.type === 'price' || options.type === 'number') {
return true
}
const mode = String(options.inputmode ?? '').trim().toLowerCase()
return mode === 'numeric' || mode === 'decimal' || mode === 'number'
}
@@ -1,5 +1,31 @@
/* Desktop standalone gold trade page */
.gold-page {
height: 100%;
min-height: 0;
width: 100%;
max-width: 100%;
box-sizing: border-box;
overflow-x: hidden;
}
.gold-embed {
max-height: min(70vh, 35rem);
overflow-x: hidden;
overflow-y: auto;
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
.gold-form-card {
min-height: 0;
}
.gold-fee-box {
margin-top: 1rem;
}
.dgold {
min-height: 100%;
background: #f6f7f9;
@@ -73,7 +99,27 @@
margin-bottom: 2rem !important;
}
.dgold__panel .bottom-18 {
.dgold__panel .gold-fee-box {
position: static !important;
margin-top: 1rem;
}
@media screen and (max-width: 900px) {
.dgold {
padding: 16px;
}
.dgold-topbar {
gap: 12px;
margin-bottom: 16px;
padding-bottom: 16px;
}
.dgold-topbar__title {
font-size: 20px;
}
.dgold__panel {
max-height: none;
}
}
+32 -7
View File
@@ -1,5 +1,5 @@
<template>
<div :class="isStandaloneDesktop ? 'dgold' : 'h-35rem overflow-y-auto'">
<div :class="rootClass">
<header v-if="isStandaloneDesktop" class="dgold-topbar">
<button type="button" class="dgold-topbar__back" @click="router.back()">
<BasicIcon icon="vuesax-linear:arrow-right-1" size="18" />
@@ -10,8 +10,18 @@
<h1 class="dgold-topbar__title">خرید و فروش {{ tradeProductLabel }}</h1>
</div>
</header>
<div :class="isStandaloneDesktop ? 'dgold__panel' : ''">
<div class=" !overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly">
<MobilePageHeader
v-else-if="isStandaloneMobile"
:title="`خرید و فروش ${tradeProductLabel}`"
show-back
/>
<div :class="panelClass">
<div
:class="[
'!overflow-y-scroll bg-white py-3 text-primary font-bold flex justify-evenly',
isStandaloneMobile ? 'mpage-subheader wrap-fix' : '',
]"
>
<!-- <div @click="isBuy = false" class="w-full text-center" :class="!isBuy?'text-primary':'text-gray-400'">خرید طلا</div>
<div @click="isBuy = true" class="w-full text-center" :class="isBuy?'text-primary':'text-gray-400'">فروش طلا</div>-->
<div class="flex justify-evenly w-full px-2 h-11 items-center bg-gray-50 relative rounded ">
@@ -28,11 +38,11 @@
class="absolute bg-white h-8 w-1/2 rounded-lg animate right"></div>
</div>
</div>
<div v-if="userStore" class=" mt-5 mb-25vh pb-2">
<div v-if="userStore" :class="contentClass">
<a-spin :spinning="!price">
<div v-if="!loading" :class="!isBuy? ' bg-red-100' : ' bg-green-100'"
class="w-full relative rounded-lg h-[88px] flex flex-col justify-center background-color-transition">
<img :src="img1" alt="" class="h-[88px] absolute">
<img :src="img1" alt="" class="h-[88px] absolute inset-0 w-full object-cover">
<div class="w-full items-center flex flex-col">
<div class="text-gray-700 font-bold">{{ !isBuy ? 'قیمت فروش' : 'قیمت خرید' }}</div>
<div v-if="price" class="flex gap-1 mt-3 text-xs items-center">
@@ -53,7 +63,7 @@
</div>
</a-spin>
<a-form :model="form" class="!mt-5" name="basic" @finish="okConfirm()">
<div class="border-gray-200 border-1 rounded-xl p-5 h-58 relative">
<div class="border-gray-200 border-1 rounded-xl p-5 gold-form-card relative">
<bg_main />
<div class="z-100 relative">
<a-form-item>
@@ -138,7 +148,7 @@
موجودی کیف پول ناکافی می باشد.
</div>
</a-form-item>
<div class=" w-full bottom-18 px-6 rounded-xl py-1 border-1 bg-white">
<div class="w-full px-4 rounded-xl py-2 border-1 bg-white gold-fee-box">
<div class="flex justify-between items-center">
<div>کارمزد:</div>
<div class="flex gap-2 items-center">
@@ -278,6 +288,7 @@ import { useUserStore } from '@/store/userStore'
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
import { useDesktopBreakpoint } from '@/layouts/composables/useAppLayout'
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
import { justNumber } from '@/utils/inputRules'
import GoldSkeleton from '@/views/base/gold/components/GoldSkeleton.vue'
import GoldLoading from '@/components/GoldLoading.vue'
@@ -336,6 +347,20 @@ interface Prop {
const props = withDefaults(defineProps<Prop>(), {})
const isDesktop = useDesktopBreakpoint()
const isStandaloneDesktop = computed(() => isDesktop.value && !props.ProductItem)
const isStandaloneMobile = computed(() => !isDesktop.value && !props.ProductItem)
const rootClass = computed(() => {
if (isStandaloneDesktop.value) return 'dgold'
if (props.ProductItem) return 'gold-embed'
return 'gold-page'
})
const panelClass = computed(() => (isStandaloneDesktop.value ? 'dgold__panel' : ''))
const contentClass = computed(() => {
if (isStandaloneDesktop.value) return 'mt-5 mb-25vh pb-2'
if (isStandaloneMobile.value) {
return 'px-3 mpage-content mpage-content--with-subheader pb-[calc(var(--app-bottom-chrome-height)+1rem)]'
}
return 'mt-5 px-3 pb-4'
})
const isBuy = ref<boolean>(true)
const tradeProductLabel = computed(() => {
@@ -277,6 +277,7 @@ import Icon from '@/components/BasicPacks/BasicIcon.vue'
import { useUserStore } from '@/store/userStore'
import BasicActionSheet from '@/components/BasicPacks/BasicActionSheet.vue'
import { justNumber2, justNumber3, required } from '@/utils/inputRules'
import { persianToEnglishDigits } from '@/utils/persianDigits'
import GoldSkeleton from '@/views/base/gold/components/GoldSkeleton.vue'
import GoldLoading from '@/components/GoldLoading.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
@@ -678,16 +679,7 @@ watch(
)
// تابع تبدیل اعداد فارسی به انگلیسی
const persianToEnglish = (str: string | number | undefined): string => {
if (!str) return ''
const persianNumbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']
const englishNumbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
let result = str.toString()
for (let i = 0; i < 10; i++) {
result = result.replace(new RegExp(persianNumbers[i], 'g'), englishNumbers[i])
}
return result
}
const persianToEnglish = persianToEnglishDigits
watch(() => form.value.goldValue, (newVal) => {
if (newVal !== undefined && newVal !== null && newVal !== '') {
+12 -4
View File
@@ -80,7 +80,7 @@
/>
</a-form-item>
<a-form-item
:rules="isBoursecuProject() ? [] : [required()]"
:rules="referralIdRules"
:class="variant === 'desktop' ? '' : 'home_appear_fade_in_down'"
label="کد معرف"
name="referral_id"
@@ -88,7 +88,7 @@
<BasicInput
v-model:value="userFormState.referral_id"
:disabled="disableForm"
:place-holder="variant === 'desktop' ? 'کد معرف (اختیاری)' : 'کد معرف'"
:place-holder="referralIdPlaceholder"
:size="variant === 'desktop' ? 'large' : undefined"
/>
</a-form-item>
@@ -226,7 +226,7 @@ import type { User } from '@/models/login'
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import { loading, URLUserType } from '@/utils'
import { onMounted, reactive, ref } from 'vue'
import { computed, onMounted, reactive, ref } from 'vue'
import type { FormInstance } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import registerDelayImage from '@/assets/img/registerDelay.svg'
@@ -234,7 +234,8 @@ import DialogVantOneButton from '@/components/DialogVantOneButton.vue'
import { registerCustomer } from '@/api/login'
import router from '@/router'
import {useConfigStore} from "@/store/configStore";
import {getProjectLoginLogo, getProjectLoginTaglineLines, hasCustomLoginLogoLayout, isBoursecuProject} from "@/utils/config";
import {useSettingsStore} from '@/store/settingsStore'
import {getProjectLoginLogo, getProjectLoginTaglineLines, hasCustomLoginLogoLayout} from "@/utils/config";
withDefaults(
defineProps<{
@@ -244,6 +245,13 @@ withDefaults(
)
const config = useConfigStore()
const settingsStore = useSettingsStore()
const referralIdRules = computed(() =>
settingsStore.isReferralRequired ? [required('کد معرف')] : [],
)
const referralIdPlaceholder = computed(() =>
settingsStore.isReferralRequired ? 'کد معرف' : 'کد معرف (اختیاری)',
)
// const stepTwoVerify = ref<boolean>(false)
const registeredInfo = ref<boolean>(false)
@@ -1,11 +1,12 @@
<script setup lang="ts">
import { computed } from 'vue'
import { mobileValidation, required } from '@/utils/inputRules'
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import DialogVantOneButton from '@/components/DialogVantOneButton.vue'
import registerDelayImage from '@/assets/img/registerDelay.svg'
import { useConfigStore } from '@/store/configStore'
import { isBoursecuProject } from '@/utils/config'
import { useSettingsStore } from '@/store/settingsStore'
import { useLoginPageContext } from '../../composables/useLoginPage'
import router from '@/router'
@@ -14,6 +15,13 @@ const emit = defineEmits<{
}>()
const config = useConfigStore()
const settingsStore = useSettingsStore()
const referralIdRules = computed(() =>
settingsStore.isReferralRequired ? [required('کد معرف')] : [],
)
const referralIdPlaceholder = computed(() =>
settingsStore.isReferralRequired ? 'کد معرف' : 'کد معرف (اختیاری)',
)
const {
registeredInfo,
disableForm,
@@ -87,14 +95,14 @@ function handleSuccessDismiss() {
</a-form-item>
<a-form-item
:rules="isBoursecuProject() ? [] : [required()]"
:rules="referralIdRules"
label="کد معرف"
name="referral_id"
>
<BasicInput
v-model:value="userFormState.referral_id"
:disabled="disableForm"
place-holder="کد معرف (اختیاری)"
:place-holder="referralIdPlaceholder"
size="large"
/>
</a-form-item>