feat: add gold day calculator and polish desktop UI
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-8
@@ -98,6 +98,8 @@
|
||||
>
|
||||
<AppCreditLink variant="float" />
|
||||
</div>
|
||||
|
||||
<GoldDayCalculatorModal />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -121,6 +123,7 @@ import { useApplyDesktopLayoutClass, useDesktopBreakpoint } from '@/layouts/comp
|
||||
import AppCreditLink from '@/components/AppCreditLink.vue'
|
||||
import { usePwaInstall } from '@/composables/usePwaInstall'
|
||||
import PwaInstallGuideModal from '@/components/pwa/PwaInstallGuideModal.vue'
|
||||
import GoldDayCalculatorModal from '@/components/GoldDayCalculatorModal.vue'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { connectChatIfExists, disconnectChat, isSettingFlagOn, unreadSupportCount, useChatService } from '@/utils/chat'
|
||||
@@ -464,12 +467,4 @@ watch(latestIncomingNotification, (payload) => {
|
||||
transform: scale(0.94);
|
||||
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
/* در دسکتاپ، دکمه پشتیبانی کنار سایدبار و پایین صفحه قرار میگیرد (مانند khaneghi_user) */
|
||||
html.app-layout--desktop {
|
||||
#support-button {
|
||||
bottom: 12px;
|
||||
right: calc(var(--app-desktop-sidebar-width, 260px) + 20px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<a-modal
|
||||
v-model:visible="localValue"
|
||||
:title="props.title"
|
||||
:title="slots.title ? undefined : props.title"
|
||||
centered
|
||||
@ok="emits('ok')"
|
||||
@cancel="emits('close')"
|
||||
@@ -14,6 +14,9 @@
|
||||
:closable="props.closable"
|
||||
:wrapClassName="props.wrapClassName"
|
||||
>
|
||||
<template v-if="slots.title" #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
<template v-if="props.customFooter" #footer> <slot name="footer" /></template>
|
||||
<a-spin :spinning="props.loading">
|
||||
<div class="overflow-y-auto py-2 px-1 relative max-h-[83vh]" id="body">
|
||||
@@ -25,8 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
//define emit
|
||||
import { computed } from 'vue'
|
||||
import { computed, useSlots } from 'vue'
|
||||
|
||||
const emits = defineEmits(['ok', 'close', 'update:visible'])
|
||||
|
||||
@@ -53,6 +55,8 @@ const props = withDefaults(defineProps<Prop>(), {
|
||||
closable: true
|
||||
})
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const localValue = computed({
|
||||
get() {
|
||||
return props.visible
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
v-model:visible="visible"
|
||||
width="400px"
|
||||
destroy-on-close
|
||||
custom-footer
|
||||
wrap-class-name="gold-day-calc-modal"
|
||||
>
|
||||
<template #title>
|
||||
<div class="gold-day-calc-modal__title">
|
||||
<BasicIcon icon="vuesax-linear:chart-2" size="20" class="gold-day-calc-modal__title-icon" />
|
||||
<span>محاسبهگر طلای شما</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="gold-day-calc" dir="rtl">
|
||||
<BasicInput
|
||||
v-model:value="weight"
|
||||
title="وزن طلا (گرم)"
|
||||
type="tel"
|
||||
place-holder="مثلاً ۱۰.۵"
|
||||
inputmode="decimal"
|
||||
/>
|
||||
|
||||
<BasicInput
|
||||
:value="unitPriceInput"
|
||||
title="قیمت روز طلا"
|
||||
type="tel"
|
||||
disabled
|
||||
addon-before="ریال"
|
||||
place-holder="قیمت صفحه اصلی"
|
||||
/>
|
||||
|
||||
<BasicInput
|
||||
v-model:value="feePercent"
|
||||
title="اجرت ساخت (%)"
|
||||
type="tel"
|
||||
place-holder="مثلاً ۷"
|
||||
inputmode="decimal"
|
||||
/>
|
||||
|
||||
<BasicInput
|
||||
v-model:value="taxPercent"
|
||||
title="سود و مالیات (%)"
|
||||
type="tel"
|
||||
place-holder="مثلاً ۹"
|
||||
inputmode="decimal"
|
||||
/>
|
||||
|
||||
<div class="gold-day-calc__result rounded-xl border border-solid border-gray-200 bg-gray-50 p-3">
|
||||
<div class="text-xs text-gray-500 mb-1">کل نهایی</div>
|
||||
<div class="gold-day-calc__total text-lg font-bold text-primary">
|
||||
<span class="gold-day-calc__num">{{ formattedTotal }}</span>
|
||||
<span class="text-sm font-medium text-gray-600">ریال</span>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-col gap-1 text-xs text-gray-600">
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>هزینه طلا</span>
|
||||
<span class="gold-day-calc__num">{{ formatRial(breakdown.goldCost) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>اجرت</span>
|
||||
<span class="gold-day-calc__num">{{ formatRial(breakdown.feeCost) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<span>سود و مالیات</span>
|
||||
<span class="gold-day-calc__num">{{ formatRial(breakdown.taxCost) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<BasicButton type="primary" class="!w-full" size="middle" @click="recalculate">
|
||||
محاسبه مجدد
|
||||
</BasicButton>
|
||||
</template>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import BasicModal from '@/components/BasicPacks/BasicModal.vue'
|
||||
import BasicInput from '@/components/BasicPacks/BasicInput.vue'
|
||||
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import { goldDayCalculatorVisible } from '@/composables/useGoldDayCalculator'
|
||||
import {
|
||||
getLastPrices,
|
||||
getTradeBuyPrice,
|
||||
lastPrice,
|
||||
startLastPricePolling,
|
||||
stopLastPricePolling,
|
||||
} from '@/utils/price'
|
||||
import { getPriceDisplayState, NO_QUOTE_MESSAGE } from '@/utils/storeHours'
|
||||
import { usePriceWithoutType } from '@/utils/usePrice'
|
||||
import { persianToEnglishDigits } from '@/utils/persianDigits'
|
||||
|
||||
const visible = goldDayCalculatorVisible
|
||||
|
||||
const weight = ref<string | number>('')
|
||||
const feePercent = ref<string | number>('')
|
||||
const taxPercent = ref<string | number>('')
|
||||
const tick = ref(0)
|
||||
|
||||
function parseNum(raw: string | number | undefined): number {
|
||||
const s = persianToEnglishDigits(String(raw ?? ''))
|
||||
.replace(/,/g, '')
|
||||
.replace(/[^\d.]/g, '')
|
||||
const n = Number.parseFloat(s)
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
const liveUnitPrice = computed(() => {
|
||||
void tick.value
|
||||
if (getPriceDisplayState() !== 'open') return 0
|
||||
return getTradeBuyPrice(lastPrice.value) || 0
|
||||
})
|
||||
|
||||
const unitPriceInput = computed(() => {
|
||||
void tick.value
|
||||
const state = getPriceDisplayState()
|
||||
if (state === 'closed') return 'فروشگاه بسته است'
|
||||
if (state === 'no_quote') return NO_QUOTE_MESSAGE
|
||||
const p = liveUnitPrice.value
|
||||
return p > 0 ? usePriceWithoutType(p) : ''
|
||||
})
|
||||
|
||||
const breakdown = computed(() => {
|
||||
void tick.value
|
||||
const w = parseNum(weight.value)
|
||||
const fee = parseNum(feePercent.value)
|
||||
const tax = parseNum(taxPercent.value)
|
||||
const unit = liveUnitPrice.value
|
||||
const goldCost = w * unit
|
||||
const feeCost = goldCost * (fee / 100)
|
||||
const taxCost = (goldCost + feeCost) * (tax / 100)
|
||||
const total = goldCost + feeCost + taxCost
|
||||
return { goldCost, feeCost, taxCost, total }
|
||||
})
|
||||
|
||||
const formattedTotal = computed(() => formatRial(breakdown.value.total))
|
||||
|
||||
function formatRial(n: number) {
|
||||
if (!Number.isFinite(n) || n <= 0) return '۰'
|
||||
return usePriceWithoutType(Math.round(n))
|
||||
}
|
||||
|
||||
function recalculate() {
|
||||
tick.value += 1
|
||||
void getLastPrices()
|
||||
}
|
||||
|
||||
watch(visible, async (open) => {
|
||||
if (open) {
|
||||
startLastPricePolling()
|
||||
await getLastPrices()
|
||||
tick.value += 1
|
||||
} else {
|
||||
stopLastPricePolling()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gold-day-calc {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* فقط متن لیبل RTL — گروه اینپوت LTR بماند تا addon ریال نشکند */
|
||||
.gold-day-calc :deep(.ant-input-group) {
|
||||
direction: ltr;
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gold-day-calc :deep(.ant-input-group > .ant-input) {
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.gold-day-calc :deep(.ant-input:not(.ant-input-group *)) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gold-day-calc__result {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gold-day-calc__total {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: flex-start;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.gold-day-calc__num {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.gold-day-calc-modal {
|
||||
.ant-modal-content {
|
||||
direction: rtl;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: min(88dvh, 640px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ant-modal-header {
|
||||
flex-shrink: 0;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
/* ضربدر فیزیکی چپ — پدینگ راست کم تا عنوان نزدیک لبه راست بماند */
|
||||
padding-left: 2.75rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.ant-modal-title {
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gold-day-calc-modal__title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.5rem;
|
||||
direction: rtl;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gold-day-calc-modal__title-icon {
|
||||
flex-shrink: 0;
|
||||
color: var(--App-secondary-color, #c9a062);
|
||||
}
|
||||
|
||||
/* ضربدر سمت چپ — عنوان سمت راست */
|
||||
.ant-modal-close {
|
||||
inset-inline-start: 0 !important;
|
||||
inset-inline-end: auto !important;
|
||||
right: auto !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: 0.25rem;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
#body {
|
||||
max-height: min(48dvh, 360px) !important;
|
||||
overflow-y: auto !important;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
flex-shrink: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid rgba(148, 163, 184, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767.98px) {
|
||||
.gold-day-calc-modal {
|
||||
.ant-modal {
|
||||
max-width: calc(100vw - 1.5rem);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.ant-modal-content {
|
||||
max-height: min(86dvh, 100%);
|
||||
}
|
||||
|
||||
/* بین هدر و دکمه — ارتفاع کمتر تا اسکرول بخورد */
|
||||
#body {
|
||||
max-height: min(42dvh, 280px) !important;
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.gold-day-calc-modal #body {
|
||||
max-height: min(38dvh, 240px) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
/** باز/بسته بودن مدال محاسبهگر طلا — مشترک بین منوی کاربر و همبرگر */
|
||||
export const goldDayCalculatorVisible = ref(false)
|
||||
|
||||
export function openGoldDayCalculator() {
|
||||
goldDayCalculatorVisible.value = true
|
||||
}
|
||||
|
||||
export function closeGoldDayCalculator() {
|
||||
goldDayCalculatorVisible.value = false
|
||||
}
|
||||
@@ -111,13 +111,17 @@ html.app-layout--desktop {
|
||||
}
|
||||
|
||||
.dhome-topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
margin-bottom: 28px;
|
||||
padding-bottom: 24px;
|
||||
margin: -4px 0 20px;
|
||||
padding: 12px 0 20px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||
background: #f6f7f9;
|
||||
|
||||
&__eyebrow {
|
||||
font-size: 12px;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const DRAG_THRESHOLD = 6
|
||||
const POSITION_VERSION = 1
|
||||
/** v2: دسکتاپ از کنار سایدبار تا انتهای viewport (نه ستون ۴۲۶px) */
|
||||
const POSITION_VERSION = 2
|
||||
const MOBILE_CONTENT_MAX = 426
|
||||
|
||||
type FabPosition = { x: number; y: number }
|
||||
|
||||
@@ -11,6 +13,11 @@ type SavedFabOffset = {
|
||||
dy: number
|
||||
}
|
||||
|
||||
type HorizontalBounds = {
|
||||
offsetX: number
|
||||
contentWidth: number
|
||||
}
|
||||
|
||||
type DraggableFloatingOptions = {
|
||||
width?: number
|
||||
height?: number
|
||||
@@ -40,6 +47,46 @@ function readBottomChromeHeight(): number {
|
||||
return measured > 0 ? measured : nav
|
||||
}
|
||||
|
||||
function isDesktopLayout(): boolean {
|
||||
const el = document.documentElement
|
||||
return (
|
||||
el.classList.contains('app-layout--desktop')
|
||||
|| el.getAttribute('data-layout') === 'desktop'
|
||||
)
|
||||
}
|
||||
|
||||
function readCssLengthPx(varName: string): number | null {
|
||||
const raw = getComputedStyle(document.documentElement).getPropertyValue(varName).trim()
|
||||
if (!raw) return null
|
||||
|
||||
if (raw.endsWith('px')) {
|
||||
const n = parseFloat(raw)
|
||||
return Number.isNaN(n) ? null : n
|
||||
}
|
||||
|
||||
if (raw.endsWith('rem')) {
|
||||
const rem = parseFloat(raw)
|
||||
const root = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16
|
||||
return Number.isNaN(rem) ? null : rem * root
|
||||
}
|
||||
|
||||
const n = parseFloat(raw)
|
||||
return Number.isNaN(n) ? null : n
|
||||
}
|
||||
|
||||
/** سایدبار دسکتاپ معمولاً سمت راست (RTL) است. */
|
||||
function readDesktopSidebarWidthPx(): number {
|
||||
if (document.documentElement.classList.contains('app-layout--desktop-no-sidebar')) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return (
|
||||
readCssLengthPx('--app-desktop-sidebar-width')
|
||||
?? readCssLengthPx('--app-sidebar-width')
|
||||
?? 260
|
||||
)
|
||||
}
|
||||
|
||||
export function useDraggableFloatingButton(
|
||||
storageKey: string,
|
||||
options: DraggableFloatingOptions = {}
|
||||
@@ -66,22 +113,28 @@ export function useDraggableFloatingButton(
|
||||
touchAction: 'none'
|
||||
}))
|
||||
|
||||
function getViewportWidth() {
|
||||
return Math.min(window.innerWidth, 426)
|
||||
}
|
||||
|
||||
function getHorizontalOffset() {
|
||||
function getHorizontalBounds(): HorizontalBounds {
|
||||
const vw = window.innerWidth
|
||||
const contentWidth = getViewportWidth()
|
||||
|
||||
return Math.max(0, (vw - contentWidth) / 2)
|
||||
if (isDesktopLayout()) {
|
||||
const sidebar = readDesktopSidebarWidthPx()
|
||||
return {
|
||||
offsetX: 0,
|
||||
contentWidth: Math.max(width, vw - sidebar)
|
||||
}
|
||||
}
|
||||
|
||||
const contentWidth = Math.min(vw, MOBILE_CONTENT_MAX)
|
||||
return {
|
||||
offsetX: Math.max(0, (vw - contentWidth) / 2),
|
||||
contentWidth
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultPosition(): FabPosition {
|
||||
const vh = window.innerHeight
|
||||
const offsetX = getHorizontalOffset()
|
||||
const contentWidth = getViewportWidth()
|
||||
const bottomChrome = readBottomChromeHeight()
|
||||
const { offsetX, contentWidth } = getHorizontalBounds()
|
||||
const bottomChrome = isDesktopLayout() ? 0 : readBottomChromeHeight()
|
||||
|
||||
return {
|
||||
x: offsetX + contentWidth - width - defaultRightOffset,
|
||||
@@ -100,8 +153,7 @@ export function useDraggableFloatingButton(
|
||||
|
||||
function clampToViewport() {
|
||||
const vh = window.innerHeight
|
||||
const offsetX = getHorizontalOffset()
|
||||
const contentWidth = getViewportWidth()
|
||||
const { offsetX, contentWidth } = getHorizontalBounds()
|
||||
const minX = offsetX
|
||||
const maxX = offsetX + contentWidth - width
|
||||
|
||||
|
||||
@@ -34,13 +34,17 @@
|
||||
}
|
||||
|
||||
.dgold-topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
max-width: 720px;
|
||||
margin: 0 auto 24px;
|
||||
padding-bottom: 24px;
|
||||
margin: -4px auto 20px;
|
||||
padding: 12px 0 20px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||
background: #f6f7f9;
|
||||
|
||||
&__back {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -53,13 +53,17 @@
|
||||
|
||||
/* Topbar */
|
||||
.dhome-topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
margin-bottom: 28px;
|
||||
padding-bottom: 24px;
|
||||
margin: -4px 0 20px;
|
||||
padding: 12px 0 20px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||
background: #f6f7f9;
|
||||
|
||||
&__eyebrow {
|
||||
font-size: 12px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<a-form ref="formRef" :model="userFormState" class="!mt-5" name="basic" @finish="ok">
|
||||
<a-form ref="formRef" :model="userFormState" class="user-profile-form !mt-5" name="basic" dir="rtl" @finish="ok">
|
||||
<a-form-item :rules="[isValidPersian()]" label="نام" name="fname">
|
||||
<BasicInput v-model:value="userFormState.fname" />
|
||||
</a-form-item>
|
||||
@@ -39,3 +39,20 @@ withDefaults(defineProps<{ showSubmit?: boolean }>(), { showSubmit: false })
|
||||
|
||||
const { userFormState, formRef, isValidPersian, requireNationalCode, ok } = useUserPageContext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-profile-form {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-input-affix-wrapper) {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useTrackingSystemsFeature } from '@/composables/useTrackingSystemsFeatu
|
||||
import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { useSupportMessenger } from '@/composables/useSupportMessenger'
|
||||
import { isUserSupportChatEnabled, unreadSupportCount } from '@/utils/chat'
|
||||
import { openGoldDayCalculator } from '@/composables/useGoldDayCalculator'
|
||||
|
||||
export interface UserMenuAction {
|
||||
id: string
|
||||
@@ -104,6 +105,13 @@ export function useUserPage() {
|
||||
onClick: () => { editPasswordVisible.value = true },
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'goldDayCalculator',
|
||||
label: 'قیمت روز طلا',
|
||||
icon: 'vuesax-linear:chart-2',
|
||||
onClick: () => openGoldDayCalculator(),
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'notify',
|
||||
label: 'هشدار قیمت',
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
</div>
|
||||
<div class="duser-profile__field">
|
||||
<span class="duser-profile__label">شماره تلفن همراه</span>
|
||||
<span class="duser-profile__value">{{ userStore.userInfo?.mobile }}</span>
|
||||
<span class="duser-profile__value duser-profile__value--ltr">{{ userStore.userInfo?.mobile }}</span>
|
||||
</div>
|
||||
<div v-if="userStore.userInfo?.referral_id" class="duser-profile__field">
|
||||
<span class="duser-profile__label">کد معرف</span>
|
||||
<span class="duser-profile__value">{{ userStore.userInfo?.referral_id }}</span>
|
||||
<span class="duser-profile__value duser-profile__value--ltr">{{ userStore.userInfo?.referral_id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -83,17 +83,17 @@
|
||||
<span class="duser-actions__label">{{ action.label }}</span>
|
||||
<BasicIcon class="duser-actions__chevron" icon="vuesax-linear:arrow-left" size="16" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="duser-actions__item duser-actions__item--logout"
|
||||
@click="requestLogout"
|
||||
>
|
||||
<span class="duser-actions__icon duser-actions__icon--logout">
|
||||
<BasicIcon icon="vuesax-linear:logout" size="18" />
|
||||
</span>
|
||||
<span class="duser-actions__label duser-actions__label--logout">خروج</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="duser-actions__item duser-actions__item--logout"
|
||||
@click="requestLogout"
|
||||
>
|
||||
<span class="duser-actions__icon duser-actions__icon--logout">
|
||||
<BasicIcon icon="vuesax-linear:logout" size="18" />
|
||||
</span>
|
||||
<span class="duser-actions__label duser-actions__label--logout">خروج</span>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,22 +4,30 @@
|
||||
min-height: 100%;
|
||||
background: #f6f7f9;
|
||||
color: #111827;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.duser__container {
|
||||
max-width: 960px;
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 28px 36px 40px;
|
||||
padding: 28px 28px 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.duser-topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 28px;
|
||||
padding-bottom: 24px;
|
||||
margin: -4px 0 20px;
|
||||
padding: 12px 0 20px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||
background: #f6f7f9;
|
||||
|
||||
&__back {
|
||||
display: inline-flex;
|
||||
@@ -62,8 +70,9 @@
|
||||
}
|
||||
|
||||
.duser-grid {
|
||||
/* تنظیمات و خدمات عریضتر از کارت پروفایل */
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(280px, 340px);
|
||||
grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.4fr);
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
@@ -71,8 +80,9 @@
|
||||
.duser-surface {
|
||||
background: #ffffff;
|
||||
border-radius: 14px;
|
||||
padding: 24px;
|
||||
padding: 22px 20px;
|
||||
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.duser-profile {
|
||||
@@ -110,24 +120,32 @@
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
&__field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex: 1 1 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
background: #f9fafb;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&__label {
|
||||
@@ -137,11 +155,18 @@
|
||||
}
|
||||
|
||||
&__value {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
|
||||
&--ltr {
|
||||
direction: ltr;
|
||||
unicode-bidi: isolate;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&__referral-actions {
|
||||
@@ -187,6 +212,9 @@
|
||||
}
|
||||
|
||||
.duser-actions {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
|
||||
&__title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
@@ -195,20 +223,28 @@
|
||||
}
|
||||
|
||||
&__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
direction: rtl;
|
||||
|
||||
@media screen and (min-width: 1100px) {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
min-width: 0;
|
||||
padding: 14px 14px;
|
||||
border: 1px solid #f3f4f6;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
@@ -220,7 +256,8 @@
|
||||
}
|
||||
|
||||
&--logout {
|
||||
margin-top: 8px;
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 4px;
|
||||
border-color: rgba(239, 68, 68, 0.25);
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -70,13 +70,25 @@
|
||||
icon=""
|
||||
iconSize="18"
|
||||
size="large"
|
||||
@click="editPasswordVisible = true"
|
||||
@click="editPasswordVisible = true"
|
||||
>
|
||||
<div class="flex">
|
||||
<BasicIcon icon="vuesax-linear:lock" class="absolute right-8" />
|
||||
<div>تغییر رمز عبور</div>
|
||||
</div>
|
||||
</BasicButton>
|
||||
<BasicButton
|
||||
class="mt-2 w-full"
|
||||
icon=""
|
||||
iconSize="18"
|
||||
size="large"
|
||||
@click="openGoldDayCalculator"
|
||||
>
|
||||
<div class="flex">
|
||||
<BasicIcon icon="vuesax-linear:chart-2" class="absolute right-8" />
|
||||
<div>قیمت روز طلا</div>
|
||||
</div>
|
||||
</BasicButton>
|
||||
<!-- <BasicButton
|
||||
class="w-full mt-2"
|
||||
size="large"
|
||||
@@ -204,6 +216,7 @@ import { useSettingsStore } from '@/store/settingsStore'
|
||||
import { useUserStore } from '@/store/userStore'
|
||||
import { useUserPageContext } from '../composables/useUserPage'
|
||||
import MobilePageHeader from '@/layouts/components/MobilePageHeader.vue'
|
||||
import { openGoldDayCalculator } from '@/composables/useGoldDayCalculator'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { trackingSystemsEnabled } = useTrackingSystemsFeature()
|
||||
|
||||
@@ -1,38 +1,41 @@
|
||||
<template>
|
||||
<div class="dwallet">
|
||||
<div class="dwallet__container">
|
||||
<DesktopPageTopbar
|
||||
eyebrow="مالی"
|
||||
title="کیف پول"
|
||||
subtitle="واریز و برداشت وجه از کیف پول خود"
|
||||
/>
|
||||
<div class="dwallet-sticky-header">
|
||||
<DesktopPageTopbar
|
||||
eyebrow="مالی"
|
||||
title="کیف پول"
|
||||
subtitle="واریز و برداشت وجه از کیف پول خود"
|
||||
:sticky="false"
|
||||
/>
|
||||
|
||||
<div class="dwallet-toolbar">
|
||||
<div class="dwallet-toggle">
|
||||
<button
|
||||
type="button"
|
||||
class="dwallet-toggle__btn"
|
||||
:class="{ 'dwallet-toggle__btn--active': isDeposit }"
|
||||
@click="setIsDeposit(true)"
|
||||
>
|
||||
واریز
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="dwallet-toggle__btn"
|
||||
:class="{ 'dwallet-toggle__btn--active': !isDeposit }"
|
||||
@click="setIsDeposit(false)"
|
||||
>
|
||||
برداشت
|
||||
</button>
|
||||
</div>
|
||||
<div class="dwallet-toolbar">
|
||||
<div class="dwallet-toggle">
|
||||
<button
|
||||
type="button"
|
||||
class="dwallet-toggle__btn"
|
||||
:class="{ 'dwallet-toggle__btn--active': isDeposit }"
|
||||
@click="setIsDeposit(true)"
|
||||
>
|
||||
واریز
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="dwallet-toggle__btn"
|
||||
:class="{ 'dwallet-toggle__btn--active': !isDeposit }"
|
||||
@click="setIsDeposit(false)"
|
||||
>
|
||||
برداشت
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="dwallet-balance-pill">
|
||||
<span class="dwallet-balance-pill__label">موجودی کیف پول</span>
|
||||
<span class="dwallet-balance-pill__value number-ltr">
|
||||
{{ usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0) }}
|
||||
<span>ریال</span>
|
||||
</span>
|
||||
<div class="dwallet-balance-pill">
|
||||
<span class="dwallet-balance-pill__label">موجودی کیف پول</span>
|
||||
<span class="dwallet-balance-pill__value number-ltr">
|
||||
{{ usePriceWithoutType(walletStore?.walletInfo?.cash ?? 0) }}
|
||||
<span>ریال</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,6 +12,21 @@
|
||||
padding: 28px 36px 40px;
|
||||
}
|
||||
|
||||
.dwallet-sticky-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
margin: -4px 0 20px;
|
||||
padding-top: 4px;
|
||||
background: #f6f7f9;
|
||||
|
||||
.dpage-topbar {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dwallet-topbar {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 24px;
|
||||
@@ -49,6 +64,12 @@
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.dwallet-sticky-header .dwallet-toolbar {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.dwallet-toggle {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
|
||||
Reference in New Issue
Block a user