change responsive]

This commit is contained in:
2026-05-31 11:51:52 +03:30
parent 06568c5858
commit 023b8ee5be
124 changed files with 6296 additions and 4233 deletions
+8 -8
View File
@@ -5,23 +5,21 @@
:style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"
>
<!-- :style="`height:${layoutStore.header.height};line-height:${layoutStore.header.height}`"-->
<div class="flex items-center">
<div class="flex-1 flex">
<img :src="getConfigApp('avatar')" style="height: 38px" class="mr-5 mt-1" />
<div class="flex w-full min-w-0 items-center overflow-hidden">
<div class="flex min-w-0 flex-1 items-center overflow-hidden">
<img :src="getConfigApp('avatar')" style="height: 38px" class="mr-5 mt-1 shrink-0" />
<BaseButton
type="text"
icon="vuesax-linear:menu"
@click="handleCollapseSideMenu"
v-if="layoutStore.layout == 'side'"
class="shrink-0"
/>
<!-- justify-center flex-1 -->
<!-- <div class="">-->
<BasicMenu/>
<!-- </div>-->
<BasicMenu />
</div>
<!-- <BaseButton icon="vuesax-linear:menu" @click="handleCollapseSideMenu"></BaseButton>-->
<!-- <div @click="handleCollapseSideMenu">بستن منو</div>-->
<div class="flex items-center gap-3">
<div class="flex shrink-0 items-center gap-3">
<!-- <div>
<BaseButton size="small" @click="openNewTabBalance">تراز</BaseButton>
</div>-->
@@ -235,6 +233,8 @@ function clearLocalStorage() {
.@{prefix} {
padding: unset;
overflow: hidden;
max-width: 100%;
//height: unset;
//@apply flex flex-col justify-center ;
//height: 64px;
+25 -11
View File
@@ -1,8 +1,8 @@
<template>
<!-- 'admin-layout', 'beauty-scroll'-->
<ALayout
:class="[prefixCls, { 'overflow-y-scroll': !layoutStore.header.fixed }]"
class="h-screen"
:class="[prefixCls]"
class="h-screen max-w-[100vw] overflow-hidden"
>
<!-- v-model="drawerOpen"-->
<ADrawer v-if="isMobile">
@@ -36,10 +36,10 @@
<!-- <setting />-->
</ADrawer>
<!-- class="admin-layout-main beauty-scroll"-->
<ALayout>
<ALayout class="flex min-h-0 min-w-0 max-w-full flex-1 flex-col overflow-hidden">
<!-- 111111 this is header-->
<BasicHeader class="hidden md:block"></BasicHeader>
<BasicMobileHeader class="md:!hidden"></BasicMobileHeader>
<BasicHeader v-if="!isMobile"></BasicHeader>
<BasicMobileHeader v-else></BasicMobileHeader>
<!-- <admin-header
:class="[{ 'fixed-tabs': fixedTabs, 'fixed-header': fixedHeader, 'multi-page': multiPage }]"
:style="headerStyle"
@@ -55,10 +55,7 @@
v-show="fixedHeader"
></ALayoutHeader>-->
<!-- :style="`min-height: ${minHeight}px;`"-->
<ALayoutContent
class="admin-layout-content"
:class="[{ 'overflow-y-scroll': layoutStore.header.fixed }]"
>
<ALayoutContent class="admin-layout-content mobile-layout-content">
<!-- :style="{`min-height: ${minHeight}px;`}"-->
<!-- this is content-->
<!-- <Btn1></Btn1>-->
@@ -82,7 +79,7 @@
<RouterView v-slot="{ Component }">
<!-- mode="out-in"-->
<!-- <transition>-->
<component :is="Component" />
<component :is="Component" class="h-full min-h-0" />
<!-- </transition>-->
</RouterView>
</ALayoutContent>
@@ -91,6 +88,7 @@
&lt;!&ndash; <page-footer :link-list="footerLinks" :copyright="copyright" />&ndash;&gt;
</LayoutFooter>-->
</ALayout>
<BasicMobileBottomNav v-if="isMobile" />
<BaseSettings />
</ALayout>
</template>
@@ -102,7 +100,8 @@ import { usePrefix } from '@core/composable/usePrefix'
import { useLayoutStore } from '@core/stores/layout'
import { isMobile } from '@core/utils/is'
import BaseSettings from '@core/layouts/BaseSettings.vue'
import BasicMobileheader from "@/components/BasicMobileHeader.vue";
import BasicMobileHeader from '@/components/BasicMobileHeader.vue'
import BasicMobileBottomNav from '@/components/BasicMobileBottomNav.vue'
const { prefixCls } = usePrefix('basic-layout')
const layoutStore = useLayoutStore()
</script>
@@ -115,6 +114,21 @@ const layoutStore = useLayoutStore()
transition: all 0.2s;
}
.admin-layout-content {
width: 100%;
max-width: 100%;
flex: 1;
min-height: 0;
min-width: 0;
overflow: hidden;
}
@media (max-width: 767px) {
.mobile-layout-content {
padding-bottom: calc(72px + env(safe-area-inset-bottom));
}
}
/* .fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
+232 -126
View File
@@ -1,84 +1,174 @@
<template>
<div :class="[prefixCls, `${layoutStore.layout}-menu`]">
<!-- :selectedKeys="selectedKeys"-->
<AMenu :mode="layoutStore.sideMenu.mode" v-model:selectedKeys="selectedKeys" >
<template v-for="item in menu">
<BasicSubMenu
<div
:class="[
prefixCls,
layoutStore.layout === 'top'
? 'top-menu-nav flex min-w-0 flex-1 items-center'
: `${layoutStore.layout}-menu`
]"
>
<div
ref="menuScrollRef"
:class="[isTopMenu ? 'top-menu top-menu-scroll scroll-none min-w-0 flex-1' : '']"
@scroll="updateScrollArrows"
>
<AMenu
:mode="layoutStore.sideMenu.mode"
v-model:selectedKeys="selectedKeys"
:disabled-overflow="isTopMenu"
>
<template v-for="item in menu">
<BasicSubMenu
v-if="!item.meta?.hideChildren && item.children && item.children.length > 0"
:menu-item="item"
:key="item.name"
:level="1"
/>
<!-- <AMenuItem
v-else
:disabled="item.meta.disabled ?? false"
:key="item.name"
@click="handleClickMenuItem(item)"
>
<template #icon>
<BaseIcon :size="menuIconSize" v-if="item.meta.icon" :icon="item.meta.icon" />
</template>
<span>{{ item.meta.title ?? item.name }}</span>
&lt;!&ndash; <template #title>1111</template>&ndash;&gt;
&lt;!&ndash; <div class="flex"></div>&ndash;&gt;
</AMenuItem>-->
<BaseMenuItem v-else :item="item" :key="item.name"/>
</template>
/>
<BaseMenuItem v-else :item="item" :key="item.name" />
</template>
</AMenu>
</div>
<button
v-if="isTopMenu"
v-show="showLeftArrow"
type="button"
class="top-menu-nav__btn top-menu-nav__btn--left"
aria-label="مشاهده ادامه منو"
@click="scrollMenu('left')"
>
<BaseIcon icon="vuesax-linear:arrow-left-1" size="18" />
</button>
</AMenu >
<button
v-if="isTopMenu"
v-show="showRightArrow"
type="button"
class="top-menu-nav__btn top-menu-nav__btn--right"
aria-label="بازگشت در منو"
@click="scrollMenu('right')"
>
<BaseIcon icon="vuesax-linear:arrow-right-1" size="18" />
</button>
</div>
</template>
<script lang="ts" setup>
import {usePrefix} from '@core/composable/usePrefix'
//import { menuIconSize } from '@root/config'
import {computed} from 'vue'
import type {ComputedRef} from 'vue'
import {sortBy} from 'lodash'
//import { routes } from '@/core/router/routes'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import type { ComputedRef } from 'vue'
import { sortBy } from 'lodash'
import { useResizeObserver } from '@vueuse/core'
import { usePrefix } from '@core/composable/usePrefix'
import BasicSubMenu from '@core/layouts/composnts/BasicSubMenu.vue'
import {useLayoutStore} from '@core/stores/layout'
import { useLayoutStore } from '@core/stores/layout'
import BaseMenuItem from '@core/layouts/composnts/BaseMenuItem.vue'
import type {RouteRecordName, RouteRecordRaw} from 'vue-router'
import {useRouterStore} from '@core/stores/router'
import type { RouteRecordName, RouteRecordRaw } from 'vue-router'
import { useRouterStore } from '@core/stores/router'
const {prefixCls} = usePrefix('basic-menu')
const SCROLL_STEP = 180
const EDGE_THRESHOLD = 2
const { prefixCls } = usePrefix('basic-menu')
const layoutStore = useLayoutStore()
const currentRoute = useRoute()
const selectedKeys = ref<(RouteRecordName | undefined)[]>([])
const routerStore = useRouterStore()
/*const selectedKeys = computed(() => {
const path: (RouteRecordName | undefined)[] = []
const menuScrollRef = ref<HTMLElement | null>(null)
const showLeftArrow = ref(false)
const showRightArrow = ref(false)
const rtlScrollMode = ref<'ltr' | 'negative' | 'positive'>('ltr')
function walkInRoutes(routes: RouteRecordRaw[]) {
for (const route of routes) {
if (route.name == currentRoute.name) {
path.push(route.name)
return true
}
if (route.children && route.children.length > 0) {
path.push(route.name)
const res = walkInRoutes(route.children)
if (!res) {
path.pop()
} else {
return true
}
}
}
return false
const isTopMenu = computed(() => layoutStore.layout === 'top')
function detectRtlScrollMode(el: HTMLElement): 'ltr' | 'negative' | 'positive' {
if (getComputedStyle(el).direction !== 'rtl') return 'ltr'
const max = el.scrollWidth - el.clientWidth
if (max <= 0) return 'ltr'
const original = el.scrollLeft
el.scrollLeft = 1
const mode = el.scrollLeft === 0 ? 'positive' : 'negative'
el.scrollLeft = original
return mode
}
function ensureRtlScrollMode(el: HTMLElement) {
if (rtlScrollMode.value !== 'ltr') return
rtlScrollMode.value = detectRtlScrollMode(el)
}
function getNormalizedScroll(el: HTMLElement) {
const max = Math.max(0, el.scrollWidth - el.clientWidth)
if (max <= 1) return { max: 0, current: 0 }
ensureRtlScrollMode(el)
let current = el.scrollLeft
if (rtlScrollMode.value === 'negative') {
current = Math.abs(current)
} else if (rtlScrollMode.value === 'positive') {
current = max - current
}
walkInRoutes(routes)
return {
max,
current: Math.max(0, Math.min(max, current))
}
}
return path
})*/
function setNormalizedScroll(el: HTMLElement, normalized: number) {
const max = Math.max(0, el.scrollWidth - el.clientWidth)
const value = Math.max(0, Math.min(max, normalized))
if (rtlScrollMode.value === 'negative') {
el.scrollTo({ left: -value, behavior: 'smooth' })
return
}
if (rtlScrollMode.value === 'positive') {
el.scrollTo({ left: max - value, behavior: 'smooth' })
return
}
el.scrollTo({ left: value, behavior: 'smooth' })
}
function updateScrollArrows() {
const el = menuScrollRef.value
if (!el || !isTopMenu.value) {
showLeftArrow.value = false
showRightArrow.value = false
return
}
const { max, current } = getNormalizedScroll(el)
if (max <= 1) {
showLeftArrow.value = false
showRightArrow.value = false
return
}
showLeftArrow.value = current < max - EDGE_THRESHOLD
showRightArrow.value = current > EDGE_THRESHOLD
}
function scrollMenu(side: 'left' | 'right') {
const el = menuScrollRef.value
if (!el) return
const { max, current } = getNormalizedScroll(el)
if (max <= 1) return
setNormalizedScroll(el, side === 'left' ? current + SCROLL_STEP : current - SCROLL_STEP)
scheduleArrowUpdate()
}
function scheduleArrowUpdate() {
window.setTimeout(updateScrollArrows, 350)
}
onMounted(() => {
//console.log('aaa')
const path: (RouteRecordName | undefined)[] = []
function walkInRoutes(routes: RouteRecordRaw[]) {
@@ -103,8 +193,7 @@ onMounted(() => {
walkInRoutes(routerStore.routes)
selectedKeys.value = path
// return path
nextTick(updateScrollArrows)
})
const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
@@ -112,33 +201,100 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
return item.meta?.order
}).filter((item) => !item.meta?.hideMenu)
})
watch(menu, () => nextTick(updateScrollArrows), { deep: true })
watch(isTopMenu, () => {
rtlScrollMode.value = 'ltr'
nextTick(updateScrollArrows)
})
watch(
() => currentRoute.name,
() => nextTick(updateScrollArrows)
)
useResizeObserver(menuScrollRef, () => {
updateScrollArrows()
})
</script>
<style lang="less">
@prefix: ~ '@{prefixCls}-basic-menu';
.@{prefix} {
&.top-menu-nav {
position: relative;
min-width: 0;
flex: 1;
}
&.top-menu {
@apply pr-5;
//display: inline-flex;
.ant-menu {
// @apply gap-3;
//@apply flex-inline;
//@apply grid;
.top-menu-nav__btn {
position: absolute;
top: 50%;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 36px;
border: none;
border-radius: 6px;
background: #fff;
color: @primary-color;
cursor: pointer;
transform: translateY(-50%);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
transition: background-color 0.2s ease;
&:hover {
background: lighten(@primary-color, 53%);
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu {
&--left {
left: 0;
padding-right: 4px;
background: linear-gradient(to right, #fff 55%, rgba(255, 255, 255, 0.85));
}
&--right {
right: 0;
padding-left: 4px;
background: linear-gradient(to left, #fff 55%, rgba(255, 255, 255, 0.85));
}
}
&.top-menu,
.top-menu-scroll {
overflow-x: auto;
overflow-y: hidden;
flex: 1;
min-width: 0;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.ant-menu {
display: flex;
flex-wrap: nowrap;
width: max-content;
border-bottom: none;
padding-top: 4px;
padding-bottom: 4px;
}
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item,
.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu {
padding: 0 5px;
}
/*TODO check for line bottom menu*/
.ant-menu-horizontal > .ant-menu-item::after, .ant-menu-horizontal > .ant-menu-submenu::after {
.ant-menu-horizontal > .ant-menu-item::after,
.ant-menu-horizontal > .ant-menu-submenu::after {
right: 0 !important;
left: 0 !important;
}
.ant-menu-horizontal > .ant-menu-item-selected::after, .ant-menu-horizontal > .ant-menu-submenu-selected::after {
.ant-menu-horizontal > .ant-menu-item-selected::after,
.ant-menu-horizontal > .ant-menu-submenu-selected::after {
position: absolute;
bottom: 0;
border-bottom: 2px solid @primary-color;
@@ -147,42 +303,16 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
}
.ant-menu-item-selected {
//background-color: red;
background-color: lighten(@primary-color, 53%);
/*.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:after{
border-bottom: 5px solid red;
background-color: red;
}*/
/*& > .ant-menu-item::after {
position: absolute;
right: 20px;
bottom: 0;
left: 20px;
border-bottom: 2px solid #0052d9;
transition: border-color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
content: '';
}*/
}
.ant-menu-item {
margin-left: 12px;
}
/*.ant-menu-item:not(.ant-menu-item-disabled):not(.ant-menu-item-selected):hover,
.ant-menu-submenu .ant-menu-submenu-title:not(.ant-menu-item-disabled):hover
{
border-radius:unset !important;
}*/
//display: inline-flex;
//justify-content: center;
}
.ant-menu-item, .ant-menu-submenu-title {
.ant-menu-item,
.ant-menu-submenu-title {
display: flex;
align-items: center;
@@ -209,20 +339,10 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
transition: background-color 0.9ms linear;
border-radius: var(--border-radius-base);
color: unset !important;
//color: red;
background-color: #f3f3f3 !important;
}
}
/* .ant-menu-inline-collapsed{
.ant-menu-item,.ant-menu-submenu-title{
//display: block;
}
}*/
.ant-menu {
padding: 10px;
user-select: none;
@@ -232,18 +352,14 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
font-size: 15px;
}
.ant-menu-item, .ant-menu-submenu-title {
//padding: unset !important;
@apply px-2 !important;
.ant-menu-item,
.ant-menu-submenu-title {
@apply px-2 !important;
}
.ant-menu.ant-menu-inline-collapsed > .ant-menu-item .ant-menu-item-icon + span,
.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu .ant-menu-item-icon + span {
display: none !important;
/*display: flex !important;
flex-direction: column !important;
justify-content: center !important;*/
}
.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu > .ant-menu-submenu-title {
@@ -253,8 +369,6 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
justify-content: center !important;
}
//,.ant-menu.ant-menu-inline-collapsed > .ant-menu-submenu
.ant-menu.ant-menu-inline-collapsed > .ant-menu-item {
padding: 0 !important;
display: flex !important;
@@ -266,14 +380,7 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
height: 36px;
}
/*.ant-menu-submenu-title,.ant-menu-submenu-arrow{
//color: #f3f3f3 !important;
}*/
.ant-menu-item.ant-menu-item-selected {
//border-radius: var(--border-radius-base);
&::after {
content: unset;
}
@@ -287,6 +394,5 @@ const menu: ComputedRef<RouteRecordRaw[]> = computed(() => {
.ant-menu-vertical > .ant-menu-item {
height: 36px;
}
}
</style>