feat: Add `<keep-alive>` (#83)

This commit is contained in:
Charlie Wang ✨ 2024-04-10 23:05:52 +08:00 committed by GitHub
parent 88b0c25b53
commit fd49f40ec9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 234 additions and 226 deletions

View File

@ -13,7 +13,6 @@ import mockDevServerPlugin from 'vite-plugin-mock-dev-server'
import { VitePWA } from 'vite-plugin-pwa'
import Sitemap from 'vite-plugin-sitemap'
import VueDevTools from 'vite-plugin-vue-devtools'
import Layouts from 'vite-plugin-vue-layouts'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import { createViteVConsole } from './vconsole'
@ -28,11 +27,6 @@ export function createVitePlugins() {
vue(),
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
Layouts({
pagesDirs: 'src/pages/**',
}),
// https://github.com/jbaubree/vite-plugin-sitemap
Sitemap(),

View File

@ -73,7 +73,6 @@
"vite-plugin-sitemap": "^0.5.3",
"vite-plugin-vconsole": "^2.1.1",
"vite-plugin-vue-devtools": "^7.0.25",
"vite-plugin-vue-layouts": "^0.11.0",
"vitest": "^1.4.0",
"vue-tsc": "^2.0.7"
},

View File

@ -169,9 +169,6 @@ devDependencies:
vite-plugin-vue-devtools:
specifier: ^7.0.25
version: 7.0.25(rollup@4.14.0)(vite@5.2.7)(vue@3.4.21)
vite-plugin-vue-layouts:
specifier: ^0.11.0
version: 0.11.0(vite@5.2.7)(vue-router@4.3.0)(vue@3.4.21)
vitest:
specifier: ^1.4.0
version: 1.4.0(@types/node@20.12.3)(less@4.2.0)(terser@5.30.2)
@ -8423,22 +8420,6 @@ packages:
- supports-color
dev: true
/vite-plugin-vue-layouts@0.11.0(vite@5.2.7)(vue-router@4.3.0)(vue@3.4.21):
resolution: {integrity: sha512-uh6NW7lt+aOXujK4eHfiNbeo55K9OTuB7fnv+5RVc4OBn/cZull6ThXdYH03JzKanUfgt6QZ37NbbtJ0og59qw==}
peerDependencies:
vite: ^4.0.0 || ^5.0.0
vue: ^3.2.4
vue-router: ^4.0.11
dependencies:
debug: 4.3.4
fast-glob: 3.3.2
vite: 5.2.7(@types/node@20.12.3)(less@4.2.0)(terser@5.30.2)
vue: 3.4.21(typescript@5.4.3)
vue-router: 4.3.0(vue@3.4.21)
transitivePeerDependencies:
- supports-color
dev: true
/vite@5.2.7(@types/node@20.12.3)(less@4.2.0)(terser@5.30.2):
resolution: {integrity: sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==}
engines: {node: ^18.0.0 || >=20.0.0}
@ -8773,6 +8754,7 @@ packages:
/workbox-google-analytics@7.0.0:
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
dependencies:
workbox-background-sync: 7.0.0
workbox-core: 7.0.0

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import useAppStore from '@/stores/modules/app'
import useRouteCache from '@/stores/modules/routeCache'
import useRouteTransitionNameStore from '@/stores/modules/routeTransitionName'
import useAutoThemeSwitcher from '@/hooks/useAutoThemeSwitcher'
@ -30,9 +31,12 @@ const { mode } = storeToRefs(appStore)
const routeTransitionNameStore = useRouteTransitionNameStore()
const { routeTransitionName } = storeToRefs(routeTransitionNameStore)
const { initializeThemeSwitcher } = useAutoThemeSwitcher(appStore)
const keepAliveRouteNames = computed(() => {
return useRouteCache().routeCaches as string[]
})
onMounted(() => {
initializeThemeSwitcher()
})
@ -43,21 +47,11 @@ onMounted(() => {
<NavBar />
<router-view v-slot="{ Component, route }">
<transition :name="routeTransitionName">
<div :key="route.name" class="app-wrapper">
<component :is="Component" />
</div>
<keep-alive :include="keepAliveRouteNames">
<component :is="Component" :key="route.name" />
</keep-alive>
</transition>
</router-view>
<TabBar />
</VanConfigProvider>
</template>
<style scoped>
.app-wrapper {
width: 100%;
height: 100%;
padding-top: 46px;
overflow-y: auto;
position: absolute;
left: 0;
}
</style>

2
src/components.d.ts vendored
View File

@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
Chart: typeof import('./components/Chart/index.vue')['default']
Container: typeof import('./components/Container.vue')['default']
NavBar: typeof import('./components/NavBar.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
@ -24,6 +25,7 @@ declare module 'vue' {
VanPopup: typeof import('vant/es')['Popup']
VanRadio: typeof import('vant/es')['Radio']
VanSpace: typeof import('vant/es')['Space']
VanStepper: typeof import('vant/es')['Stepper']
VanSwitch: typeof import('vant/es')['Switch']
VanTabbar: typeof import('vant/es')['Tabbar']
VanTabbarItem: typeof import('vant/es')['TabbarItem']

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
defineProps({
paddingX: {
type: Number,
default: 16,
},
})
</script>
<template>
<main
class="absolute left-0 h-full w-full overflow-y-auto pt-46"
:style="`padding-left: ${paddingX}px; padding-right: ${paddingX}px`"
>
<slot />
</main>
</template>

View File

@ -1,21 +1,28 @@
<script setup lang="ts">
const { t } = useI18n()
const active = ref(0)
</script>
<template>
<van-tabbar v-model="active" route>
<van-tabbar-item replace to="/">
{{ t('layouts.home') }}
<template #icon>
<div class="i-carbon:home" />
</template>
</van-tabbar-item>
<van-tabbar-item replace to="/profile">
{{ t('layouts.profile') }}
<template #icon>
<div class="i-carbon:user" />
</template>
</van-tabbar-item>
</van-tabbar>
</template>
<script setup lang="ts">
const { t } = useI18n()
const active = ref(0)
const route = useRoute()
const display = computed(() => {
if (route.meta.level && route.meta.level !== 2)
return true
return false
})
</script>
<template>
<van-tabbar v-show="display" v-model="active" route>
<van-tabbar-item replace to="/">
{{ t('layouts.home') }}
<template #icon>
<div class="i-carbon:home" />
</template>
</van-tabbar-item>
<van-tabbar-item replace to="/profile">
{{ t('layouts.profile') }}
<template #icon>
<div class="i-carbon:user" />
</template>
</van-tabbar-item>
</van-tabbar>
</template>

View File

@ -1,24 +0,0 @@
<script setup lang="ts">
const router = useRouter()
function onBack() {
if (window.history.state.back)
history.back()
else
router.replace('/')
}
</script>
<template>
<main p="x4 y16" text-18 text="center gray-300 dark:gray-200">
<van-icon name="warn-o" size="3em" />
<RouterView />
<div class="mt-10">
<button van-haptics-feedback btn m="3 t8" @click="onBack">
Back
</button>
</div>
</main>
</template>

View File

@ -1,14 +0,0 @@
# Layouts
Vue components in this dir are used as layouts.
By default, `default.vue` will be used unless an alternative is specified in the route meta.
With [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) and [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts), you can specify the layout in the page's SFCs like this:
```vue
<route lang="yaml">
meta:
layout: home
</route>
```

View File

@ -1,13 +0,0 @@
<script setup lang="ts">
const { t } = useI18n()
</script>
<template>
<main p="16" text="gray-700 dark:gray-200">
<RouterView />
<div mx-auto mb-60 pt-15 text-center text-10 text-gray-500>
[{{ t('layouts.defaultLayout') }}]
</div>
</main>
</template>

View File

@ -1,15 +0,0 @@
<script setup lang="ts">
const { t } = useI18n()
</script>
<template>
<main text="gray-700 dark:gray-200">
<RouterView />
<div mx-auto mt-15 text-center text-10 text-gray-500>
[{{ t('layouts.homeLayout') }}]
</div>
<TabBar />
</main>
</template>

View File

@ -6,7 +6,8 @@
"404Demo": "🙅 Page 404 Demo",
"echartsDemo": "📊 Echarts Demo",
"persistPiniaState": "🍍 Persistent Pinia state",
"unocssExample": "🎨 Unocss example"
"unocssExample": "🎨 Unocss example",
"keepAlive": "🧡 KeepAlive Demo"
},
"mock": {
"fromAsyncData": "Data from asynchronous requests",
@ -24,11 +25,12 @@
},
"layouts": {
"home": "HOME",
"profile": "PROFILE",
"homeLayout": "Home Layout",
"defaultLayout": "Default Layout"
"profile": "PROFILE"
},
"profile": {
"placeholder": "WIP"
},
"keepAlive": {
"label": "The current component will be cached"
}
}

View File

@ -6,7 +6,8 @@
"echartsDemo": "📊 Echarts 演示",
"persistPiniaState": "🍍 持久化 Pinia 状态",
"404Demo": "🙅 404页 演示",
"unocssExample": "🎨 Unocss 示例"
"unocssExample": "🎨 Unocss 示例",
"keepAlive": "🧡 KeepAlive 演示"
},
"mock": {
"fromAsyncData": "来自异步请求的数据",
@ -24,11 +25,12 @@
},
"layouts": {
"home": "首页",
"profile": "我的",
"homeLayout": "首页布局",
"defaultLayout": "默认布局"
"profile": "我的"
},
"profile": {
"placeholder": "未完成"
},
"keepAlive": {
"label": "当前组件将会被缓存"
}
}

View File

@ -1,10 +1,25 @@
<template>
<div>
Not found
</div>
</template>
<route lang="yaml">
meta:
layout: 404
</route>
<script setup lang="ts">
const router = useRouter()
function onBack() {
if (window.history.state.back)
history.back()
else
router.replace('/')
}
</script>
<template>
<Container>
<div text="center gray-300 dark:gray-200 18">
<van-icon name="warn-o" size="3em" />
<div> Not found </div>
<div class="mt-10">
<button van-haptics-feedback btn m="3 t8" @click="onBack">
Back
</button>
</div>
</div>
</Container>
</template>

View File

@ -69,9 +69,9 @@ const refScoreOption = ref(scoreOption)
</script>
<template>
<div>
<Container>
<Chart :option="refBarOption" :style="{ height: '330px' }" />
<Chart :option="refLineOption" :style="{ height: '330px' }" />
<Chart :option="refScoreOption" :style="{ height: '330px' }" />
</div>
</Container>
</template>

View File

@ -20,7 +20,7 @@ function add() {
</script>
<template>
<div>
<Container>
<h1 class="text-6xl color-pink font-semibold">
Hello, Pinia!
</h1>
@ -37,5 +37,5 @@ function add() {
<button class="btn" @click="add">
Add
</button>
</div>
</container>
</template>

View File

@ -43,40 +43,38 @@ const menuItems = computed(() => ([
{ title: t('home.unocssExample'), route: 'unocss' },
{ title: t('home.persistPiniaState'), route: 'counter' },
{ title: t('home.404Demo'), route: 'unknown' },
{ title: t('home.keepAlive'), route: 'keepalive' },
]))
</script>
<template>
<VanCellGroup inset>
<VanCell center :title="$t('home.darkMode')">
<template #right-icon>
<VanSwitch v-model="checked" size="20px" aria-label="on/off Dark Mode" @click="toggle()" />
</template>
</VanCell>
<Container :padding-x="0">
<VanCellGroup inset>
<VanCell center :title="$t('home.darkMode')">
<template #right-icon>
<VanSwitch v-model="checked" size="20px" aria-label="on/off Dark Mode" @click="toggle()" />
</template>
</VanCell>
<VanCell
is-link
:title="$t('home.language')"
:value="language"
@click="showLanguagePicker = true"
/>
<van-popup v-model:show="showLanguagePicker" position="bottom">
<van-picker
v-model="languageValues"
:columns="languageColumns"
@confirm="onLanguageConfirm"
@cancel="showLanguagePicker = false"
<VanCell
is-link
:title="$t('home.language')"
:value="language"
@click="showLanguagePicker = true"
/>
</van-popup>
<template v-for="item in menuItems" :key="item.route">
<VanCell :title="item.title" :to="item.route" is-link />
</template>
</VanCellGroup>
<van-popup v-model:show="showLanguagePicker" position="bottom">
<van-picker
v-model="languageValues"
:columns="languageColumns"
@confirm="onLanguageConfirm"
@cancel="showLanguagePicker = false"
/>
</van-popup>
<template v-for="item in menuItems" :key="item.route">
<VanCell :title="item.title" :to="item.route" is-link />
</template>
</VanCellGroup>
</Container>
</template>
<route lang="yaml">
meta:
layout: home
</route>

View File

@ -0,0 +1,24 @@
<script setup lang="ts">
defineOptions({
name: 'KeepAlive',
})
definePage({
name: 'KeepAlive',
meta: {
level: 2,
title: '🧡 KeepAlive',
i18n: 'home.keepAlive',
keepAlive: true,
},
})
const value = ref(1)
</script>
<template>
<Container>
<p>{{ $t('keepAlive.label') }}</p>
<van-stepper v-model="value" />
</Container>
</template>

View File

@ -23,7 +23,7 @@ const reset = () => messages.value = ''
</script>
<template>
<div>
<Container>
<div class="data-label">
{{ $t('mock.fromAsyncData') }}
</div>
@ -43,7 +43,7 @@ const reset = () => messages.value = ''
{{ $t('mock.reset') }}
</VanButton>
</van-space>
</div>
</Container>
</template>
<style lang="less" scoped>
@ -52,6 +52,7 @@ const reset = () => messages.value = ''
font-weight: 400;
font-size: 14px;
line-height: 16px;
margin-top: 10px;
}
.data-content {

View File

@ -1,21 +1,18 @@
<script setup lang="ts">
definePage({
name: 'profile',
meta: {
level: 1,
},
})
const { t } = useI18n()
</script>
<template>
<div mx-auto mb-60 pt-15 text-center text-16 text-dark dark:text-white>
{{ t('profile.placeholder') }}
</div>
</template>
<route lang="yaml">
meta:
layout: home
</route>
<script setup lang="ts">
definePage({
name: 'profile',
meta: {
level: 1,
},
})
const { t } = useI18n()
</script>
<template>
<Container>
<div mx-auto mb-60 pt-15 text-center text-16 text-dark dark:text-white>
{{ t('profile.placeholder') }}
</div>
</Container>
</template>

View File

@ -10,7 +10,7 @@ definePage({
</script>
<template>
<div>
<Container>
<h1 class="text-6xl color-pink font-semibold">
Hello, Unocss!
</h1>
@ -22,5 +22,5 @@ definePage({
<button class="btn">
Button
</button>
</div>
</Container>
</template>

View File

@ -1,21 +1,28 @@
import { createRouter, createWebHistory } from 'vue-router/auto'
import { setupLayouts } from 'virtual:generated-layouts'
import NProgress from 'nprogress'
import useRouteTransitionNameStore from '@/stores/modules/routeTransitionName'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import type { EnhancedRouteLocation } from './types'
import useRouteTransitionNameStore from '@/stores/modules/routeTransitionName'
import useRouteCacheStore from '@/stores/modules/routeCache'
NProgress.configure({ showSpinner: true, parent: '#app' })
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_APP_PUBLIC_PATH),
extendRoutes: routes => setupLayouts(routes),
extendRoutes: routes => routes,
})
router.beforeEach((to, from, next) => {
router.beforeEach((to: EnhancedRouteLocation, from, next) => {
NProgress.start()
const routeCacheStore = useRouteCacheStore()
const routeTransitionNameStore = useRouteTransitionNameStore()
// Route cache
routeCacheStore.addRoute(to)
if (to.meta.level > from.meta.level)
routeTransitionNameStore.setName('slide-fadein-left')

9
src/router/types.ts Normal file
View File

@ -0,0 +1,9 @@
import type { RouteLocationNormalized } from 'vue-router'
export interface EnhancedRouteLocation extends RouteLocationNormalized {
meta: {
level?: number | unknown
name?: string
keepAlive?: boolean
}
}

View File

@ -0,0 +1,22 @@
import { defineStore } from 'pinia'
import type { RouteRecordName } from 'vue-router'
import type { EnhancedRouteLocation } from '@/router/types'
const useRouteCacheStore = defineStore('route-cache', () => {
const routeCaches = ref<RouteRecordName[]>([])
const addRoute = (route: EnhancedRouteLocation) => {
if (routeCaches.value.includes(route.name))
return
if (route?.meta?.keepAlive)
routeCaches.value.push(route.name)
}
return {
routeCaches,
addRoute,
}
})
export default useRouteCacheStore

View File

@ -1,16 +1,16 @@
import { defineStore } from 'pinia'
const useRouteTransitionNameStore = defineStore('route-transition-name', () => {
const routeTransitionName = ref('')
const setName = (name: string) => {
routeTransitionName.value = name
}
return {
routeTransitionName,
setName,
}
})
export default useRouteTransitionNameStore
import { defineStore } from 'pinia'
const useRouteTransitionNameStore = defineStore('route-transition-name', () => {
const routeTransitionName = ref('')
const setName = (name: string) => {
routeTransitionName.value = name
}
return {
routeTransitionName,
setName,
}
})
export default useRouteTransitionNameStore

View File

@ -22,6 +22,7 @@ declare module 'vue-router/auto-routes' {
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
'charts': RouteRecordInfo<'charts', '/charts', Record<never, never>, Record<never, never>>,
'counter': RouteRecordInfo<'counter', '/counter', Record<never, never>, Record<never, never>>,
'KeepAlive': RouteRecordInfo<'KeepAlive', '/keepalive', Record<never, never>, Record<never, never>>,
'mock': RouteRecordInfo<'mock', '/mock', Record<never, never>, Record<never, never>>,
'profile': RouteRecordInfo<'profile', '/profile', Record<never, never>, Record<never, never>>,
'unocss': RouteRecordInfo<'unocss', '/unocss', Record<never, never>, Record<never, never>>,

2
src/vue-router.d.ts vendored
View File

@ -4,6 +4,8 @@ declare module 'vue-router' {
title?: string
/** i18n key */
i18n?: string
/** keepalive */
keepAlive?: boolean
}
}
export {}

View File

@ -13,7 +13,6 @@
"types": [
"node",
"unplugin-vue-router/client",
"vite-plugin-vue-layouts/client",
"vite-plugin-pwa/client",
"@intlify/unplugin-vue-i18n/messages"
],