Appearance
页面缓存、刷新返回与浏览器同步
本页分别介绍三个不同层次:应用运行实例、页签组件缓存、浏览器公开导航快照。它们不能相互替代:恢复页签不等于恢复表单,缓存页面不等于持久化,刷新某页也不是重载整个应用。
1. 三层生命周期
| 层次 | 负责对象 | 关键入口 | 不负责什么 |
|---|---|---|---|
| 应用运行层 | 每个 appId 的子应用运行实例 | MicroAppContainer、runtime | 不替每个 tab 创建独立应用 |
| 页面组件层 | 每个 tab.key 的 Vue 页面实例 | MicroTabCache、useMicroPage | 不持久化数据,不自动提供独立路由上下文 |
| 浏览器层 | URL 与 sessionStorage 里的公开导航元信息 | connectMicroBrowser | 不保存组件、业务数据、凭据或 back 来源关系 |
宿主不要因为切 tab 就销毁 MicroAppContainer。同应用多个 tab 应共享应用实例,再由子应用按 tab.key 缓存页面。关闭重开或单页刷新后 tabId 可以不变,但 key 变化,页面应是新实例。
2. MicroTabCache 的真实接口
vue
<MicroTabCache :tabs="tabs" :active-key="activeKey" :max="20">
<MicroRouteView />
</MicroTabCache>| 属性/插槽 | 类型与语义 |
|---|---|
tabs | 必填 readonly MicroTab[],仅传本应用仍打开的页签 |
activeKey | 可选 string,当前要显示的生命周期 key;不是 tabId |
max | Number,默认 20,控制驻留组件数,不控制宿主页签数 |
| 默认插槽 | 接收创建时 { tab } 快照;后代组件也可用 useMicroTab() 取得 |
只有激活过的页签才创建页面;切换会触发 Vue 的 activated/deactivated。每个 key 有独立 KeepAlive 边界,关闭或淘汰时删除边界,真正卸载其中的停用页面,不依赖 Vue 私有缓存字段。
max 为有限数时向下取整,最小为 1;非有限数回落为 20。超限使用访问顺序驱逐非活动页,永不驱逐当前活动 key。超限后再访问仍打开的被驱逐页,会重新创建组件,但不会把宿主页签关闭。
3. 接入 Vue Router:先提供每个 key 的路由快照
如果所有缓存页面直接读取全局活动路由,打开详情 A 后再打开 B,A 的 useRoute() 也可能读到 B。仅仅给 <RouterView> 加 key 并不能完成路由上下文隔离。
库提供 MicroRouteView 统一解析固定 tab.route,等待路由懒加载完成,并向后代提供本页独立的 Vue Router 上下文。子应用不必再解析后台菜单或按字符串查找组件文件。
第一步:使用公共 MicroRouteView
ts
import { MicroRouteView } from '@axewo/fin-micro/vue'前提:应用安装 Vue Router ^4.5.0,通过 app.use(router) 注册自己的本地路由表;组件必须放在 MicroTabCache 内。name 属性可选择命名视图,默认 default。只有使用此页面组件时需要路由器,框架无关 SDK/core 不依赖 Vue Router。
它按固定 key 解析页面,使用公开 loadRouteLocation 加载路由组件,支持嵌套路由、路由 props 以及记录上的字符串/对象/函数 redirect;重定向超过上限或循环会显示错误,不会无限等待。解析始终以应用根的 START_LOCATION 为起点,不读取其他页签的全局 currentRoute;函数 redirect 的 from 也是 START_LOCATION,相对 redirect 按应用根解析。业务应优先使用绝对 path 或命名路由,避免依赖上一个全局页面。
**重要边界:**这不是第二个导航事务系统。组件解析不会调用全局 router.push/replace,不会执行 Vue Router 导航守卫,也不修改宿主 tab.route、菜单或地址栏。页面跳转、离页确认仍通过 PageSDK/core;本地 redirect 目标必须属于已授权页面,不能把 redirect 当成绕过 canOpen 的方法。若必须按最终重定向地址鉴权,应将规范化目标作为宿主导航地址,而不是依赖显示层转向。
未匹配地址、模块下载失败与重定向错误有默认可见提示及重试按钮。loading 插槽收到 { tab },error 插槽收到 { tab, code, retry }。code 是脱敏分类:NOT_FOUND、LOAD_FAILED、REDIRECT_LOOP、RESOLVE_FAILED;不暴露模块 URL、原始异常或堆栈。
vue
<MicroRouteView>
<template #loading>正在加载当前页面…</template>
<template #error="{ code, retry }">
<p role="alert">页面暂不可用:{{ code }}</p>
<button type="button" @click="retry">重试</button>
</template>
</MicroRouteView>重试不重新提交宿主导航,不重新执行保存业务。懒加载请求本身不能强制取消,但卸载后迟到结果不会重新渲染旧页面。已加载组件的 setup/render 业务异常仍由 Vue 的应用错误边界处理。
模板里的 TabRouteView.vue 仅作为旧引用的兼容薄封装,新的 App 直接使用公共组件:
第二步:在应用根组件接缓存
下面的最小示例要求外部已安装 Vue Router,并由真实宿主注入 bridge;没有桥时直接使用独立预览路由,不伪造 SDK。
vue
<script setup lang="ts">
import { computed } from 'vue'
import { RouterView } from 'vue-router'
import { getMicroBridge } from '@axewo/fin-micro/sdk'
import { MicroRouteView, MicroTabCache, useMicroHost } from '@axewo/fin-micro/vue'
const bridge = getMicroBridge()
const state = bridge ? useMicroHost(bridge) : undefined
const tabs = computed(() =>
state?.value.tabs.filter(tab => tab.appId === bridge?.appId) ?? [],
)
const activeKey = computed(() =>
state?.value.tabs.find(tab => tab.tabId === state.value.activeTabId)?.key,
)
</script>
<template>
<MicroTabCache v-if="bridge && state" :tabs="tabs" :active-key="activeKey" :max="20">
<MicroRouteView />
</MicroTabCache>
<RouterView v-else />
</template>当活动页属于其他应用时,activeKey 在本应用 tabs 中不存在,于是本应用所有已缓存页停用,缓存实例仍保留。接入生成模板时沿用公共会话注入,见 packages/fin-micro/scaffold/vue/src/App.vue,不要为每个页面重建应用会话。
第三步:连接活动导航,不重复发起全局路由跳转
使用 MicroRouteView 后,宿主模式的页面已经按各自 tab.route 解析,不需要再对每次回放调用共享 router.replace。模板仍复用 connectMicroRouter 的回放/串行生命周期,但适配回调只同步挂载会话元信息:
ts
import { createApp, watch } from 'vue'
import { disposePinia } from 'pinia'
import ElementPlus from 'element-plus'
import type { Composer } from 'vue-i18n'
import { connectFinLocaleStorage } from '@axewo/fin-locale'
import { connectMicroRouter, connectMicroLocale, connectPermissionRouter, installMicroSession } from '@axewo/fin-micro'
import { installPermissionDirectives, installMicroConfirmation, connectUnsavedChangesRouter } from '@axewo/fin-micro/vue'
import { isMicroFrontend } from '@axewo/fin-micro/runtime'
import { installSessionAuth } from './bridge/auth'
import { reportError, toSafeError, cleanupAll } from './utils/errorUtils'
import { appConfig } from './utils/appConfig'
import App from './App.vue'
import setupI18n from './locales'
import { createBridgeSession } from './bridge/session'
import { createMicroRouter } from './router'
import { createAppPinia } from './stores'
let cleanup: (() => void) | undefined
/** 幂等卸载:先撤销凭据读取,再释放订阅、组件、路由历史与当前应用状态。 */
export function unmount(): void { cleanup?.(); cleanup = undefined }
/** 每次挂载独立创建会话、i18n、router 和 Pinia;重试不复用上轮身份与订阅。 */
export async function mount(): Promise<void> {
unmount()
const session = createBridgeSession()
const { bridge } = session
const embedded = isMicroFrontend()
const router = createMicroRouter(embedded)
const app = createApp(App)
const pinia = createAppPinia()
let stopped = false
let mounted = false
const subscriptions: (() => void)[] = []
cleanup = () => {
if (stopped) return
stopped = true
cleanupAll([
...subscriptions, () => session.dispose(),
() => { if (mounted) app.unmount() },
() => router.options.history.destroy(), () => disposePinia(pinia),
])
}
try {
app.config.errorHandler = reportError
// locales 统一组装业务词条并安装当前应用的国际化实例。
const i18n = setupI18n(app)
const composer = i18n.global as Composer
// 独立应用保存自身语言;嵌入时只读宿主偏好,不读写独立运行的存储。
if (!embedded && !bridge) subscriptions.push(connectFinLocaleStorage(composer.locale, 'fin-micro:locale:app:__APP_ID__'))
installMicroSession(app, session)
installPermissionDirectives(app, session.identity)
const confirmation = installMicroConfirmation(app, { pageTitle: () => String(router.currentRoute.value.meta.title ?? '') })
subscriptions.push(() => confirmation.dispose())
subscriptions.push(watch(() => session.identity.value.authenticated, value => { if (!value) confirmation.cancel() }, { flush: 'sync' }))
app.use(ElementPlus)
app.use(pinia)
if (bridge) {
if (bridge.appId !== '__APP_ID__') throw new Error('宿主 appId 与子应用 __APP_ID__ 配置不一致')
if (!bridge.negotiate({ version: '1.0' }).compatible) throw new Error('宿主协议不兼容')
subscriptions.push(connectMicroLocale(composer.locale, bridge))
subscriptions.push(connectMicroRouter(bridge, {
async replace() { if (!stopped) session.status.value = 'fin.micro.connected' },
}, error => { if (!stopped) session.status.value = toSafeError(error, composer.locale.value).message }))
} else if (embedded) session.status.value = 'fin.micro.disconnected'
// 独立与宿主认证共用请求入口;Token 从闭包按请求读取,卸载后不能继续复用。
subscriptions.unshift(installSessionAuth(session))
// 独立开发优先验证本地 Token;无本地值时仅在配置 Cookie 后请求恢复接口。
// 过期或网络失败仍挂载未登录页面,展示脱敏提示,让开发者可以重新设置凭据。
if (session.mode === 'independent') {
try { await session.restore({ cookie: appConfig.withCredentials }) }
catch (error) { if (!stopped) session.status.value = toSafeError(error, composer.locale.value).message }
}
if (stopped) return
// 同一公共守卫处理登录准入、登录成功去主页、功能权限和失效回登录;嵌入不安装它。
if (!embedded) subscriptions.push(connectPermissionRouter(router, session.identity, '/forbidden', reportError, { login: '/login', home: '/home' }))
if (!embedded) subscriptions.push(connectUnsavedChangesRouter(router, confirmation, () => session.identity.value.authenticated))
app.use(router)
await router.isReady()
if (stopped) return
app.mount('#app')
mounted = true
} catch (error) {
if (stopped) return
unmount()
throw error
}
}返回值是停止订阅函数,应用卸载时调用。无页面缓存的旧接入仍可使用 connectMicroRouter(bridge, { replace: route => router.replace(route) }),这个低层适配接口保持兼容;不要将该模式与 MicroRouteView 重复叠加成两套导航。
独立预览仍由真正的 Vue Router 导航和 RouterView 渲染。宿主模式中的 Vue Router 主要作为本地路由注册表与加载器,跳转/离页守卫统一由宿主事务负责。
**路由读取约束:**页级隔离支持 Composition API 的 useRoute()、路由 props 与显式 RouterView。Options API 的 this.$route 和模板 $route 来自全局实例属性,不能视为每个缓存页的独立快照;业务页面应使用 script setup + useRoute()。
4. useMicroPage 与业务 MicroPageScope
useMicroPage(bridge, tab) 必须在组件 setup 中调用,按 tab.key 创建页面 SDK,并在真正卸载时自动 dispose。useMicroTab() 返回边界创建时快照,而不是随活动页变化的引用。
MicroPageScope 不是 fin-micro 当前导出的组件或 API。 如果项目想把“每页创建 SDK 并注入后代”封装成这一名称,需要自行实现。以下是一组可复制的业务封装,文件都放在子应用 src 内。
micro-page-context.ts
ts
import { inject, type InjectionKey } from 'vue'
import type { PageSDK } from '@axewo/fin-micro/sdk'
export const pageSDKKey: InjectionKey<PageSDK> = Symbol('business-micro-page-sdk')
export function usePageSDK(): PageSDK {
const sdk = inject(pageSDKKey)
if (!sdk) throw new Error('usePageSDK 必须在业务 MicroPageScope 后代中调用')
return sdk
}components/MicroPageScope.vue
vue
<script setup lang="ts">
import { provide } from 'vue'
import type { AppBridge } from '@axewo/fin-micro/sdk'
import { useMicroPage, useMicroTab } from '@axewo/fin-micro/vue'
import { pageSDKKey } from '../micro-page-context'
const props = defineProps<{ bridge: AppBridge }>()
// 此组件必须位于 MicroTabCache 后代,而不是包在缓存外面。
const tab = useMicroTab()
const sdk = useMicroPage(props.bridge, tab)
provide(pageSDKKey, sdk)
</script>
<template><slot /></template>把根组件的缓存内容改为:
vue
<MicroTabCache v-if="bridge && state" :tabs="tabs" :active-key="activeKey">
<MicroPageScope :bridge="bridge">
<MicroRouteView />
</MicroPageScope>
</MicroTabCache>根组件中需从本地文件导入 MicroPageScope。后代页面使用 const sdk = usePageSDK(),不要在每个按钮组件重复创建页面 SDK。使用自定义能力契约时,将上下文中的 PageSDK 和 props 中的 AppBridge 都改为同一业务泛型,沿用模板的 DemoCapabilities 等契约即可。
不要写 import { MicroPageScope } from '@axewo/fin-micro/vue'。库实际提供的绑定方式是 useMicroPage,上述作用域只是业务选择。
5. 停用、卸载与缓存驱逐
| 事件 | 页签还存在吗 | 页面组件 | 原 SDK |
|---|---|---|---|
| 切到其他页签 | 存在 | 停用/缓存 | 保留,可参与批量关闭守卫 |
| 关闭原页 | 不存在 | 真正卸载 | 失效并清理订阅及守卫 |
| 原位刷新 | 同 tabId 存在,新 key | 旧实例卸载,新实例按需创建 | 旧 SDK 失效 |
| 同 tabId 改 route 的 open 事务 | 同 tabId 存在,新 key | 旧实例卸载 | 旧 SDK 失效 |
| LRU 驱逐 | 存在,原 key 仍在宿主 | 被驱逐实例卸载 | useMicroPage 自动释放该实例 SDK |
| 浏览器整页刷新 | 新上下文重新恢复 | 所有旧实例消失 | 不复活旧 SDK |
LRU 驱逐不会等待 beforeLeave,也不会关闭宿主页签。卸载的 SDK 守卫已经注销,因此该页之后批量关闭时,不存在“仍缓存的旧表单守卫”。这也是不能把缓存当成防丢数据保障的原因。
重要草稿应在业务层另外保存,并制定用户隔离、数据版本、有效期、清理及冲突策略。敏感数据不要放在 route、URL 或导航快照。组件卸载后,业务自行启动的网络请求不会仅因 SDK.dispose 自动取消,业务应自行使用 AbortController 和卸载清理。
6. 刷新、关闭与 back 的差别
以下函数均接收已绑定原 key 的 SDK,不查询执行时活动页。
ts
import type { PageSDK } from '@axewo/fin-micro/sdk'
export async function refreshPage(sdk: PageSDK) {
const result = await sdk.refreshCurrent()
// committed 后 sdk 属于旧生命周期,不要再用它追加一般导航操作。
return result.status
}
export async function closePage(sdk: PageSDK) {
// 功能页面关闭并返回来源,返回 NavigationResult。
return sdk.back()
}
export async function backToList(sdk: PageSDK) {
return sdk.back({
appId: sdk.tab.appId,
tabId: `${sdk.tab.appId}:list`,
title: '列表',
route: '/list',
})
}refreshCurrent
- 经离页守卫,原位置生成新 key,保留 tabId、标题、路由、菜单归属和顺序。
- 刷新绑定页,不一定是点击瞬间活动页;刷新停用页也不会强制激活它。
- 不重载整个应用,不清除同应用其他页签缓存;最后一个页签原位刷新不会因此释放应用桥。
- 守卫拒绝或事务过期时不销毁原页面。成功仅表示导航状态提交,实际新组件渲染及业务请求仍可能失败。
closeCurrent
- 按创建时 key 关闭原页,异步保存期间切换活动页也不会误关其他页。
- 已关闭、同 tabId 重开、宿主销毁或未完成关闭时可能返回 false;守卫执行失败等仍可能拒绝 Promise,调用方应 catch。
- 它是 SDK dispose 后唯一允许的写操作特例,适合卸载后异步保存完成再尝试关闭原页;并不允许误关同 ID 新生命周期。
sdk.dispose()本身不会关闭宿主页签,不应把两者混为一谈。- 不承诺回到来源。功能页面的“关闭并返回”按钮应使用
sdk.back();只需要邻页规则时才使用closeCurrent()。
back
back(destination?: OpenTab) 是原子关闭绑定页并选择返回目标,不是浏览器 history.back()。
- 传入 destination 时优先打开/激活该目标,即使存在来源也不改变此优先级;跨应用显式目标须 canNavigate 和 canOpen 放行。
- 不传参数时寻找宿主内存中的存活来源 key;页面级同应用 open 自动记录来源,跨应用 navigateTo 须开启 crossAppBack。中间来源关闭后衔接到存活祖先。
- 无有效来源时,按本应用左侧最近、再右侧页签寻找邻页。
- 当前实现最后还会按宿主剩余页签位置回落,因此仅剩其他应用页签时也可能激活该剩余页;无任何页签则为空工作区。
来源不被其他入口覆盖,也不形成循环;返回目标撤权、守卫拒绝或路由取消会保留当前页。刷新浏览器后来源映射不会从存储恢复。同 tabId 重开不等于原来源实例。完整宿主配置、子页面按钮、结果处理和旧 fallback 参数迁移见功能页面关闭。
7. 宿主连接 URL 与 sessionStorage
当前宿主模板由 Vue Router 管理 /micro/{appId}/{业务路径}?query,通过 connectMicroHostRouter 的双向映射连接 core;connectMicroBrowser 使用 url: false 只恢复及保存页签,并传入从当前 URL 解析的 initialLocation。旧 /workspace?tab=... 已移除。下面的 Hash 编解码与默认 URL 同步是独立浏览器连接器的底层 API,模板不会用它们生成地址。
真实导出入口是 @axewo/fin-micro/browser:
text
connectMicroBrowser(host, options?) // 返回 { dispose() }
encodeMicroLocation(tab) // 返回 hash 字符串
// decodeMicroLocation(hash) 返回 { appId, tabId, route } | null,格式错误抛异常。options 包含 window、storageKey、onError、url 与 initialLocation。没有 restore()、save()、mode: 'localStorage'、includeDrafts 等配置。默认使用当前 window;Node/SSR 测试必须显式提供浏览器适配对象。url: false 时不读取或写入 location/history,也不监听历史事件;initialLocation 用于接收宿主路由解析后的首次深链,仍经过 core 准入。
推荐启动顺序与完整初始化示例
先完成认证和权限 ready,再创建 host,接浏览器同步,最后渲染宿主和挂载 runtime。不要先恢复旧用户页签,再等待权限请求。
ts
import { createMicroHost } from '@axewo/fin-micro/core'
import { connectMicroBrowser } from '@axewo/fin-micro/browser'
// 此函数须由认证 ready 成功后的启动阶段调用。
export function createAuthenticatedWorkspace(userId: string) {
const host = createMicroHost([
{ appId: 'order', name: '订单', entry: 'http://127.0.0.1:5181/' },
{ appId: 'inventory', name: '库存', entry: 'http://127.0.0.1:5181/' },
], {
// 示例用户已获两个应用准入;生产替换为已初始化的权限快照。
canOpen: input => ['order', 'inventory'].includes(input.appId),
})
const browser = connectMicroBrowser(host, {
storageKey: `fin-micro:tabs:v1:user:${encodeURIComponent(userId)}`,
onError: error => console.error('导航恢复或地址同步异常', error),
})
// 此处起才把 host 交给宿主 UI / runtime 的创建流程。
return {
host,
dispose() {
// 实际项目还应按自身挂载结构先停止 UI/runtime;浏览器连接必须在 host 前释放。
browser.dispose()
host.dispose()
},
}
}连接器不是认证模块。登出/切换用户应停止旧连接、卸载旧运行上下文并销毁旧 host,再按新用户重新初始化。生成宿主的 src/bootstrap.ts 与 src/hooks/usePlatformSession.ts 负责就绪、取消和代次隔离,src/layout/HostLayout.vue 使用包含当前 user.id 的 storageKey 隔离页签存储。
8. URL 编码、深链与恢复优先级
ts
import { encodeMicroLocation, decodeMicroLocation } from '@axewo/fin-micro/browser'
const hash = encodeMicroLocation({
appId: 'order',
tabId: 'order:detail:1001',
route: '/detail?id=1001#summary',
})
// #/apps/order/order%3Adetail%3A1001?route=%2Fdetail%3Fid%3D1001%23summary
const location = decodeMicroLocation(hash)
// { appId: 'order', tabId: 'order:detail:1001', route: '/detail?id=1001#summary' }整个 route 被编码为外层 hash 的一个参数,内部 query/hash 不应拆成宿主 URL 字段。decodeMicroLocation 只负责格式解码,不验证应用注册、业务权限或路由实际存在;连接器再校验并经过 core 准入。
初始化会同步读取存储,恢复有效导航元信息,再应用深链,深链优先激活。若有效深链与存储相同 tabId 指向不同应用/路由,会在恢复前跳过该冲突存储页,避免先挂载旧页再替换。
- 默认存储键为
fin-micro:tabs:v1,使用 sessionStorage,不是 localStorage 或云端同步。 - 存储内容为
version: 1、tabs、activeTabId;页签只存 appId/tabId/title/route/可选 activeMenuId。 - 不存
key、组件实例、表单草稿、令牌、整个认证会话或页面来源映射。 - 恢复生成新 key,必须重新认证、重新执行准入校验、重新加载页面及业务数据。
- 存储 tabs 不是数组、版本不支持、或数量大于 100 时该存储快照被拒绝;重复、未知应用、无效导航条目不会当作可信页面恢复。
- route 应为应用内
/开头路径,不能是//、含反斜杠或控制字符的值;URL 解析失败、存储不可用、history 写入失败通过 onError 报告并安全回落。 activeMenuId恢复仍须经 core 的叶菜单合法性校验。菜单归属不替代权限。- 只有
#/的深链不会关闭已恢复页签;有页签时回落当前页,真正关闭全部后才写#/和空 tabs。
如果宿主初始化时已经预置了相同 ID 的页签,仍须遵守 core 的冲突规则;不要在接浏览器连接前批量预开与深链冲突的业务页面。可在同步恢复后仅为空状态补默认入口。
9. 浏览器前进后退并非页签复活器
连接器使用独立的 history.state['fin-micro:navigation:v1'] 名空间记录本次连接会话和当前 key,并保留其他 history state 字段。
- 一般状态变化在地址变更且没有移除旧 key 时可 push;关闭、刷新、原位替换等发生 key 移除时 replace,初始化也 replace,避免逐个恢复页签创建一串历史。
popstate历史遍历只允许激活本次连接会话里仍存活且 key、应用、route 都匹配的页面,经过异步离页守卫。- 已关闭页签不会仅因浏览器后退被重新打开;整页刷新后的旧会话历史不会复活旧 key。
- 普通直接 hash 修改走 open 事务,可打开合法目标;同 tabId 换路由走原位替换确认,不是先 close 再 open。
- 同一次 popstate/hashchange 会归并,避免重复导航;新 URL 意图会取消未提交的旧意图。
- 导航被拒绝、过期或失败后以 replace 回落当前有效地址,但不保证恢复遍历前的 history 索引。
因此 sdk.back()、浏览器后退、关闭活动页签是三种不同交互,不能用一个统一“返回”承诺描述。
10. 整页刷新与卸载限制
浏览器整页刷新会销毁旧 JavaScript 上下文,无法 await 旧页面的异步离页守卫。可另行使用 beforeunload 请求浏览器通用同步确认,但浏览器不保证触发,也不能承诺自定义提示文案或异步保存成功。
browser.dispose() 幂等移除宿主订阅、popstate/hashchange 监听并取消未提交浏览器导航;不会清空存储、关闭页签或销毁 host。重要草稿请提前在业务层保存,不要等卸载阶段再提交。
11. 常见错误清单
| 错误做法 | 正确方向 |
|---|---|
:active-key="activeTabId" | 通过活动 tab 找到其 key |
| 缓存 tabs 传整个宿主数组 | 按 bridge.appId 过滤本应用 tabs |
| 页面 SDK 跟随活动 tab 改绑 | setup 捕获本页 key,一次创建 |
| onDeactivated 中 dispose | 只在真正卸载释放,useMicroPage 已自动处理 |
| 期望 max=20 保住所有草稿 | max 只限制内存实例,重要草稿业务持久化 |
| 从包里导入 MicroPageScope | 自行封装业务组件,或直接用 useMicroPage |
| 已恢复 tabs 就显示“业务恢复完成” | 导航元信息恢复与实际页面/数据加载分别展示 |
| 用户权限尚未 ready 就 connectMicroBrowser | ready 完成后再创建 host 与连接 |
| 期待 sessionStorage 保存跨设备表单 | 该存储只保存本浏览器会话公开导航元信息 |
| 用 history.back 实现 sdk.back | 一个是浏览器历史遍历,一个是页签原子关闭返回 |
12. 对照源码
- 缓存及 Vue 绑定:
packages/fin-micro/src/vue/index.ts。 - 浏览器连接实现:
packages/fin-micro/src/browser/index.ts。 - 单页刷新与来源映射:
packages/fin-micro/src/core/index.ts中的enqueue。 - 应用缓存装配:
packages/fin-micro/scaffold/vue/src/App.vue。 - 页面动作及守卫:
packages/fin-micro/scaffold/vue/src/views/demo/modules/detail/index.vue;完整业务接入见第三章。