fix(ui): cap khanegi bottom nav at 4 slots with drag reorder

Merge all versions onto the shared nav sample so extra items cannot stay enabled.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-24 11:42:42 +03:30
co-authored by Cursor
parent 349031d3bc
commit 238fb7966c
2 changed files with 186 additions and 4 deletions
+100 -4
View File
@@ -2,12 +2,14 @@
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { message, Modal } from 'ant-design-vue'
import { ArrowRightOutlined } from '@ant-design/icons-vue'
import { ArrowRightOutlined, HolderOutlined } from '@ant-design/icons-vue'
import api from '@/api/http'
import VersionPageTabs from '@/components/VersionPageTabs.vue'
import {
applyKhanegiNavSample,
cloneKhanegiAppSnapshot,
createDefaultKhanegiUiConfig,
MAX_BOTTOM_NAV_SLOTS,
normalizeKhanegiUiConfigForClient,
} from '@/config/defaultKhanegiUiConfig'
import {
@@ -37,6 +39,7 @@ const advancedJson = ref('')
const jsonError = ref('')
const selectedPreset = ref<KhanegiPresetId>('navy-gold')
const previewMode = ref<'dark' | 'light'>('dark')
const bottomDragFrom = ref<number | null>(null)
const config = reactive(createDefaultKhanegiUiConfig()) as ReturnType<
typeof createDefaultKhanegiUiConfig
@@ -44,6 +47,57 @@ const config = reactive(createDefaultKhanegiUiConfig()) as ReturnType<
defaultSnapshot?: ReturnType<typeof cloneKhanegiAppSnapshot>
}
function syncBottomNavOrder() {
config.app.navigation.bottom.forEach((item, index) => {
item.order = index + 1
})
}
function applySharedNavSample() {
applyKhanegiNavSample(config.app)
if (config.defaultSnapshot?.app) applyKhanegiNavSample(config.defaultSnapshot.app)
syncBottomNavOrder()
}
const visibleBottomCount = computed(
() => config.app.navigation.bottom.filter((item) => item.visible).length,
)
function onBottomVisibleChange(item: { visible: boolean }, checked: boolean) {
if (checked && visibleBottomCount.value >= MAX_BOTTOM_NAV_SLOTS && !item.visible) {
message.warning('منوی پایین فقط ۴ جایگاه دارد')
return
}
item.visible = checked
}
function onBottomDragStart(index: number, e: DragEvent) {
bottomDragFrom.value = index
e.dataTransfer?.setData('text/plain', String(index))
if (e.dataTransfer) e.dataTransfer.effectAllowed = 'move'
}
function onBottomDragOver(index: number, e: DragEvent) {
e.preventDefault()
const from = bottomDragFrom.value
if (from == null || from === index) return
const list = config.app.navigation.bottom
const [moved] = list.splice(from, 1)
list.splice(index, 0, moved)
bottomDragFrom.value = index
syncBottomNavOrder()
}
function onBottomDrop() {
bottomDragFrom.value = null
syncBottomNavOrder()
}
function onBottomDragEnd() {
bottomDragFrom.value = null
syncBottomNavOrder()
}
const hasDefaultSnapshot = computed(() => Boolean(config.defaultSnapshot))
const isCustom = computed(() => selectedPreset.value === 'custom')
@@ -129,6 +183,7 @@ function applyAdvancedJson() {
if (keepSnapshot && !config.defaultSnapshot) {
config.defaultSnapshot = keepSnapshot
}
applySharedNavSample()
ensureSchemes()
selectedPreset.value = resolvePresetId(config)
jsonError.value = ''
@@ -180,6 +235,7 @@ async function load() {
Object.assign(config, createDefaultKhanegiUiConfig())
}
}
applySharedNavSample()
ensureSchemes()
if (!config.defaultSnapshot) {
config.defaultSnapshot = cloneKhanegiAppSnapshot(config)
@@ -285,6 +341,7 @@ function resetToDefault() {
const snap = config.defaultSnapshot!
config.app = JSON.parse(JSON.stringify(snap.app))
config.pages = JSON.parse(JSON.stringify(snap.pages || {}))
applySharedNavSample()
ensureSchemes()
selectedPreset.value = resolvePresetId(config)
previewMode.value = config.app.theme.mode === 'light' ? 'light' : 'dark'
@@ -523,8 +580,30 @@ onMounted(load)
</section>
<section class="form-section">
<h3>منوی پایین موبایل</h3>
<div v-for="item in config.app.navigation.bottom" :key="item.id" class="nav-row">
<a-switch v-model:checked="item.visible" />
<p class="page-sub" style="margin-bottom: 12px">
۴ جایگاه ثابت — بیشتر در نوار پایین جا نمی‌شود. با دستگیره، ترتیب را عوض کنید.
</p>
<div
v-for="(item, index) in config.app.navigation.bottom"
:key="item.id"
class="nav-row"
:class="{ 'is-dragging': bottomDragFrom === index }"
@dragover.prevent="onBottomDragOver(index, $event)"
@drop.prevent="onBottomDrop"
>
<span
class="drag-handle"
draggable="true"
title="جابه‌جایی"
@dragstart="onBottomDragStart(index, $event)"
@dragend="onBottomDragEnd"
>
<HolderOutlined />
</span>
<a-switch
:checked="item.visible"
@change="(v: boolean) => onBottomVisibleChange(item, v)"
/>
<a-input v-model:value="item.label" />
<span class="nav-route" dir="ltr">{{ item.route }}</span>
</div>
@@ -807,12 +886,29 @@ onMounted(load)
.nav-row {
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-columns: auto auto 1fr auto;
gap: 10px;
align-items: center;
margin-bottom: 10px;
}
.nav-row.is-dragging {
opacity: 0.55;
}
.drag-handle {
display: inline-flex;
align-items: center;
cursor: grab;
color: var(--ink-soft);
padding: 4px;
touch-action: none;
}
.drag-handle:active {
cursor: grabbing;
}
.nav-route {
font-size: 12px;
color: var(--ink-soft);