19 移动端模式开发 - udo-bit/naive_admin_pro GitHub Wiki
- 开发视图断点查询可组合式api
- 基本的移动端布局
我们在开发中和视图大小相关的代名词像是:xl、lg、md等等。 我们这里定义的视图大小范围值也是一样的。
在composables文件夹下创建一个query-breakpoints的文件。 然后首先定义我们的断点枚举值:
export const breakpointsEnum = {
xl: 1600,
lg: 1199,
md: 991,
sm: 767,
xs: 575,
}
接下来我们通过vueuse中的useBreakpoints�来实现我们响应式的值的功能。 在query-breakpoints中:
export const useQueryBreakpoints = () => {
const breakpoints = reactive(useBreakpoints(breakpointsEnum))
const isMobile = breakpoints.smaller('sm')
const isPad = breakpoints.between('sm', 'md')
const isDesktop = breakpoints.greater('md')
return { breakpoints, isMobile, isPad, isDesktop }
}
分别获取到我们的三种模式: 移动端布局、pad端、桌面端
接下来我们先创建基础的移动端的布局模式,在layouts中创建一个mobile-layout的布局文件夹,然后创建一个index.vue的文件。我们直接拷贝top-layout的布局进行改造。
<script lang="ts" setup>
import { LayoutContent, Logo, Title } from '~/layouts/common'
const props = withDefaults(defineProps<{
headerHeight?: number
logo?: string
title?: string
inverted?: boolean
}>(), {
headerHeight: 48,
})
const headerHeightVar = computed(() => `${props.headerHeight}px`)
</script>
<template>
<n-layout class="h-screen" style="--n-color:var(--pro-admin-layout-content-bg)">
<n-layout-header
:inverted="inverted"
class="pro-admin-mix-layout-header flex justify-between items-center px-4"
>
<div class="flex items-center">
<Logo :src="logo" />
<Title :title="title" />
</div>
<slot name="headerRight">
<div>
右侧
</div>
</slot>
</n-layout-header>
<LayoutContent content-style="padding: 24px;">
<slot />
</LayoutContent>
</n-layout>
</template>
<style scoped>
.pro-admin-mix-layout-header{
height: v-bind(headerHeightVar);
}
</style>
当我们收起来的时候,我们不需要让标题再展示在header中,占用空间。 所以我们删掉先把title删除掉。 侧边栏我们使用抽屉代替。如下:
<script lang="ts" setup>
import { LayoutContent, Logo, Title } from '~/layouts/common'
const props = withDefaults(defineProps<{
headerHeight?: number
logo?: string
title?: string
inverted?: boolean
visible?: boolean
}>(), {
headerHeight: 48,
visible: false,
})
defineEmits(['update:visible'])
const headerHeightVar = computed(() => `${props.headerHeight}px`)
</script>
<template>
<n-layout class="h-screen" style="--n-color:var(--pro-admin-layout-content-bg)">
<n-layout-header
:inverted="inverted"
class="pro-admin-mix-layout-header flex justify-between items-center px-4"
>
<div class="flex items-center">
<Logo :src="logo" />
</div>
<slot name="headerRight">
<div>
右侧
</div>
</slot>
</n-layout-header>
<LayoutContent content-style="padding: 24px;">
<slot />
</LayoutContent>
</n-layout>
<n-drawer
:show="visible"
:width="240"
placement="left"
@update:show="(val) => $emit('update:visible', val)"
>
<n-drawer-content>
《斯通纳》是美国作家约翰·威廉姆斯在 1965 年出版的小说。
</n-drawer-content>
</n-drawer>
</template>
<style scoped>
.pro-admin-mix-layout-header{
height: v-bind(headerHeightVar);
}
</style>
接下来我们在base-layout中支持一下我们的移动端布局
<script lang="ts" setup>
import MixLayout from '../mix-layout/index.vue'
import SideLayout from '../side-layout/index.vue'
import TopLayout from '../top-layout/index.vue'
import MobileLayout from '../mobile-layout/index.vue'
const appStore = useAppStore()
const { layout } = storeToRefs(appStore)
const { isMobile } = useQueryBreakpoints()
</script>
<template>
<MobileLayout
v-if="isMobile"
:logo="layout.logo"
:title="layout.title"
>
<template #headerRight>
<div>
测试右侧插槽
</div>
</template>
<router-view />
</MobileLayout>
<template v-else>
<MixLayout
v-if="layout.layout === 'mix'"
v-model:collapsed="layout.collapsed"
:logo="layout.logo"
:title="layout.title"
:show-sider-trigger="layout.showSiderTrigger"
:sider-width="layout.siderWidth"
:sider-collapsed-width="layout.siderCollapsedWidth"
>
<template #headerRight>
<div>
测试右侧插槽
</div>
</template>
<router-view />
</MixLayout>
<SideLayout
v-if="layout.layout === 'side'"
v-model:collapsed="layout.collapsed"
:logo="layout.logo"
:title="layout.title"
:show-sider-trigger="layout.showSiderTrigger"
:sider-width="layout.siderWidth"
:sider-collapsed-width="layout.siderCollapsedWidth"
>
<template #headerRight>
<div>
测试右侧插槽
</div>
</template>
<router-view />
</SideLayout>
<TopLayout
v-if="layout.layout === 'top'"
v-model:collapsed="layout.collapsed"
:logo="layout.logo"
:title="layout.title"
:show-sider-trigger="layout.showSiderTrigger"
:sider-width="layout.siderWidth"
:sider-collapsed-width="layout.siderCollapsedWidth"
>
<template #headerRight>
<div>
测试右侧插槽
</div>
</template>
<router-view />
</TopLayout>
</template>
</template>
接下来我们实现一下展开收起部分 在stores/app.ts中做如下新增:
import { layoutThemeConfig } from '~/config/layout-theme'
export const useAppStore = defineStore('app', () => {
const defaultTheme = import.meta.env.DEV ? layoutThemeConfig : useLayoutTheme()
const layout = reactive(unref(defaultTheme))
const visible = ref(false)
const updateVisible = (val: boolean) => {
visible.value = val
}
const updateCollapsed = (val: boolean) => {
layout.collapsed = val
}
return {
layout,
visible,
updateVisible,
updateCollapsed,
}
})
然后再base-layout中导入:
const { layout, visible } = storeToRefs(appStore)
在布局中使用:
<MobileLayout
v-if="isMobile"
v-model:visible="visible"
:logo="layout.logo"
:title="layout.title"
>
<template #headerRight>
<div>
测试右侧插槽
</div>
</template>
<router-view />
</MobileLayout>
接下来我们在mobile-layout.vue中配置展开的图标: 我们的图标库使用官方给我们提供的库地址, 首先我们先来安装一下图标库,为了方便我们还是使用antd的图标库:
pnpm add -D @vicons/antd
然后在mobile-layout中使用:
<script lang="ts" setup>
import { MenuUnfoldOutlined } from '@vicons/antd'
const emit = defineEmits(['update:visible'])
const onShow = () => {
emit('update:visible', true)
}
</script>
<template>
<div class="flex items-center">
<Logo :src="logo" size="26" />
<n-icon size="20" class="ml-2" @click="onShow">
<MenuUnfoldOutlined />
</n-icon>
</div>
</template>
测试实现效果