新增 ledger.css 设计令牌与桌面端布局,AppRail 左导航、PageHead 桌面页头、ResultsColumn 常驻结果栏;做菜/菜谱/发现/我的/设置/帮助/关于各页做桌面窄屏双形态适配,窄屏仍走原有底部标签栏与弹层交互。
119 lines
4.3 KiB
Vue
119 lines
4.3 KiB
Vue
<script lang="ts" setup>
|
|
/**
|
|
* 桌面端左侧固定导航栏(原型 prototype/cook-desktop 的 .app-rail)。
|
|
* 窄屏由 .ledger-rail 的 display:none 隐藏,导航仍走底部标签栏。
|
|
*/
|
|
/**
|
|
* 左导航挂在 app.vue 上,不在页面组件内,
|
|
* 而 useRoute() 在页面外拿到的是 nuxtApp._route,直接进页面(如 /library)时还是旧路径。
|
|
* 所以这里改用 router.currentRoute,它始终是当前路由。
|
|
*/
|
|
const router = useRouter()
|
|
const currentPath = computed(() => router.currentRoute.value.path)
|
|
const { isDark, toggleDark } = useDarkMode()
|
|
const rStore = useRecipeStore()
|
|
|
|
const primary = [
|
|
{ key: 'cook', label: '做菜', path: '/home', icon: ioniconsBookOutline },
|
|
{ key: 'library', label: '菜谱', path: '/library', icon: ioniconsLibraryOutline },
|
|
{ key: 'discover', label: '发现', path: '/apps', icon: ioniconsRestaurantOutline },
|
|
{ key: 'me', label: '我的', path: '/my', icon: ioniconsPersonOutline },
|
|
]
|
|
|
|
const more = [
|
|
{ key: 'settings', label: '设置', path: '/settings', icon: ioniconsSettingsOutline },
|
|
{ key: 'help', label: '帮助', path: '/help', icon: ioniconsHelpCircleOutline },
|
|
{ key: 'about', label: '关于', path: '/about', icon: ioniconsInformationCircleOutline },
|
|
]
|
|
|
|
/** 菜谱库和「做菜」都有别名,这里按前缀归类,保证左侧高亮和底部标签一致 */
|
|
function isActive(item: { key: string, path: string }) {
|
|
const path = currentPath.value
|
|
if (item.key === 'cook')
|
|
return path === '/' || path === '/home' || path === '/tabs'
|
|
if (item.key === 'library')
|
|
return path.startsWith('/library') || path.startsWith('/recipes') || path.startsWith('/cookbooks')
|
|
if (item.key === 'discover')
|
|
return path.startsWith('/apps')
|
|
if (item.key === 'me')
|
|
return path.startsWith('/my')
|
|
return path.startsWith(item.path)
|
|
}
|
|
|
|
/** 是否正停留在这一页(做菜页有 / 、/home、/tabs 三个等价路径) */
|
|
function isCurrentPage(item: { key: string, path: string }) {
|
|
if (item.key === 'cook')
|
|
return currentPath.value === '/' || currentPath.value === '/home' || currentPath.value === '/tabs'
|
|
return currentPath.value === item.path
|
|
}
|
|
|
|
/** 已经在这一页时,拦下跳转改成滚回顶部,和底部标签栏的行为一致 */
|
|
function onLinkClick(item: { key: string, path: string }, event: MouseEvent) {
|
|
if (!isCurrentPage(item))
|
|
return
|
|
event.preventDefault()
|
|
scrollTop()
|
|
}
|
|
|
|
/** 和底部标签栏一样:再点一次当前项,页面滚回顶部 */
|
|
function scrollTop() {
|
|
const content = document.querySelector<HTMLIonContentElement>('.ion-page:not(.ion-page-hidden) ion-content')
|
|
void content?.scrollToTop(300)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<aside class="ledger-rail" aria-label="主导航">
|
|
<div class="ledger-rail-brand">
|
|
<span class="ledger-rail-mark">
|
|
<ion-icon :icon="ioniconsRestaurantOutline" aria-hidden="true" />
|
|
</span>
|
|
<span class="ledger-rail-brand-text">
|
|
<b>食用手册</b>
|
|
<small>冰箱里有什么就做什么</small>
|
|
</span>
|
|
</div>
|
|
|
|
<nav class="ledger-rail-nav">
|
|
<NuxtLink
|
|
v-for="item in primary"
|
|
:key="item.key"
|
|
:to="item.path"
|
|
class="ledger-rail-link"
|
|
:class="{ 'is-on': isActive(item) }"
|
|
:aria-current="isActive(item) ? 'page' : undefined"
|
|
@click="onLinkClick(item, $event)"
|
|
>
|
|
<ion-icon :icon="item.icon" aria-hidden="true" />
|
|
<span>{{ item.label }}</span>
|
|
</NuxtLink>
|
|
</nav>
|
|
|
|
<nav class="ledger-rail-nav">
|
|
<span class="ledger-rail-label">更多</span>
|
|
<NuxtLink
|
|
v-for="item in more"
|
|
:key="item.key"
|
|
:to="item.path"
|
|
class="ledger-rail-link"
|
|
:class="{ 'is-on': isActive(item) }"
|
|
:aria-current="isActive(item) ? 'page' : undefined"
|
|
@click="onLinkClick(item, $event)"
|
|
>
|
|
<ion-icon :icon="item.icon" aria-hidden="true" />
|
|
<span>{{ item.label }}</span>
|
|
</NuxtLink>
|
|
</nav>
|
|
|
|
<div class="ledger-rail-foot">
|
|
<button type="button" class="ledger-rail-theme" @click="toggleDark()">
|
|
<ion-icon :icon="isDark ? ioniconsSunnyOutline : ioniconsMoonOutline" aria-hidden="true" />
|
|
<span>{{ isDark ? '浅色模式' : '深色模式' }}</span>
|
|
</button>
|
|
<p class="ledger-rail-stat">
|
|
菜谱库收录 <b>{{ rStore.recipesLength }}</b> 道菜
|
|
</p>
|
|
</div>
|
|
</aside>
|
|
</template>
|