diff --git a/src/config/defaultKhanegiUiConfig.ts b/src/config/defaultKhanegiUiConfig.ts index 37ad871..ad0bb46 100644 --- a/src/config/defaultKhanegiUiConfig.ts +++ b/src/config/defaultKhanegiUiConfig.ts @@ -178,6 +178,64 @@ export function mergeNavItemsFromSample( return kept.map((item, index) => ({ ...item, order: index + 1 })) } +/** گزینه‌های قابل انتخاب برای هر جایگاه منوی پایین (سایدبار + خرید و فروش) */ +export function getKhanegiBottomNavChoices(): KhanegiNavItem[] { + const sample = getKhanegiNavSample() + const byId = new Map() + for (const item of sample.bottom) byId.set(item.id, cloneJson(item)) + for (const item of sample.sidebar) { + if (item.id === 'home') continue // خانه همان FAB وسط است + if (!byId.has(item.id)) byId.set(item.id, cloneJson(item)) + } + return Array.from(byId.values()) +} + +/** دقیقاً ۴ جایگاه؛ انتخاب ذخیره‌شده از کاتالوگ، بقیه از پیش‌فرض پر می‌شود */ +export function mergeBottomNavSlots( + stored: KhanegiNavItem[] | undefined, +): KhanegiNavItem[] { + const choices = getKhanegiBottomNavChoices() + const defaults = getKhanegiNavSample().bottom.slice(0, MAX_BOTTOM_NAV_SLOTS) + const byId = new Map(choices.map((item) => [item.id, item])) + const slots: KhanegiNavItem[] = [] + const seen = new Set() + const storedList = Array.isArray(stored) ? stored : [] + + for (const prev of storedList) { + if (slots.length >= MAX_BOTTOM_NAV_SLOTS) break + if (!prev?.id || seen.has(prev.id)) continue + const base = byId.get(prev.id) + if (!base) continue + seen.add(prev.id) + slots.push({ + ...base, + label: prev.label || base.label, + icon: prev.icon || base.icon, + visible: typeof prev.visible === 'boolean' ? prev.visible : true, + query: prev.query ?? base.query, + }) + } + + for (const item of defaults) { + if (slots.length >= MAX_BOTTOM_NAV_SLOTS) break + if (seen.has(item.id)) continue + seen.add(item.id) + slots.push({ ...item, visible: true }) + } + + for (const item of choices) { + if (slots.length >= MAX_BOTTOM_NAV_SLOTS) break + if (seen.has(item.id)) continue + seen.add(item.id) + slots.push({ ...item, visible: true }) + } + + return slots.slice(0, MAX_BOTTOM_NAV_SLOTS).map((item, index) => ({ + ...item, + order: index + 1, + })) +} + export function applyKhanegiNavSample(app: { navigation?: any } | null | undefined) { if (!app || typeof app !== 'object') return const sample = getKhanegiNavSample() @@ -191,11 +249,7 @@ export function applyKhanegiNavSample(app: { navigation?: any } | null | undefin return } app.navigation.sidebar = mergeNavItemsFromSample(sample.sidebar, app.navigation.sidebar) - app.navigation.bottom = mergeNavItemsFromSample( - sample.bottom.slice(0, MAX_BOTTOM_NAV_SLOTS), - app.navigation.bottom, - MAX_BOTTOM_NAV_SLOTS, - ).slice(0, MAX_BOTTOM_NAV_SLOTS) + app.navigation.bottom = mergeBottomNavSlots(app.navigation.bottom) if (!app.navigation.sidebarReferral || typeof app.navigation.sidebarReferral !== 'object') { app.navigation.sidebarReferral = sample.sidebarReferral } diff --git a/src/pages/UiConfigPage.vue b/src/pages/UiConfigPage.vue index 1f648b5..64fb510 100644 --- a/src/pages/UiConfigPage.vue +++ b/src/pages/UiConfigPage.vue @@ -9,8 +9,10 @@ import { applyKhanegiNavSample, cloneKhanegiAppSnapshot, createDefaultKhanegiUiConfig, + getKhanegiBottomNavChoices, MAX_BOTTOM_NAV_SLOTS, normalizeKhanegiUiConfigForClient, + type KhanegiNavItem, } from '@/config/defaultKhanegiUiConfig' import { applyKhanegiPreset, @@ -40,6 +42,7 @@ const jsonError = ref('') const selectedPreset = ref('navy-gold') const previewMode = ref<'dark' | 'light'>('dark') const bottomDragFrom = ref(null) +const sidebarDragFrom = ref(null) const config = reactive(createDefaultKhanegiUiConfig()) as ReturnType< typeof createDefaultKhanegiUiConfig @@ -53,16 +56,62 @@ function syncBottomNavOrder() { }) } +function syncSidebarNavOrder() { + config.app.navigation.sidebar.forEach((item, index) => { + item.order = index + 1 + }) +} + function applySharedNavSample() { applyKhanegiNavSample(config.app) if (config.defaultSnapshot?.app) applyKhanegiNavSample(config.defaultSnapshot.app) + syncSidebarNavOrder() syncBottomNavOrder() } +const bottomNavChoices = computed(() => getKhanegiBottomNavChoices()) + const visibleBottomCount = computed( () => config.app.navigation.bottom.filter((item) => item.visible).length, ) +function bottomSlotOptions(slotIndex: number) { + const used = new Set( + config.app.navigation.bottom + .map((item, i) => (i === slotIndex ? null : item.id)) + .filter(Boolean) as string[], + ) + return bottomNavChoices.value.map((choice) => ({ + value: choice.id, + label: `${choice.label} (${choice.route})`, + disabled: used.has(choice.id), + })) +} + +function onBottomSlotMenuChange(slotIndex: number, choiceId: string) { + const choice = bottomNavChoices.value.find((item) => item.id === choiceId) + if (!choice) return + const list = config.app.navigation.bottom + const current = list[slotIndex] + if (!current || current.id === choiceId) return + + const otherIndex = list.findIndex((item, i) => i !== slotIndex && item.id === choiceId) + if (otherIndex >= 0) { + list[otherIndex] = { + ...current, + order: otherIndex + 1, + } as KhanegiNavItem + } + + list[slotIndex] = { + ...choice, + label: choice.label, + visible: current.visible, + order: slotIndex + 1, + } as KhanegiNavItem + syncBottomNavOrder() +} + function onBottomVisibleChange(item: { visible: boolean }, checked: boolean) { if (checked && visibleBottomCount.value >= MAX_BOTTOM_NAV_SLOTS && !item.visible) { message.warning('منوی پایین فقط ۴ جایگاه دارد') @@ -98,6 +147,33 @@ function onBottomDragEnd() { syncBottomNavOrder() } +function onSidebarDragStart(index: number, e: DragEvent) { + sidebarDragFrom.value = index + e.dataTransfer?.setData('text/plain', String(index)) + if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move' +} + +function onSidebarDragOver(index: number, e: DragEvent) { + e.preventDefault() + const from = sidebarDragFrom.value + if (from == null || from === index) return + const list = config.app.navigation.sidebar + const [moved] = list.splice(from, 1) + list.splice(index, 0, moved) + sidebarDragFrom.value = index + syncSidebarNavOrder() +} + +function onSidebarDrop() { + sidebarDragFrom.value = null + syncSidebarNavOrder() +} + +function onSidebarDragEnd() { + sidebarDragFrom.value = null + syncSidebarNavOrder() +} + const hasDefaultSnapshot = computed(() => Boolean(config.defaultSnapshot)) const isCustom = computed(() => selectedPreset.value === 'custom') @@ -572,7 +648,26 @@ onMounted(load)

منوی کناری

-