@@ -25,8 +28,7 @@
+
+
+
+
diff --git a/src/composables/useGoldDayCalculator.ts b/src/composables/useGoldDayCalculator.ts
new file mode 100644
index 0000000..39b6aef
--- /dev/null
+++ b/src/composables/useGoldDayCalculator.ts
@@ -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
+}
diff --git a/src/layouts/desktop/styles/desktop-page-common.less b/src/layouts/desktop/styles/desktop-page-common.less
index ccd26ea..8e20870 100644
--- a/src/layouts/desktop/styles/desktop-page-common.less
+++ b/src/layouts/desktop/styles/desktop-page-common.less
@@ -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;
diff --git a/src/utils/useDraggableFloatingButton.ts b/src/utils/useDraggableFloatingButton.ts
index 6f28c07..6c2c515 100644
--- a/src/utils/useDraggableFloatingButton.ts
+++ b/src/utils/useDraggableFloatingButton.ts
@@ -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
diff --git a/src/views/base/gold/desktop/styles/desktop-gold.less b/src/views/base/gold/desktop/styles/desktop-gold.less
index d53b767..e095a0f 100644
--- a/src/views/base/gold/desktop/styles/desktop-gold.less
+++ b/src/views/base/gold/desktop/styles/desktop-gold.less
@@ -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;
diff --git a/src/views/base/home/desktop/styles/desktop-home.less b/src/views/base/home/desktop/styles/desktop-home.less
index 72d8973..94e7c23 100644
--- a/src/views/base/home/desktop/styles/desktop-home.less
+++ b/src/views/base/home/desktop/styles/desktop-home.less
@@ -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;
diff --git a/src/views/base/user/components/UserProfileForm.vue b/src/views/base/user/components/UserProfileForm.vue
index 7b92ab4..580ad79 100644
--- a/src/views/base/user/components/UserProfileForm.vue
+++ b/src/views/base/user/components/UserProfileForm.vue
@@ -1,5 +1,5 @@
-
+
@@ -39,3 +39,20 @@ withDefaults(defineProps<{ showSubmit?: boolean }>(), { showSubmit: false })
const { userFormState, formRef, isValidPersian, requireNationalCode, ok } = useUserPageContext()
+
+
diff --git a/src/views/base/user/composables/useUserPage.ts b/src/views/base/user/composables/useUserPage.ts
index 6e64e0c..ba134d5 100644
--- a/src/views/base/user/composables/useUserPage.ts
+++ b/src/views/base/user/composables/useUserPage.ts
@@ -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: 'هشدار قیمت',
diff --git a/src/views/base/user/desktop/DesktopUserView.vue b/src/views/base/user/desktop/DesktopUserView.vue
index 14590f6..6c22f07 100644
--- a/src/views/base/user/desktop/DesktopUserView.vue
+++ b/src/views/base/user/desktop/DesktopUserView.vue
@@ -31,11 +31,11 @@